aboutsummaryrefslogtreecommitdiff
path: root/modules/core/module-aiassist.js
diff options
context:
space:
mode:
Diffstat (limited to 'modules/core/module-aiassist.js')
-rw-r--r--modules/core/module-aiassist.js934
1 files changed, 908 insertions, 26 deletions
diff --git a/modules/core/module-aiassist.js b/modules/core/module-aiassist.js
index 8a0a7c8..73078d0 100644
--- a/modules/core/module-aiassist.js
+++ b/modules/core/module-aiassist.js
@@ -86,10 +86,10 @@ buildSystemPrompt() {
} catch (e) {}
let base = `Current date: ${dateStr} (${dayStr}) ${timeStr}${nvfsNote}\n`
+ 'VFS→Modules→AI. nvfs-index for commands. Tool→Observe→Answer. nvfs-resolve. Destructive cmds need confirm.\n'
- + 'FS: list/read/write/delete | NVFS: nvfs-resolve | Web: search/fetch [POST=method+body] | Mod: view/edit/test/reload | Config | Skills | VaultMind: vaultmind_connect(network) — connect wallet, vaultmind_status — check session, vaultmind_query(ep,msg,agent) — Hyperliquid prices/portfolio/chat | wallet_lookup(address) — read-only portfolio for any wallet | get_usage_stats(model,provider,since_hours) — LLM token/latency stats\n'
- + 'web_fetch tool: GET for normal pages (returns title+content+word_count), POST for JSON APIs like Hyperliquid /info (pass method="POST" and body as JSON string).\n'
- + 'Other: floating, webdav, cron, vault, sandbox, agents, wpm, mindmap, memgraph, guardrail, wget, tar, secret-* — discoverable via help. '
- + 'JS: node <file.js> executes JS from VFS (alias for js-run), node -e "expr" for inline eval. No real Node.js runtime — browser JS only.';
+ + 'FS: list/read/write/delete | NVFS: nvfs-resolve | Web: search/fetch [POST=method+body] | Mod: view/edit/test/reload | Config | Skills | VaultMind | wallet-lookup | get_usage_stats\\n'
+ + 'web_fetch: GET for pages, POST with method+body for JSON APIs. JS: node <file.js> or node -e "expr" (browser JS only).\n'
+ + 'Other: floating, webdav, cron, vault, sandbox, agents, wpm, mindmap, memgraph, world-model, predict, guardrail, tar, secret-* — via help.\n'
+ + 'predict_task: estimate duration before complex tasks. world_model_query: ask about system state, changes, health.';
if (profilePrompt) {
base = profilePrompt + '\n\n' + base;
}
@@ -99,6 +99,18 @@ buildSystemPrompt() {
if (lp.systemPrompt.prepend) base = lp.systemPrompt.prepend + '\n\n' + base;
if (lp.systemPrompt.append) base = base + '\n\n' + lp.systemPrompt.append;
}
+ // Inject auto-fix suggestions from DebugSupervisorModule
+ try {
+ var ds = this.terminal?.moduleManager?.modules?.DebugSupervisorModule;
+ if (ds && this._autoFixSuggestions && this._autoFixSuggestions.length > 0) {
+ base += '\n\n[Auto-Fix Suggestions]\n';
+ for (var fi = 0; fi < this._autoFixSuggestions.length; fi++) {
+ var s = this._autoFixSuggestions[fi];
+ base += '- ' + s.module + ': ' + s.suggestion + ' (' + s.reason + ')\n';
+ }
+ base += '[/Auto-Fix Suggestions]';
+ }
+ } catch (e) {}
return base;
}
getToolDefinitions() {
@@ -165,6 +177,13 @@ buildSystemPrompt() {
}, {
type: 'function',
function: {
+ name: 'get_dashboard_data',
+ description: 'Full dashboard telemetry: modules, agents, memory, VFS, Ollama, token usage, tool latency, system health',
+ parameters: { type: 'object', properties: {}, required: [] }
+ }
+ }, {
+ type: 'function',
+ function: {
name: 'get_help',
description: 'Command reference',
parameters: { type: 'object', properties: {}, required: [] }
@@ -231,15 +250,18 @@ buildSystemPrompt() {
type: 'function',
function: {
name: 'edit_module',
- description: 'Edit module source (DESTRUCTIVE)',
- parameters: { type: 'object', properties: { name: { type: 'string', description: 'Module class name' } }, required: ['name'] }
+ description: 'Edit module source (DESTRUCTIVE). If code is provided, writes directly to VFS and auto-tests. If no code, opens the visual editor (requires manual Save).',
+ parameters: { type: 'object', properties: { name: { type: 'string', description: 'Module class name' }, code: { type: 'string', description: 'Full new source code. When provided, writes directly and auto-tests without opening the editor.' } }, required: ['name'] }
}
}, {
type: 'function',
function: {
name: 'test_module',
- description: 'Test edited module from /_mods/',
- parameters: { type: 'object', properties: { name: { type: 'string', description: 'Module class name' } }, required: ['name'] }
+ description: 'Test an edited module: loads it, checks JS errors, verifies commands registered, runs optional test assertions. Auto-rolls-back on failure.',
+ parameters: { type: 'object', properties: {
+ name: { type: 'string', description: 'Module class name' },
+ tests: { type: 'array', description: 'Optional test assertions: [{command, expect}]', items: { type: 'object', properties: { command: { type: 'string' }, expect: { type: 'string' } } } }
+ }, required: ['name'] }
}
}, {
type: 'function',
@@ -440,7 +462,7 @@ buildSystemPrompt() {
required: []
}
}
-}, {
+ }, {
type: 'function',
function: {
name: 'smart_context_set',
@@ -471,7 +493,7 @@ buildSystemPrompt() {
type: 'function',
function: {
name: 'vaultmind_query',
- description: "Query VaultMind AI for Hyperliquid market data (prices, portfolio, transactions, copy-trade) or ask an AI agent. Uses same-origin /vmai/ API.",
+ description: "Query VaultMind AI for Hyperliquid market data (prices, portfolio, transactions, copy-trade) or ask an AI agent. Uses same-origin /vmai-api/ API.",
parameters: {
type: 'object',
properties: {
@@ -533,11 +555,11 @@ buildSystemPrompt() {
},
required: []
}
- },
- {
- type: 'function',
- function: {
- name: 'agent_loop_list',
+ }
+ }, {
+ type: 'function',
+ function: {
+ name: 'agent_loop_list',
description: 'List all available agent loop profiles with their settings and benchmark results.',
parameters: { type: 'object', properties: {}, required: [] }
}
@@ -589,9 +611,77 @@ buildSystemPrompt() {
required: []
}
}
- }
- ];
- }
+ },
+ {
+ type: 'function',
+ function: {
+ name: 'predict_task',
+ description: 'Predict task duration, tool turns, and confidence based on historical execution data. Use before starting complex tasks to estimate resource needs.',
+ parameters: {
+ type: 'object',
+ properties: {
+ task: { type: 'string', description: 'Task description to predict' }
+ },
+ required: ['task']
+ }
+ }
+ },
+ {
+ type: 'function',
+ function: {
+ name: 'world_model_query',
+ description: 'Query the system world model for self-awareness: modules, skills, files, agents, health, memory graph, recent changes, active projects, telemetry. Ask questions like "what changed" or "how many modules".',
+ parameters: {
+ type: 'object',
+ properties: {
+ question: { type: 'string', description: 'What to ask: modules, skills, files, agents, health, memory, changes, projects, telemetry, summary' }
+ },
+ required: ['question']
+ }
+ }
+ },
+ {
+ type: 'function',
+ function: {
+ name: 'health_check',
+ description: 'Run a system health check after a module edit. Verifies: no JS errors, all core commands work, world model is valid, agents are alive.',
+ parameters: { type: 'object', properties: {} }
+ }
+ },
+ {
+ type: 'function',
+ function: {
+ name: 'diff_module',
+ description: 'Show a diff between the original module source and the edited version in _mods/. Returns line-by-line changes.',
+ parameters: { type: 'object', properties: { name: { type: 'string', description: 'Module class name' } }, required: ['name'] }
+ }
+ },
+ {
+ type: 'function',
+ function: {
+ name: 'ship_module',
+ description: 'Full pipeline: edit → test → health_check → regression suite. Auto-rolls-back on any failure. Set commit:true to also persist to server (requires human approval). Without commit, runs as dry-run.',
+ parameters: { type: 'object', properties: {
+ name: { type: 'string', description: 'Module class name' },
+ code: { type: 'string', description: 'Full new source code' },
+ tests: { type: 'array', description: 'Test assertions [{command, expect}]', items: { type: 'object', properties: { command: { type: 'string' }, expect: { type: 'string' } } } },
+ commit: { type: 'boolean', description: 'If true, request server persistence (needs human approval). Default: false.' }
+ }, required: ['name', 'code'] }
+ }
+ },
+ {
+ type: 'function',
+ function: {
+ name: 'commit_module',
+ description: 'Request to persist an edited module from VFS _mods/ to the server file system via WebDAV. Creates a pending commit — user must type commit-approve in the terminal to execute. Includes bundle sync and version bump.',
+ parameters: { type: 'object', properties: {
+ name: { type: 'string', description: 'Module class name' },
+ file: { type: 'string', description: 'Target server path (e.g. modules/ext/shell.js). Auto-detect from module registry if omitted.' }
+ }, required: ['name'] }
+ }
+ }
+ ];
+ }
getHelpInfo() {
return {
'ai': 'ai <question> — Ask the AI assistant anything',
@@ -614,6 +704,8 @@ buildSystemPrompt() {
'reset-mem': 'reset-mem — Reset AI memory (logs, context, profile, guardrails) without losing VFS or config',
'vaultmind-connect': 'vaultmind-connect [testnet|mainnet] — Connect VaultMind wallet using hyperliquid_key from SecretVault',
'vaultmind-status': 'vaultmind-status — Check VaultMind session status',
+ 'vaultmind-query': 'vaultmind-query <prices|portfolio|transactions|copy-trade|chat> [message] — Query VaultMind API',
+ 'wallet-lookup': 'wallet-lookup <address> — Look up Hyperliquid wallet summary',
};
}
registerCommands() {
@@ -629,6 +721,21 @@ 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['commit-approve'] = (args) => this._executeCommit(args.join(' '));
+ this.commands['commit-deny'] = () => {
+ if (!this._pendingCommit) return this.terminal.print('No pending commit.', 'warning');
+ const name = this._pendingCommit.name;
+ this._pendingCommit = null;
+ this.terminal.print('❌ Commit denied for ' + name, 'warning');
+ };
+ this.commands['commit-status'] = () => {
+ if (!this._pendingCommit) return this.terminal.print('No pending commit.', 'info');
+ const pc = this._pendingCommit;
+ this.terminal.print('📋 Pending commit: ' + pc.name, 'info');
+ this.terminal.print(' Target: ' + pc.serverPath, 'info');
+ if (pc.bundlePath) this.terminal.print(' Bundle: ' + pc.bundlePath, 'info');
+ this.terminal.print(' Changes: ' + pc.changes + ' line(s)', 'info');
+ };
this.commands['agent-loops'] = (args) => this.agentLoopsCmd(args);
this.commands['bench'] = (args) => this.benchCmd(args);
this.commands['bench-compare'] = (args) => this.benchCompareCmd(args);
@@ -649,6 +756,8 @@ buildSystemPrompt() {
this.commands['reset-mem'] = () => this.resetMem();
this.commands['vaultmind-connect'] = (args) => this._vaultmindConnectCmd(args);
this.commands['vaultmind-status'] = () => this._vaultmindStatusCmd();
+ this.commands['vaultmind-query'] = (args) => this._vaultmindQueryCmd(args);
+ this.commands['wallet-lookup'] = (args) => this._walletLookupCmd(args);
}
async chat(args) {
const userMessage = args.join(' ');
@@ -668,6 +777,18 @@ buildSystemPrompt() {
}
this._chatLock = true;
this.terminal.markDirty();
+ // Auto-route context based on intent
+ try {
+ const scm = this.terminal?.moduleManager?.modules?.SmartContextModule;
+ if (scm && typeof scm.autoRouteContext === 'function') {
+ const routeResult = scm.autoRouteContext(userMessage);
+ if (routeResult.switched) {
+ this.terminal.print('🔄 Context auto-switched: ' + routeResult.from + ' → ' + routeResult.to + ' (' + routeResult.confidence + '% confidence)', 'info');
+ // Rebuild system prompt after context switch
+ this.systemPrompt = this.buildSystemPrompt();
+ }
+ }
+ } catch (e) { console.warn('[AI] Auto-routing error:', e.message); }
// Re-read context from localStorage (auto-restore may have written it after init)
this._loadContext();
// Compress context if it exceeds the token threshold
@@ -751,6 +872,24 @@ buildSystemPrompt() {
if (loopResult.toolTurns >= 2 && !this._cancelled) {
await this.afterActionReflection(userMessage, loopResult.toolTurns);
}
+ // Feed result to predictor for learning (cold module activation)
+ if (loopResult && !this._cancelled && loopResult.toolTurns >= 1) {
+ try {
+ const predictor = this.terminal.moduleManager.modules['PredictorModule'];
+ if (predictor && typeof predictor.recordResult === 'function') {
+ const features = predictor._extractFeatures(userMessage);
+ const duration = (Date.now() - lockStart) / 1000;
+ predictor.recordResult(features, duration, loopResult.toolTurns, !this._cancelled);
+ }
+ } catch (e) { /* predictor learning is best-effort */ }
+ // Record task in memory graph for direct AI conversations (not just orchestrator)
+ try {
+ const mg = this.terminal.moduleManager.modules['MemoryGraphModule'];
+ if (mg && typeof mg.recordTask === 'function') {
+ mg.recordTask(userMessage, loopResult.finalAnswer || '', 'ai-assistant');
+ }
+ } catch (e) { /* memory graph is best-effort */ }
+ }
// Self-improvement: post-run anomaly detection
if (loopResult && !this._cancelled) {
try {
@@ -804,6 +943,280 @@ buildSystemPrompt() {
});
}
}
+ // ─── Self-Improvement Helpers ───
+ async _captureCommandOutput(cmd) {
+ const originalPrint = this.terminal.print.bind(this.terminal);
+ let output = '';
+ this.terminal.print = (text, type) => { output += text + '\n'; };
+ try { await this.terminal.security.checkAndRun(cmd); }
+ finally { this.terminal.print = originalPrint; }
+ return output.trim();
+ }
+ async _captureShellCommand(cmd) {
+ // Routes through executeCommand (shell parser) for >, |, &&, export, etc.
+ const originalPrint = this.terminal.print.bind(this.terminal);
+ let output = '';
+ this.terminal.print = (text, type) => { output += text + '\n'; };
+ try { await this.terminal.executeCommand(cmd); }
+ finally { this.terminal.print = originalPrint; }
+ return output.trim();
+ }
+ async _captureConsoleErrors(durationMs) {
+ durationMs = durationMs || 500;
+ var errors = [];
+ var origError = console.error;
+ var origOnError = window.onerror;
+ console.error = function() {
+ var args = Array.prototype.slice.call(arguments);
+ errors.push(args.join(' '));
+ };
+ window.onerror = function(msg, src, line, col, err) {
+ errors.push(msg + (err ? ' — ' + err.message : ''));
+ };
+ await new Promise(function(r) { setTimeout(r, durationMs); });
+ console.error = origError;
+ window.onerror = origOnError;
+ return errors;
+ }
+ _regressionTasks() {
+ return [
+ { id: 'shell-export', module: 'ShellModule', command: 'export REGTEST=ok', expect: 'REGTEST', useShell: true },
+ { id: 'shell-redirect', module: 'ShellModule', command: 'echo hi > _regtest.txt', expect: 'Redirected', useShell: true },
+ { id: 'shell-chain', module: 'ShellModule', command: 'echo a && echo b', expect: 'b', useShell: true },
+ { id: 'shell-pipe', module: 'ShellModule', command: 'echo hello | grep hello', expect: 'hello', useShell: true },
+ { id: 'fs-ls', module: 'SystemModule', command: 'ls', expect: '' },
+ { id: 'fs-pwd', module: 'SystemModule', command: 'pwd', expect: '/' },
+ { id: 'orch-agents', module: 'OrchestratorModule', command: 'list-agents', expect: 'agent' },
+ { id: 'ai-tools', module: 'WebX11AIAssistantModule', command: 'list_modules', expect: 'Module' },
+ { id: 'calc-basic', module: 'CalcModule', command: 'calc 1+1', expect: '2' },
+ { id: 'calc-advanced', module: 'CalcModule', command: 'calc 2*3', expect: '6' },
+ { id: 'webdav-ls', module: 'WebDAVModule', command: 'webdav-ls', expect: '' },
+ { id: 'cron-list', module: 'CronModule', command: 'cron-list', expect: '' },
+ { id: 'theme-list', module: 'ThemeModule', command: 'theme-list', expect: '' },
+ { id: 'mindmap-list', module: 'MindmapModule', command: 'mindmap-list', expect: '' },
+ { id: 'media-status', module: 'MediaModule', command: 'media-status', expect: '' },
+ { id: 'skill-list', module: 'SkillPackCoreModule', command: 'skill-list', expect: '' },
+ { id: 'crypto-encode', module: 'CryptoGzipModule', command: 'crypto-encode test', expect: '' },
+ { id: 'debug-toggle', module: 'DebugPanelModule', command: 'debug', expect: '' },
+ { id: 'voice-status', module: 'VoiceModule', command: 'voice-status', expect: '' },
+ { id: 'wpm-status', module: 'WPMModule', command: 'wpm-status', expect: '' },
+ { id: 'console-help', module: 'ConsoleModule', command: 'console-help', expect: '' },
+ { id: 'tutorial-status', module: 'TutorialModule', command: 'tutorial-status', expect: '' },
+ ];
+ }
+ async _runRegressionSuite(moduleName) {
+ const tests = this._regressionTasks().filter(t => t.module === moduleName);
+ if (tests.length === 0) return { module: moduleName, total: 0, passed: 0, failed: 0, results: [], allPassed: true, message: 'No regression tests for ' + moduleName };
+ const results = [];
+ for (const t of tests) {
+ try {
+ const out = t.useShell ? await this._captureShellCommand(t.command) : await this._captureCommandOutput(t.command);
+ const pass = t.expect ? out.includes(t.expect) : out.length > 0;
+ results.push({ id: t.id, command: t.command, pass, output: out.substring(0, 100) });
+ } catch (e) {
+ results.push({ id: t.id, command: t.command, pass: false, output: 'Error: ' + e.message });
+ }
+ }
+ const passed = results.filter(r => r.pass).length;
+ return { module: moduleName, total: results.length, passed, failed: results.length - passed, results, allPassed: passed === results.length };
+ }
+ // ─── Phase D: Server Persistence ───
+ async _webdavPush(remotePath, content) {
+ const webdav = this.terminal.moduleManager.modules['WebDAVModule'];
+ if (!webdav) throw new Error('WebDAV module not loaded');
+ // Use the webdav module's internal request method
+ const resp = await webdav._webdavRequest('PUT', remotePath, {
+ body: content,
+ headers: { 'Content-Type': 'text/plain' }
+ });
+ if (!resp.ok && resp.status !== 201 && resp.status !== 204) {
+ throw new Error('WebDAV PUT failed: HTTP ' + resp.status);
+ }
+ return true;
+ }
+ async _webdavGet(remotePath) {
+ const webdav = this.terminal.moduleManager.modules['WebDAVModule'];
+ if (!webdav) throw new Error('WebDAV module not loaded');
+ const resp = await webdav._webdavRequest('GET', remotePath);
+ if (!resp.ok) throw new Error('WebDAV GET failed: HTTP ' + resp.status);
+ return await resp.text();
+ }
+ _extractClassName(code) {
+ const m = code.match(/^class\s+(\w+)/m);
+ return m ? m[1] : null;
+ }
+ _extractClassBlock(code, className) {
+ // Extract the class definition from standalone code (without self-registration block)
+ const classStart = code.indexOf('class ' + className);
+ if (classStart === -1) return null;
+ // Find matching closing brace by counting
+ let depth = 0;
+ let end = classStart;
+ for (let i = classStart; i < code.length; i++) {
+ if (code[i] === '{') depth++;
+ else if (code[i] === '}') {
+ depth--;
+ if (depth === 0) { end = i + 1; break; }
+ }
+ }
+ return code.substring(classStart, end);
+ }
+ async _syncBundle(bundlePath, modName, newCode) {
+ try {
+ // Download current bundle
+ const bundle = await this._webdavGet(bundlePath);
+ // Find the class block in the bundle
+ const className = modName;
+ const classStart = bundle.indexOf('class ' + className);
+ if (classStart === -1) return { synced: false, reason: 'Class not found in bundle' };
+ // Find matching closing brace
+ let depth = 0;
+ let classEnd = classStart;
+ for (let i = classStart; i < bundle.length; i++) {
+ if (bundle[i] === '{') depth++;
+ else if (bundle[i] === '}') {
+ depth--;
+ if (depth === 0) { classEnd = i + 1; break; }
+ }
+ }
+ // Extract new class block from the edited code
+ const newClassBlock = this._extractClassBlock(newCode, className);
+ if (!newClassBlock) return { synced: false, reason: 'Could not extract class from new code' };
+ // Replace in bundle
+ const newBundle = bundle.substring(0, classStart) + newClassBlock + bundle.substring(classEnd);
+ // Validate with vm.Script equivalent (Function constructor)
+ try { new Function(newBundle); } catch (e) {
+ return { synced: false, reason: 'Bundle syntax error after replace: ' + e.message };
+ }
+ // Upload
+ await this._webdavPush(bundlePath, newBundle);
+ return { synced: true, bytes: newBundle.length };
+ } catch (e) {
+ return { synced: false, reason: e.message };
+ }
+ }
+ _parseVersion(ver) {
+ // Handle both old format (2026-07-06-v4.6.01) and new format (v4.6.01)
+ const m = ver.match(/v(\d+)\.(\d+)\.(\d+)/);
+ if (!m) return { major: 4, minor: 6, patch: 0 };
+ return { major: parseInt(m[1]), minor: parseInt(m[2]), patch: parseInt(m[3]) };
+ }
+ _bumpVersionString(ver) {
+ const v = this._parseVersion(ver);
+ return 'v' + v.major + '.' + v.minor + '.' + (v.patch + 1);
+ }
+ async _bumpVersion() {
+ // Read current version from the page
+ const currentVer = window.NEWOS4_VERSION || 'v4.6.01';
+ const newVer = this._bumpVersionString(currentVer);
+ // 1. Update main.html — replace all ?v= occurrences
+ const mainHtml = await this._webdavGet('main.html');
+ const updatedHtml = mainHtml.replace(/\?v=[^"']+/g, '?v=' + newVer);
+ await this._webdavPush('main.html', updatedHtml);
+ // 2. Update module-loader.js — replace LATEST_VER
+ const loader = await this._webdavGet('modules/core/module-loader.js');
+ const updatedLoader = loader.replace(/const LATEST_VER = '[^']+'/, "const LATEST_VER = '" + newVer + "'");
+ await this._webdavPush('modules/core/module-loader.js', updatedLoader);
+ return { from: currentVer, to: newVer };
+ }
+ _calculateConfidence(modName, testResult, healthResult, regResult, changeCount) {
+ var score = 100;
+ if (changeCount > 50) score -= 20;
+ if (changeCount > 100) score -= 20;
+ if (regResult.total === 0) score -= 30;
+ var critical = ['ShellModule', 'SystemModule', 'OrchestratorModule', 'WebX11AIAssistantModule'];
+ if (critical.indexOf(modName) !== -1) score -= 15;
+ if (testResult.tests && testResult.tests.length >= 3) score += 10;
+ if (healthResult.jsErrors && healthResult.jsErrors.length > 0) score -= 50;
+ return Math.max(0, Math.min(100, score));
+ }
+
+ async _executeCommit(comment) {
+ const pc = this._pendingCommit;
+ if (!pc) return this.terminal.print('No pending commit. Use commit_module first.', 'warning');
+ // Rate limit check
+ const today = new Date().toISOString().split('T')[0];
+ const commitLog = this.terminal.fs.fsReadJSON('/_mods/commit-log.json') || {};
+ if (commitLog[today] && commitLog[today] >= 5) {
+ return this.terminal.print('⚠️ Rate limit: 5 commits today. Try tomorrow.', 'warning');
+ }
+ // Cooldown check (10 min)
+ if (commitLog._lastCommitTime && Date.now() - commitLog._lastCommitTime < 600000) {
+ const wait = Math.ceil((600000 - (Date.now() - commitLog._lastCommitTime)) / 60000);
+ return this.terminal.print('⏱ Cooldown: ' + wait + ' min until next commit.', 'warning');
+ }
+ this.terminal.print('🚀 Executing commit for ' + pc.name + '...', 'info');
+ const results = { module: pc.name, steps: [] };
+ // Step 1: Push standalone file
+ try {
+ await this._webdavPush(pc.serverPath, pc.code);
+ results.steps.push({ step: 'standalone', status: 'ok', path: pc.serverPath });
+ this.terminal.print(' ✅ Standalone: ' + pc.serverPath, 'success');
+ } catch (e) {
+ results.steps.push({ step: 'standalone', status: 'fail', error: e.message });
+ this.terminal.print(' ❌ Standalone failed: ' + e.message, 'error');
+ return results;
+ }
+ // Step 1b: Post-commit syntax verification
+ try {
+ const verifyResp = await fetch(pc.serverPath + '?v=' + Date.now());
+ const committedCode = await verifyResp.text();
+ new Function(committedCode);
+ results.steps.push({ step: 'verify', status: 'ok' });
+ this.terminal.print(' ✅ Post-commit syntax check passed', 'success');
+ } catch (e) {
+ results.steps.push({ step: 'verify', status: 'fail', error: e.message });
+ this.terminal.print(' ❌ Post-commit syntax check failed: ' + e.message, 'error');
+ // Auto-revert
+ try {
+ await this._webdavPush(pc.serverPath, pc.origCode || '');
+ this.terminal.print(' ⏪ Auto-reverted to original code', 'warning');
+ results.reverted = true;
+ results.syntaxError = e.message;
+ } catch (revertErr) {
+ this.terminal.print(' ❌ Auto-revert FAILED: ' + revertErr.message, 'error');
+ results.revertFailed = true;
+ }
+ this._pendingCommit = null;
+ return results;
+ }
+ // Step 2: Sync bundle
+ if (pc.bundlePath) {
+ try {
+ const syncResult = await this._syncBundle(pc.bundlePath, pc.name, pc.code);
+ results.steps.push({ step: 'bundle', status: syncResult.synced ? 'ok' : 'skip', detail: syncResult });
+ if (syncResult.synced) this.terminal.print(' ✅ Bundle: ' + pc.bundlePath, 'success');
+ else this.terminal.print(' ⚠️ Bundle sync skipped: ' + syncResult.reason, 'warning');
+ } catch (e) {
+ results.steps.push({ step: 'bundle', status: 'fail', error: e.message });
+ this.terminal.print(' ❌ Bundle sync failed: ' + e.message, 'error');
+ }
+ }
+ // Step 3: Bump version
+ try {
+ const verResult = await this._bumpVersion();
+ results.steps.push({ step: 'version', status: 'ok', from: verResult.from, to: verResult.to });
+ this.terminal.print(' ✅ Version: ' + verResult.from + ' → ' + verResult.to, 'success');
+ results.newVersion = verResult.to;
+ } catch (e) {
+ results.steps.push({ step: 'version', status: 'fail', error: e.message });
+ this.terminal.print(' ⚠️ Version bump failed: ' + e.message, 'warning');
+ }
+ // Step 4: Cleanup _mods/
+ this.terminal.fs.deleteFile('_mods/' + pc.name + '.js');
+ this.terminal.fs.deleteFile('_mods/' + pc.name + '.orig.js');
+ this.terminal.fs.deleteFile('_mods/' + pc.name + '.meta.json');
+ // Step 5: Rate limit update
+ commitLog[today] = (commitLog[today] || 0) + 1;
+ commitLog._lastCommitTime = Date.now();
+ this.terminal.fs.fsWriteJSON('/_mods/commit-log.json', commitLog);
+ // Clear pending
+ this._pendingCommit = null;
+ this.terminal.print('🎉 Commit complete! Refresh to load ' + (results.newVersion || 'new version'), 'success');
+ if (comment) this.terminal.print(' Comment: ' + comment, 'info');
+ results.success = true;
+ return results;
+ }
_summarizeObservation(result, maxLen) {
if (!result || typeof result !== 'string' || result.length <= maxLen) return result || '';
const lines = result.split('\n');
@@ -1630,7 +2043,7 @@ buildSystemPrompt() {
}
try {
this.terminal.fs.root = JSON.parse(JSON.stringify(this._shadowSnapshot));
- this.terminal.fs.saveToLocalStorage();
+ this.terminal.fs.saveToStorage();
this._shadowSnapshot = null;
this.terminal.print('↩️ VFS rolled back to pre-shadow state', 'success');
} catch (e) {
@@ -1651,8 +2064,6 @@ buildSystemPrompt() {
'nvfs-index','nvfs-status','nvfs-resolve','nvfs-tree','nvfs-config','nvfs-alias','nvfs-sync',
'floating-add','floating-remove','floating-list','floating-pos','floating-clear','floating-toggle',
'debug','debug-ai','session-new','mem-debug','reset-mem',
- // Node bridge — alias for js-run
- 'node',
// Synced from terminal whitelist — read-only orchestration/A2A/agent commands
'orchestrate','orchestrate-parallel','orchestrate-decompose','orchestrate-result',
'orchestrator-status','orchestrator-list','orchestrator-config','orchestrator-restart-policy',
@@ -1769,6 +2180,9 @@ buildSystemPrompt() {
memory: performance.memory ? (performance.memory.usedJSHeapSize / 1048576).toFixed(1) + 'MB' : 'N/A'
});
}
+ case 'get_dashboard_data': {
+ return JSON.stringify(await this._collectDashboardData());
+ }
case 'get_help': {
const cmds = this.terminal.moduleManager.getAllCommands();
return JSON.stringify(Object.keys(cmds).sort());
@@ -1982,8 +2396,12 @@ buildSystemPrompt() {
if (!task) return JSON.stringify({ error: 'No task provided' });
const orch = this.terminal.moduleManager.modules['OrchestratorModule'];
if (!orch) return JSON.stringify({ error: 'Orchestrator not available' });
- orch.delegateTask(task);
- return JSON.stringify({ message: `Task delegated: ${task.substring(0, 80)}` });
+ try {
+ const result = await orch.delegateTask(task);
+ return JSON.stringify({ success: true, task: task.substring(0, 80), result: typeof result === 'string' ? result.substring(0, 500) : 'completed' });
+ } catch (e) {
+ return JSON.stringify({ error: 'Delegation failed: ' + e.message });
+ }
}
case 'spawn_agent': {
const role = a.role || 'worker';
@@ -2236,7 +2654,7 @@ buildSystemPrompt() {
return JSON.stringify({ error: 'Invalid endpoint. Valid: ' + validEps.join(', ') });
}
try {
- const epMap = { prices: '/vmai/market/prices', portfolio: '/vmai/portfolio', transactions: '/vmai/transactions/summary', 'copy-trade': '/vmai/copy-trading/traders', chat: '/vmai/chat' };
+ const epMap = { prices: '/vmai-api/market/prices', portfolio: '/vmai-api/portfolio', transactions: '/vmai-api/transactions/summary', 'copy-trade': '/vmai-api/copy-trading/traders', chat: '/vmai-api/chat' };
let url = epMap[ep];
let opts = { method: 'GET', headers: { 'Accept': 'application/json' } };
// Portfolio, transactions, copy-trade need auth token
@@ -2322,12 +2740,48 @@ buildSystemPrompt() {
}.bind(this));
return JSON.stringify({ success: true, message: "Benchmark started on '" + profileName + "'", tasks: taskIds || 'all' });
}
+ case 'predict_task': {
+ var predictor = this.terminal.moduleManager.modules['PredictorModule'];
+ if (!predictor) return JSON.stringify({ error: 'Predictor module not loaded' });
+ var taskDesc = a.task || '';
+ if (!taskDesc) return JSON.stringify({ error: 'task parameter required' });
+ var pFeatures = predictor._extractFeatures(taskDesc);
+ var pSimilar = predictor._findSimilar(pFeatures);
+ var pResult = predictor._computePrediction(pSimilar, pFeatures);
+ return JSON.stringify({
+ task: taskDesc.substring(0, 80),
+ estimatedDuration: pResult.duration + 's',
+ estimatedToolTurns: pResult.toolTurns,
+ confidence: Math.round(pResult.confidence * 100) + '%',
+ similarTasks: pResult.similarCount,
+ recommendation: pResult.confidence >= predictor._CONFIDENCE_THRESHOLD && pResult.duration > 30
+ ? 'Set TTL to ' + Math.ceil(pResult.duration * 1.5) + 's'
+ : 'No special action needed'
+ });
+ }
+ case 'world_model_query': {
+ var wm = this.terminal.moduleManager.modules['WorldModelModule'];
+ if (!wm) return JSON.stringify({ error: 'WorldModelModule not loaded' });
+ if (!wm._model) return JSON.stringify({ error: 'World model not synthesized yet — run world-model-refresh' });
+ var wmAnswer = wm._answerQuery(a.question || '');
+ return JSON.stringify({ question: a.question, answer: wmAnswer, version: wm._model.version, synthesized: wm._model.synthesized });
+ }
+ case 'health_check':
+ case 'diff_module':
+ case 'ship_module':
+ case 'commit_module':
+ return await this.runModuleTool(name, argsStr);
default:
return JSON.stringify({ error: `Unknown tool: ${name}` });
}
} catch (e) {
this._errorLog.push({ tool: name, args: a, error: e.message, timestamp: Date.now() });
if (this._errorLog.length > 20) this._errorLog.shift();
+ // Also feed into DebugSupervisorModule
+ try {
+ var ds = this.terminal?.moduleManager?.modules?.DebugSupervisorModule;
+ if (ds) ds.recordToolError(name, a, e.message);
+ } catch (dsErr) {}
return JSON.stringify({ error: e.message, stack: e.stack?.substring(0, 200) });
}
}
@@ -2421,10 +2875,61 @@ buildSystemPrompt() {
}
case 'test_module': {
const modName = a.name || '';
+ const tests = a.tests || null;
const code = this.terminal.fs.readFile(`_mods/${modName}.js`);
if (!code) return JSON.stringify({ error: `No local edit for ${modName}` });
- this.terminal.moduleManager.loadModuleFromCode(code, `blob://_mods/${modName}.js`);
- return `Module "${modName}" loaded from local edit for testing`;
+ // Save current module state for rollback
+ const oldInstance = this.terminal.moduleManager.modules[modName];
+ const oldCommands = oldInstance ? Object.keys(oldInstance.commands) : [];
+ // Load new version
+ const ok = await this.terminal.moduleManager.loadModuleFromCode(code, `blob://_mods/${modName}.js`);
+ if (!ok) {
+ if (oldInstance) this.terminal.moduleManager.modules[modName] = oldInstance;
+ return JSON.stringify({ error: 'Module failed to load', rolledBack: true });
+ }
+ // Check for JS errors after load
+ const jsErrors = await this._captureConsoleErrors(300);
+ if (jsErrors.length > 0) {
+ if (oldInstance) this.terminal.moduleManager.modules[modName] = oldInstance;
+ return JSON.stringify({ error: 'JS errors after load', errors: jsErrors.slice(0, 5), rolledBack: true });
+ }
+ // Verify commands registered
+ const newInstance = this.terminal.moduleManager.modules[modName];
+ const newCommands = newInstance ? Object.keys(newInstance.commands) : [];
+ const lostCommands = oldCommands.filter(c => !newCommands.includes(c));
+ if (lostCommands.length > 0) {
+ if (oldInstance) this.terminal.moduleManager.modules[modName] = oldInstance;
+ return JSON.stringify({ error: 'Commands lost after edit', lost: lostCommands, rolledBack: true });
+ }
+ // Run test assertions if provided
+ if (tests && Array.isArray(tests) && tests.length > 0) {
+ const results = [];
+ for (const t of tests) {
+ const cmd = t.command || '';
+ // Use shell parser for commands with >, |, &&, ||, ;, export, set
+ const useShell = /[>|&;]/.test(cmd) || cmd.startsWith('export ') || cmd.startsWith('set ');
+ const output = useShell ? await this._captureShellCommand(cmd) : await this._captureCommandOutput(cmd);
+ const passed = t.expect ? output.includes(t.expect) : output.length > 0;
+ results.push({ command: cmd, expected: t.expect || '(any output)', actual: output.substring(0, 200), pass: passed });
+ }
+ const failed = results.filter(r => !r.pass).length;
+ if (failed > 0) {
+ if (oldInstance) this.terminal.moduleManager.modules[modName] = oldInstance;
+ return JSON.stringify({ error: failed + '/' + results.length + ' tests failed', results, rolledBack: true });
+ }
+ return JSON.stringify({ success: true, module: modName, commands: newCommands.length, tests: results });
+ }
+ // Smoke test: run first command if available
+ if (newCommands.length > 0) {
+ try {
+ const smokeOut = await this._captureCommandOutput(newCommands[0]);
+ return JSON.stringify({ success: true, module: modName, commands: newCommands.length, smokeTest: { command: newCommands[0], output: smokeOut.substring(0, 200) } });
+ } catch (e) {
+ if (oldInstance) this.terminal.moduleManager.modules[modName] = oldInstance;
+ return JSON.stringify({ error: 'Smoke test failed: ' + e.message, rolledBack: true });
+ }
+ }
+ return JSON.stringify({ success: true, module: modName, commands: newCommands.length });
}
case 'reload_module': {
const modName = a.name || '';
@@ -2488,11 +2993,181 @@ buildSystemPrompt() {
}
return JSON.stringify({ modules, total: modules.length, hasLocalEdits: hasMods });
}
+ case 'health_check': {
+ const results = {};
+ const coreCmds = ['ls', 'pwd', 'echo test', 'help', 'grep', 'find', 'tree', 'cat'];
+ results.coreCommands = {};
+ for (const cmd of coreCmds) {
+ try {
+ const out = await this._captureCommandOutput(cmd);
+ results.coreCommands[cmd] = (out.length > 0 || cmd === 'ls') ? 'ok' : 'empty output';
+ } catch (e) {
+ results.coreCommands[cmd] = 'FAIL: ' + e.message;
+ }
+ }
+ const mm = this.terminal.moduleManager;
+ results.moduleCount = Object.keys(mm.modules).length;
+ const wm = this.terminal.fs.fsReadJSON('system/world-model.json');
+ results.worldModelVersion = wm ? wm.version : 'not found';
+ results.versionMatch = wm && wm.version === (typeof window !== 'undefined' ? window.NEWOS4_VERSION : '');
+ const orch = mm.modules['OrchestratorModule'];
+ if (orch) {
+ const agents = Object.values(orch._agents);
+ results.agents = { total: agents.length, healthy: agents.filter(a => a.status !== 'failed').length, failed: agents.filter(a => a.status === 'failed').length };
+ }
+ results.jsErrors = await this._captureConsoleErrors(500);
+ const allCoreOk = Object.values(results.coreCommands).every(v => v === 'ok');
+ results.healthy = allCoreOk && results.versionMatch && results.jsErrors.length === 0;
+ return JSON.stringify(results);
+ }
+ case 'diff_module': {
+ const modName = a.name || '';
+ const newCode = this.terminal.fs.readFile(`_mods/${modName}.js`);
+ if (!newCode) return JSON.stringify({ error: `No edit for ${modName}` });
+ let origCode = this.terminal.fs.readFile(`_mods/${modName}.orig.js`);
+ if (!origCode || origCode === 'placeholder') {
+ const instance = this.terminal.moduleManager.modules[modName];
+ if (instance) origCode = instance.constructor.toString();
+ }
+ if (!origCode) return JSON.stringify({ error: 'No original source found' });
+ const origLines = origCode.split('\n');
+ const newLines = newCode.split('\n');
+ const changes = [];
+ const maxLines = Math.max(origLines.length, newLines.length);
+ for (let i = 0; i < maxLines; i++) {
+ if (origLines[i] !== newLines[i]) {
+ if (origLines[i] && !newLines[i]) changes.push({ line: i+1, type: 'removed', content: origLines[i].substring(0, 120) });
+ else if (!origLines[i] && newLines[i]) changes.push({ line: i+1, type: 'added', content: newLines[i].substring(0, 120) });
+ else changes.push({ line: i+1, type: 'changed', old: (origLines[i]||'').substring(0, 80), new: (newLines[i]||'').substring(0, 80) });
+ }
+ }
+ return JSON.stringify({ module: modName, originalLines: origLines.length, newLines: newLines.length, changes: changes.length, diff: changes.slice(0, 50) });
+ }
+ case 'ship_module': {
+ const modName = a.name || '';
+ const newCode = a.code || '';
+ const tests = a.tests || [];
+ const shouldCommit = a.commit === true;
+ if (!modName || !newCode) return JSON.stringify({ error: 'name and code required' });
+ // Step 1: Write to _mods/
+ this.terminal.fs.writeFile(`_mods/${modName}.js`, newCode);
+ this.terminal.fs.writeFile(`_mods/${modName}.meta.json`, JSON.stringify({ editedAt: new Date().toISOString(), editedBy: 'ship_module' }));
+ // Step 2: Test
+ const testResult = await this.runModuleTool('test_module', JSON.stringify({ name: modName, tests }));
+ const testParsed = JSON.parse(testResult);
+ if (testParsed.error) {
+ return JSON.stringify({ step: 'test', error: testParsed.error, rolledBack: testParsed.rolledBack, pipeline: 'aborted at test' });
+ }
+ // Step 3: Health check
+ const healthResult = await this.runModuleTool('health_check', '{}');
+ const healthParsed = JSON.parse(healthResult);
+ if (!healthParsed.healthy) {
+ await this.runModuleTool('reload_module', JSON.stringify({ name: modName }));
+ return JSON.stringify({ step: 'health_check', error: 'System unhealthy after edit', health: healthParsed, pipeline: 'aborted at health_check, rolled back' });
+ }
+ // Step 4: Regression suite
+ const regResult = await this._runRegressionSuite(modName);
+ if (!regResult.allPassed) {
+ await this.runModuleTool('reload_module', JSON.stringify({ name: modName }));
+ return JSON.stringify({ step: 'regression', error: 'Regression tests failed', regression: regResult, pipeline: 'aborted at regression, rolled back' });
+ }
+ // Step 5: Calculate confidence score
+ const changeCount = newCode.split('\n').length;
+ const confidence = this._calculateConfidence(modName, testParsed, healthParsed, regResult, changeCount);
+ // Step 6: Commit (if requested)
+ if (shouldCommit) {
+ if (confidence >= 70) {
+ // Auto-commit: execute directly
+ const commitResult = await this.runModuleTool('commit_module', JSON.stringify({ name: modName }));
+ const commitParsed = JSON.parse(commitResult);
+ if (commitParsed.pendingCommit) {
+ // Execute the pending commit immediately
+ const execResult = await this._executeCommit('auto: confidence=' + confidence);
+ return JSON.stringify({ success: true, pipeline: 'edit → test → health → regression → AUTO-COMMIT (confidence=' + confidence + ')', module: modName, confidence: confidence, testResult: testParsed, health: healthParsed, regression: regResult, autoCommitted: true, commitResult: execResult });
+ }
+ return JSON.stringify({ success: true, pipeline: 'edit → test → health → regression → auto-commit', module: modName, confidence: confidence, testResult: testParsed, health: healthParsed, regression: regResult, commit: commitParsed });
+ } else {
+ // Confidence too low — human approval required
+ const commitResult = await this.runModuleTool('commit_module', JSON.stringify({ name: modName }));
+ return JSON.stringify({ success: true, pipeline: 'edit → test → health → regression → HUMAN APPROVAL REQUIRED (confidence=' + confidence + ' < 70)', module: modName, confidence: confidence, testResult: testParsed, health: healthParsed, regression: regResult, commit: JSON.parse(commitResult), humanApprovalRequired: true });
+ }
+ }
+ return JSON.stringify({ success: true, pipeline: 'edit → test → health → regression (dry-run)', module: modName, confidence: confidence, testResult: testParsed, health: healthParsed, regression: regResult, next: 'Call commit_module or ship_module with commit:true to persist' });
+ }
+ case 'commit_module': {
+ const modName = a.name || '';
+ const targetFile = a.file || '';
+ const newCode = this.terminal.fs.readFile(`_mods/${modName}.js`);
+ if (!newCode) return JSON.stringify({ error: `No local edit for ${modName}. Run edit_module or ship_module first.` });
+
+ // Auto-detect target file from module registry
+ let serverPath = targetFile;
+ if (!serverPath) {
+ const entry = this.terminal.moduleManager.moduleRegistry.find(m => m.name === modName);
+ if (entry && entry.url) {
+ serverPath = entry.url.replace(/^https?:\/\/[^/]+/, '').replace(/\?v=.*/, '');
+ }
+ }
+ if (!serverPath) return JSON.stringify({ error: 'Cannot determine server path. Provide file= parameter (e.g. modules/ext/shell.js).' });
+
+ // Get diff
+ let origCode = this.terminal.fs.readFile(`_mods/${modName}.orig.js`);
+ if (!origCode || origCode === 'placeholder') {
+ const instance = this.terminal.moduleManager.modules[modName];
+ if (instance) origCode = instance.constructor.toString();
+ }
+ const origLines = origCode ? origCode.split('\n') : [];
+ const newLines = newCode.split('\n');
+ let changeCount = 0;
+ for (let i = 0; i < Math.max(origLines.length, newLines.length); i++) {
+ if (origLines[i] !== newLines[i]) changeCount++;
+ }
+
+ // Detect bundle path
+ let bundlePath = null;
+ if (serverPath.includes('/modules/core/') || serverPath.includes('module-aiassist')) {
+ bundlePath = 'modules/core/core-bundle.js';
+ } else if (serverPath.includes('/modules/ext/')) {
+ bundlePath = 'modules/ext/critical-ext-bundle.js';
+ }
+
+ // Store pending commit
+ this._pendingCommit = {
+ name: modName,
+ code: newCode,
+ origCode: origCode || '',
+ serverPath: serverPath,
+ bundlePath: bundlePath,
+ changes: changeCount,
+ createdAt: Date.now()
+ };
+
+ this.terminal.print('📋 Commit request created for ' + modName, 'info');
+ this.terminal.print(' Target: ' + serverPath, 'info');
+ if (bundlePath) this.terminal.print(' Bundle: ' + bundlePath, 'info');
+ this.terminal.print(' Changes: ' + changeCount + ' line(s) different', 'info');
+ this.terminal.print(' Type \'commit-approve\' to persist or \'commit-deny\' to cancel', 'warning');
+
+ return JSON.stringify({
+ pendingCommit: true,
+ module: modName,
+ serverPath: serverPath,
+ bundlePath: bundlePath,
+ changes: changeCount,
+ newLines: newLines.length,
+ message: 'Commit request created. User must type commit-approve to execute.'
+ });
+ }
default: return JSON.stringify({ error: `Unknown module tool: ${name}` });
}
} catch (e) {
this._errorLog.push({ tool: name, args: a, error: e.message, timestamp: Date.now() });
if (this._errorLog.length > 20) this._errorLog.shift();
+ // Also feed into DebugSupervisorModule
+ try {
+ var ds = this.terminal?.moduleManager?.modules?.DebugSupervisorModule;
+ if (ds) ds.recordToolError(name, a, e.message);
+ } catch (dsErr) {}
return JSON.stringify({ error: e.message, stack: e.stack?.substring(0, 200) });
}
}
@@ -2827,6 +3502,19 @@ buildSystemPrompt() {
}
try { localStorage.setItem('webx11_ai_patterns', JSON.stringify(this._commandFreq)); } catch (e) {}
}
+ _buildToolToModuleMap() {
+ this._toolToModuleMap = {};
+ const mm = this.terminal.moduleManager;
+ if (!mm || !mm.modules) return;
+ for (const [modName, mod] of Object.entries(mm.modules)) {
+ if (mod.commands) {
+ for (const cmdName of Object.keys(mod.commands)) {
+ this._toolToModuleMap[cmdName] = modName;
+ }
+ }
+ }
+ }
+
async _autoCreateErrorSkill() {
const grouped = {};
for (const e of this._errorLog) {
@@ -3106,6 +3794,49 @@ buildSystemPrompt() {
this.terminal.print('❌ No VaultMind session. Use: vaultmind-connect [testnet|mainnet]', 'warning');
}
}
+ async _vaultmindQueryCmd(args) {
+ const ep = args[0];
+ const validEps = ['prices', 'portfolio', 'transactions', 'copy-trade', 'chat'];
+ if (!validEps.includes(ep)) {
+ this.terminal.print('Usage: vaultmind-query <prices|portfolio|transactions|copy-trade|chat> [message|agent]', 'warning'); return;
+ }
+ try {
+ const epMap = { prices: '/vmai-api/market/prices', portfolio: '/vmai-api/portfolio', transactions: '/vmai-api/transactions/summary', 'copy-trade': '/vmai-api/copy-trading/traders', chat: '/vmai-api/chat' };
+ let url = epMap[ep];
+ let opts = { method: 'GET', headers: { 'Accept': 'application/json' } };
+ const authEps = ['portfolio', 'transactions', 'copy-trade'];
+ if (authEps.includes(ep)) {
+ const token = localStorage.getItem('vaultmind_token');
+ if (token) opts.headers['Authorization'] = 'Bearer ' + token;
+ }
+ if (ep === 'chat') {
+ opts.method = 'POST';
+ opts.headers['Content-Type'] = 'application/json';
+ const token = localStorage.getItem('vaultmind_token');
+ if (token) opts.headers['Authorization'] = 'Bearer ' + token;
+ opts.body = JSON.stringify({
+ message: args.slice(1).join(' ') || 'What is the current market status?',
+ agent: 'market'
+ });
+ }
+ const resp = await fetch(url, opts);
+ const data = await resp.json();
+ this.terminal.print(JSON.stringify(data, null, 2), 'success');
+ } catch (e) {
+ this.terminal.print('VaultMind query error: ' + e.message, 'error');
+ }
+ }
+ async _walletLookupCmd(args) {
+ const addr = args[0];
+ if (!addr) { this.terminal.print('Usage: wallet-lookup <address>', 'warning'); return; }
+ try {
+ const resp = await fetch('/wallet/' + encodeURIComponent(addr));
+ const data = await resp.json();
+ this.terminal.print(JSON.stringify(data, null, 2), 'success');
+ } catch (e) {
+ this.terminal.print('Wallet lookup error: ' + e.message, 'error');
+ }
+ }
// ── Agent Loop Profile System ──
_defaultLoopProfile(name, desc) {
return {
@@ -3411,6 +4142,20 @@ buildSystemPrompt() {
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');
+ // Auto-fix: map failed tools to modules and suggest fix
+ if (!this._autoFixSuggestions) this._autoFixSuggestions = [];
+ if (!this._toolToModuleMap) this._buildToolToModuleMap();
+ for (var fi = 0; fi < a.tools.length; fi++) {
+ var failedMod = this._toolToModuleMap[a.tools[fi]];
+ if (failedMod && this._autoFixSuggestions.length < 3) {
+ this._autoFixSuggestions.push({
+ module: failedMod,
+ tool: a.tools[fi],
+ reason: 'Repeated tool failure in agent loop',
+ suggestion: 'Consider running ship_module to fix ' + failedMod
+ });
+ }
+ }
// Auto-correct: inject negative constraint into profile append
if (this._activeLoopProfile && this._activeLoopProfile.systemPrompt) {
var constraint = '[Constraint] Avoid tools that failed: ' + a.tools.join(', ') + '. Verify module state before calling these tools.';
@@ -3476,4 +4221,141 @@ buildSystemPrompt() {
if (profileAdjusted) msg += ' — profile auto-adjusted';
this.terminal.print(msg, 'info');
}
+ async _collectDashboardData() {
+ try {
+ const terminal = this.terminal;
+ const mm = terminal.moduleManager;
+ const om = terminal.ollamaManager;
+ const fs = terminal.fs;
+ const data = {};
+
+ // Version
+ data.version = window.NEWOS4_VERSION || 'v?';
+
+ // Modules
+ const mods = mm ? Object.keys(mm.modules) : [];
+ const registry = mm ? (mm.moduleRegistry || []) : [];
+ data.modules = {
+ loaded: mods.length,
+ total: registry.length || mods.length,
+ modifiedCount: 0
+ };
+ try {
+ const modsDir = fs ? fs.readDir('/_mods/') : null;
+ if (modsDir) data.modules.modifiedCount = modsDir.filter(function(e) { return e.type === 'file' && e.name.endsWith('.js') && !e.name.endsWith('.orig.js'); }).length;
+ } catch (e) {}
+
+ // Agents
+ const orch = mm ? mm.modules['OrchestratorModule'] : null;
+ if (orch && orch._agents) {
+ var agentList = Object.values(orch._agents);
+ data.agents = {
+ total: agentList.length,
+ busy: agentList.filter(function(a) { return a.status === 'busy'; }).length,
+ idle: agentList.filter(function(a) { return a.status === 'idle'; }).length,
+ sleeping: agentList.filter(function(a) { return a.status === 'sleeping'; }).length,
+ failed: agentList.filter(function(a) { return a.status === 'failed' || a.status === 'restarting'; }).length,
+ list: agentList.map(function(a) { return { id: a.id, role: a.role, status: a.status, caps: (a.capabilities || []).length }; })
+ };
+ } else {
+ data.agents = { total: 0, busy: 0, idle: 0, sleeping: 0, failed: 0, list: [] };
+ }
+
+ // Memory
+ var heapStr = 'N/A';
+ try { if (performance.memory) heapStr = (performance.memory.usedJSHeapSize / 1048576).toFixed(1) + 'MB'; } catch (e) {}
+ var graphNodes = 0;
+ var mg = mm ? mm.modules['MemoryGraphModule'] : null;
+ if (mg && mg._nodes) graphNodes = Object.keys(mg._nodes).length;
+ data.memory = { heap: heapStr, graphNodes: graphNodes };
+
+ // VFS
+ try {
+ if (fs) {
+ var fileCount = 0, dirCount = 0;
+ var walk = function(path) {
+ var entries = fs.ls(path);
+ if (!entries) return;
+ for (var i = 0; i < entries.length; i++) {
+ if (entries[i].type === 'file') fileCount++;
+ else { dirCount++; walk(path === '/' ? '/' + entries[i].name : path + '/' + entries[i].name); }
+ }
+ };
+ walk('/');
+ data.vfs = { files: fileCount, dirs: dirCount, size: (JSON.stringify(fs.root || {}).length / 1024).toFixed(1) + 'KB' };
+ }
+ } catch (e) { data.vfs = { files: 0, dirs: 0, size: '0KB' }; }
+
+ // Ollama
+ data.ollama = {
+ connected: om ? om.connected : false,
+ model: om ? om.currentModel : null,
+ provider: om && om.currentProvider ? om.currentProvider.name : null
+ };
+
+ // Skills
+ try {
+ var skillIdx = fs ? fs.fsReadJSON('/skills/index.json') : null;
+ data.skills = { count: skillIdx ? Object.keys(skillIdx).length : 0 };
+ } catch (e) { data.skills = { count: 0 }; }
+
+ // Token usage
+ try {
+ if (om && typeof om.getUsageStats === 'function') {
+ var usage = om.getUsageStats({ since_hours: 24 });
+ data.usage = usage;
+ }
+ } catch (e) { data.usage = { total: 0 }; }
+
+ // Tool latency
+ var sm = mm ? mm.modules['ServerMonitorModule'] : null;
+ if (sm && sm._latencyLog) {
+ var toolStats = {};
+ for (var i = 0; i < sm._latencyLog.length; i++) {
+ var entry = sm._latencyLog[i];
+ if (!toolStats[entry.name]) toolStats[entry.name] = { calls: 0, totalMs: 0, errors: 0, maxMs: 0 };
+ toolStats[entry.name].calls++;
+ toolStats[entry.name].totalMs += entry.duration;
+ toolStats[entry.name].errors += entry.error ? 1 : 0;
+ toolStats[entry.name].maxMs = Math.max(toolStats[entry.name].maxMs, entry.duration);
+ }
+ data.latency = Object.entries(toolStats)
+ .map(function(e) { return { name: e[0], avg: Math.round(e[1].totalMs / e[1].calls) + 'ms', max: e[1].maxMs + 'ms', calls: e[1].calls, errors: e[1].errors }; })
+ .sort(function(a, b) { return parseInt(b.avg) - parseInt(a.avg); })
+ .slice(0, 10);
+ } else {
+ data.latency = [];
+ }
+
+ // System health
+ var totalErrors = 0;
+ try {
+ var ds = mm ? mm.modules['DebugSupervisorModule'] : null;
+ if (ds && ds._errorDB) totalErrors += Object.keys(ds._errorDB.errors).length;
+ if (sm && sm._latencyLog) totalErrors += sm._latencyLog.filter(function(e) { return e.error; }).length;
+ } catch (e) {}
+ var predictor = mm ? mm.modules['PredictorModule'] : null;
+ var predictorAcc = 'N/A';
+ if (predictor && predictor._accuracy && predictor._accuracy.total > 0) {
+ predictorAcc = Math.round(predictor._accuracy.correct / predictor._accuracy.total * 100) + '%';
+ }
+ data.health = {
+ modules: data.modules.loaded + '/' + data.modules.total,
+ vfsSize: data.vfs ? data.vfs.size : '?',
+ online: typeof navigator !== 'undefined' ? navigator.onLine : true,
+ errors: totalErrors,
+ predictorAccuracy: predictorAcc
+ };
+
+ data.timestamp = Date.now();
+
+ // Write to localStorage for the dashboard HTML page to read
+ try { localStorage.setItem('webx11_dashboard_data', JSON.stringify(data)); } catch (e) {}
+
+ return data;
+ } catch (e) {
+ console.error('[Dashboard] Collection error:', e.message);
+ return { error: e.message, timestamp: Date.now() };
+ }
+ }
}