diff options
| author | Agando <roduccomm@gmail.com> | 2026-06-16 06:01:21 +0200 |
|---|---|---|
| committer | Agando <roduccomm@gmail.com> | 2026-06-16 06:01:21 +0200 |
| commit | 6aa24b976952a698c673d350977721b27193183d (patch) | |
| tree | 5586529d0f9932c4a2db19b9aca82b05423d10ab /modules/core | |
| parent | daff0939a7997b45aed12c7d157f6adcfbd087ec (diff) | |
auto: 2026-06-16_06:00 changes
Diffstat (limited to 'modules/core')
| -rw-r--r-- | modules/core/core-bundle.js | 168 | ||||
| -rw-r--r-- | modules/core/module-aiassist.js | 7 | ||||
| -rw-r--r-- | modules/core/module-loader.js | 4 | ||||
| -rw-r--r-- | modules/core/terminal.js | 13 |
4 files changed, 165 insertions, 27 deletions
diff --git a/modules/core/core-bundle.js b/modules/core/core-bundle.js index 8b73403..24ec5af 100644 --- a/modules/core/core-bundle.js +++ b/modules/core/core-bundle.js @@ -918,6 +918,13 @@ window.VirtualFileSystem = VirtualFileSystem; localStorage.setItem('webx11_modules', JSON.stringify(this.moduleRegistry)); } catch (e) { console.error('Module save error:', e); } } + async restorePersistedModules() { + for (const entry of this.moduleRegistry) { + if (entry.enabled && entry.url) { + await this.loadModuleFromURL(entry.url); + } + } + } async loadModuleFromURL(url) { try { this.terminal.print(`Loading module from ${url}...`, 'info'); @@ -927,10 +934,17 @@ window.VirtualFileSystem = VirtualFileSystem; const ModuleClass = this.createModuleClass(code, url); if (ModuleClass) { const instance = new ModuleClass(); - this.registerModule(instance, url); if (instance.init) { - await instance.init(this.terminal); + try { + await instance.init(this.terminal); + instance._initComplete = true; + } catch (e) { + console.error(`[ModuleManager] init() failed for ${instance.constructor.name}:`, e); + this.terminal.print(`Module '${instance.constructor.name}' init() failed: ${e.message}`, 'error'); + return false; + } } + this.registerModule(instance, url); this.terminal.print(`Module loaded successfully from ${url}`, 'success'); return true; } @@ -943,6 +957,13 @@ window.VirtualFileSystem = VirtualFileSystem; try { // Strip leading comments to avoid ASI: `return\n/**` → `return; /**` const cleanCode = code.replace(/^\s*(?:\/\/[^\n]*\n|\/\*[\s\S]*?\*\/\s*)*\s*/, ''); + // Try wrapper convention first: `return class MyModule { ... };` + const wrapperMatch = cleanCode.match(/return\s+class\s+(\w+)/); + if (wrapperMatch) { + const mod = new Function(cleanCode)(); + if (mod && typeof mod === 'function') return mod; + } + // Fall back to regex class extraction const classMatch = cleanCode.match(/class\s+(\w+)\s+(extends\s+\S+)?\s*\{/); if (classMatch) { const className = classMatch[1]; @@ -959,7 +980,15 @@ window.VirtualFileSystem = VirtualFileSystem; const name = instance.constructor.name; this.modules[name] = instance; instance.terminal = this.terminal; - if (instance.registerCommands) { instance.registerCommands(); } + instance.fs = this.terminal.fs; + try { + if (instance.registerCommands) { instance.registerCommands(); } + } catch (e) { + console.error(`[ModuleManager] registerCommands() failed for ${name}:`, e); + delete this.modules[name]; + this.terminal.print(`Module '${name}' registration failed: ${e.message}`, 'error'); + return; + } // Only persist modules loaded from absolute URLs (not boot-time ext modules) if (url && url.startsWith('http') && !this.moduleRegistry.find(m => m.name === name)) { this.moduleRegistry.push({ @@ -979,16 +1008,17 @@ window.VirtualFileSystem = VirtualFileSystem; } return false; } - loadModuleFromCode(code, url) { + async loadModuleFromCode(code, url) { try { this.terminal.print('Loading module from editor...', 'info'); const ModuleClass = this.createModuleClass(code, url); if (ModuleClass) { const instance = new ModuleClass(); - this.registerModule(instance, url); if (instance.init) { - instance.init(this.terminal); + await instance.init(this.terminal); + instance._initComplete = true; } + this.registerModule(instance, url); this.terminal.print(`Module loaded from editor: ${instance.constructor.name}`, 'success'); return true; } @@ -1054,6 +1084,31 @@ window.VirtualFileSystem = VirtualFileSystem; } return help; } + diagnose() { + const report = { + totalInModules: Object.keys(this.modules).length, + totalInRegistry: this.moduleRegistry.length, + orphaned: [], + missingFs: [], + missingTerminal: [], + uninitialized: [], + healthy: 0 + }; + const registryNames = new Set(this.moduleRegistry.map(m => m.name)); + for (const name in this.modules) { + const mod = this.modules[name]; + if (!registryNames.has(name)) { + report.orphaned.push(name); + } + if (!mod.fs) report.missingFs.push(name); + if (!mod.terminal) report.missingTerminal.push(name); + if (typeof mod.init === 'function' && !mod._initComplete) { + report.uninitialized.push(name); + } + if (mod.fs && mod.terminal) report.healthy++; + } + return report; + } } /* === ollama-manager.js === */ @@ -1915,6 +1970,11 @@ class OllamaManager { if (!response.ok) throw new Error(`HTTP ${response.status}`); const data = await response.json(); const text = data.choices?.[0]?.message?.content || ''; + // Sync provider state after successful non-Ollama fallback + if (provider !== this.currentProvider) { + this.currentProvider = provider; + this._syncCompatProperties(provider); + } return { message: { role: 'assistant', content: text }, tool_calls: null, error: null }; } catch (e) { if (e.name === 'AbortError') { @@ -1941,10 +2001,11 @@ ${toolDesc} </tools> Only output XML function calls for tool tasks. If no tool is needed, answer normally.` }; - const msgsWithTools = [toolContext, ...messages]; +const msgsWithTools = [toolContext, ...messages]; const body = { model: useModel, messages: msgsWithTools, stream: false, options: { temperature: 0.1 } }; - const response = await fetch(`${this.endpoint}/api/chat`, { + const ep = this.currentProvider ? this.currentProvider.endpoint : this.endpoint; + const response = await fetch(`${ep}/api/chat`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), signal }); @@ -3411,7 +3472,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', 'cron', 'theme', 'wpm', 'mindmap', 'webdav', 'skill-pack-core', 'cryptogzip', 'tutorial', 'floating', 'console']; + const autoLoadKeys = filterKeys || ['calc', 'voice', 'debug-panel', 'cron', 'theme', 'wpm', 'mindmap', 'webdav', 'skill-pack-core', 'cryptogzip', 'tutorial', 'floating', 'console']; const cacheVer = localStorage.getItem('webx11_cache_ver') || '2026-05-27-14'; let index = null; let lastErr = null; @@ -3444,7 +3505,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', 'cron', 'theme', 'wpm', 'mindmap', 'webdav', 'skill-pack-core', 'editor', 'diff', 'alias', 'finder', 'filemgr', 'cryptogzip', 'shell', 'tutorial', 'busybox', 'floating', 'console']; + 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']; for (const mod of registry) { if (mod.enabled && mod.url) { // Skip ext modules — they load via _loadExtModules @@ -4641,6 +4702,15 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm voiceMod.handle([]); } } + toggleDebugPanel() { + const mod = this.moduleManager.modules['DebugPanel']; + if (mod) { + mod.terminal = this; + mod.toggle(); + } else { + this.print('Debug panel not loaded', 'warning'); + } + } setFontSize(size) { const s = parseInt(size); if (isNaN(s) || s < 8 || s > 32) { @@ -5909,11 +5979,17 @@ class OllamaModule { }; this.commands['ai'] = async (args) => { if(!args[0]) return this.terminal.print("Usage: ai <prompt>", 'warning'); - await this.terminal.ollamaManager.chat(args.join(' ')); + const result = await this.terminal.ollamaManager.chat(args.join(' ')); + if (result === null) { + this.terminal.print('AI returned no response — check provider connection', 'warning'); + } }; this.commands['ask'] = async (args) => { if(!args[0]) return this.terminal.print("Usage: ask <prompt>", 'warning'); - await this.terminal.ollamaManager.chat(args.join(' ')); + const result = await this.terminal.ollamaManager.chat(args.join(' ')); + if (result === null) { + this.terminal.print('AI returned no response — check provider connection', 'warning'); + } }; // Merged from OllamaModelsModule this.commands['models'] = () => this.listModels(); @@ -6139,7 +6215,29 @@ class MyModule { return mm.modules[name]; } registerCommands() { - this.commands['modules'] = () => { this.terminal.openModuleManager(); }; + this.commands['modules'] = (args) => { + if (args[0] === '--diagnose' || args[0] === '-d') { + const report = this.terminal.moduleManager.diagnose(); + this.terminal.print(`=== Module Diagnostics ===`, 'info'); + this.terminal.print(`Total in modules: ${report.totalInModules}`); + this.terminal.print(`Total in registry: ${report.totalInRegistry}`); + this.terminal.print(`Healthy: ${report.healthy}`); + if (report.orphaned.length) { + this.terminal.print(`Orphaned (in modules but not registry): ${report.orphaned.join(', ')}`, 'warning'); + } + if (report.missingFs.length) { + this.terminal.print(`Missing fs: ${report.missingFs.join(', ')}`, 'error'); + } + if (report.missingTerminal.length) { + this.terminal.print(`Missing terminal: ${report.missingTerminal.join(', ')}`, 'error'); + } + if (report.uninitialized.length) { + this.terminal.print(`Uninitialized (init() never completed): ${report.uninitialized.join(', ')}`, 'warning'); + } + return; + } + this.terminal.openModuleManager(); + }; this.commands['loadmod'] = async (args) => { if(!args[0]) return this.terminal.print("Usage: loadmod <url>", 'warning'); await this.terminal.moduleManager.loadModuleFromURL(args[0]); @@ -7187,7 +7285,9 @@ class ServerMonitorModule { // Extracted from newOS4 index.html — WebX11AIAssistantModule (Full Agent Loop) class WebX11AIAssistantModule { constructor() { - this.commands = {}; this.terminal = null; + this.commands = {}; this.commandsMeta = { + 'ai-config': { argType: 'enum', enumFn: '_getAiConfigKeys' } + }; this.terminal = null; this.systemPrompt = this.buildSystemPrompt(); this.context = []; this.maxContext = 10; this.maxContextTokens = 2000; @@ -7888,6 +7988,16 @@ buildSystemPrompt() { } async agenticLoop(messages) { let toolTurns = 0; + // Auto-connect if not connected + const om = this.terminal.ollamaManager; + if (om && !om.connected) { + this.terminal.print('⏳ AI provider not connected — attempting auto-connect...', 'info'); + const connected = await om.connect(); + if (!connected) { + this.terminal.print('❌ Could not connect to any AI provider.', 'error'); + return { finalAnswer: null, toolTurns: 0 }; + } + } while (toolTurns < this.MAX_TOOL_TURNS) { if (this._cancelled) { this.terminal.print('AI loop cancelled', 'warning'); return { finalAnswer: null, toolTurns }; } toolTurns++; @@ -7936,9 +8046,9 @@ buildSystemPrompt() { const sm = this.terminal.moduleManager.modules['ServerMonitorModule']; if (sm && sm.logToolCall) sm.logToolCall(fn.name, (t1 * 1000) || 1, this._isToolError(toolResult)); if (this._debugPanelEnabled) this._updateDebugPanel(); - return { fn, toolResult, t1 }; + return { fn, toolResult, t1, toolCallId: tc.id }; })); - for (const { fn, toolResult, t1 } of safeResults) { + for (const { fn, toolResult, t1, toolCallId } of safeResults) { this.terminal.print(`🔧 Tool: ${fn.name}(${typeof fn.arguments === 'string' ? fn.arguments : JSON.stringify(fn.arguments)})`, 'info'); // Memory graph: record tool call + file access try { @@ -7965,7 +8075,7 @@ buildSystemPrompt() { this.terminal.log('INFO', 'AI', `Tool ${fn.name} ok (${t1}s)`); this.terminal.print(`📊 Result (${t1}s): ${obs}`, 'info'); } - messages.push({ role: 'tool', content: toolResult }); + messages.push({ role: 'tool', tool_call_id: toolCallId, content: toolResult }); } // Only block on destructive tools if (destructiveTools.length > 0) { @@ -8027,7 +8137,7 @@ buildSystemPrompt() { this.terminal.log('INFO', 'AI', `Tool ${fn.name} ok (${t1}s)`); this.terminal.print(`📊 Result (${t1}s): ${dObs}`, 'info'); } - messages.push({ role: 'tool', content: toolResult }); + messages.push({ role: 'tool', tool_call_id: tc.id, content: toolResult }); } } else { messages.push({ @@ -9062,13 +9172,24 @@ buildSystemPrompt() { 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(', ') }); + const epMap = { + 'prices': '/vmai-api/market/prices', + 'portfolio': '/vmai-api/portfolio', + 'transactions': '/vmai-api/transactions/summary', + 'copy-trade': '/vmai-api/copy-trading/start', + 'chat': '/vmai-api/chat', + }; + const url = epMap[ep]; + if (!url) { + return JSON.stringify({ error: 'Invalid endpoint. Valid: ' + Object.keys(epMap).join(', ') }); } try { - let url = '/vmai/' + ep; + // Read VaultMind session token from localStorage (set by VaultMind dashboard) + const vmToken = typeof localStorage !== 'undefined' ? localStorage.getItem('vaultmind_token') : null; let opts = { method: 'GET', headers: { 'Accept': 'application/json' } }; + if (vmToken) { + opts.headers['Authorization'] = 'Bearer ' + vmToken; + } if (ep === 'chat') { opts.method = 'POST'; opts.headers['Content-Type'] = 'application/json'; @@ -9409,6 +9530,9 @@ buildSystemPrompt() { this.terminal.print('No models available. Use ollamaconnect first.', 'warning'); } } + _getAiConfigKeys() { + return ['max-turns', 'max-context', 'max-context-tokens', 'confirm-timeout', 'chat-lock-timeout', 'skill-debounce', 'trivial-optimization']; + } aiConfig(args) { if (args.length === 0) { this.terminal.print('=== AI Config ===', 'info'); diff --git a/modules/core/module-aiassist.js b/modules/core/module-aiassist.js index 44a7574..c5a91c9 100644 --- a/modules/core/module-aiassist.js +++ b/modules/core/module-aiassist.js @@ -1,7 +1,9 @@ // Extracted from newOS4 index.html — WebX11AIAssistantModule (Full Agent Loop) class WebX11AIAssistantModule { constructor() { - this.commands = {}; this.terminal = null; + this.commands = {}; this.commandsMeta = { + 'ai-config': { argType: 'enum', enumFn: '_getAiConfigKeys' } + }; this.terminal = null; this.systemPrompt = this.buildSystemPrompt(); this.context = []; this.maxContext = 10; this.maxContextTokens = 2000; @@ -2223,6 +2225,9 @@ buildSystemPrompt() { this.terminal.print('No models available. Use ollamaconnect first.', 'warning'); } } + _getAiConfigKeys() { + return ['max-turns', 'max-context', 'max-context-tokens', 'confirm-timeout', 'chat-lock-timeout', 'skill-debounce', 'trivial-optimization']; + } aiConfig(args) { if (args.length === 0) { this.terminal.print('=== AI Config ===', 'info'); diff --git a/modules/core/module-loader.js b/modules/core/module-loader.js index bade62f..bd02873 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-15-v4.5.55'; + const LATEST_VER = '2026-06-16-v4.5.61'; const CACHE = '?v=' + LATEST_VER; const stored = localStorage.getItem('webx11_cache_ver'); if (stored !== LATEST_VER) { @@ -51,7 +51,7 @@ // Optional ext modules via packages.json if (typeof webx11._loadExtModules === 'function') { - let extKeys = ['calc', 'voice', 'cron', 'theme', 'wpm', 'mindmap', 'webdav', 'skill-pack-core', 'cryptogzip', 'tutorial', 'floating', 'console']; + let extKeys = ['calc', 'voice', 'debug-panel', 'cron', 'theme', 'wpm', 'mindmap', 'webdav', 'skill-pack-core', 'cryptogzip', 'tutorial', 'floating', 'console']; const saved = localStorage.getItem('webx11_startup_modules'); let selected = null; try { if (saved) selected = JSON.parse(saved); } catch (e) {} diff --git a/modules/core/terminal.js b/modules/core/terminal.js index c416b8b..504e08d 100644 --- a/modules/core/terminal.js +++ b/modules/core/terminal.js @@ -1279,7 +1279,7 @@ } catch (e) {} } async _loadExtModules(filterKeys) { - const autoLoadKeys = filterKeys || ['calc', 'voice', 'cron', 'theme', 'wpm', 'mindmap', 'webdav', 'skill-pack-core', 'cryptogzip', 'tutorial', 'floating', 'console']; + const autoLoadKeys = filterKeys || ['calc', 'voice', 'debug-panel', 'cron', 'theme', 'wpm', 'mindmap', 'webdav', 'skill-pack-core', 'cryptogzip', 'tutorial', 'floating', 'console']; const cacheVer = localStorage.getItem('webx11_cache_ver') || '2026-05-27-14'; let index = null; let lastErr = null; @@ -1312,7 +1312,7 @@ } async reloadPersistedModules() { const registry = this.moduleManager.moduleRegistry; - const skipList = ['calc', 'voice', 'cron', 'theme', 'wpm', 'mindmap', 'webdav', 'skill-pack-core', 'editor', 'diff', 'alias', 'finder', 'filemgr', 'cryptogzip', 'shell', 'tutorial', 'busybox', 'floating', 'console']; + 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']; for (const mod of registry) { if (mod.enabled && mod.url) { // Skip ext modules — they load via _loadExtModules @@ -2509,6 +2509,15 @@ voiceMod.handle([]); } } + toggleDebugPanel() { + const mod = this.moduleManager.modules['DebugPanel']; + if (mod) { + mod.terminal = this; + mod.toggle(); + } else { + this.print('Debug panel not loaded', 'warning'); + } + } setFontSize(size) { const s = parseInt(size); if (isNaN(s) || s < 8 || s > 32) { |