From dd50ae715f3a648aeb42fb524fe1b106a0c084c7 Mon Sep 17 00:00:00 2001 From: Agando Date: Tue, 29 Sep 2026 13:48:35 +0200 Subject: newOS4 v4.6.14: race-safe AI loop guard (depth counter), cover _capture* entry points, structured JSON tool error --- modules/core/core-bundle.js | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) (limited to 'modules/core/core-bundle.js') diff --git a/modules/core/core-bundle.js b/modules/core/core-bundle.js index 29c6cc6..942f836 100644 --- a/modules/core/core-bundle.js +++ b/modules/core/core-bundle.js @@ -2441,6 +2441,9 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm this.systemCmds = new Set(this._baseSystem); this.suspiciousFlags = new Set(this._baseSuspiciousFlags); this.whitelist = new Set(this._baseWhitelist); + // AI loop-guard: depth counter (reentrant, race-safe vs parallel tool calls) + this._aiToolDepth = 0; + this._lastUnknownCmd = null; // Load persisted user deltas (add/remove) so hardcoded defaults can be demoted. this._loadSecurityDeltas(); this._registerBuiltIn(); @@ -2594,6 +2597,7 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm return { cmd, args, known, destructive, system, unknown, hasSuspicious }; } async checkAndRun(rawInput) { + this._lastUnknownCmd = null; const { cmd, args, known, destructive, system, unknown, hasSuspicious } = this._classify(rawInput); // Level 3 Audit: just log and allow if (this.level === 3) { @@ -2617,13 +2621,14 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm return; } // Level 1: typo of a safe whitelisted command → one-tap clickable fix, no AI call burned - if (sug && sug.dist <= 1 && this.whitelist.has(sug.cmd) && this.terminal.showSuggestionBar && !this._aiToolCall) { + if (sug && sug.dist <= 1 && this.whitelist.has(sug.cmd) && this.terminal.showSuggestionBar && !(this._aiToolDepth > 0)) { this.terminal.print(`Unknown command "${cmd}" — did you mean:`, 'warning'); this.terminal.showSuggestionBar(sug.cmd, args); return; } // AI-invoked commands must not route back to AI (infinite loop) — return structured error - if (this._aiToolCall) { + if (this._aiToolDepth > 0) { + this._lastUnknownCmd = cmd; this.terminal.print(`Command not found: ${cmd}${sug ? `. Did you mean: ${sug.cmd}?` : ''}`, 'error'); return; } @@ -8938,8 +8943,10 @@ buildSystemPrompt() { const originalPrint = this.terminal.print.bind(this.terminal); let output = ''; this.terminal.print = (text, type) => { output += text + '\n'; }; + // Loop guard: AI-initiated commands must not route back to AI on unknown command + this.terminal.security._aiToolDepth++; try { await this.terminal.security.checkAndRun(cmd); } - finally { this.terminal.print = originalPrint; } + finally { this.terminal.security._aiToolDepth--; this.terminal.print = originalPrint; } return output.trim(); } async _captureShellCommand(cmd) { @@ -8947,8 +8954,10 @@ buildSystemPrompt() { const originalPrint = this.terminal.print.bind(this.terminal); let output = ''; this.terminal.print = (text, type) => { output += text + '\n'; }; + // Loop guard: AI-initiated commands must not route back to AI on unknown command + this.terminal.security._aiToolDepth++; try { await this.terminal.executeCommand(cmd); } - finally { this.terminal.print = originalPrint; } + finally { this.terminal.security._aiToolDepth--; this.terminal.print = originalPrint; } return output.trim(); } async _captureConsoleErrors(durationMs) { @@ -10249,10 +10258,15 @@ buildSystemPrompt() { const originalPrint = this.terminal.print.bind(this.terminal); let output = ''; this.terminal.print = (text, type) => { output += text + '\n'; }; - // Flag security so unknown commands return an error instead of routing back to AI (loop guard) - const prevAiFlag = this.terminal.security._aiToolCall; - this.terminal.security._aiToolCall = true; - try { await this.terminal.security.checkAndRun(cmd); } finally { this.terminal.security._aiToolCall = prevAiFlag; this.terminal.print = originalPrint; } + // Loop guard (depth counter — race-safe under Promise.all tool fan-out): + // unknown commands return an error instead of routing back to AI + this.terminal.security._aiToolDepth++; + try { await this.terminal.security.checkAndRun(cmd); } finally { this.terminal.security._aiToolDepth--; this.terminal.print = originalPrint; } + const unknownCmd = this.terminal.security._lastUnknownCmd; + if (unknownCmd) { + const sug = this.terminal.security.suggest(unknownCmd); + return JSON.stringify({ error: `Command not found: ${unknownCmd}`, suggestion: sug ? sug.cmd : null }); + } // 📸 Diff and append shadow report const shadowDiff = this._diffShadowVFS(); if (shadowDiff && shadowDiff.length > 0) { -- cgit v1.2.3