// Extracted from newOS4 index.html β€” WebX11AIAssistantModule (Full Agent Loop) class WebX11AIAssistantModule { constructor() { this.commands = {}; this.terminal = null; this.systemPrompt = this.buildSystemPrompt(); this.context = []; this.maxContext = 10; this.maxContextTokens = 2000; this.MAX_TOOL_TURNS = 8; this._pendingConfirmation = null; this._confirmResolve = null; this._errorLog = []; this._commandFreq = {}; this._messageCount = 0; this._userProfile = {}; this._sessionMemoryLoaded = false; this._sessionSaved = false; this._thinkingTimer = null; this._thinkingStartTime = 0; this._validatorSuppressed = false; this._toolDefs = null; this._toolCallLog = []; this._debugPanelEnabled = false; // πŸ”’ Guardrail Layer this._guardrailPolicy = null; this._shadowSnapshot = null; this._guardrailViolations = {}; this._confirmTimeoutMs = 120000; this._skillDebounceMs = 1800000; this._chatLockTimeoutMs = 10000; this._loadGuardrailPolicy(); // Restore AI config from localStorage (maxContext, MAX_TOOL_TURNS, etc.) try { const cfg = localStorage.getItem('webx11_ai_config'); if (cfg) { const p = JSON.parse(cfg); if (p.maxTurns) this.MAX_TOOL_TURNS = p.maxTurns; if (p.maxContext) this.maxContext = p.maxContext; if (p.maxContextTokens) this.maxContextTokens = p.maxContextTokens; if (p.confirmTimeoutMs) this._confirmTimeoutMs = p.confirmTimeoutMs; if (p.skillDebounceMs) this._skillDebounceMs = p.skillDebounceMs; if (p.chatLockTimeoutMs) this._chatLockTimeoutMs = p.chatLockTimeoutMs; } } catch (e) {} try { const s = localStorage.getItem('webx11_ai_patterns'); if (s) this._commandFreq = JSON.parse(s); } catch (e) {} try { const p = localStorage.getItem('webx11_user_profile'); if (p) this._userProfile = JSON.parse(p); } catch (e) {} // Restore AI conversation context from localStorage try { const saved = localStorage.getItem('webx11_ai_context'); if (saved) { const parsed = JSON.parse(saved); if (parsed && Array.isArray(parsed.context)) { this.context = parsed.context; this._messageCount = parsed.messageCount || 0; } } } catch (e) {} } buildSystemPrompt() { const now = new Date(); const dateStr = now.toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }); const timeStr = now.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }); const optimizationActive = this.terminal?.ollamaManager?.optimizationEnabled !== false; // Neural VFS staleness check let nvfsNote = ''; try { const nvfs = this.terminal?.moduleManager?.modules?.NeuralVFSModule; if (nvfs && nvfs.lastScan && nvfs.lastScan !== '(loaded from disk)') { const ageMs = Date.now() - new Date(nvfs.lastScan).getTime(); if (ageMs > 14400000) { // > 4 hours nvfsNote = `\nNote: Neural VFS index is ${Math.floor(ageMs/3600000)}h old. nvfs-resolve results may be stale. Use list_files/read_file directly for live data.`; } } } catch (e) {} const CR = String.fromCharCode(10); const optNote = CR + CR + '== TOOL EXECUTION RULES (STRICT) ==' + CR + 'STRICT RULE 1: NEVER describe what the filesystem looks like from your training data. ALWAYS call list_files() or read_file() first, then report the ACTUAL result.' + CR + 'STRICT RULE 2: If you return text without calling any tool, you have failed. Use tools for every factual question.' + CR + 'STRICT RULE 3: A response like "/bin/, /etc/, /home/" without calling list_files is ILLEGAL. Call the tool.' + CR + 'STRICT RULE 4: When calling a tool, base your answer ENTIRELY on the tool result. If result is empty, say "empty".' + CR + CR; return `Current date: ${dateStr}\nCurrent time: ${timeStr}\n${nvfsNote}\n\nYou are WebX11AI, the AI assistant embedded in the WebX11 browser terminal. You have direct access to read/write files, search the web, install packages, edit modules, and run terminal commands β€” all via tool calls. Use tools proactively. Keep responses concise.${optNote}== TOOL REFERENCE == You have these tools available. Use them WITHOUT asking permission unless the tool description says (DESTRUCTIVE): FILESYSTEM: - list_files(path?) β€” List files in VFS directory (omit for current dir) - read_file(path) β€” Read file content (truncated past 8000 chars) - write_file(path, content) β€” Write to file (DESTRUCTIVE β€” confirm with user first) - make_directory(path) β€” Create a directory (DESTRUCTIVE β€” confirm first) - delete_file(path) β€” Delete file (DESTRUCTIVE β€” confirm first) - get_current_dir β€” Show current working directory SYSTEM: - get_status β€” Show filesystem, modules, Ollama, memory status - get_help β€” List all available terminal commands - run_command(command) β€” Execute any terminal command and capture output - font-size <8-32> β€” Change terminal font size (persists in localStorage) SEARCH: - web_search(query) β€” Search the web via proxy API (SearXNG + DDG fallback, results with title/url/snippet) - web_fetch(url) β€” Fetch and extract readable content from a URL via proxy API (returns title + clean text + word count) PACKAGES: - wpm_install(package) β€” Install WPM package by name (e.g. "widget", "calc") - wpm_search(keyword) β€” Search available WPM packages SKILLS (PERSISTENT PROCEDURAL MEMORY): - list_skills(category?) β€” Browse available skills, optionally filtered by category - load_skills(query) β€” Search skills relevant to current task. Call at START of every task. - create_skill(name, description, content, tags, category) β€” Save a new stored procedure. Call after completing complex multi-step tasks (3+ tool calls). - patch_skill(name, content) β€” Update an existing skill when the approach changes FLOATING UI (PERSISTENT UI ELEMENTS): Use 'run_command' tool with these terminal commands: - 'floating-add btn