From 14c3cb36a3b037111a9fbdf8ff6ea32ee62b3be1 Mon Sep 17 00:00:00 2001 From: Agando Date: Thu, 22 Oct 2026 00:43:13 +0200 Subject: fix(newos4): coreutils flags + loop-guard arg key, both found by the agent's live run (v4.6.49) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Agent session surfaced two real defects: 1. The A4 prompt told it to use grep -n / sed -n 'N,Mp' / wc -l / head -N — none of those flags parsed: grep treated '-n' as the pattern (6/6 variants failed), wc read '-l' as a filename, head looked for a file named '-2', sed had no range parser. Fixed the tools, not just the text: grep now accepts -n/-i/-c/-v/-l (line numbers were already printed), wc accepts -l/-w/-c, head accepts -N and -n N, sed accepts Np and N,Mp, tail no longer prints a blank line for a trailing newline. Prompt re-ranked to the working forms. 2. Loop guard keyed calls by tool name plus the raw arguments field. When arguments was already an object (parsed upstream), the key became 'run_command:[object Object]', collapsing distinct batched commands into one signature and producing false "called 3x" warnings. Now the key is JSON.stringify(arguments) whenever it is not a string (3 sites: sig, _sigR, _sigD). Verified live v4.6.49: grep -n/-c, wc -l, head -2, sed -n '2,3p', tail -n 1 all correct. --- modules/core/module-aiassist.js | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) (limited to 'modules/core/module-aiassist.js') diff --git a/modules/core/module-aiassist.js b/modules/core/module-aiassist.js index 8d1fc9d..f69494e 100644 --- a/modules/core/module-aiassist.js +++ b/modules/core/module-aiassist.js @@ -122,7 +122,7 @@ buildSystemPrompt() { } catch (e) {} base += '\n\n[File reads, tools, verification]\n' + '- read_file returns 8000 chars by default (limit up to 16000). If the reply says truncated=true, call read_file again with offset= before concluding anything about the file.\n' + - '- Prefer targeted reads over whole files: run_command with grep -n "" , sed -n \'10,40p\' , head -40 , tail -40 , wc -l .\n' + + '- Prefer targeted reads over whole files: run_command with grep "" (prints line: text), sed -n \'10,40p\' , head -40 , tail -n 40 , wc -l . Flags are tolerated, but plain `cat ` and read_file offset/limit always work.\n' + '- sed without -i is read-only and allowed; sed -i, rm, mv and writes need user confirmation.\n' + '- After ANY write or edit: re-read the changed lines (cat or sed -n) and run a check that exists in THIS terminal — `test` for the suite, `grep -n` to confirm the new line, `wc -l` for size. There is no shell, no node and no curl here.\n' + '- If a tool fails twice, change approach (different tool, smaller scope) instead of retrying the same call.'; @@ -1540,7 +1540,14 @@ buildSystemPrompt() { const now = Date.now(); const blocked = []; for (const tc of res.tool_calls) { - const sig = tc.function.name + ':' + (tc.function.arguments || ''); + // 2026-10-21: arguments can be an object (already parsed upstream). + // `+ object` produced "name:[object Object]", collapsing every call to + // the same tool into one key → false "called 3x" loop warnings when the + // model legitimately batched distinct commands. + const _argKey = (typeof tc.function.arguments === 'string') + ? tc.function.arguments + : JSON.stringify(tc.function.arguments || {}); + const sig = tc.function.name + ':' + _argKey; let entry = _seenCalls.get(sig); if (!entry || (now - entry.ts) > _loopWindow) { entry = { count: 0, ts: now, resultHash: null, blocked: false, lastResult: null, epoch: this._toolEpoch }; @@ -1555,7 +1562,7 @@ buildSystemPrompt() { entry.ts = now; _seenCalls.set(sig, entry); - const argPreview = String(tc.function.arguments || '') + const argPreview = String(_argKey || '') .replace(/(token|key|secret|password|bearer)[^,}]*/ig, '$1:[redacted]') .slice(0, 60); if (entry.blocked || entry.count >= _blockAt) { @@ -1668,7 +1675,7 @@ buildSystemPrompt() { this.terminal.print(`📊 Result (${t1}s): ${obs}`, 'info'); } try { - const _sigR = fn.name + ':' + (fn.arguments || ''); + const _sigR = fn.name + ':' + ((typeof fn.arguments === 'string') ? fn.arguments : JSON.stringify(fn.arguments || {})); const _eR = _seenCalls.get(_sigR); if (_eR) { const _rh = _sigHash(toolResult); @@ -1743,7 +1750,7 @@ buildSystemPrompt() { this.terminal.print(`📊 Result (${t1}s): ${dObs}`, 'info'); } try { - const _sigD = fn.name + ':' + (fn.arguments || ''); + const _sigD = fn.name + ':' + ((typeof fn.arguments === 'string') ? fn.arguments : JSON.stringify(fn.arguments || {})); const _eD = _seenCalls.get(_sigD); if (_eD) { const _rhD = _sigHash(toolResult); -- cgit v1.2.3