From ea4d09fb5ccba12ed774fcf7c913edfa1e6a851a Mon Sep 17 00:00:00 2001 From: Agando Date: Thu, 2 Jul 2026 06:00:40 +0200 Subject: auto: 2026-07-02_06:00 changes --- modules/core/core-bundle.js | 552 +++++++++++++++++++++++++++++++++++++--- modules/core/module-aiassist.js | 333 ++++++++++++++++++++++-- modules/core/module-loader.js | 4 +- modules/core/module-manager.js | 5 + modules/core/module-modmgr.js | 29 ++- modules/core/module-smartctx.js | 42 ++- 6 files changed, 896 insertions(+), 69 deletions(-) (limited to 'modules/core') diff --git a/modules/core/core-bundle.js b/modules/core/core-bundle.js index 6f94a44..d576f14 100644 --- a/modules/core/core-bundle.js +++ b/modules/core/core-bundle.js @@ -1018,6 +1018,11 @@ window.VirtualFileSystem = VirtualFileSystem; } async loadModuleFromURL(url) { try { + // Security: block cross-origin URLs for module loading + if (url && url.match(/^https?:\/\//) && !url.startsWith(window.location.origin)) { + this.terminal.print(`⚠️ Security: only same-origin URLs allowed for module loading`, 'error'); + return false; + } this.terminal.print(`Loading module from ${url}...`, 'info'); const response = await fetch(url); if (!response.ok) throw new Error(`HTTP ${response.status}`); @@ -2000,7 +2005,15 @@ class OllamaManager { // DeepSeek: empty response with no tools — retry once with stream forcing if (isDeepSeek && content === '' && (!toolCalls || toolCalls.length === 0)) { + this.terminal.log('WARN', 'Ollama', 'DeepSeek empty response — model=' + useModel + ', messages=' + messages.length + ', tools=' + (tools ? tools.length : 0)); this.terminal.print('⚠️ DeepSeek returned empty — retrying with stream...', 'warning'); + // Auto-truncate context: keep system prompt + last 5 messages + if (messages.length > 6) { + var sysMsg = messages[0]; + var recentMsgs = messages.slice(-5); + body.messages = [sysMsg].concat(recentMsgs); + this.terminal.print('📏 Context truncated from ' + messages.length + ' to ' + body.messages.length + ' messages for retry', 'info'); + } body.stream = true; delete body.stop; this._abortController = new AbortController(); @@ -2035,12 +2048,16 @@ class OllamaManager { } } if (fullText) { + this.terminal.log('INFO', 'Ollama', 'DeepSeek stream retry succeeded: ' + fullText.length + ' chars'); return { message: { role: 'assistant', content: fullText }, tool_calls: null, error: null }; } + this.terminal.log('WARN', 'Ollama', 'DeepSeek stream retry also returned empty'); + } else { + this.terminal.log('WARN', 'Ollama', 'DeepSeek stream retry HTTP ' + retryResponse.status); } return { message: { role: 'assistant', content: '' }, @@ -3720,7 +3737,7 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm } catch (e) {} } async _loadExtModules(filterKeys) { - const autoLoadKeys = filterKeys || ['calc', 'voice', 'debug-panel', 'cron', 'theme', 'wpm', 'mindmap', 'webdav', 'skill-pack-core', 'cryptogzip', 'tutorial', 'floating', 'console', 'media']; + const autoLoadKeys = filterKeys || ['debug-panel']; const cacheVer = localStorage.getItem('webx11_cache_ver') || '2026-05-27-14'; let index = null; let lastErr = null; @@ -3743,6 +3760,9 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm return; } + // Cache packages index for lazy-load dispatch + try { localStorage.setItem('webx11_packages_index', JSON.stringify(index)); } catch (e) {} + for (const key of autoLoadKeys) { const pkg = index.packages[key]; if (pkg) { @@ -3753,7 +3773,7 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm } async reloadPersistedModules() { const registry = this.moduleManager.moduleRegistry; - const skipList = ['calc', 'voice', 'debug-panel', 'cron', 'theme', 'wpm', 'mindmap', 'webdav', 'skill-pack-core', 'editor', 'diff', 'alias', 'finder', 'filemgr', 'cryptogzip', 'shell', 'tutorial', 'busybox', 'floating', 'console', 'media']; + const skipList = ['calc', 'voice', 'debug-panel', 'cron', 'theme', 'wpm', 'mindmap', 'webdav', 'skill-pack-core', 'editor', 'diff', 'alias', 'finder', 'filemgr', 'cryptogzip', 'shell', 'tutorial', 'busybox', 'floating', 'console', 'media', 'three']; for (const mod of registry) { if (mod.enabled && mod.url) { // Skip ext modules — they load via _loadExtModules @@ -4131,6 +4151,40 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm } } // Enhanced: check fuzzy suggestions before showing "not found" + // Lazy-load: check if command belongs to an unloaded ext module + if (!this._extModuleCommandMap) { + this._extModuleCommandMap = {}; + this._extModuleUrlMap = {}; + try { + const idx = JSON.parse(localStorage.getItem('webx11_packages_index') || '{}'); + if (idx.packages) { + for (const [key, pkg] of Object.entries(idx.packages)) { + if (pkg.commands && Array.isArray(pkg.commands)) { + for (const c of pkg.commands) { + this._extModuleCommandMap[c] = key; + this._extModuleUrlMap[key] = pkg.url; + } + } + } + } + } catch (e) {} + } + if (this._extModuleCommandMap[cmd] && !this.moduleManager.modules[this._extModuleCommandMap[cmd]]) { + const key = this._extModuleCommandMap[cmd]; + const url = this._extModuleUrlMap[key]; + if (url) { + this.print('⏳ Loading module for "' + cmd + '"...', 'info'); + const loaded = await this.moduleManager.loadModuleFromURL(url); + if (loaded) { + // Retry dispatch + const mod = this.moduleManager.modules[key]; + if (mod && mod.commands && mod.commands[cmd]) { + try { await mod.commands[cmd](args); return; } + catch (err) { this.print('Error: ' + err.message, 'error'); return; } + } + } + } + } const suggestion = this.security.suggest(cmd); if (suggestion && suggestion.dist <= 2 && suggestion.dist > 0) { this.print(`Command not found: ${cmd}`, 'error'); @@ -7032,22 +7086,24 @@ class OllamaModule { this.terminal.print(`✅ Module '${name}' marked as unloaded (commands will not be available)`, 'success'); }; - // module-reload — unload + reload with diff - this.commands['module-reload'] = (args) => { + // module-reload — re-fetch, re-parse, re-register + this.commands['module-reload'] = async (args) => { if (!args[0]) return this.terminal.print('Usage: module-reload ', 'warning'); const name = args[0]; const mod = this._getMod(name); if (!mod) return this.terminal.print(`Module not found: ${name}`, 'error'); - const wasLoaded = mod.loaded !== false; - const wasLazy = mod.lazy; - this.terminal.print(`🔄 Reloading '${name}' (was: ${wasLoaded ? 'loaded' : 'unloaded'}, lazy=${wasLazy})`, 'info'); - mod.loaded = false; - setTimeout(() => { - mod.loaded = true; - this.terminal.print(`✅ Module '${name}' reloaded`, 'success'); - const cmdCount = mod.commands ? Object.keys(mod.commands).length : 0; - this.terminal.print(` Now: loaded=true, lazy=${mod.lazy}, ${cmdCount} commands`, 'info'); - }, 100); + const mm = this.terminal.moduleManager; + const regEntry = mm.moduleRegistry.find(m => m.name === name); + const url = regEntry ? regEntry.url : null; + if (!url) return this.terminal.print(`Module '${name}' has no URL — cannot reload (built-in)`, 'warning'); + this.terminal.print(`🔄 Reloading '${name}' from ${url}...`, 'info'); + mm.unregisterModule(name); + const success = await mm.loadModuleFromURL(url); + if (success) { + this.terminal.print(`✅ Module '${name}' reloaded successfully`, 'success'); + } else { + this.terminal.print(`❌ Failed to reload '${name}'`, 'error'); + } }; // module-reset — clear module state and reload @@ -7648,6 +7704,8 @@ class ServerMonitorModule { this._toolDefs = null; this._toolCallLog = []; this._debugPanelEnabled = false; + this._lastRawResponse = null; + this._lastRawMessages = null; // 🔒 Guardrail Layer this._guardrailPolicy = null; this._shadowSnapshot = null; @@ -8051,7 +8109,7 @@ buildSystemPrompt() { type: 'function', function: { name: 'smart_context_list', - description: 'List available AI context profiles (e.g. coding, debug, creative, technical). Returns profile names and system prompts.', + description: 'List available AI context profiles (e.g. coding, debug, creative, technical, chat, system-admin, research, security). Each profile sets the AI system prompt, temperature, and behavior for a specific task type. Also switches the agent loop profile (max turns, tool set) to match. Returns profile names, descriptions, and system prompts. Use this when the user task has a clear domain (coding, debugging, research, etc.) to optimize AI behavior.', parameters: { type: 'object', properties: {}, @@ -8062,11 +8120,11 @@ buildSystemPrompt() { type: 'function', function: { name: 'smart_context_set', - description: "Set the active AI context profile. Changes the AI's behavior (temperature, style) for subsequent responses.", + description: "Set the active AI context profile. Changes the AI system prompt, temperature, and behavior for subsequent responses. Also switches the agent loop profile (max turns, tool set) to match. Available profiles: coding (code editing), debug (bug fixing), creative (brainstorming), technical (documentation), chat (casual), system-admin (maintenance), research (information gathering), security (threat assessment). Use this when the user task changes domain.", parameters: { type: 'object', properties: { - name: { type: 'string', description: 'Profile name (e.g. "coding", "debug", "creative", "technical", "chat")' } + name: { type: 'string', description: 'Profile name (e.g. "coding", "debug", "creative", "technical", "chat", "system-admin", "research", "security")' } }, required: ['name'] } @@ -8085,6 +8143,30 @@ buildSystemPrompt() { required: ['topic'] } } + }, { + type: 'function', + function: { + name: 'create_3d_scene', + description: 'Create a 3D scene with Three.js objects. Supports: boxes, spheres, torus, text, particles, lines. Also supports presets: "solar" (solar system), "grid" (color grid). Describe what you want in natural language and the module will parse it.', + parameters: { + type: 'object', + properties: { + description: { type: 'string', description: 'Natural language description of the 3D scene (e.g. "a red box and two blue spheres with particles")' } + }, + required: ['description'] + } + } + }, { + type: 'function', + function: { + name: 'visualize_mindmap_3d', + description: 'Render the current mindmap as an interactive 3D force-directed graph with colored spheres and curved connecting lines. Requires MindmapModule to have an active mindmap.', + parameters: { + type: 'object', + properties: {}, + required: [] + } + } }, { type: 'function', function: { @@ -8196,6 +8278,13 @@ buildSystemPrompt() { this.commands['bench-compare'] = (args) => this.benchCompareCmd(args); this.commands['debug'] = () => this.toggleDebugPanel(); this.commands['debug-ai'] = () => this.toggleDebug(); + this.commands['debug-ai-last'] = () => this._showLastRawResponse(); + this.commands['debug-ai-send'] = () => this._showLastRawRequest(); + this.commands['debug-ai-log'] = () => this._showToolCallLog(); + this.commands['archive-list'] = () => this._archiveList(); + this.commands['archive-read'] = (args) => this._archiveRead(args); + this.commands['workflow-list'] = () => this._workflowList(); + this.commands['workflow-run'] = (args) => this._workflowRun(args); this.commands['guardrail'] = (args) => this.guardrailCmd(args); this.commands['guardrail-add'] = (args) => this.guardrailCmd(['add', ...args]); this.commands['guardrail-remove'] = (args) => this.guardrailCmd(['remove', ...args]); @@ -8336,9 +8425,14 @@ buildSystemPrompt() { // Log the AI interaction this.terminal.log('INFO', 'AI', 'Query: ' + userMessage.substring(0, 100)); if (loopResult && loopResult.toolTurns) { - this.terminal.log('INFO', 'AI', 'Used ' + loopResult.toolTurns + ' tool(s)'); + var toolNames = this._toolCallLog.slice(-(loopResult.toolTurns || 0)).map(function(e) { return e.name; }).join(', '); + this.terminal.log('INFO', 'AI', 'Used ' + loopResult.toolTurns + ' tool(s): ' + toolNames); } } + // Auto-archive every 10 messages + if (this._messageCount > 0 && this._messageCount % 10 === 0) { + this._archiveSession('Auto-archive at message ' + this._messageCount); + } // Register session save on page unload (runs once per session) if (!this._sessionSaved) { this._sessionSaved = true; @@ -8370,12 +8464,13 @@ buildSystemPrompt() { if (!this._debugPanelEnabled) return; const panel = document.getElementById('debug-ai-panel'); if (!panel) return; + const activeTab = panel.getAttribute('data-tab') || 'tools'; const recent = this._toolCallLog.slice(-20); - let html = ''; + let toolHtml = ''; for (const entry of recent) { const statusClass = entry.error ? 'debug-entry-error' : 'debug-entry-ok'; const argsStr = typeof entry.args === 'string' ? entry.args.substring(0, 60) : JSON.stringify(entry.args).substring(0, 60); - html += '
' + + toolHtml += '
' + '#' + entry.turn + ' ' + '' + this._escapeHtml(entry.name) + ' ' + '' + this._escapeHtml(argsStr) + ' ' + @@ -8383,14 +8478,41 @@ buildSystemPrompt() { (entry.error ? ' ⚠️' : '') + '
'; } + // Raw I/O tab content + let rawHtml = ''; + if (this._lastRawResponse) { + const r = this._lastRawResponse; + const content = (r.message && r.message.content) || ''; + const hasTools = r.tool_calls && r.tool_calls.length > 0; + rawHtml += '
'; + if (r.error) rawHtml += '
⚠️ ' + this._escapeHtml(r.error) + '
'; + if (content) { + const safe = this._escapeHtml(content.length > 300 ? content.substring(0, 300) + '...' : content); + rawHtml += '
Content: ' + safe + '
'; + } + if (hasTools) { + rawHtml += '
Tools: ' + r.tool_calls.map(function(tc) { return tc.function.name; }).join(', ') + '
'; + } + if (this._lastRawMessages) { + rawHtml += '
Messages: ' + this._lastRawMessages.length + '
'; + } + rawHtml += '
'; + } else { + rawHtml = '
No AI response yet. Send a message first.
'; + } + const tabBar = '
' + + '🔧 Tools' + + '📡 Raw I/O' + + '
'; + const bodyHtml = activeTab === 'raw' ? rawHtml : toolHtml; panel.innerHTML = '
' + - '🔧 Tool Log ' + this._toolCallLog.length + ' calls' + + '🔧 AI Debug ' + this._toolCallLog.length + ' calls' + '' + '' + '' + '' + '✕' + - '
' + html; + '
' + tabBar + bodyHtml; } _isToolError(result) { if (!result || typeof result !== 'string') return null; @@ -8441,6 +8563,28 @@ buildSystemPrompt() { 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.'; + // Memory-augmented planning: inject prior loop lessons as negative constraints + try { + var fs = this.terminal.fs; + if (fs) { + var index = fs.fsReadJSON('/skills/index.json') || {}; + var lessonKeys = Object.keys(index).filter(function(k) { return k.indexOf('loop-lesson-') === 0; }); + if (lessonKeys.length > 0) { + var recentLessons = lessonKeys.sort().slice(-3); + var lessonTexts = []; + for (var li = 0; li < recentLessons.length; li++) { + var lc = fs.readFile('/skills/' + recentLessons[li] + '.md'); + if (lc) { + var excerpt = lc.substring(0, 300); + lessonTexts.push('[Lesson: ' + recentLessons[li] + '] ' + excerpt); + } + } + if (lessonTexts.length > 0) { + messages.push({ role: 'system', content: 'Prior loop lessons (read before planning):\n' + lessonTexts.join('\n---\n') + '\n\nUse these to avoid repeating past mistakes. If lessons mention tool failures, verify those tools before calling them.' }); + } + } + } + } catch (e) { /* VFS not available — skip memory-augmented planning */ } let toolTurns = 0; const _seenCalls = new Map(); const _loopWindow = loopDetect.windowMs || 30000; @@ -8455,6 +8599,9 @@ buildSystemPrompt() { const thinkStart = Date.now(); const res = await this.terminal.ollamaManager.chatWithTools(messages, this.getToolDefinitions()); const thinkTime = Math.round((Date.now() - thinkStart) / 1000); + // Store raw response for debug-ai-last + this._lastRawResponse = res; + this._lastRawMessages = messages.slice(); if (this._cancelled) return { finalAnswer: null, toolTurns }; if (res.error) { this.terminal.print(`⚠️ AI model error (${thinkTime}s): ${res.error}`, 'error'); return { finalAnswer: null, toolTurns }; } if (res.message && !res.tool_calls) { @@ -9259,12 +9406,33 @@ buildSystemPrompt() { const fs = this.terminal.fs; const mm = this.terminal.moduleManager; const om = this.terminal.ollamaManager; + var agentInfo = {}; + try { + var orch = mm.modules['OrchestratorModule']; + var agentReg = mm.modules['AgentRegistryModule']; + if (orch) { + var agents = orch._agents || {}; + var busy = 0, failed = 0; + for (var ak in agents) { + var s = agents[ak].status; + if (s === 'busy') busy++; + if (s === 'failed' || s === 'restarting') failed++; + } + agentInfo.activeAgents = Object.keys(agents).length; + agentInfo.busyAgents = busy; + agentInfo.failedAgents = failed; + } + if (agentReg) { + agentInfo.registeredAgents = Object.keys(agentReg._agents || {}).length; + } + } catch (e) {} return JSON.stringify({ files: fs.ls().length, dirs: fs.currentPath.length ? '/' + fs.currentPath.join('/') : '/', modules: Object.keys(mm.modules).length, ollama: om.connected ? `Connected (${om.currentModel})` : 'Disconnected', - memory: performance.memory ? (performance.memory.usedJSHeapSize / 1048576).toFixed(1) + 'MB' : 'N/A' + memory: performance.memory ? (performance.memory.usedJSHeapSize / 1048576).toFixed(1) + 'MB' : 'N/A', + agents: agentInfo }); } case 'get_help': { @@ -9631,10 +9799,26 @@ buildSystemPrompt() { return JSON.stringify({ error: `Profile '${name}' not found`, available: Object.keys(scm.contextProfiles) }); } scm.activeProfile = name; + scm.saveActiveProfile(); const profile = scm.contextProfiles[name]; // Rebuild system prompt to reflect new context profile this.systemPrompt = this.buildSystemPrompt(); - return JSON.stringify({ success: true, name, displayName: profile.name, systemPrompt: profile.systemPrompt, temperature: profile.temperature }); + // Also switch agent loop profile to match + const PROFILE_MAP = { + 'coding': 'coder', + 'debug': 'minimal', + 'creative': 'creative', + 'technical': 'research', + 'chat': 'default', + 'system-admin': 'safe', + 'research': 'research', + 'security': 'safe' + }; + const targetLoop = PROFILE_MAP[name]; + if (targetLoop && this._switchLoopProfile) { + this._switchLoopProfile(targetLoop); + } + return JSON.stringify({ success: true, name, displayName: profile.name, systemPrompt: profile.systemPrompt, temperature: profile.temperature, agentLoop: targetLoop || null }); } case 'create_mindmap': { const mindmap = this.terminal.moduleManager.modules['MindmapModule']; @@ -9657,6 +9841,26 @@ buildSystemPrompt() { mindmap._svg([]); return JSON.stringify({ success: true, topic, children: added.length ? added : 'none', nodes: mindmap._countNodes(cur.root), rendered: 'inline SVG in terminal' }); } + case 'create_3d_scene': { + const three = this.terminal.moduleManager.modules['ThreeModule']; + if (!three) return JSON.stringify({ error: 'ThreeModule not loaded' }); + try { + const result = await three.createSceneFromDescription(a.description || ''); + return JSON.stringify({ success: true, result }); + } catch (e) { + return JSON.stringify({ error: '3D scene creation failed: ' + e.message }); + } + } + case 'visualize_mindmap_3d': { + const three = this.terminal.moduleManager.modules['ThreeModule']; + if (!three) return JSON.stringify({ error: 'ThreeModule not loaded' }); + try { + const result = await three.visualizeMindmap3D(a.name || ''); + return JSON.stringify({ success: true, result }); + } catch (e) { + return JSON.stringify({ error: '3D mindmap visualization failed: ' + e.message }); + } + } case 'view_module': case 'edit_module': case 'test_module': @@ -10331,6 +10535,191 @@ buildSystemPrompt() { } return total; } + _showToolCallLog() { + if (!this._toolCallLog || this._toolCallLog.length === 0) { + this.terminal.print('No tool calls recorded this session.', 'warning'); + return; + } + let out = '── Tool Call Log (' + this._toolCallLog.length + ' calls) ──\n'; + for (var i = 0; i < this._toolCallLog.length; i++) { + var e = this._toolCallLog[i]; + var err = e.error ? ' ⚠️' : ''; + var argsStr = typeof e.args === 'string' ? e.args.substring(0, 80) : JSON.stringify(e.args).substring(0, 80); + out += ' #' + e.turn + ' ' + e.name + '(' + argsStr + ') ' + e.duration + 's' + err + '\n'; + } + out += '── End ──'; + this.terminal.print(out, 'info'); + } + _archiveList() { + try { + const fs = this.terminal.fs; + const files = fs.ls('/memory/archive') || []; + const jsonFiles = files.filter(function(f) { return f.name && f.name.endsWith('.json'); }); + if (jsonFiles.length === 0) { + this.terminal.print('No archives found. Run ai-compress to create one.', 'warning'); + return; + } + jsonFiles.sort(function(a, b) { return (a.name || '') < (b.name || '') ? 1 : -1; }); + let out = '── Archives (' + jsonFiles.length + ') ──\n'; + for (var i = 0; i < Math.min(jsonFiles.length, 30); i++) { + var f = jsonFiles[i]; + var size = f.size ? '(' + f.size + 'b)' : ''; + out += ' ' + f.name + ' ' + size + '\n'; + } + out += '── End ──\nUse: archive-read '; + this.terminal.print(out, 'info'); + } catch (e) { + this.terminal.print('Error listing archives: ' + e.message, 'error'); + } + } + _archiveRead(args) { + var name = (args && args[0]) || ''; + if (!name) { + this.terminal.print('Usage: archive-read (e.g. 2026-06-25-14-30.json)', 'warning'); + return; + } + try { + const fs = this.terminal.fs; + var data = fs.fsReadJSON('/memory/archive/' + name); + if (!data) { + this.terminal.print('Archive not found: ' + name, 'error'); + return; + } + var out = '── Archive: ' + name + ' ──\n'; + out += 'Archived: ' + (data.archivedAt || '?') + '\n'; + out += 'Summary: ' + (data.summary || '(none)') + '\n'; + out += 'User messages: ' + (data.user?.messageCount || 0) + '\n'; + out += 'System: ' + (data.system?.modules || 0) + ' modules, ' + (data.system?.model || '?') + '\n'; + out += 'Context: ' + (data.context?.messageCount || 0) + ' msgs, ~' + (data.context?.estimatedTokens || 0) + ' tokens\n'; + out += '── End ──'; + this.terminal.print(out, 'info'); + } catch (e) { + this.terminal.print('Error reading archive: ' + e.message, 'error'); + } + } + _workflowList() { + var workflows = [ + { name: 'daily-health-report', desc: 'Schedule a daily system health report using Cron + WorldModel + ServerMonitor' }, + { name: 'predict-and-monitor', desc: 'Predict task duration with Predictor, then schedule monitoring via Cron' }, + { name: 'compress-archives', desc: 'Compress and archive old session files using CryptoGzip + Cron' }, + { name: 'media-summarize', desc: 'Analyze a media file and summarize it with AI' }, + { name: 'sandbox-test', desc: 'Run a command in the sandbox and report results' } + ]; + var out = '── Workflows (' + workflows.length + ') ──\n'; + for (var i = 0; i < workflows.length; i++) { + out += ' ' + workflows[i].name + ' — ' + workflows[i].desc + '\n'; + } + out += '── End ──\nUse: workflow-run '; + this.terminal.print(out, 'info'); + } + _workflowRun(args) { + var name = (args && args[0]) || ''; + if (!name) { + this.terminal.print('Usage: workflow-run (e.g. daily-health-report)', 'warning'); + return; + } + var workflows = { + 'daily-health-report': { + steps: [ + 'Run: status — check current system state', + 'Run: cron-every 1h "status" — schedule hourly health check', + 'The WorldModel will auto-synthesize system awareness each cycle' + ] + }, + 'predict-and-monitor': { + steps: [ + 'Run: predict "run system health check" — estimate duration', + 'Run: cron-every 30m "status" — monitor progress', + 'Check cron-list to see scheduled tasks' + ] + }, + 'compress-archives': { + steps: [ + 'Run: archive-list — see current archives', + 'Run: gzip /memory/archive/2026-06-25-*.json — compress old archives', + 'Run: cron-every 1d "gzip /memory/archive/*.json" — auto-compress daily' + ] + }, + 'media-summarize': { + steps: [ + 'Run: media list — see available media files', + 'Run: media analyze — analyze with AI', + 'The AI will summarize the media content' + ] + }, + 'sandbox-test': { + steps: [ + 'Run: sandbox-status — check sandbox pool', + 'Run: sandbox-run "ls /" — test command in sandbox', + 'Check output for isolation verification' + ] + } + }; + var wf = workflows[name]; + if (!wf) { + this.terminal.print('Workflow not found: ' + name + '. Use workflow-list to see available workflows.', 'error'); + return; + } + var out = '── Workflow: ' + name + ' ──\n'; + for (var i = 0; i < wf.steps.length; i++) { + out += ' ' + (i + 1) + '. ' + wf.steps[i] + '\n'; + } + out += '── End ──'; + this.terminal.print(out, 'info'); + } + _showLastRawResponse() { + if (!this._lastRawResponse) { + this.terminal.print('No AI response recorded yet. Send a message first.', 'warning'); + return; + } + const r = this._lastRawResponse; + var content = (r.message && r.message.content) || ''; + var hasTools = r.tool_calls && r.tool_calls.length > 0; + var toolStr = hasTools ? r.tool_calls.map(function(tc) { return tc.function.name + '(' + (tc.function.arguments || '{}') + ')'; }).join(', ') : ''; + // Render as HTML card for better readability + var html = '
' + + '
' + + '🤖 Last AI Response' + + '' + + '' + + '
' + + '
'; + if (r.error) { + html += '
⚠️ Error: ' + this._escapeHtml(r.error) + '
'; + } + if (content) { + var safe = this._escapeHtml(content.length > 1000 ? content.substring(0, 1000) + '...(' + content.length + ' chars)' : content); + html += '
Content:
' + safe + '
'; + } else if (!hasTools) { + html += '
⚠️ Empty response (no content, no tool calls)
'; + } + if (hasTools) { + html += '
Tool calls: ' + this._escapeHtml(toolStr) + '
'; + } + html += '
'; + this.terminal.printHTML(html); + } + _showLastRawRequest() { + if (!this._lastRawMessages) { + this.terminal.print('No AI request recorded yet. Send a message first.', 'warning'); + return; + } + const msgs = this._lastRawMessages; + let out = '── Last AI Request (' + msgs.length + ' messages) ──\n'; + for (let i = 0; i < msgs.length; i++) { + const m = msgs[i]; + const role = m.role || '?'; + const content = (m.content || '').substring(0, 200); + const suffix = (m.content || '').length > 200 ? '...' : ''; + out += ' [' + i + '] ' + role + ': ' + content + suffix + '\n'; + if (i > 8 && i < msgs.length - 2) { + out += ' ... (' + (msgs.length - i - 2) + ' more messages) ...\n'; + i = msgs.length - 3; + } + } + out += '── End ──'; + this.terminal.print(out, 'info'); + } async _compressContext() { if (this.context.length <= 6) return; const totalTokens = this._estimateContextTokens(); @@ -10356,10 +10745,58 @@ buildSystemPrompt() { } catch (e) { /* fallback to truncation */ } this.context = [{ role: 'system', content: compressed }, ...recentMsgs]; this.terminal.print('📦 Context compressed (' + oldMsgs.length + ' messages → 1 summary, ~' + totalTokens + ' tokens)', 'info'); + // Also archive session snapshot + this._archiveSession(compressed); + } + _archiveSession(summary) { + try { + const fs = this.terminal.fs; + fs.mkdirRecursive('/memory/archive'); + const now = new Date(); + const pad = function(n) { return String(n).padStart(2, '0'); }; + const ts = now.getFullYear() + '-' + pad(now.getMonth()+1) + '-' + pad(now.getDate()) + '-' + pad(now.getHours()) + '-' + pad(now.getMinutes()); + const archive = { + archivedAt: now.toISOString(), + summary: summary || '', + user: { + profile: this._userProfile || {}, + topics: [], + messageCount: this._messageCount + }, + system: { + modules: this.terminal?.moduleManager?.modules ? Object.keys(this.terminal.moduleManager.modules).length : 0, + model: this.terminal?.ollamaManager?.currentModel || 'unknown', + provider: this.terminal?.ollamaManager?.currentProvider?.name || 'unknown' + }, + context: { + messageCount: this.context.length, + estimatedTokens: this._estimateContextTokens() + } + }; + fs.fsWriteJSON('/memory/archive/' + ts + '.json', archive); + // Rotation: keep last 30 + try { + const files = fs.ls('/memory/archive') || []; + const jsonFiles = files.filter(function(f) { return f.name && f.name.endsWith('.json'); }); + if (jsonFiles.length > 30) { + jsonFiles.sort(function(a, b) { return (a.name || '') < (b.name || '') ? -1 : 1; }); + const toDelete = jsonFiles.slice(0, jsonFiles.length - 30); + for (var i = 0; i < toDelete.length; i++) { + fs.deleteFile('/memory/archive/' + toDelete[i].name); + } + } + } catch (e) { /* rotation best-effort */ } + } catch (e) { /* archive best-effort */ } } _manualCompress() { this._compressContext().then(() => { - this.terminal.print('Context: ' + this.context.length + ' message(s), ~' + this._estimateContextTokens() + ' estimated tokens', 'info'); + const fs = this.terminal.fs; + let archiveCount = 0; + try { + const files = fs.ls('/memory/archive') || []; + archiveCount = files.filter(function(f) { return f.name && f.name.endsWith('.json'); }).length; + } catch (e) {} + this.terminal.print('Context: ' + this.context.length + ' message(s), ~' + this._estimateContextTokens() + ' estimated tokens, ' + archiveCount + ' archive(s)', 'info'); }).catch(e => { this.terminal.print('Compression error: ' + e.message, 'error'); }); @@ -10466,10 +10903,21 @@ buildSystemPrompt() { 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 suggestions = []; var profileAdjusted = false; + 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"); + if (this._activeLoopProfile && this._activeLoopProfile.loop) { var newMax = (this._activeLoopProfile.loop.maxTurns || this.MAX_TOOL_TURNS) + 2; this._activeLoopProfile.loop.maxTurns = newMax; this._saveLoopProfile(this._activeLoopProfile); suggestions.push(" → Auto-adjusted maxTurns to " + newMax); profileAdjusted = true; } + } 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"); + if (this._activeLoopProfile && this._activeLoopProfile.systemPrompt) { var constraint = "[Constraint] Avoid tools that failed: " + a.tools.join(", ") + ". Verify module state before calling these tools."; if (this._activeLoopProfile.systemPrompt.append) { if (this._activeLoopProfile.systemPrompt.append.indexOf(constraint) === -1) { this._activeLoopProfile.systemPrompt.append += "\n" + constraint; } } else { this._activeLoopProfile.systemPrompt.append = constraint; } this._saveLoopProfile(this._activeLoopProfile); suggestions.push(" → Added constraint to profile: avoid " + a.tools.join(", ")); profileAdjusted = true; } + } 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"); + 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 (!this._loopLessonCleaned) { this._loopLessonCleaned = true; autoSkills.forEach(function(e) { try { fs.unlink("/skills/" + e[0] + ".md"); } catch (ex) {} delete index[e[0]]; }); autoSkills = []; } var now = Date.now(); autoSkills = autoSkills.filter(function(e) { var created = new Date(e[1].created).getTime(); if (now - created > 86400000) { try { fs.unlink("/skills/" + e[0] + ".md"); } catch (ex) {} delete index[e[0]]; return false; } return true; }); var anomalyTypes = anomalies.map(function(a) { return a.type; }).sort().join(","); var isDuplicate = autoSkills.length > 0 && autoSkills[autoSkills.length - 1][1].anomalyTypes === anomalyTypes; if (!isDuplicate && autoSkills.length >= 3) { var oldest = autoSkills[0]; try { fs.unlink("/skills/" + oldest[0] + ".md"); } catch (ex) {} delete index[oldest[0]]; autoSkills.shift(); } if (!isDuplicate) { 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(), anomalyTypes: anomalyTypes }; fs.fsWriteJSON("/skills/index.json", index); } } catch (e) {} + var msg = "💡 Self-improvement: " + anomalies.length + " anomaly(ies)"; if (profileAdjusted) msg += " — profile auto-adjusted"; + this.terminal.print(msg, "info"); } _vaultmindStatusCmd() { const token = localStorage.getItem('vaultmind_token'); @@ -15858,6 +16306,12 @@ class NeuralVFSModule { resolve(args) { if (!args[0]) return this.terminal.print('Usage: nvfs-resolve ', 'warning'); + // Auto-sync dirty paths before resolving + if (this._dirtyPaths.size > 0) { + this.terminal.print('🔄 Auto-syncing ' + this._dirtyPaths.size + ' dirty file(s) before resolve...', 'info'); + this._reindexDirtyPaths(); + } + const concept = args.join(' ').toLowerCase(); // Check aliases first @@ -16700,11 +17154,14 @@ if (typeof window !== 'undefined') { } getDefaultProfiles() { return { - 'coding': { name: 'Coding Assistant', systemPrompt: "You are an expert programmer.", temperature: 0.7 }, - 'debug': { name: 'Debug Helper', systemPrompt: "You are a debugging expert.", temperature: 0.5 }, - 'creative': { name: 'Creative Writer', systemPrompt: "You are a creative writer.", temperature: 0.9 }, - 'technical': { name: 'Technical Docs', systemPrompt: "You are a technical writer.", temperature: 0.3 }, - 'chat': { name: 'Casual Chat', systemPrompt: "You are a friendly assistant.", temperature: 0.8 } + 'coding': { name: 'Coding Assistant', systemPrompt: "You are a WebX11 newOS4 coding expert. You have access to the full terminal, VFS, and module system.\n\nAvailable commands: view_module, edit_module, test_module, reload_module, list_modified_modules, check_updates, load-local, loadmod, removemod, startup-modules, node, js-run, git, diff, patch\n\nSkills available: module-management (view/edit/test/reload modules), skills-system-guide (create/patch skills)\n\nBehavior:\n- Always show exact code changes when editing modules\n- Use edit_module for modifications, test_module to verify\n- Create checkpoints before risky edits: checkpoint mod-edit-\n- Reference the module-management skill for the full workflow\n- When the user reports a bug, first view_module to inspect, then edit\n- Use nvfs_resolve to find relevant files before editing\n- Prefer write_file for creating new files, edit_module for modifying modules", temperature: 0.7 }, + 'debug': { name: 'Debug Helper', systemPrompt: "You are a WebX11 newOS4 debugging expert. Your goal is to diagnose and fix issues systematically.\n\nAvailable commands: debug, debug-ai, debug-ai-last, debug-ai-send, debug-ai-log, logs, logrotate, health-check, check, mem, status, archive-list, archive-read, guardrail, shadow, shadow-commit, shadow-rollback\n\nSkills available: debug-panel-and-logs (toggle debug panel, session logging, diagnostics), system-maintenance (health checks, config, security)\n\nBehavior:\n- Start with health-check and check to assess system state\n- Use debug-ai to see the last AI tool call and response\n- Use logs 50 ERROR to find error entries\n- Check browser console for JS errors\n- Use guardrail to inspect security policies\n- Use shadow for safe execution of risky commands\n- Always check module loading status first", temperature: 0.3 }, + 'creative': { name: 'Creative Writer', systemPrompt: "You are a creative assistant for WebX11 newOS4. You help with brainstorming, content creation, and design.\n\nAvailable commands: mindmap new/add/show/save/load/export, floating-add/remove/list, ai-system, ai-config\n\nSkills available: mindmap-workflow (create/view/edit mindmaps), floating-ui (create floating buttons/menus/panels)\n\nBehavior:\n- Use mindmap for brainstorming and organizing ideas\n- Use floating-add to create quick-access UI elements\n- Use web_search and web_fetch for research and inspiration\n- Higher temperature (0.9) for creative divergence\n- When the user says \"brainstorm\" or \"diagram\", create a mindmap\n- Use create_mindmap AI tool for visual architecture diagrams\n- Reference the mindmap-workflow skill for full capabilities", temperature: 0.9 }, + 'technical': { name: 'Technical Docs', systemPrompt: "You are a technical documentation expert for WebX11 newOS4. You help with docs, guides, and system understanding.\n\nAvailable commands: help, commands, status, modules, health-check, nvfs-index, nvfs-resolve, view_module, cat, edit, grep, find, tree\n\nSkills available: navigating-terminal (explore terminal, filesystem, system state), web-search-and-fetch (search web, fetch URLs), skills-system-guide (create/patch skills)\n\nBehavior:\n- Use help and commands to discover available functionality\n- Use nvfs-resolve to find files by concept\n- Use view_module to inspect module source for documentation\n- When creating documentation, use write_file to save to VFS\n- Reference the navigating-terminal skill for filesystem exploration\n- Use web_search for external research", temperature: 0.3 }, + 'chat': { name: 'Casual Chat', systemPrompt: "You are a friendly, helpful assistant for WebX11 newOS4. You handle general questions, casual conversation, and light tasks.\n\nAvailable commands: ai, ask, search, fetch, status, help, user_get, user_set, session_save, session_recent, my-set, my-get\n\nSkills available: memory-and-profile (user profile, session memory, checkpoints), web-search-and-fetch (search web, fetch URLs)\n\nBehavior:\n- Be conversational and approachable\n- Use user_get/user_set to remember user preferences\n- Use session_recent at start to recall past work\n- Use session_save after completing tasks\n- For complex tasks, suggest switching to a more specific context profile\n- Reference the memory-and-profile skill for persistence features", temperature: 0.8 }, + 'system-admin': { name: 'System Admin', systemPrompt: "You are a WebX11 newOS4 system administrator. You handle maintenance, backup, security, and configuration.\n\nAvailable commands: config-list, config-set, config-delete, config-reset, security-level, security, cmd-allow, cmd-deny, backup, vfs-export, webdav-backup, webdav-restore, webdav-config, webdav-status, factory-reset, reset-vfs, checkpoint, checkpoint-list, rollback, health-check, check_updates, font-size\n\nSkills available: system-maintenance (reset, config, security, health checks), backup-and-restore (VFS backup, WebDAV cloud backup, restore), webdav-operations (cloud storage, file sync), security-and-safety (security levels, command blocking)\n\nBehavior:\n- Always create a checkpoint before making system changes\n- Use webdav-backup before destructive operations\n- Check security-level before running commands\n- Use health-check for full system audit\n- Reference the system-maintenance skill for reset procedures\n- Reference the backup-and-restore skill for data safety", temperature: 0.3 }, + 'research': { name: 'Research Assistant', systemPrompt: "You are a research assistant for WebX11 newOS4. You gather information, analyze data, and synthesize findings.\n\nAvailable commands: search, fetch, web_search, web_fetch, nvfs-resolve, nvfs-index, grep, find, cat, view_module, session_recent, session_load, session_stats\n\nSkills available: web-search-and-fetch (search web, fetch URL content), memory-and-profile (session memory, checkpoints), navigating-terminal (explore filesystem)\n\nBehavior:\n- Start with web_search for external research\n- Use nvfs-resolve to find relevant VFS files\n- Use session_recent to check past work on the topic\n- Use fetch to read full articles from URLs\n- Synthesize findings before presenting\n- Reference the web-search-and-fetch skill for search flags", temperature: 0.5 }, + 'security': { name: 'Security Expert', systemPrompt: "You are a WebX11 newOS4 security expert. You handle threat assessment, command control, and safety.\n\nAvailable commands: security, security-level, cmd-allow, cmd-deny, guardrail, guardrail-add, guardrail-remove, shadow, shadow-commit, shadow-rollback, health-check, check, logs, debug\n\nSkills available: security-and-safety (security levels, command blocking, safety checklist), debug-panel-and-logs (diagnostics, logging), system-maintenance (config, health checks)\n\nBehavior:\n- Always check current security level first with security\n- Use guardrail to inspect and manage security policies\n- Use shadow for safe execution of suspicious commands\n- Never recommend factory-reset without backup first\n- Use cmd-deny to block dangerous commands\n- Reference the security-and-safety skill for level descriptions", temperature: 0.2 } }; } registerCommands() { @@ -16722,6 +17179,32 @@ if (typeof window !== 'undefined') { this.activeProfile = args[0]; this.saveActiveProfile(); this.terminal.print(`Context set to: ${this.contextProfiles[args[0]].name}`, "success"); + // Also switch agent loop profile to match + const PROFILE_MAP = { + 'coding': 'coder', + 'debug': 'minimal', + 'creative': 'creative', + 'technical': 'research', + 'chat': 'default', + 'system-admin': 'safe', + 'research': 'research', + 'security': 'safe' + }; + const targetLoop = PROFILE_MAP[args[0]]; + if (targetLoop) { + const aiMod = this.terminal?.moduleManager?.modules?.WebX11AIAssistantModule; + if (aiMod && aiMod._switchLoopProfile) { + aiMod._switchLoopProfile(targetLoop); + this.terminal.print(` Agent loop switched to: ${targetLoop}`, "text-muted"); + } + } else { + // Custom profile — rebuild system prompt without loop switch + const aiMod = this.terminal?.moduleManager?.modules?.WebX11AIAssistantModule; + if (aiMod && aiMod.buildSystemPrompt) { + aiMod.systemPrompt = aiMod.buildSystemPrompt(); + } + this.terminal.print(` No agent loop mapped for '${args[0]}'`, "text-muted"); + } } else { this.terminal.print(`Profile '${args[0]}' not found`, "error"); } @@ -16771,7 +17254,8 @@ if (typeof window !== 'undefined') { const aiModule = this.terminal?.moduleManager?.modules?.WebX11AIAssistantModule; if (aiModule) { // Set temperature on ollamaManager modelParams temporarily - const om = this.terminal.ollamaManager; + const om = this.terminal?.ollamaManager; + if (!om) { this.terminal?.print('⚠️ Ollama manager not available', 'error'); return; } const savedTemp = om.modelParams ? om.modelParams.temperature : undefined; if (om.modelParams) { om.modelParams.temperature = temperature; diff --git a/modules/core/module-aiassist.js b/modules/core/module-aiassist.js index 6359f17..8a0a7c8 100644 --- a/modules/core/module-aiassist.js +++ b/modules/core/module-aiassist.js @@ -22,6 +22,8 @@ this._toolDefs = null; this._toolCallLog = []; this._debugPanelEnabled = false; + this._lastRawResponse = null; + this._lastRawMessages = null; // 🔒 Guardrail Layer this._guardrailPolicy = null; this._shadowSnapshot = null; @@ -431,7 +433,7 @@ buildSystemPrompt() { type: 'function', function: { name: 'smart_context_list', - description: 'List available AI context profiles (e.g. coding, debug, creative, technical). Returns profile names and system prompts.', + description: 'List available AI context profiles (e.g. coding, debug, creative, technical, chat, system-admin, research, security). Each profile sets the AI system prompt, temperature, and behavior for a specific task type. Also switches the agent loop profile (max turns, tool set) to match. Returns profile names, descriptions, and system prompts. Use this when the user task has a clear domain (coding, debugging, research, etc.) to optimize AI behavior.', parameters: { type: 'object', properties: {}, @@ -442,11 +444,11 @@ buildSystemPrompt() { type: 'function', function: { name: 'smart_context_set', - description: "Set the active AI context profile. Changes the AI's behavior (temperature, style) for subsequent responses.", + description: "Set the active AI context profile. Changes the AI system prompt, temperature, and behavior for subsequent responses. Also switches the agent loop profile (max turns, tool set) to match. Available profiles: coding (code editing), debug (bug fixing), creative (brainstorming), technical (documentation), chat (casual), system-admin (maintenance), research (information gathering), security (threat assessment). Use this when the user task changes domain.", parameters: { type: 'object', properties: { - name: { type: 'string', description: 'Profile name (e.g. "coding", "debug", "creative", "technical", "chat")' } + name: { type: 'string', description: 'Profile name (e.g. "coding", "debug", "creative", "technical", "chat", "system-admin", "research", "security")' } }, required: ['name'] } @@ -632,6 +634,11 @@ buildSystemPrompt() { this.commands['bench-compare'] = (args) => this.benchCompareCmd(args); this.commands['debug'] = () => this.toggleDebugPanel(); this.commands['debug-ai'] = () => this.toggleDebug(); + this.commands['debug-ai-last'] = () => this._showLastRawResponse(); + this.commands['debug-ai-send'] = () => this._showLastRawRequest(); + this.commands['debug-ai-log'] = () => this._showToolCallLog(); + this.commands['archive-list'] = () => this._archiveList(); + this.commands['archive-read'] = (args) => this._archiveRead(args); this.commands['guardrail'] = (args) => this.guardrailCmd(args); this.commands['guardrail-add'] = (args) => this.guardrailCmd(['add', ...args]); this.commands['guardrail-remove'] = (args) => this.guardrailCmd(['remove', ...args]); @@ -773,9 +780,14 @@ buildSystemPrompt() { // Log the AI interaction this.terminal.log('INFO', 'AI', 'Query: ' + userMessage.substring(0, 100)); if (loopResult && loopResult.toolTurns) { - this.terminal.log('INFO', 'AI', 'Used ' + loopResult.toolTurns + ' tool(s)'); + const toolNames = this._toolCallLog.slice(-(loopResult.toolTurns || 0)).map(function(e) { return e.name; }).join(', '); + this.terminal.log('INFO', 'AI', 'Used ' + loopResult.toolTurns + ' tool(s): ' + toolNames); } } + // Auto-archive every 10 messages + if (this._messageCount > 0 && this._messageCount % 10 === 0) { + this._archiveSession('Auto-archive at message ' + this._messageCount); + } // Register session save on page unload (runs once per session) if (!this._sessionSaved) { this._sessionSaved = true; @@ -807,12 +819,13 @@ buildSystemPrompt() { if (!this._debugPanelEnabled) return; const panel = document.getElementById('debug-ai-panel'); if (!panel) return; + const activeTab = panel.getAttribute('data-tab') || 'tools'; const recent = this._toolCallLog.slice(-20); - let html = ''; + let toolHtml = ''; for (const entry of recent) { const statusClass = entry.error ? 'debug-entry-error' : 'debug-entry-ok'; const argsStr = typeof entry.args === 'string' ? entry.args.substring(0, 60) : JSON.stringify(entry.args).substring(0, 60); - html += '
' + + toolHtml += '
' + '#' + entry.turn + ' ' + '' + this._escapeHtml(entry.name) + ' ' + '' + this._escapeHtml(argsStr) + ' ' + @@ -820,14 +833,41 @@ buildSystemPrompt() { (entry.error ? ' ⚠️' : '') + '
'; } + // Raw I/O tab content + let rawHtml = ''; + if (this._lastRawResponse) { + const r = this._lastRawResponse; + const content = (r.message && r.message.content) || ''; + const hasTools = r.tool_calls && r.tool_calls.length > 0; + rawHtml += '
'; + if (r.error) rawHtml += '
⚠️ ' + this._escapeHtml(r.error) + '
'; + if (content) { + const safe = this._escapeHtml(content.length > 300 ? content.substring(0, 300) + '...' : content); + rawHtml += '
Content: ' + safe + '
'; + } + if (hasTools) { + rawHtml += '
Tools: ' + r.tool_calls.map(function(tc) { return tc.function.name; }).join(', ') + '
'; + } + if (this._lastRawMessages) { + rawHtml += '
Messages: ' + this._lastRawMessages.length + '
'; + } + rawHtml += '
'; + } else { + rawHtml = '
No AI response yet. Send a message first.
'; + } + const tabBar = '
' + + '🔧 Tools' + + '📡 Raw I/O' + + '
'; + const bodyHtml = activeTab === 'raw' ? rawHtml : toolHtml; panel.innerHTML = '
' + - '🔧 Tool Log ' + this._toolCallLog.length + ' calls' + + '🔧 AI Debug ' + this._toolCallLog.length + ' calls' + '' + '' + '' + '' + '✕' + - '
' + html; + '
' + tabBar + bodyHtml; } _isToolError(result) { if (!result || typeof result !== 'string') return null; @@ -878,6 +918,28 @@ buildSystemPrompt() { var loopDetect = loopCfg.loopDetection || { enabled: true, 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.'; + // Memory-augmented planning: inject prior loop lessons as negative constraints + try { + var fs = this.terminal.fs; + if (fs) { + var index = fs.fsReadJSON('/skills/index.json') || {}; + var lessonKeys = Object.keys(index).filter(function(k) { return k.indexOf('loop-lesson-') === 0; }); + if (lessonKeys.length > 0) { + var recentLessons = lessonKeys.sort().slice(-3); + var lessonTexts = []; + for (var li = 0; li < recentLessons.length; li++) { + var lc = fs.readFile('/skills/' + recentLessons[li] + '.md'); + if (lc) { + var excerpt = lc.substring(0, 300); + lessonTexts.push('[Lesson: ' + recentLessons[li] + '] ' + excerpt); + } + } + if (lessonTexts.length > 0) { + messages.push({ role: 'system', content: 'Prior loop lessons (read before planning):\n' + lessonTexts.join('\n---\n') + '\n\nUse these to avoid repeating past mistakes. If lessons mention tool failures, verify those tools before calling them.' }); + } + } + } + } catch (e) { /* VFS not available — skip memory-augmented planning */ } let toolTurns = 0; const _seenCalls = new Map(); const _loopWindow = loopDetect.windowMs || 30000; @@ -892,6 +954,9 @@ buildSystemPrompt() { const thinkStart = Date.now(); const res = await this.terminal.ollamaManager.chatWithTools(messages, this.getToolDefinitions()); const thinkTime = Math.round((Date.now() - thinkStart) / 1000); + // Store raw response for debug-ai-last + this._lastRawResponse = res; + this._lastRawMessages = messages.slice(); if (this._cancelled) return { finalAnswer: null, toolTurns }; if (res.error) { this.terminal.print(`⚠️ AI model error (${thinkTime}s): ${res.error}`, 'error'); return { finalAnswer: null, toolTurns }; } if (res.message && !res.tool_calls) { @@ -2068,10 +2133,26 @@ buildSystemPrompt() { return JSON.stringify({ error: `Profile '${name}' not found`, available: Object.keys(scm.contextProfiles) }); } scm.activeProfile = name; + scm.saveActiveProfile(); const profile = scm.contextProfiles[name]; // Rebuild system prompt to reflect new context profile this.systemPrompt = this.buildSystemPrompt(); - return JSON.stringify({ success: true, name, displayName: profile.name, systemPrompt: profile.systemPrompt, temperature: profile.temperature }); + // Also switch agent loop profile to match + const PROFILE_MAP = { + 'coding': 'coder', + 'debug': 'minimal', + 'creative': 'creative', + 'technical': 'research', + 'chat': 'default', + 'system-admin': 'safe', + 'research': 'research', + 'security': 'safe' + }; + const targetLoop = PROFILE_MAP[name]; + if (targetLoop && this._switchLoopProfile) { + this._switchLoopProfile(targetLoop); + } + return JSON.stringify({ success: true, name, displayName: profile.name, systemPrompt: profile.systemPrompt, temperature: profile.temperature, agentLoop: targetLoop || null }); } case 'create_mindmap': { const mindmap = this.terminal.moduleManager.modules['MindmapModule']; @@ -2796,6 +2877,121 @@ buildSystemPrompt() { } return total; } + _showToolCallLog() { + if (!this._toolCallLog || this._toolCallLog.length === 0) { + this.terminal.print('No tool calls recorded this session.', 'warning'); + return; + } + let out = '── Tool Call Log (' + this._toolCallLog.length + ' calls) ──\n'; + for (var i = 0; i < this._toolCallLog.length; i++) { + var e = this._toolCallLog[i]; + var err = e.error ? ' ⚠️' : ''; + var argsStr = typeof e.args === 'string' ? e.args.substring(0, 80) : JSON.stringify(e.args).substring(0, 80); + out += ' #' + e.turn + ' ' + e.name + '(' + argsStr + ') ' + e.duration + 's' + err + '\n'; + } + out += '── End ──'; + this.terminal.print(out, 'info'); + } + _archiveList() { + try { + const fs = this.terminal.fs; + const files = fs.ls('/memory/archive') || []; + const jsonFiles = files.filter(function(f) { return f.name && f.name.endsWith('.json'); }); + if (jsonFiles.length === 0) { + this.terminal.print('No archives found. Run ai-compress to create one.', 'warning'); + return; + } + jsonFiles.sort(function(a, b) { return (a.name || '') < (b.name || '') ? 1 : -1; }); + let out = '── Archives (' + jsonFiles.length + ') ──\n'; + for (var i = 0; i < Math.min(jsonFiles.length, 30); i++) { + var f = jsonFiles[i]; + var size = f.size ? '(' + f.size + 'b)' : ''; + out += ' ' + f.name + ' ' + size + '\n'; + } + out += '── End ──\nUse: archive-read '; + this.terminal.print(out, 'info'); + } catch (e) { + this.terminal.print('Error listing archives: ' + e.message, 'error'); + } + } + _archiveRead(args) { + var name = (args && args[0]) || ''; + if (!name) { + this.terminal.print('Usage: archive-read (e.g. 2026-06-25-14-30.json)', 'warning'); + return; + } + try { + const fs = this.terminal.fs; + var data = fs.fsReadJSON('/memory/archive/' + name); + if (!data) { + this.terminal.print('Archive not found: ' + name, 'error'); + return; + } + var out = '── Archive: ' + name + ' ──\n'; + out += 'Archived: ' + (data.archivedAt || '?') + '\n'; + out += 'Summary: ' + (data.summary || '(none)') + '\n'; + out += 'User messages: ' + (data.user?.messageCount || 0) + '\n'; + out += 'System: ' + (data.system?.modules || 0) + ' modules, ' + (data.system?.model || '?') + '\n'; + out += 'Context: ' + (data.context?.messageCount || 0) + ' msgs, ~' + (data.context?.estimatedTokens || 0) + ' tokens\n'; + out += '── End ──'; + this.terminal.print(out, 'info'); + } catch (e) { + this.terminal.print('Error reading archive: ' + e.message, 'error'); + } + } + _showLastRawResponse() { + if (!this._lastRawResponse) { + this.terminal.print('No AI response recorded yet. Send a message first.', 'warning'); + return; + } + const r = this._lastRawResponse; + var content = (r.message && r.message.content) || ''; + var hasTools = r.tool_calls && r.tool_calls.length > 0; + var toolStr = hasTools ? r.tool_calls.map(function(tc) { return tc.function.name + '(' + (tc.function.arguments || '{}') + ')'; }).join(', ') : ''; + // Render as HTML card for better readability + var html = '
' + + '
' + + '🤖 Last AI Response' + + '' + + '' + + '
' + + '
'; + if (r.error) { + html += '
⚠️ Error: ' + this._escapeHtml(r.error) + '
'; + } + if (content) { + var safe = this._escapeHtml(content.length > 1000 ? content.substring(0, 1000) + '...(' + content.length + ' chars)' : content); + html += '
Content:
' + safe + '
'; + } else if (!hasTools) { + html += '
⚠️ Empty response (no content, no tool calls)
'; + } + if (hasTools) { + html += '
Tool calls: ' + this._escapeHtml(toolStr) + '
'; + } + html += '
'; + this.terminal.printHTML(html); + } + _showLastRawRequest() { + if (!this._lastRawMessages) { + this.terminal.print('No AI request recorded yet. Send a message first.', 'warning'); + return; + } + const msgs = this._lastRawMessages; + let out = '── Last AI Request (' + msgs.length + ' messages) ──\n'; + for (let i = 0; i < msgs.length; i++) { + const m = msgs[i]; + const role = m.role || '?'; + const content = (m.content || '').substring(0, 200); + const suffix = (m.content || '').length > 200 ? '...' : ''; + out += ' [' + i + '] ' + role + ': ' + content + suffix + '\n'; + if (i > 8 && i < msgs.length - 2) { + out += ' ... (' + (msgs.length - i - 2) + ' more messages) ...\n'; + i = msgs.length - 3; + } + } + out += '── End ──'; + this.terminal.print(out, 'info'); + } async _compressContext() { if (this.context.length <= 6) return; const totalTokens = this._estimateContextTokens(); @@ -2821,10 +3017,58 @@ buildSystemPrompt() { } catch (e) { /* fallback to truncation */ } this.context = [{ role: 'system', content: compressed }, ...recentMsgs]; this.terminal.print('📦 Context compressed (' + oldMsgs.length + ' messages → 1 summary, ~' + totalTokens + ' tokens)', 'info'); + // Also archive session snapshot + this._archiveSession(compressed); + } + _archiveSession(summary) { + try { + const fs = this.terminal.fs; + fs.mkdirRecursive('/memory/archive'); + const now = new Date(); + const pad = function(n) { return String(n).padStart(2, '0'); }; + const ts = now.getFullYear() + '-' + pad(now.getMonth()+1) + '-' + pad(now.getDate()) + '-' + pad(now.getHours()) + '-' + pad(now.getMinutes()); + const archive = { + archivedAt: now.toISOString(), + summary: summary || '', + user: { + profile: this._userProfile || {}, + topics: [], + messageCount: this._messageCount + }, + system: { + modules: this.terminal?.moduleManager?.modules ? Object.keys(this.terminal.moduleManager.modules).length : 0, + model: this.terminal?.ollamaManager?.currentModel || 'unknown', + provider: this.terminal?.ollamaManager?.currentProvider?.name || 'unknown' + }, + context: { + messageCount: this.context.length, + estimatedTokens: this._estimateContextTokens() + } + }; + fs.fsWriteJSON('/memory/archive/' + ts + '.json', archive); + // Rotation: keep last 30 + try { + const files = fs.ls('/memory/archive') || []; + const jsonFiles = files.filter(function(f) { return f.name && f.name.endsWith('.json'); }); + if (jsonFiles.length > 30) { + jsonFiles.sort(function(a, b) { return (a.name || '') < (b.name || '') ? -1 : 1; }); + const toDelete = jsonFiles.slice(0, jsonFiles.length - 30); + for (var i = 0; i < toDelete.length; i++) { + fs.deleteFile('/memory/archive/' + toDelete[i].name); + } + } + } catch (e) { /* rotation best-effort */ } + } catch (e) { /* archive best-effort */ } } _manualCompress() { this._compressContext().then(() => { - this.terminal.print('Context: ' + this.context.length + ' message(s), ~' + this._estimateContextTokens() + ' estimated tokens', 'info'); + const fs = this.terminal.fs; + let archiveCount = 0; + try { + const files = fs.ls('/memory/archive') || []; + archiveCount = files.filter(function(f) { return f.name && f.name.endsWith('.json'); }).length; + } catch (e) {} + this.terminal.print('Context: ' + this.context.length + ' message(s), ~' + this._estimateContextTokens() + ' estimated tokens, ' + archiveCount + ' archive(s)', 'info'); }).catch(e => { this.terminal.print('Compression error: ' + e.message, 'error'); }); @@ -3150,11 +3394,38 @@ buildSystemPrompt() { } if (anomalies.length === 0) return; var suggestions = []; + var profileAdjusted = false; 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'); + if (a.type === 'max_turns_near_limit') { + suggestions.push('This task used ' + result.toolTurns + '/' + maxT + ' turns — consider increasing max-turns or splitting the task'); + // Auto-correct: bump maxTurns by +2 + if (this._activeLoopProfile && this._activeLoopProfile.loop) { + var newMax = (this._activeLoopProfile.loop.maxTurns || this.MAX_TOOL_TURNS) + 2; + this._activeLoopProfile.loop.maxTurns = newMax; + this._saveLoopProfile(this._activeLoopProfile); + suggestions.push(' → Auto-adjusted maxTurns to ' + newMax); + profileAdjusted = true; + } + } 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 === 'tool_failures') { + suggestions.push(a.count + ' tool failures (' + a.tools.join(', ') + ') — check if modules are loaded or paths correct'); + // 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.'; + if (this._activeLoopProfile.systemPrompt.append) { + if (this._activeLoopProfile.systemPrompt.append.indexOf(constraint) === -1) { + this._activeLoopProfile.systemPrompt.append += '\n' + constraint; + } + } else { + this._activeLoopProfile.systemPrompt.append = constraint; + } + this._saveLoopProfile(this._activeLoopProfile); + suggestions.push(' → Added constraint to profile: avoid ' + a.tools.join(', ')); + profileAdjusted = true; + } + } 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'); @@ -3162,15 +3433,47 @@ buildSystemPrompt() { 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) { + // One-time cleanup: remove all existing loop-lesson files on first run + if (!this._loopLessonCleaned) { + this._loopLessonCleaned = true; + autoSkills.forEach(function(e) { + try { fs.unlink('/skills/' + e[0] + '.md'); } catch (ex) {} + delete index[e[0]]; + }); + autoSkills = []; + } + // Age limit: remove lessons older than 24h + var now = Date.now(); + autoSkills = autoSkills.filter(function(e) { + var created = new Date(e[1].created).getTime(); + if (now - created > 86400000) { + try { fs.unlink('/skills/' + e[0] + '.md'); } catch (ex) {} + delete index[e[0]]; + return false; + } + return true; + }); + // Dedup: skip if latest lesson has same anomaly types + var anomalyTypes = anomalies.map(function(a) { return a.type; }).sort().join(','); + var isDuplicate = autoSkills.length > 0 && autoSkills[autoSkills.length - 1][1].anomalyTypes === anomalyTypes; + // FIFO rotation: if at cap, delete oldest before creating + if (!isDuplicate && autoSkills.length >= 3) { + var oldest = autoSkills[0]; + try { fs.unlink('/skills/' + oldest[0] + '.md'); } catch (ex) {} + delete index[oldest[0]]; + autoSkills.shift(); + } + if (!isDuplicate) { 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() }; + 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(), anomalyTypes: anomalyTypes }; fs.fsWriteJSON('/skills/index.json', index); } } catch (e) { /* VFS not available */ } - this.terminal.print('💡 Self-improvement: ' + suggestion, 'info'); + var msg = '💡 Self-improvement: ' + anomalies.length + ' anomaly(ies)'; + if (profileAdjusted) msg += ' — profile auto-adjusted'; + this.terminal.print(msg, 'info'); } } diff --git a/modules/core/module-loader.js b/modules/core/module-loader.js index f024d99..7bb7c89 100644 --- a/modules/core/module-loader.js +++ b/modules/core/module-loader.js @@ -4,7 +4,7 @@ // Core module bundle loaded synchronously in main.html. // This loader only handles version cache-busting, splash, terminal init, and optional ext modules. - const LATEST_VER = '2026-06-24-v4.5.80'; + const LATEST_VER = '2026-06-30-v4.5.83'; const CACHE = '?v=' + LATEST_VER; const stored = localStorage.getItem('webx11_cache_ver'); if (stored !== LATEST_VER) { @@ -53,7 +53,7 @@ setSplash('Initializing terminal (3/3)', total, total, ''); // webx11 already created synchronously above — just init ext modules if (typeof webx11._loadExtModules === 'function') { - let extKeys = ['calc', 'voice', 'debug-panel', 'cron', 'theme', 'wpm', 'mindmap', 'webdav', 'skill-pack-core', 'cryptogzip', 'tutorial', 'floating', 'console', 'media']; + let extKeys = ['calc', 'voice', 'debug-panel', 'cron', 'theme', 'wpm', 'mindmap', 'webdav', 'skill-pack-core', 'cryptogzip', 'tutorial', 'floating', 'console', 'media', 'three']; const saved = localStorage.getItem('webx11_startup_modules'); let selected = null; try { if (saved) selected = JSON.parse(saved); } catch (e) {} diff --git a/modules/core/module-manager.js b/modules/core/module-manager.js index 8cecc4d..4723bff 100644 --- a/modules/core/module-manager.js +++ b/modules/core/module-manager.js @@ -36,6 +36,11 @@ } async loadModuleFromURL(url) { try { + // Security: block cross-origin URLs for module loading + if (url && url.match(/^https?:\/\//) && !url.startsWith(window.location.origin)) { + this.terminal.print(`⚠️ Security: only same-origin URLs allowed for module loading`, 'error'); + return false; + } this.terminal.print(`Loading module from ${url}...`, 'info'); const response = await fetch(url); if (!response.ok) throw new Error(`HTTP ${response.status}`); diff --git a/modules/core/module-modmgr.js b/modules/core/module-modmgr.js index ec8c384..8c39e04 100644 --- a/modules/core/module-modmgr.js +++ b/modules/core/module-modmgr.js @@ -338,22 +338,27 @@ this.terminal.print(`✅ Module '${name}' marked as unloaded (commands will not be available)`, 'success'); }; - // module-reload — unload + reload with diff - this.commands['module-reload'] = (args) => { + // module-reload — re-fetch, re-parse, re-register + this.commands['module-reload'] = async (args) => { if (!args[0]) return this.terminal.print('Usage: module-reload ', 'warning'); const name = args[0]; const mod = this._getMod(name); if (!mod) return this.terminal.print(`Module not found: ${name}`, 'error'); - const wasLoaded = mod.loaded !== false; - const wasLazy = mod.lazy; - this.terminal.print(`🔄 Reloading '${name}' (was: ${wasLoaded ? 'loaded' : 'unloaded'}, lazy=${wasLazy})`, 'info'); - mod.loaded = false; - setTimeout(() => { - mod.loaded = true; - this.terminal.print(`✅ Module '${name}' reloaded`, 'success'); - const cmdCount = mod.commands ? Object.keys(mod.commands).length : 0; - this.terminal.print(` Now: loaded=true, lazy=${mod.lazy}, ${cmdCount} commands`, 'info'); - }, 100); + const mm = this.terminal.moduleManager; + // Find the module's URL from registry + const regEntry = mm.moduleRegistry.find(m => m.name === name); + const url = regEntry ? regEntry.url : null; + if (!url) return this.terminal.print(`Module '${name}' has no URL — cannot reload (built-in)`, 'warning'); + this.terminal.print(`🔄 Reloading '${name}' from ${url}...`, 'info'); + // Unregister old instance + mm.unregisterModule(name); + // Re-fetch and re-register + const success = await mm.loadModuleFromURL(url); + if (success) { + this.terminal.print(`✅ Module '${name}' reloaded successfully`, 'success'); + } else { + this.terminal.print(`❌ Failed to reload '${name}'`, 'error'); + } }; // module-reset — clear module state and reload diff --git a/modules/core/module-smartctx.js b/modules/core/module-smartctx.js index 81929f8..0bfc91a 100644 --- a/modules/core/module-smartctx.js +++ b/modules/core/module-smartctx.js @@ -40,11 +40,14 @@ } getDefaultProfiles() { return { - 'coding': { name: 'Coding Assistant', systemPrompt: "You are an expert programmer.", temperature: 0.7 }, - 'debug': { name: 'Debug Helper', systemPrompt: "You are a debugging expert.", temperature: 0.5 }, - 'creative': { name: 'Creative Writer', systemPrompt: "You are a creative writer.", temperature: 0.9 }, - 'technical': { name: 'Technical Docs', systemPrompt: "You are a technical writer.", temperature: 0.3 }, - 'chat': { name: 'Casual Chat', systemPrompt: "You are a friendly assistant.", temperature: 0.8 } + 'coding': { name: 'Coding Assistant', systemPrompt: "You are a WebX11 newOS4 coding expert. You have access to the full terminal, VFS, and module system.\n\nAvailable commands: view_module, edit_module, test_module, reload_module, list_modified_modules, check_updates, load-local, loadmod, removemod, startup-modules, node, js-run, git, diff, patch\n\nSkills available: module-management (view/edit/test/reload modules), skills-system-guide (create/patch skills)\n\nBehavior:\n- Always show exact code changes when editing modules\n- Use edit_module for modifications, test_module to verify\n- Create checkpoints before risky edits: checkpoint mod-edit-\n- Reference the module-management skill for the full workflow\n- When the user reports a bug, first view_module to inspect, then edit\n- Use nvfs_resolve to find relevant files before editing\n- Prefer write_file for creating new files, edit_module for modifying modules", temperature: 0.7 }, + 'debug': { name: 'Debug Helper', systemPrompt: "You are a WebX11 newOS4 debugging expert. Your goal is to diagnose and fix issues systematically.\n\nAvailable commands: debug, debug-ai, debug-ai-last, debug-ai-send, debug-ai-log, logs, logrotate, health-check, check, mem, status, archive-list, archive-read, guardrail, shadow, shadow-commit, shadow-rollback\n\nSkills available: debug-panel-and-logs (toggle debug panel, session logging, diagnostics), system-maintenance (health checks, config, security)\n\nBehavior:\n- Start with health-check and check to assess system state\n- Use debug-ai to see the last AI tool call and response\n- Use logs 50 ERROR to find error entries\n- Check browser console for JS errors\n- Use guardrail to inspect security policies\n- Use shadow for safe execution of risky commands\n- Always check module loading status first", temperature: 0.3 }, + 'creative': { name: 'Creative Writer', systemPrompt: "You are a creative assistant for WebX11 newOS4. You help with brainstorming, content creation, and design.\n\nAvailable commands: mindmap new/add/show/save/load/export, floating-add/remove/list, ai-system, ai-config\n\nSkills available: mindmap-workflow (create/view/edit mindmaps), floating-ui (create floating buttons/menus/panels)\n\nBehavior:\n- Use mindmap for brainstorming and organizing ideas\n- Use floating-add to create quick-access UI elements\n- Use web_search and web_fetch for research and inspiration\n- Higher temperature (0.9) for creative divergence\n- When the user says \"brainstorm\" or \"diagram\", create a mindmap\n- Use create_mindmap AI tool for visual architecture diagrams\n- Reference the mindmap-workflow skill for full capabilities", temperature: 0.9 }, + 'technical': { name: 'Technical Docs', systemPrompt: "You are a technical documentation expert for WebX11 newOS4. You help with docs, guides, and system understanding.\n\nAvailable commands: help, commands, status, modules, health-check, nvfs-index, nvfs-resolve, view_module, cat, edit, grep, find, tree\n\nSkills available: navigating-terminal (explore terminal, filesystem, system state), web-search-and-fetch (search web, fetch URLs), skills-system-guide (create/patch skills)\n\nBehavior:\n- Use help and commands to discover available functionality\n- Use nvfs-resolve to find files by concept\n- Use view_module to inspect module source for documentation\n- When creating documentation, use write_file to save to VFS\n- Reference the navigating-terminal skill for filesystem exploration\n- Use web_search for external research", temperature: 0.3 }, + 'chat': { name: 'Casual Chat', systemPrompt: "You are a friendly, helpful assistant for WebX11 newOS4. You handle general questions, casual conversation, and light tasks.\n\nAvailable commands: ai, ask, search, fetch, status, help, user_get, user_set, session_save, session_recent, my-set, my-get\n\nSkills available: memory-and-profile (user profile, session memory, checkpoints), web-search-and-fetch (search web, fetch URLs)\n\nBehavior:\n- Be conversational and approachable\n- Use user_get/user_set to remember user preferences\n- Use session_recent at start to recall past work\n- Use session_save after completing tasks\n- For complex tasks, suggest switching to a more specific context profile\n- Reference the memory-and-profile skill for persistence features", temperature: 0.8 }, + 'system-admin': { name: 'System Admin', systemPrompt: "You are a WebX11 newOS4 system administrator. You handle maintenance, backup, security, and configuration.\n\nAvailable commands: config-list, config-set, config-delete, config-reset, security-level, security, cmd-allow, cmd-deny, backup, vfs-export, webdav-backup, webdav-restore, webdav-config, webdav-status, factory-reset, reset-vfs, checkpoint, checkpoint-list, rollback, health-check, check_updates, font-size\n\nSkills available: system-maintenance (reset, config, security, health checks), backup-and-restore (VFS backup, WebDAV cloud backup, restore), webdav-operations (cloud storage, file sync), security-and-safety (security levels, command blocking)\n\nBehavior:\n- Always create a checkpoint before making system changes\n- Use webdav-backup before destructive operations\n- Check security-level before running commands\n- Use health-check for full system audit\n- Reference the system-maintenance skill for reset procedures\n- Reference the backup-and-restore skill for data safety", temperature: 0.3 }, + 'research': { name: 'Research Assistant', systemPrompt: "You are a research assistant for WebX11 newOS4. You gather information, analyze data, and synthesize findings.\n\nAvailable commands: search, fetch, web_search, web_fetch, nvfs-resolve, nvfs-index, grep, find, cat, view_module, session_recent, session_load, session_stats\n\nSkills available: web-search-and-fetch (search web, fetch URL content), memory-and-profile (session memory, checkpoints), navigating-terminal (explore filesystem)\n\nBehavior:\n- Start with web_search for external research\n- Use nvfs-resolve to find relevant VFS files\n- Use session_recent to check past work on the topic\n- Use fetch to read full articles from URLs\n- Synthesize findings before presenting\n- Reference the web-search-and-fetch skill for search flags", temperature: 0.5 }, + 'security': { name: 'Security Expert', systemPrompt: "You are a WebX11 newOS4 security expert. You handle threat assessment, command control, and safety.\n\nAvailable commands: security, security-level, cmd-allow, cmd-deny, guardrail, guardrail-add, guardrail-remove, shadow, shadow-commit, shadow-rollback, health-check, check, logs, debug\n\nSkills available: security-and-safety (security levels, command blocking, safety checklist), debug-panel-and-logs (diagnostics, logging), system-maintenance (config, health checks)\n\nBehavior:\n- Always check current security level first with security\n- Use guardrail to inspect and manage security policies\n- Use shadow for safe execution of suspicious commands\n- Never recommend factory-reset without backup first\n- Use cmd-deny to block dangerous commands\n- Reference the security-and-safety skill for level descriptions", temperature: 0.2 } }; } registerCommands() { @@ -62,6 +65,32 @@ this.activeProfile = args[0]; this.saveActiveProfile(); this.terminal.print(`Context set to: ${this.contextProfiles[args[0]].name}`, "success"); + // Also switch agent loop profile to match + const PROFILE_MAP = { + 'coding': 'coder', + 'debug': 'minimal', + 'creative': 'creative', + 'technical': 'research', + 'chat': 'default', + 'system-admin': 'safe', + 'research': 'research', + 'security': 'safe' + }; + const targetLoop = PROFILE_MAP[args[0]]; + if (targetLoop) { + const aiMod = this.terminal?.moduleManager?.modules?.WebX11AIAssistantModule; + if (aiMod && aiMod._switchLoopProfile) { + aiMod._switchLoopProfile(targetLoop); + this.terminal.print(` Agent loop switched to: ${targetLoop}`, "text-muted"); + } + } else { + // Custom profile — rebuild system prompt without loop switch + const aiMod = this.terminal?.moduleManager?.modules?.WebX11AIAssistantModule; + if (aiMod && aiMod.buildSystemPrompt) { + aiMod.systemPrompt = aiMod.buildSystemPrompt(); + } + this.terminal.print(` No agent loop mapped for '${args[0]}'`, "text-muted"); + } } else { this.terminal.print(`Profile '${args[0]}' not found`, "error"); } @@ -111,7 +140,8 @@ const aiModule = this.terminal?.moduleManager?.modules?.WebX11AIAssistantModule; if (aiModule) { // Set temperature on ollamaManager modelParams temporarily - const om = this.terminal.ollamaManager; + const om = this.terminal?.ollamaManager; + if (!om) { this.terminal?.print('⚠️ Ollama manager not available', 'error'); return; } const savedTemp = om.modelParams ? om.modelParams.temperature : undefined; if (om.modelParams) { om.modelParams.temperature = temperature; -- cgit v1.2.3