agents: fix resolve_placeholders infinite loop

The placeholder resolver re-scanned from the beginning of the string
after each expansion. If expanded node content contained {{...}}
patterns (which core-personality does), those got expanded recursively.
Cyclic node references caused infinite string growth.

Fix: track a position offset that advances past each substitution,
so expanded content is never re-scanned for placeholders.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
ProofOfConcept 2026-03-24 12:27:31 -04:00
parent 38816dc56e
commit 9a0121250b

View file

@ -611,19 +611,25 @@ pub fn resolve_placeholders(
) -> (String, Vec<String>) { ) -> (String, Vec<String>) {
let mut result = template.to_string(); let mut result = template.to_string();
let mut extra_keys = Vec::new(); let mut extra_keys = Vec::new();
let mut pos = 0;
loop { loop {
let Some(start) = result.find("{{") else { break }; let Some(rel_start) = result[pos..].find("{{") else { break };
let Some(end) = result[start + 2..].find("}}") else { break }; let start = pos + rel_start;
let end = start + 2 + end; let Some(rel_end) = result[start + 2..].find("}}") else { break };
let end = start + 2 + rel_end;
let name = result[start + 2..end].trim().to_lowercase(); let name = result[start + 2..end].trim().to_lowercase();
match resolve(&name, store, graph, keys, count) { match resolve(&name, store, graph, keys, count) {
Some(resolved) => { Some(resolved) => {
let len = resolved.text.len();
extra_keys.extend(resolved.keys); extra_keys.extend(resolved.keys);
result.replace_range(start..end + 2, &resolved.text); result.replace_range(start..end + 2, &resolved.text);
pos = start + len;
} }
None => { None => {
let msg = format!("(unknown: {})", name); let msg = format!("(unknown: {})", name);
let len = msg.len();
result.replace_range(start..end + 2, &msg); result.replace_range(start..end + 2, &msg);
pos = start + len;
} }
} }
} }