diff options
| -rw-r--r-- | main.html | 6 | ||||
| -rw-r--r-- | modules/core/core-bundle.js | 36 | ||||
| -rw-r--r-- | modules/core/module-aiassist.js | 36 | ||||
| -rw-r--r-- | modules/core/module-loader.js | 2 |
4 files changed, 74 insertions, 6 deletions
@@ -1349,13 +1349,13 @@ </script> <!-- v4.5.63: A2A scratchpad fixes + agent-register arg fix --> -<script src="modules/core/core-bundle.js?v=v4.6.30"></script> +<script src="modules/core/core-bundle.js?v=v4.6.31"></script> <!-- Async dependency-ordered module loader now only initializes terminal and loads optional ext modules --> -<script src="modules/core/module-loader.js?v=v4.6.30"></script> +<script src="modules/core/module-loader.js?v=v4.6.31"></script> <!-- Critical ext bundle loads AFTER webx11 exists so registerModule calls work --> -<script src="modules/ext/critical-ext-bundle.js?v=v4.6.30"></script> +<script src="modules/ext/critical-ext-bundle.js?v=v4.6.31"></script> <script> // ═══ ONE TRUE VERSION SOURCE ═══ diff --git a/modules/core/core-bundle.js b/modules/core/core-bundle.js index 6022a37..5bbc840 100644 --- a/modules/core/core-bundle.js +++ b/modules/core/core-bundle.js @@ -8030,6 +8030,14 @@ class ServerMonitorModule { this.systemPrompt = this.buildSystemPrompt(); this.context = []; this.maxContext = 10; this._sessions = new Map(); // agentTag -> {context, profile}; 'main' aliases this.context + this._toolCache = new Map(); // D2: key -> {result, ts, epoch} + this._toolEpoch = 0; + this._mutatingTools = new Set(['write_file', 'edit_module', 'run_command', 'delete_file', + 'make_directory', 'commit_module', 'ship_module', 'patch_skill', 'delegate_task', + 'reload_module', 'create_skill']); + // Only deterministic reads are cached — never status/log/polling tools. + this._cacheableTools = new Set(['read_file', 'list_files', 'list_modules', 'view_module', + 'nvfs_resolve', 'get_help', 'list_skills', 'diff_module', 'get_module_status']); this.maxContextTokens = 2000; this.MAX_TOOL_TURNS = 8; this._pendingConfirmation = null; @@ -8783,6 +8791,23 @@ buildSystemPrompt() { this.commands['vaultmind-query'] = (args) => this._vaultmindQueryCmd(args); this.commands['wallet-lookup'] = (args) => this._walletLookupCmd(args); } + _cacheGet(name, args) { + const key = name + ':' + (typeof args === 'string' ? args : JSON.stringify(args || {})); + if (!this._cacheableTools.has(name)) return { hit: false, key: key }; + const e = this._toolCache.get(key); + if (e && e.epoch === this._toolEpoch && (Date.now() - e.ts) < 30000) { + return { hit: true, key: key, result: e.result }; + } + return { hit: false, key: key }; + } + _cacheSet(name, key, result) { + if (this._mutatingTools.has(name)) { this._toolEpoch++; return; } // invalidate all + if (!this._cacheableTools.has(name) || !key || typeof result !== 'string') return; + this._toolCache.set(key, { result: result, ts: Date.now(), epoch: this._toolEpoch }); + if (this._toolCache.size > 200) { + this._toolCache.delete(this._toolCache.keys().next().value); + } + } _sessionCtx(tag) { const k = tag || 'main'; // main is always the live this.context (commands like `ai-clear`/`load` reassign it) @@ -9500,7 +9525,15 @@ buildSystemPrompt() { for (const tc of safeTools) { const fn = tc.function; const t0 = Date.now(); - const toolResult = await this.runTool(fn.name, fn.arguments); + const _cg = this._cacheGet(fn.name, fn.arguments); + let toolResult; + if (_cg.hit) { + toolResult = _cg.result; + this.terminal.print(`♻️ ${fn.name} → cached (${toolResult.length}b, not re-run)`, 'info'); + } else { + toolResult = await this.runTool(fn.name, fn.arguments); + this._cacheSet(fn.name, _cg.key, toolResult); + } const t1 = Math.round((Date.now() - t0) / 1000); this._toolCallLog.push({ turn: toolTurns, @@ -9581,6 +9614,7 @@ buildSystemPrompt() { const t0 = Date.now(); this.terminal.print(`🔧 Tool: ${fn.name}(${typeof fn.arguments === 'string' ? fn.arguments : JSON.stringify(fn.arguments)})`, 'info'); const toolResult = await this.runTool(fn.name, fn.arguments); + this._toolEpoch++; // D2: mutating action invalidates every cached read const t1 = Math.round((Date.now() - t0) / 1000); this._toolCallLog.push({ turn: toolTurns, diff --git a/modules/core/module-aiassist.js b/modules/core/module-aiassist.js index 74a071b..a5c5022 100644 --- a/modules/core/module-aiassist.js +++ b/modules/core/module-aiassist.js @@ -7,6 +7,14 @@ this.systemPrompt = this.buildSystemPrompt(); this.context = []; this.maxContext = 10; this._sessions = new Map(); // agentTag -> {context, profile}; 'main' aliases this.context + this._toolCache = new Map(); // D2: key -> {result, ts, epoch} + this._toolEpoch = 0; + this._mutatingTools = new Set(['write_file', 'edit_module', 'run_command', 'delete_file', + 'make_directory', 'commit_module', 'ship_module', 'patch_skill', 'delegate_task', + 'reload_module', 'create_skill']); + // Only deterministic reads are cached — never status/log/polling tools. + this._cacheableTools = new Set(['read_file', 'list_files', 'list_modules', 'view_module', + 'nvfs_resolve', 'get_help', 'list_skills', 'diff_module', 'get_module_status']); this.maxContextTokens = 2000; this.MAX_TOOL_TURNS = 8; this._pendingConfirmation = null; @@ -760,6 +768,23 @@ buildSystemPrompt() { this.commands['vaultmind-query'] = (args) => this._vaultmindQueryCmd(args); this.commands['wallet-lookup'] = (args) => this._walletLookupCmd(args); } + _cacheGet(name, args) { + const key = name + ':' + (typeof args === 'string' ? args : JSON.stringify(args || {})); + if (!this._cacheableTools.has(name)) return { hit: false, key: key }; + const e = this._toolCache.get(key); + if (e && e.epoch === this._toolEpoch && (Date.now() - e.ts) < 30000) { + return { hit: true, key: key, result: e.result }; + } + return { hit: false, key: key }; + } + _cacheSet(name, key, result) { + if (this._mutatingTools.has(name)) { this._toolEpoch++; return; } // invalidate all + if (!this._cacheableTools.has(name) || !key || typeof result !== 'string') return; + this._toolCache.set(key, { result: result, ts: Date.now(), epoch: this._toolEpoch }); + if (this._toolCache.size > 200) { + this._toolCache.delete(this._toolCache.keys().next().value); + } + } _sessionCtx(tag) { const k = tag || 'main'; // main is always the live this.context (commands like `ai-clear`/`load` reassign it) @@ -1477,7 +1502,15 @@ buildSystemPrompt() { for (const tc of safeTools) { const fn = tc.function; const t0 = Date.now(); - const toolResult = await this.runTool(fn.name, fn.arguments); + const _cg = this._cacheGet(fn.name, fn.arguments); + let toolResult; + if (_cg.hit) { + toolResult = _cg.result; + this.terminal.print(`♻️ ${fn.name} → cached (${toolResult.length}b, not re-run)`, 'info'); + } else { + toolResult = await this.runTool(fn.name, fn.arguments); + this._cacheSet(fn.name, _cg.key, toolResult); + } const t1 = Math.round((Date.now() - t0) / 1000); this._toolCallLog.push({ turn: toolTurns, @@ -1558,6 +1591,7 @@ buildSystemPrompt() { const t0 = Date.now(); this.terminal.print(`🔧 Tool: ${fn.name}(${typeof fn.arguments === 'string' ? fn.arguments : JSON.stringify(fn.arguments)})`, 'info'); const toolResult = await this.runTool(fn.name, fn.arguments); + this._toolEpoch++; // D2: mutating action invalidates every cached read const t1 = Math.round((Date.now() - t0) / 1000); this._toolCallLog.push({ turn: toolTurns, diff --git a/modules/core/module-loader.js b/modules/core/module-loader.js index 0674d07..c1571fc 100644 --- a/modules/core/module-loader.js +++ b/modules/core/module-loader.js @@ -4,7 +4,7 @@ // Core module bundle loaded synchronously in main.html. // This loader only handles version cache-busting, splash, terminal init, and optional ext modules. - const LATEST_VER = 'v4.6.30'; + const LATEST_VER = 'v4.6.31'; const CACHE = '?v=' + LATEST_VER; const stored = localStorage.getItem('webx11_cache_ver'); if (stored !== LATEST_VER) { |