aboutsummaryrefslogtreecommitdiff
path: root/modules/core/module-aiassist.js
diff options
context:
space:
mode:
authorAgando <roduccomm@gmail.com>2026-10-22 00:43:13 +0200
committerAgando <roduccomm@gmail.com>2026-10-22 00:43:24 +0200
commit14c3cb36a3b037111a9fbdf8ff6ea32ee62b3be1 (patch)
tree0802e16ee1fe2538bcf783c7a4c44e3c5f8ed5c1 /modules/core/module-aiassist.js
parent0c45056c7c51daae9b99c9395b94196537c7d32d (diff)
fix(newos4): coreutils flags + loop-guard arg key, both found by the agent's live run (v4.6.49)
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.
Diffstat (limited to 'modules/core/module-aiassist.js')
-rw-r--r--modules/core/module-aiassist.js17
1 files changed, 12 insertions, 5 deletions
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=<next_offset> before concluding anything about the file.\n' +
- '- Prefer targeted reads over whole files: run_command with grep -n "<pattern>" <file>, sed -n \'10,40p\' <file>, head -40 <file>, tail -40 <file>, wc -l <file>.\n' +
+ '- Prefer targeted reads over whole files: run_command with grep "<pattern>" <file> (prints line: text), sed -n \'10,40p\' <file>, head -40 <file>, tail -n 40 <file>, wc -l <file>. Flags are tolerated, but plain `cat <file>` 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);