diff options
Diffstat (limited to 'modules/core/module-aiassist.js')
| -rw-r--r-- | modules/core/module-aiassist.js | 124 |
1 files changed, 115 insertions, 9 deletions
diff --git a/modules/core/module-aiassist.js b/modules/core/module-aiassist.js index 5273678..44a7574 100644 --- a/modules/core/module-aiassist.js +++ b/modules/core/module-aiassist.js @@ -67,7 +67,7 @@ buildSystemPrompt() { } catch (e) {} return 'Current date: ' + dateStr + 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\n' + + 'FS: list/read/write/delete | NVFS: nvfs-resolve | Web: search/fetch [POST=method+body] | Mod: view/edit/test/reload | Config | Skills | VaultMind: vaultmind_query(ep,msg,agent) — Hyperliquid prices/portfolio/chat\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'; } @@ -423,6 +423,21 @@ buildSystemPrompt() { required: ['topic'] } } + }, { + 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.", + parameters: { + type: 'object', + properties: { + endpoint: { type: 'string', description: 'prices, portfolio, transactions, copy-trade, or chat' }, + message: { type: 'string', description: 'Question for the AI agent (chat endpoint only)' }, + agent: { type: 'string', enum: ['market', 'trading', 'transaction', 'copy'], description: 'Which AI agent to query (chat endpoint only)' } + }, + required: ['endpoint'] + } + } }]; } getHelpInfo() { @@ -439,7 +454,7 @@ buildSystemPrompt() { '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))', 'mem-debug': 'mem-debug — Show memory breakdown by component', - 'reset-mem': 'reset-mem — Reset AI memory (logs, patterns) without losing VFS or config' + 'reset-mem': 'reset-mem — Reset AI memory (logs, context, profile, guardrails) without losing VFS or config' }; } registerCommands() { @@ -1859,6 +1874,30 @@ buildSystemPrompt() { case 'list_modified_modules': case 'get_module_status': return await this.runModuleTool(name, argsStr); + case 'vaultmind_query': { + const ep = a.endpoint; + const validEps = ['prices', 'portfolio', 'transactions', 'copy-trade', 'chat']; + if (!validEps.includes(ep)) { + return JSON.stringify({ error: 'Invalid endpoint. Valid: ' + validEps.join(', ') }); + } + try { + let url = '/vmai/' + ep; + let opts = { method: 'GET', headers: { 'Accept': 'application/json' } }; + if (ep === 'chat') { + opts.method = 'POST'; + opts.headers['Content-Type'] = 'application/json'; + opts.body = JSON.stringify({ + message: a.message || 'What is the current market status?', + agent: a.agent || 'market' + }); + } + const resp = await fetch(url, opts); + const data = await resp.json(); + return JSON.stringify({ success: true, data }); + } catch (e) { + return JSON.stringify({ error: 'VaultMind API error: ' + e.message }); + } + } default: return JSON.stringify({ error: `Unknown tool: ${name}` }); } @@ -2087,31 +2126,80 @@ buildSystemPrompt() { const errLogSize = this._errorLog.length; const freqKeys = Object.keys(this._commandFreq).length; const contextSize = this.context.length; + const factKeys = this._userProfile?.knownFacts ? Object.keys(this._userProfile.knownFacts).length : 0; + const shadowSize = this._shadowSnapshot ? JSON.stringify(this._shadowSnapshot).length : 0; + const guardrailSize = this._guardrailPolicy ? JSON.stringify(this._guardrailPolicy).length : 0; const vfsSize = (localStorage.getItem('webx11_filesystem') || '').length + (localStorage.getItem('webx11_filesystem_v2') || '').length; const aiCtxSize = (localStorage.getItem('webx11_ai_context') || '').length; + const aiPatternSize = (localStorage.getItem('webx11_ai_patterns') || '').length; + const userProfileSize = (localStorage.getItem('webx11_user_profile') || '').length; + const systemPromptSize = (localStorage.getItem('webx11_ai_system_prompt') || '').length; + const guardrailPolicySize = (localStorage.getItem('webx11_guardrail_policy') || '').length; this.terminal.print('=== Memory Debug ===', 'info'); this.terminal.print(` _toolCallLog: ${toolLogSize} entries (cap: 50)`, toolLogSize > 30 ? 'warning' : 'text-muted'); this.terminal.print(` _errorLog: ${errLogSize} entries (cap: 20)`, errLogSize > 10 ? 'warning' : 'text-muted'); - this.terminal.print(` _commandFreq: ${freqKeys} tracked patterns`, freqKeys > 15 ? 'warning' : 'text-muted'); + this.terminal.print(` _commandFreq: ${freqKeys} tracked patterns (cap: 30)`, freqKeys > 20 ? 'warning' : 'text-muted'); this.terminal.print(` AI context: ${contextSize} messages (max: ${this.maxContext * 2})`, 'text-muted'); + this.terminal.print(` User facts: ${factKeys} facts (cap: 50)`, 'text-muted'); this.terminal.print(` VFS localStorage: ${(vfsSize / 1024).toFixed(1)} KB`, 'text-muted'); this.terminal.print(` AI context localStorage: ${(aiCtxSize / 1024).toFixed(1)} KB`, 'text-muted'); + this.terminal.print(` AI patterns localStorage: ${(aiPatternSize / 1024).toFixed(1)} KB`, 'text-muted'); + this.terminal.print(` User profile localStorage: ${(userProfileSize / 1024).toFixed(1)} KB`, userProfileSize > 10240 ? 'warning' : 'text-muted'); + this.terminal.print(` System prompt localStorage: ${(systemPromptSize / 1024).toFixed(1)} KB`, systemPromptSize > 10240 ? 'warning' : 'text-muted'); + this.terminal.print(` Guardrail policy localStorage: ${(guardrailPolicySize / 1024).toFixed(1)} KB`, 'text-muted'); + this.terminal.print(` Runtime shadow snapshot: ${(shadowSize / 1024).toFixed(1)} KB`, shadowSize > 10240 ? 'warning' : 'text-muted'); + this.terminal.print(` Runtime guardrail policy: ${(guardrailSize / 1024).toFixed(1)} KB`, 'text-muted'); const heap = performance.memory ? performance.memory.usedJSHeapSize : null; if (heap) { this.terminal.print(` JS heap: ${(heap / 1048576).toFixed(1)} MB / ${(performance.memory.jsHeapSizeLimit / 1048576).toFixed(1)} MB`, 'text-muted'); } - this.terminal.print('Use reset-mem to clear AI internal logs without touching VFS/config', 'info'); + this.terminal.print('Use reset-mem to clear AI internal logs, context, profile, and guardrail policy', 'info'); } resetMem() { + // Capture size before clearing for the success message + const aiCtxSize = (localStorage.getItem('webx11_ai_context') || '').length; + const userProfileSize = (localStorage.getItem('webx11_user_profile') || '').length; + const patternSize = (localStorage.getItem('webx11_ai_patterns') || '').length; + const guardrailSize = (localStorage.getItem('webx11_guardrail_policy') || '').length; + + // Runtime in-memory resets this._toolCallLog = []; this._errorLog = []; this._commandFreq = {}; this.context = []; + this._messageCount = 0; + this._userProfile = {}; + this._guardrailViolations = {}; + this._shadowSnapshot = null; + this._guardrailPolicy = null; + this._toolDefs = null; // will be rebuilt on next use + + // Persisted localStorage resets localStorage.removeItem('webx11_ai_context'); localStorage.removeItem('webx11_ai_patterns'); - this.terminal.print('AI memory reset: tool logs, error logs, command patterns, and context cleared', 'success'); - this.terminal.print('VFS, config, skills, system prompt, and floating elements preserved', 'text-muted'); + localStorage.removeItem('webx11_user_profile'); + localStorage.removeItem('webx11_guardrail_policy'); + localStorage.removeItem('webx11_ai_system_prompt'); + + // VFS backups that _autoRestoreSession can resurrect + try { + const fs = this.terminal.fs; + if (fs) { + fs.deleteFile('_sessions/_auto-context.json'); + fs.deleteFile('_sessions/_auto.json'); + } + } catch (e) {} + + // Mark reset timestamp so auto-restore skips stale backups + localStorage.setItem('webx11_last_reset', Date.now().toString()); + + const totalCleared = aiCtxSize + userProfileSize + patternSize + guardrailSize; + this.terminal.print(`✅ AI memory reset: ${(totalCleared / 1024).toFixed(1)} KB cleared`, 'success'); + this.terminal.print(' • tool logs, error logs, command patterns, conversation context', 'text-muted'); + this.terminal.print(' • user profile / known facts, guardrail policy, shadow snapshot', 'text-muted'); + this.terminal.print(' • VFS session backups and cached system prompt', 'text-muted'); + this.terminal.print('VFS files, terminal history, AI config, and module registry preserved', 'text-muted'); } showModel(args) { const om = this.terminal.ollamaManager; @@ -2204,6 +2292,7 @@ buildSystemPrompt() { this.terminal.print(`${icon} ${content}...`); }); } + // Extract facts about user from conversation _extractFacts(userMsg, aiResp) { const patterns = [ { re: /my name is (\w+)/i, key: 'name' }, @@ -2215,16 +2304,26 @@ buildSystemPrompt() { { re: /i (?:like|prefer|love) (.+)/i, key: 'preference' }, ]; let changed = false; + if (!this._userProfile.knownFacts) this._userProfile.knownFacts = {}; for (const { re, key } of patterns) { const m = (userMsg + ' ' + (aiResp || '')).match(re); if (m && m[1]) { - if (!this._userProfile.knownFacts) this._userProfile.knownFacts = {}; - if (this._userProfile.knownFacts[key] !== m[1].trim()) { - this._userProfile.knownFacts[key] = m[1].trim(); + const val = m[1].trim(); + if (this._userProfile.knownFacts[key] !== val) { + this._userProfile.knownFacts[key] = val; changed = true; } } } + // v4.5.47: cap knownFacts so the profile doesn't bloat indefinitely + const MAX_FACTS = 50; + const factKeys = Object.keys(this._userProfile.knownFacts); + if (factKeys.length > MAX_FACTS) { + this._userProfile.knownFacts = Object.fromEntries( + factKeys.slice(-MAX_FACTS).map(k => [k, this._userProfile.knownFacts[k]]) + ); + changed = true; + } if (changed) { try { localStorage.setItem('webx11_user_profile', JSON.stringify(this._userProfile)); } catch (e) {} } @@ -2235,6 +2334,13 @@ buildSystemPrompt() { for (const p of patterns) { if (lower.includes(p)) this._commandFreq[p] = (this._commandFreq[p] || 0) + 1; } + // v4.5.47: cap command frequency map to top 30 patterns to prevent unbounded growth + const MAX_PATTERNS = 30; + const keys = Object.keys(this._commandFreq); + if (keys.length > MAX_PATTERNS) { + const sorted = keys.sort((a, b) => this._commandFreq[b] - this._commandFreq[a]); + this._commandFreq = Object.fromEntries(sorted.slice(0, MAX_PATTERNS).map(k => [k, this._commandFreq[k]])); + } try { localStorage.setItem('webx11_ai_patterns', JSON.stringify(this._commandFreq)); } catch (e) {} } async _autoCreateErrorSkill() { |