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>) {
let mut result = template.to_string();
let mut extra_keys = Vec::new();
let mut pos = 0;
loop {
let Some(start) = result.find("{{") else { break };
let Some(end) = result[start + 2..].find("}}") else { break };
let end = start + 2 + end;
let Some(rel_start) = result[pos..].find("{{") else { break };
let start = pos + rel_start;
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();
match resolve(&name, store, graph, keys, count) {
Some(resolved) => {
let len = resolved.text.len();
extra_keys.extend(resolved.keys);
result.replace_range(start..end + 2, &resolved.text);
pos = start + len;
}
None => {
let msg = format!("(unknown: {})", name);
let len = msg.len();
result.replace_range(start..end + 2, &msg);
pos = start + len;
}
}
}