diff options
| author | Agando <roduccomm@gmail.com> | 2026-06-25 06:00:45 +0200 |
|---|---|---|
| committer | Agando <roduccomm@gmail.com> | 2026-06-25 06:00:45 +0200 |
| commit | 8b3e3e4527772bf060984f98777a2cff8e88aa09 (patch) | |
| tree | 0ff8cf6db6563be2cf53062f73aa7e06fea76ce4 /modules/core/core-bundle.js | |
| parent | b08aa5b479395441462effed59ff1ee02d9d8c7f (diff) | |
auto: 2026-06-25_06:00 changes
Diffstat (limited to 'modules/core/core-bundle.js')
| -rw-r--r-- | modules/core/core-bundle.js | 182 |
1 files changed, 171 insertions, 11 deletions
diff --git a/modules/core/core-bundle.js b/modules/core/core-bundle.js index 00e8db3..6f94a44 100644 --- a/modules/core/core-bundle.js +++ b/modules/core/core-bundle.js @@ -2436,7 +2436,7 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm this._registerBuiltIn(); } _registerBuiltIn() { - const internals = ['clear','help','ls','cat','df','free','env','history','cd','pwd','touch','mkdir','rm','cp','mv','edit','cat','run','font-size','load-local','loadlocal','mem','security','security-level','security-destructive','security-system','security-whitelist','security-flag','config','ai-config','startup-modules','smart-prompt','cmd-allow','cmd-deny','system-health','server','servers','server-check','monitor','vfs','vfs-clean','vfs-size','empty-trash','expand','sysmsg','orchestrate','orchestrate-parallel','orchestrate-decompose','orchestrate-result','orchestrator-status','orchestrator-spawn','orchestrator-kill','orchestrator-purge','orchestrator-config','orchestrator-list','orchestrator-sleep','orchestrator-wake','orchestrator-restart-policy','a2a-send','a2a-list','a2a-post','a2a-poll','a2a-ack','a2a-reject','a2a-status','a2a-prune','consensus','consensus-status','consensus-history','pipeline','agent-list','agent-register','agent-status','agent-unregister','contract-list','contract-register','voice','speak','analyze']; + const internals = ['clear','help','ls','cat','df','free','env','history','cd','pwd','touch','mkdir','rm','cp','mv','edit','cat','run','font-size','load-local','loadlocal','mem','security','security-level','security-destructive','security-system','security-whitelist','security-flag','config','ai-config','startup-modules','smart-prompt','cmd-allow','cmd-deny','system-health','server','servers','server-check','monitor','vfs','vfs-clean','vfs-size','empty-trash','expand','sysmsg','orchestrate','orchestrate-parallel','orchestrate-decompose','orchestrate-result','orchestrator-status','orchestrator-spawn','orchestrator-kill','orchestrator-purge','orchestrator-config','orchestrator-list','orchestrator-sleep','orchestrator-wake','orchestrator-restart-policy','a2a-send','a2a-list','a2a-post','a2a-poll','a2a-ack','a2a-reject','a2a-status','a2a-prune','consensus','consensus-status','consensus-history','pipeline','agent-list','agent-register','agent-status','agent-unregister','contract-list','contract-register','voice','speak','analyze','agent-loops','bench','bench-compare']; internals.forEach(c => this.registerCommand(c, { category: 'builtin' })); } setLevel(n) { @@ -7656,6 +7656,10 @@ class ServerMonitorModule { this._skillDebounceMs = 1800000; this._chatLockTimeoutMs = 10000; this._loadGuardrailPolicy(); + // ── Agent Loop Profile System ── + this._activeLoopProfile = null; + this._loopProfiles = {}; + this._initLoopProfiles(); // Restore AI config from localStorage (maxContext, MAX_TOOL_TURNS, etc.) try { const cfg = localStorage.getItem('webx11_ai_config'); if (cfg) { const p = JSON.parse(cfg); if (p.maxTurns) this.MAX_TOOL_TURNS = p.maxTurns; if (p.maxContext) this.maxContext = p.maxContext; if (p.maxContextTokens) this.maxContextTokens = p.maxContextTokens; if (p.confirmTimeoutMs) this._confirmTimeoutMs = p.confirmTimeoutMs; if (p.skillDebounceMs) this._skillDebounceMs = p.skillDebounceMs; if (p.chatLockTimeoutMs) this._chatLockTimeoutMs = p.chatLockTimeoutMs; } } catch (e) {} try { const s = localStorage.getItem('webx11_ai_patterns'); if (s) this._commandFreq = JSON.parse(s); } catch (e) {} @@ -7717,7 +7721,21 @@ buildSystemPrompt() { } getToolDefinitions() { if (!this._toolDefs) this._toolDefs = this._buildToolDefs(); - return this._toolDefs; + return this._filterToolsByProfile(this._toolDefs); + } + _filterToolsByProfile(allTools) { + var p = this._activeLoopProfile; + if (!p || !p.tools) return allTools; + if (p.tools.mode === "all") { + if (p.tools.disabled && p.tools.disabled.length > 0) { + return allTools.filter(function(t) { return p.tools.disabled.indexOf(t.function.name) === -1; }); + } + return allTools; + } + if (p.tools.mode === "custom" && p.tools.enabled && p.tools.enabled.length > 0) { + return allTools.filter(function(t) { return p.tools.enabled.indexOf(t.function.name) !== -1; }); + } + return allTools; } _buildToolDefs() { return [{ @@ -8149,6 +8167,9 @@ buildSystemPrompt() { 'ai-stop': 'ai-stop — Cancel current AI operation', 'ai-model': 'ai-model [model] — Show or set AI model', 'ai-config': 'ai-config [key] [value] — View or set AI loop config (max-turns, max-context, confirm-timeout(0=disabled))', + 'agent-loops': 'agent-loops [name|new|edit|delete|clone|export|reset] — Manage agent loop profiles', + 'bench': 'bench [profile] [--tasks=ids] [--repeat=N] — Run benchmark suite on a profile', + 'bench-compare': 'bench-compare <p1> <p2> — Compare benchmark results of two profiles', 'debug': 'debug — Toggle system debug console (logs, fetch, errors)', 'debug-ai': 'debug-ai — Toggle AI tool-call debug panel', 'mem-debug': 'mem-debug — Show memory breakdown by component', @@ -8170,6 +8191,9 @@ buildSystemPrompt() { this.commands['ai-model'] = (args) => this.showModel(args); this.commands['ai-compress'] = () => this._manualCompress(); this.commands['ai-config'] = (args) => this.aiConfig(args); + this.commands['agent-loops'] = (args) => this.agentLoopsCmd(args); + this.commands['bench'] = (args) => this.benchCmd(args); + this.commands['bench-compare'] = (args) => this.benchCompareCmd(args); this.commands['debug'] = () => this.toggleDebugPanel(); this.commands['debug-ai'] = () => this.toggleDebug(); this.commands['guardrail'] = (args) => this.guardrailCmd(args); @@ -8284,6 +8308,13 @@ buildSystemPrompt() { if (loopResult.toolTurns >= 2 && !this._cancelled) { await this.afterActionReflection(userMessage, loopResult.toolTurns); } + if (loopResult && !this._cancelled) { + try { + var om = this.terminal ? this.terminal.ollamaManager : null; + var usageData = om && om._usageLog && om._usageLog.length > 0 ? om._usageLog[om._usageLog.length - 1] : null; + this._postRunReview(loopResult, this._toolCallLog.slice(-(loopResult.toolTurns || 0)), usageData); + } catch (e) {} + } // Auto-create skills from repeated errors if (this._errorLog.length >= 3) { await this._autoCreateErrorSkill(); @@ -8404,10 +8435,16 @@ buildSystemPrompt() { } catch (e) { /* ignore */ } } async agenticLoop(messages) { + var profile = this._activeLoopProfile || {}; + var loopCfg = profile.loop || {}; + var maxTurns = loopCfg.maxTurns || this.MAX_TOOL_TURNS; + var loopDetect = loopCfg.loopDetection || { enabled: false, threshold: 6, windowMs: 30000 }; + var confEsc = loopCfg.confidenceEscalation || { enabled: true, threshold: 90 }; + var finalPrompt = loopCfg.finalTurnPrompt || 'This is your last tool turn. You MUST provide a final answer after this tool call. Synthesize what you know.'; let toolTurns = 0; - const _seenCalls = new Map(); // v4.5.66: loop detection — track repeated tool calls - const _loopWindow = 30000; // 30s window for loop detection - while (toolTurns < this.MAX_TOOL_TURNS) { + const _seenCalls = new Map(); + const _loopWindow = loopDetect.windowMs || 30000; + while (toolTurns < maxTurns) { if (this._cancelled) { this.terminal.print('AI loop cancelled', 'warning'); return { finalAnswer: null, toolTurns }; } toolTurns++; // On last turn, inject wrap-up prompt @@ -8447,7 +8484,7 @@ buildSystemPrompt() { entry.count++; entry.ts = now; _seenCalls.set(sig, entry); - if (entry.count >= 6) { + if (loopDetect.enabled !== false && entry.count >= (loopDetect.threshold || 6)) { this.terminal.print(`🔄 Loop detected: ${tc.function.name} called ${entry.count}x with same args in 30s — breaking loop`, 'warning'); looping = true; break; @@ -9946,10 +9983,40 @@ buildSystemPrompt() { this.terminal.print(`=== End (${this.systemPrompt.length} chars) ===`, 'info'); return; } - this.systemPrompt = args.join(' '); - // Persist to localStorage — next page load will use this instead of built-in + var arg = args.join(' '); + if (arg === 'reset') { + localStorage.removeItem('webx11_ai_system_prompt'); + this.systemPrompt = this.buildSystemPrompt(); + this.terminal.print('System prompt reset to default', 'success'); + return; + } + if (arg.charAt(0) === '+') { + var directive = arg.slice(1).trim(); + var lp = this._activeLoopProfile; + if (lp) { + if (!lp.systemPrompt) lp.systemPrompt = { base: null, prepend: '', append: '', useDefault: true }; + lp.systemPrompt.prepend = (lp.systemPrompt.prepend ? lp.systemPrompt.prepend + '\n' : '') + directive; + this._saveLoopProfile(lp); + } + this.systemPrompt = this.buildSystemPrompt(); + this.terminal.print('Directive prepended to system prompt (profile: ' + (lp ? lp.name : 'none') + ')', 'success'); + return; + } + if (arg.charAt(0) === '-') { + var directive = arg.slice(1).trim(); + var lp = this._activeLoopProfile; + if (lp) { + if (!lp.systemPrompt) lp.systemPrompt = { base: null, prepend: '', append: '', useDefault: true }; + lp.systemPrompt.append = (lp.systemPrompt.append ? lp.systemPrompt.append + '\n' : '') + directive; + this._saveLoopProfile(lp); + } + this.systemPrompt = this.buildSystemPrompt(); + this.terminal.print('Directive appended to system prompt (profile: ' + (lp ? lp.name : 'none') + ')', 'success'); + return; + } + this.systemPrompt = arg; try { localStorage.setItem('webx11_ai_system_prompt', this.systemPrompt); } catch (e) {} - this.terminal.print('System prompt updated (persisted)', 'success'); + this.terminal.print('System prompt updated (persisted, full replace)', 'success'); } memDebug() { const toolLogSize = this._toolCallLog.length; @@ -10066,6 +10133,8 @@ buildSystemPrompt() { this.terminal.print(`confirm-timeout: ${this._confirmTimeoutMs > 0 ? this._confirmTimeoutMs / 1000 + 's' : 'disabled (no auto-deny)'} (default: 120s)`, ''); this.terminal.print(`chat-lock-timeout: ${this._chatLockTimeoutMs / 1000}s (default: 10s)`, ''); this.terminal.print(`skill-debounce: ${this._skillDebounceMs / 60000}min (default: 30min)`, ''); + var lp = this._activeLoopProfile; + this.terminal.print('loop-detection: ' + (lp && lp.loop && lp.loop.loopDetection ? (lp.loop.loopDetection.enabled ? 'on' : 'off') : 'off (default)') + ' (profile: ' + (lp ? lp.name : 'none') + ')', ''); const om = this.terminal?.ollamaManager; if (om && om.modelParams) { this.terminal.print(`temperature: ${om.modelParams.temperature} (default: 0.7)`, ''); @@ -10074,7 +10143,7 @@ buildSystemPrompt() { this.terminal.print(`thinking: ${om.modelParams.thinking} (default: off)`, ''); } this.terminal.print('Usage: ai-config <key> <value>', 'info'); - this.terminal.print('Keys: max-turns, max-context, max-context-tokens, confirm-timeout(0=disabled), chat-lock-timeout, skill-debounce, trivial-optimization, temperature, top-p, max-tokens, thinking', 'info'); + this.terminal.print('Keys: max-turns, max-context, max-context-tokens, confirm-timeout(0=disabled), chat-lock-timeout, skill-debounce, trivial-optimization, temperature, top-p, max-tokens, thinking, loop-detection', 'info'); return; } const key = args[0]; @@ -10127,8 +10196,17 @@ buildSystemPrompt() { if (!validModes.includes(val)) return this.terminal.print(`thinking must be one of: ${validModes.join(', ')}`, 'warning'); const om = this.terminal?.ollamaManager; if (om && om.modelParams) { om.modelParams.thinking = val; om._saveModelParams(); } + } else if (key === 'loop-detection') { + var lp = this._activeLoopProfile; + if (!lp) { this.terminal.print('No active profile', 'error'); return; } + if (!lp.loop) lp.loop = {}; + if (!lp.loop.loopDetection) lp.loop.loopDetection = { enabled: false, threshold: 6, windowMs: 30000 }; + lp.loop.loopDetection.enabled = val !== 'off'; + this._saveLoopProfile(lp); + this.terminal.print('Loop detection ' + (lp.loop.loopDetection.enabled ? 'enabled' : 'disabled') + ' (profile: ' + lp.name + ')', 'success'); + return; } else { - return this.terminal.print(`Unknown key: ${key}. Valid: max-turns, max-context, max-context-tokens, confirm-timeout, chat-lock-timeout, skill-debounce, trivial-optimization, temperature, top-p, max-tokens, thinking`, 'warning'); + return this.terminal.print(`Unknown key: ${key}. Valid: max-turns, max-context, max-context-tokens, confirm-timeout, chat-lock-timeout, skill-debounce, trivial-optimization, temperature, top-p, max-tokens, thinking, loop-detection`, 'warning'); } // Persist localStorage.setItem('webx11_ai_config', JSON.stringify({ @@ -10311,6 +10389,88 @@ buildSystemPrompt() { this.terminal.print('Error: ' + e.message, 'error'); } } + // ── Agent Loop Profile System ── + _defaultLoopProfile(name, desc) { + return { name: name, displayName: desc, description: desc, version: 1, created: new Date().toISOString(), updated: new Date().toISOString(), systemPrompt: { base: null, prepend: "", append: "", useDefault: true }, loop: { strategy: "standard", maxTurns: this.MAX_TOOL_TURNS, loopDetection: { enabled: false, threshold: 6, windowMs: 30000 }, confidenceEscalation: { enabled: true, threshold: 90 }, finalTurnPrompt: "This is your last tool turn. You MUST provide a final answer after this tool call. Synthesize what you know." }, tools: { mode: "all", enabled: [], disabled: [] }, model: { temperature: null, topP: null, maxTokens: null, thinking: null }, provider: { name: null, model: null }, benchmark: { lastRun: null, avgLatency: null, avgTokens: null, taskCompletionRate: null, tasks: [] } }; + } + _initLoopProfiles() { + try { var saved = localStorage.getItem("webx11_agent_loops"); if (saved) { this._loopProfiles = JSON.parse(saved); } } catch (e) {} + if (!this._loopProfiles || Object.keys(this._loopProfiles).length === 0) { + this._loopProfiles = {}; + this._loopProfiles["default"] = this._defaultLoopProfile("default", "Balanced loop for general tasks"); + var p = this._defaultLoopProfile("minimal", "Fast, low-token for simple tasks"); p.loop.maxTurns = 4; p.tools = { mode: "custom", enabled: ["list_files","read_file","write_file","nvfs_resolve"], disabled: [] }; p.model.temperature = 0.3; this._loopProfiles["minimal"] = p; + p = this._defaultLoopProfile("research", "Read-only exploration, web + files"); p.loop.maxTurns = 6; p.tools = { mode: "custom", enabled: ["web_search","web_fetch","read_file","list_files","nvfs_resolve","view_module","list_modules"], disabled: [] }; p.model.temperature = 0.5; this._loopProfiles["research"] = p; + p = this._defaultLoopProfile("safe", "No destructive tools"); p.tools = { mode: "all", enabled: [], disabled: ["delete_file","edit_module","run_command","make_directory"] }; p.model.temperature = 0.5; this._loopProfiles["safe"] = p; + p = this._defaultLoopProfile("creative", "More turns, higher temp"); p.loop.maxTurns = 12; p.model.temperature = 0.9; this._loopProfiles["creative"] = p; + p = this._defaultLoopProfile("coder", "Code-focused, low temp"); p.loop.maxTurns = 10; p.model.temperature = 0.2; p.systemPrompt.append = "You are a code expert. Always show the exact code changes. Use edit_module for modifications."; this._loopProfiles["coder"] = p; + this._saveLoopProfiles(); + } + try { var active = localStorage.getItem("webx11_active_loop_profile"); if (active && this._loopProfiles[active]) { this._activeLoopProfile = this._loopProfiles[active]; } else { this._activeLoopProfile = this._loopProfiles["default"]; } } catch (e) { this._activeLoopProfile = this._loopProfiles["default"]; } + } + _saveLoopProfiles() { try { localStorage.setItem("webx11_agent_loops", JSON.stringify(this._loopProfiles)); } catch (e) {} } + _saveLoopProfile(p) { if (p && p.name) { p.updated = new Date().toISOString(); this._loopProfiles[p.name] = p; this._saveLoopProfiles(); } } + _switchLoopProfile(name) { + if (!this._loopProfiles[name]) return false; + this._activeLoopProfile = this._loopProfiles[name]; + localStorage.setItem("webx11_active_loop_profile", name); + this._toolDefs = null; + this.systemPrompt = this.buildSystemPrompt(); + var mp = this._activeLoopProfile.model; + var om = this.terminal ? this.terminal.ollamaManager : null; + if (om && mp) { if (mp.temperature !== null) { om.modelParams.temperature = mp.temperature; om._saveModelParams(); } if (mp.topP !== null) { om.modelParams.top_p = mp.topP; om._saveModelParams(); } if (mp.maxTokens !== null) { om.modelParams.max_tokens = mp.maxTokens; om._saveModelParams(); } if (mp.thinking !== null) { om.modelParams.thinking = mp.thinking; om._saveModelParams(); } } + return true; + } + agentLoopsCmd(args) { + if (!args[0]) { + this.terminal.print("=== Agent Loop Profiles ===", "info"); + var keys = Object.keys(this._loopProfiles); + for (var ki = 0; ki < keys.length; ki++) { var k = keys[ki]; var p = this._loopProfiles[k]; var active = this._activeLoopProfile === p ? " ✅" : ""; var bench = p.benchmark && p.benchmark.taskCompletionRate != null ? " (bench: " + Math.round(p.benchmark.taskCompletionRate * 100) + "%)" : ""; var tools = p.tools && p.tools.mode === "custom" ? (p.tools.enabled ? p.tools.enabled.length : 0) + " tools" : "all tools"; this.terminal.print(" " + k + ": " + p.displayName + active + " — " + (p.loop ? p.loop.maxTurns : this.MAX_TOOL_TURNS) + " turns, " + tools + bench); } + this.terminal.print("Usage: agent-loops <name> | new <name> <desc> | edit <name> <key> <val> | delete <name> | clone <src> <dst> | export <name> | reset <name>", "text-muted"); return; + } + var sub = args[0]; + if (sub === "new") { var name = args[1] ? args[1].replace(/s+/g, "-").toLowerCase() : ""; if (!name) { this.terminal.print("Usage: agent-loops new <name> <description>", "warning"); return; } var desc = args.slice(2).join(" ") || "Custom profile"; this._loopProfiles[name] = this._defaultLoopProfile(name, desc); this._saveLoopProfiles(); this.terminal.print("Profile '" + name + "' created. Use: agent-loops edit " + name + " <key> <value>", "success"); } + else if (sub === "edit") { var name = args[1]; if (!name || !this._loopProfiles[name]) { this.terminal.print("Profile '" + name + "' not found", "error"); return; } var key = args[2]; var val = args.slice(3).join(" "); if (!key || !val) { this.terminal.print("Usage: agent-loops edit <name> <key> <value>", "warning"); return; } this._setNestedKey(this._loopProfiles[name], key, val); this._saveLoopProfiles(); if (this._activeLoopProfile === this._loopProfiles[name]) { this._toolDefs = null; this.systemPrompt = this.buildSystemPrompt(); } this.terminal.print("Edited " + name + "." + key + " = " + val, "success"); } + else if (sub === "delete") { var name = args[1]; if (!name) { this.terminal.print("Usage: agent-loops delete <name>", "warning"); return; } if (name === "default") { this.terminal.print("Cannot delete default profile", "error"); return; } if (this._activeLoopProfile === this._loopProfiles[name]) { this.terminal.print("Cannot delete active profile — switch first", "error"); return; } delete this._loopProfiles[name]; this._saveLoopProfiles(); this.terminal.print("Profile '" + name + "' deleted", "success"); } + else if (sub === "clone") { var src = args[1], dst = args[2] ? args[2].replace(/s+/g, "-").toLowerCase() : ""; if (!src || !dst || !this._loopProfiles[src]) { this.terminal.print("Usage: agent-loops clone <src> <dst>", "warning"); return; } this._loopProfiles[dst] = JSON.parse(JSON.stringify(this._loopProfiles[src])); this._loopProfiles[dst].name = dst; this._loopProfiles[dst].created = new Date().toISOString(); this._saveLoopProfiles(); this.terminal.print("Cloned '" + src + "' -> '" + dst + "'", "success"); } + else if (sub === "export") { var name = args[1]; if (!name || !this._loopProfiles[name]) { this.terminal.print("Profile '" + name + "' not found", "error"); return; } this.terminal.printJSON(JSON.stringify(this._loopProfiles[name], null, 2)); } + else if (sub === "reset") { var name = args[1]; if (!name) { this.terminal.print("Usage: agent-loops reset <name>", "warning"); return; } var desc = this._loopProfiles[name] ? this._loopProfiles[name].displayName : "Reset profile"; this._loopProfiles[name] = this._defaultLoopProfile(name, desc); this._saveLoopProfiles(); if (this._activeLoopProfile && this._activeLoopProfile.name === name) { this._switchLoopProfile(name); } this.terminal.print("Profile '" + name + "' reset to defaults", "success"); } + else { if (this._loopProfiles[sub]) { this._switchLoopProfile(sub); var p = this._activeLoopProfile; this.terminal.print("Switched to '" + sub + "': " + p.displayName, "success"); this.terminal.print(" Max turns: " + (p.loop ? p.loop.maxTurns : this.MAX_TOOL_TURNS) + " | Tools: " + (p.tools ? p.tools.mode : "all") + " | Temp: " + (p.model && p.model.temperature !== null ? p.model.temperature : "inherit"), "text-muted"); } else { this.terminal.print("Profile '" + sub + "' not found. Available: " + Object.keys(this._loopProfiles).join(", "), "error"); } } + } + _setNestedKey(obj, key, val) { var parts = key.split("."); var o = obj; for (var i = 0; i < parts.length - 1; i++) { if (!o[parts[i]]) o[parts[i]] = {}; o = o[parts[i]]; } var last = parts[parts.length - 1]; try { o[last] = JSON.parse(val); } catch (e) { o[last] = val; } } + // ── BENCHMARK SUITE ── + _benchmarkTasks() { return [ { id: "fs-navigate", prompt: "List all files in the root directory and tell me how many .js files exist", maxTurns: 3, successCheck: function(r, log) { return r.length > 10 && log.some(function(t) { return t.name === "list_files"; }); } }, { id: "fs-create-read", prompt: "Create a file called /tmp/bench-test.txt with 'hello bench', then read it back to verify", maxTurns: 4, successCheck: function(r) { return r.toLowerCase().indexOf("hello") !== -1; }, destructive: true }, { id: "nvfs-resolve", prompt: "What terminal command handles file operations? Use nvfs-resolve to find it", maxTurns: 3, successCheck: function(r, log) { return log.some(function(t) { return t.name === "nvfs_resolve"; }) && r.length > 20; } }, { id: "web-search", prompt: "Search the web for 'what is the capital of France' and tell me the answer", maxTurns: 3, successCheck: function(r) { return r.toLowerCase().indexOf("paris") !== -1; } }, { id: "multi-tool", prompt: "Read the file /package.json, then list all modules, then summarize both results", maxTurns: 6, successCheck: function(r, log) { return log.filter(function(t) { return ["read_file","list_files","list_modules"].indexOf(t.name) !== -1; }).length >= 2; } }, { id: "code-edit", prompt: "Create a file /tmp/bench-code.js with a function that adds two numbers, then read it back to verify", maxTurns: 5, successCheck: function(r, log) { return log.some(function(t) { return t.name === "write_file"; }); }, destructive: true } ]; } + async _runBenchmark(profileName, taskIds) { + var tasks = this._benchmarkTasks(); if (taskIds) tasks = tasks.filter(function(t) { return taskIds.indexOf(t.id) !== -1; }); + var prevProfile = this._activeLoopProfile ? this._activeLoopProfile.name : null; + if (profileName && this._loopProfiles[profileName]) this._switchLoopProfile(profileName); + this.terminal.print("📊 Benchmark: " + profileName + " (" + tasks.length + " tasks)", "info"); + var results = []; + for (var ti = 0; ti < tasks.length; ti++) { var task = tasks[ti]; this.terminal.print(" ▶ " + task.id + "...", "text-muted"); var t0 = Date.now(); var toolLogBefore = this._toolCallLog.length; var messages = [{ role: "user", content: task.prompt }]; messages.unshift({ role: "system", content: this.systemPrompt }); var savedMax = this._activeLoopProfile && this._activeLoopProfile.loop ? this._activeLoopProfile.loop.maxTurns : null; if (this._activeLoopProfile && this._activeLoopProfile.loop) { this._activeLoopProfile.loop.maxTurns = task.maxTurns; } try { var result = await this.agenticLoop(messages); var latency = Date.now() - t0; var newTools = this._toolCallLog.slice(toolLogBefore); var passed = task.successCheck(result.finalAnswer || "", newTools); var tokens = await this._estimateTokensForRun(newTools, result.finalAnswer); results.push({ id: task.id, turns: result.toolTurns, tokens: tokens, latency: latency, pass: passed }); this.terminal.print(" " + (passed ? "✅" : "❌") + " " + result.toolTurns + " turns, " + tokens + " tokens, " + (latency/1000).toFixed(1) + "s", passed ? "success" : "warning"); } catch (e) { results.push({ id: task.id, turns: 0, tokens: 0, latency: Date.now() - t0, pass: false, error: e.message }); this.terminal.print(" ❌ Error: " + e.message, "error"); } if (this._activeLoopProfile && this._activeLoopProfile.loop && savedMax != null) { this._activeLoopProfile.loop.maxTurns = savedMax; } this.context = []; } + var profile = this._loopProfiles[profileName]; + if (profile) { var passed = results.filter(function(r) { return r.pass; }).length; profile.benchmark = { lastRun: new Date().toISOString(), tasks: results, avgLatency: Math.round(results.reduce(function(s, r) { return s + r.latency; }, 0) / results.length), avgTokens: Math.round(results.reduce(function(s, r) { return s + r.tokens; }, 0) / results.length), taskCompletionRate: passed / results.length }; this._saveLoopProfile(profile); } + this.terminal.print("┌──────────────────────────────────────────────────────────┐", "info"); this.terminal.print("│ Benchmark: " + profileName, "info"); this.terminal.print("├──────────────────────────────────────────────────────────┤", "info"); + for (var ri = 0; ri < results.length; ri++) { var r = results[ri]; var status = r.pass ? "✅" : "❌"; this.terminal.print("│ " + r.id + " " + r.turns + "t " + r.tokens + "tok " + (r.latency/1000).toFixed(1) + "s " + status, "info"); } + this.terminal.print("├──────────────────────────────────────────────────────────┤", "info"); + var passed = results.filter(function(r) { return r.pass; }).length; var avgLat = (results.reduce(function(s, r) { return s + r.latency; }, 0) / results.length / 1000).toFixed(1); var avgTok = Math.round(results.reduce(function(s, r) { return s + r.tokens; }, 0) / results.length); + this.terminal.print("│ " + passed + "/" + results.length + " passed (" + Math.round(passed/results.length*100) + "%) | Avg: " + avgLat + "s, " + avgTok + "tok", "info"); this.terminal.print("└──────────────────────────────────────────────────────────┘", "info"); + if (prevProfile && prevProfile !== profileName) this._switchLoopProfile(prevProfile); return results; + } + async _estimateTokensForRun(toolLog, finalAnswer) { var om = this.terminal ? this.terminal.ollamaManager : null; if (om && om._usageLog && om._usageLog.length > 0) { var last = om._usageLog[om._usageLog.length - 1]; return last.total_tokens || 0; } var toolChars = toolLog.reduce(function(s, t) { return s + (t.result ? t.result.length : 0); }, 0); var answerChars = (finalAnswer || "").length; return Math.round((toolChars + answerChars) / 4); } + benchCmd(args) { var profile = null; var taskIds = null; for (var ai = 0; ai < args.length; ai++) { if (args[ai].indexOf("--tasks=") === 0) taskIds = args[ai].split("=")[1].split(","); else if (args[ai].indexOf("--") !== 0) profile = args[ai]; } if (!profile) { this.terminal.print("Usage: bench [profile] [--tasks=id1,id2] [--repeat=N]", "warning"); this.terminal.print("Available tasks: " + this._benchmarkTasks().map(function(t) { return t.id; }).join(", "), "text-muted"); return; } this._runBenchmark(profile, taskIds); } + benchCompareCmd(args) { var p1 = args[0], p2 = args[1]; if (!p1 || !p2) { this.terminal.print("Usage: bench-compare <profile1> <profile2>", "warning"); return; } var b1 = this._loopProfiles[p1] ? this._loopProfiles[p1].benchmark : null; var b2 = this._loopProfiles[p2] ? this._loopProfiles[p2].benchmark : null; if (!b1 || !b1.lastRun) { this.terminal.print("No benchmark for '" + p1 + "'. Run: bench " + p1, "warning"); return; } if (!b2 || !b2.lastRun) { this.terminal.print("No benchmark for '" + p2 + "'. Run: bench " + p2, "warning"); return; } this.terminal.print("┌──────────────────────────────────────────────────────────┐", "info"); this.terminal.print("│ " + p1 + " vs " + p2, "info"); this.terminal.print("├──────────────────────────────────────────────────────────┤", "info"); this.terminal.print("│ Pass rate: " + (b1.taskCompletionRate*100).toFixed(0) + "% vs " + (b2.taskCompletionRate*100).toFixed(0) + "%", "info"); this.terminal.print("│ Avg tokens: " + b1.avgTokens + " vs " + b2.avgTokens, "info"); this.terminal.print("│ Avg latency: " + (b1.avgLatency/1000).toFixed(1) + "s vs " + (b2.avgLatency/1000).toFixed(1) + "s", "info"); this.terminal.print("│ Last run: " + b1.lastRun.substring(0,10) + " vs " + b2.lastRun.substring(0,10), "info"); this.terminal.print("└──────────────────────────────────────────────────────────┘", "info"); } + // ── SELF-IMPROVEMENT LOOP ── + _postRunReview(result, toolLog, usageData) { + var anomalies = []; var maxT = this._activeLoopProfile && this._activeLoopProfile.loop ? this._activeLoopProfile.loop.maxTurns : this.MAX_TOOL_TURNS; + if (result.toolTurns >= maxT * 0.8) { anomalies.push({ type: "max_turns_near_limit", value: result.toolTurns, max: maxT }); } + if (usageData && usageData.total_tokens > 50000) { anomalies.push({ type: "high_token_usage", value: usageData.total_tokens }); } + var failedTools = toolLog.filter(function(t) { return t.error; }); if (failedTools.length > 2) { var uniqueTools = []; failedTools.forEach(function(t) { if (uniqueTools.indexOf(t.name) === -1) uniqueTools.push(t.name); }); anomalies.push({ type: "tool_failures", count: failedTools.length, tools: uniqueTools }); } + var bench = this._activeLoopProfile ? this._activeLoopProfile.benchmark : null; if (bench && bench.avgTokens && usageData) { if (usageData.total_tokens > bench.avgTokens * 1.5) { anomalies.push({ type: "above_benchmark", value: usageData.total_tokens, benchmark: bench.avgTokens, ratio: (usageData.total_tokens / bench.avgTokens).toFixed(1) }); } } + if (anomalies.length === 0) return; + var suggestions = []; for (var ai = 0; ai < anomalies.length; ai++) { var a = anomalies[ai]; if (a.type === "max_turns_near_limit") suggestions.push("This task used " + result.toolTurns + "/" + maxT + " turns — consider increasing max-turns or splitting the task"); else if (a.type === "high_token_usage") suggestions.push("High token usage (" + (a.value/1000).toFixed(0) + "K) — consider a minimal-tools profile for simpler tasks"); else if (a.type === "tool_failures") suggestions.push(a.count + " tool failures (" + a.tools.join(", ") + ") — check if modules are loaded or paths correct"); else if (a.type === "above_benchmark") suggestions.push("This run used " + a.ratio + "x more tokens than benchmark avg (" + a.benchmark + ") — consider profile optimization"); } + var suggestion = suggestions.join("\n"); + try { var fs = this.terminal.fs; var index = fs.fsReadJSON("/skills/index.json") || {}; var autoSkills = Object.entries(index).filter(function(e) { return (e[1].tags || []).indexOf("auto-improvement") !== -1; }); if (autoSkills.length < 10) { var skillName = "loop-lesson-" + Date.now(); var content = "---\nname: " + skillName + "\ndescription: Auto-saved from agent loop anomaly detection\ntags: [auto-improvement, agent-loop, auto]\ncategory: system\n---\n## Anomalies Detected\n\n" + JSON.stringify(anomalies, null, 2) + "\n\n## Suggestions\n\n" + suggestion + "\n\n## Context\n- Profile: " + (this._activeLoopProfile ? this._activeLoopProfile.name : "none") + "\n- Turns: " + result.toolTurns + "\n- Tools used: " + toolLog.map(function(t) { return t.name; }).join(", "); fs.mkdirRecursive("/skills"); fs.writeFile("/skills/" + skillName + ".md", content); index[skillName] = { name: skillName, description: "Auto-saved loop lesson", tags: ["auto-improvement", "agent-loop", "auto"], category: "system", created: new Date().toISOString(), updated: new Date().toISOString() }; fs.fsWriteJSON("/skills/index.json", index); } } catch (e) {} + this.terminal.print("💡 Self-improvement: " + suggestion, "info"); + } _vaultmindStatusCmd() { const token = localStorage.getItem('vaultmind_token'); if (token) { |