diff options
| author | Agando <roduccomm@gmail.com> | 2026-06-07 18:00:19 +0200 |
|---|---|---|
| committer | Agando <roduccomm@gmail.com> | 2026-06-07 18:00:19 +0200 |
| commit | 0fa227601d51263b0eb787d53225de3f0b44cad1 (patch) | |
| tree | 2d9f440af92f5b3ead1bea66bd2270a6eb270131 | |
| parent | ab235396568a7e61f02eed436e2400975c752d01 (diff) | |
auto: 2026-06-07_18:00 changes
| -rw-r--r-- | cheatsheet.html | 2 | ||||
| -rw-r--r-- | downloads/evolution.html | 135 | ||||
| -rw-r--r-- | help.html | 44 | ||||
| -rw-r--r-- | main.html | 4 | ||||
| -rw-r--r-- | modules/core/module-aiassist.js | 72 | ||||
| -rw-r--r-- | modules/core/module-loader.js | 2 | ||||
| -rw-r--r-- | modules/core/module-neural-vfs.js | 370 | ||||
| -rw-r--r-- | modules/core/terminal.js | 2 | ||||
| -rw-r--r-- | modules/core/virtual-fs.js | 16 |
9 files changed, 609 insertions, 38 deletions
diff --git a/cheatsheet.html b/cheatsheet.html index 74381e9..79601f9 100644 --- a/cheatsheet.html +++ b/cheatsheet.html @@ -176,7 +176,9 @@ pre{background:var(--bg2);border:1px solid var(--border);border-radius:var(--rad <h2>๐ง Neural VFS</h2> <div class="grid"> <div class="item"><span class="cmd">nvfs-index</span><span class="desc">Build semantic concept index</span></div> +<div class="item"><span class="cmd">nvfs-sync</span><span class="desc">Incremental dirty file sync</span></div> <div class="item"><span class="cmd">nvfs-resolve <c></span><span class="desc">Find files by concept</span></div> +<div class="item"><span class="cmd">nvfs-config</span><span class="desc">Config (deep-index on|off, limits)</span></div> <div class="item"><span class="cmd">concept://<c></span><span class="desc">Read file via semantic concept</span></div> <div class="item"><span class="cmd">nvfs-tree [c]</span><span class="desc">Concept tree view</span></div> <div class="item"><span class="cmd">nvfs-status</span><span class="desc">Index stats</span></div> diff --git a/downloads/evolution.html b/downloads/evolution.html new file mode 100644 index 0000000..04cafca --- /dev/null +++ b/downloads/evolution.html @@ -0,0 +1,135 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>System Evolution | Sovereign</title> + <style> + :root { + --bg: #050505; + --accent: #00ffcc; + --glass: rgba(255, 255, 255, 0.05); + --border: rgba(255, 255, 255, 0.1); + --text: #e0e0e0; + } + body { + background: var(--bg); + color: var(--text); + font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; + line-height: 1.6; + margin: 0; + display: flex; + justify-content: center; + padding: 40px 20px; + } + .container { + max-width: 900px; + width: 100%; + background: var(--glass); + backdrop-filter: blur(10px); + border: 1px solid var(--border); + border-radius: 24px; + padding: 40px; + box-shadow: 0 20px 50px rgba(0,0,0,0.5); + } + h1 { + font-size: 2.5rem; + text-align: center; + color: var(--accent); + text-transform: uppercase; + letter-spacing: 4px; + margin-bottom: 40px; + border-bottom: 2px solid var(--accent); + padding-bottom: 10px; + } + .phase { + background: rgba(255,255,255,0.03); + border-left: 4px solid var(--accent); + margin-bottom: 30px; + padding: 20px; + border-radius: 0 12px 12px 0; + transition: transform 0.2s ease; + } + .phase:hover { + transform: translateX(10px); + background: rgba(255,255,255,0.06); + } + .phase-title { + font-size: 1.4rem; + font-weight: bold; + margin-bottom: 15px; + display: flex; + align-items: center; + } + .check { + color: var(--accent); + margin-right: 10px; + } + ul { + list-style: none; + padding: 0; + } + li { + margin-bottom: 10px; + padding-left: 20px; + position: relative; + } + li::before { + content: 'โ'; + position: absolute; + left: 0; + color: var(--accent); + } + .footer { + text-align: center; + font-size: 0.8rem; + color: #666; + margin-top: 40px; + font-family: monospace; + } + .highlight { + color: var(--accent); + font-weight: bold; + } + </style> +</head> +<body> + <div class="container"> + <h1>System Evolution</h1> + + <div class="phase"> + <div class="phase-title">๐ข Stage 1: The Sentinel (Stability & Self-Healing)</div> + <ul> + <li><span class="check">โ</span> Health-Daemon: Implementation of <code>/friction.log</code> monitoring.</li> + <li><span class="check">โ</span> Auto-Diagnostics: World Model Sync correlation.</li> + <li><span class="check">โ</span> Self-Patching Loop: Active <code>patch_skill</code> automation.</li> + <li>โ Latency Tracking: Pending optimization.</li> + </ul> + </div> + + <div class="phase"> + <div class="---phase-title">๐ก Stage 2: The Archivist (Cognitive Architecture)</div> + <ul> + <li><span class="check">โ</span> Memory Synthesizer: NVFS Knowledge Concept transformation.</li> + <li><span class="check">โ</span> Intent Mapping: Semantic linkage via <code>/memory/concepts/</code>.</li> + <li><span class="check">โ</span> <span class="highlight">Sovereign Hardening:</span> Zero-Trust Linguistic Audit & security refactoring.</li> + <li><span class="check">โ</span> <span class="highlight">Visualization:</span> Deployment of <code>weaver_goals.html</code>.</li> + </ul> + </div> + + <div class="phase"> + <div class="phase-title">๐ด Stage 3: The Overmind (Autonomous Orchestration)</div> + <ul> + <li>โ Dynamic Task Decomposition: Atomic DAG implementation.</li> + <li>โ Agent Swarm Engine: Specialized agent spawning.</li> + <li>โ Consensus Layer: Implementation of agent debate.</li> + <li>โ Autonomous CI/CD: Hands-free <code>test_module</code> pipeline.</li> + </ul> + </div> + + <div class="footer"> + LOGGED BY WEBX11AI | VERSION 4.5.9 | LAST SYNC: 2026-06-07 + </div> + </div> +</body> +</html> @@ -231,7 +231,7 @@ footer .tagline{color:var(--accent2);margin-top:10px;font-weight:500} <h1>WebX11 <span>Docs</span></h1> </div> <div class="header-meta"> - <span class="badge badge-v">v4.5.9</span> + <span class="badge badge-v">v4.5.11</span> <span class="badge badge-new">28 core ยท 17 ext</span> <span style="color:var(--text2)">160+ commands</span> </div> @@ -522,7 +522,7 @@ diff a.txt b.txt</pre> <tr><td><code>ai-deny</code></td><td>Deny pending destructive action</td><td><code>ai-deny</code></td></tr> <tr><td><code>ai-stop</code></td><td>Cancel running AI request</td><td><code>ai-stop</code></td></tr> <tr><td><code>ai-compress</code></td><td>Manually compress conversation context</td><td><code>ai-compress</code></td></tr> - <tr><td><code>ai-config</code></td><td>View/set AI loop config (max-turns, max-context, max-context-tokens, confirm-timeout, chat-lock-timeout, skill-debounce, trivial-optimization)</td><td><code>ai-config max-turns 12</code></td></tr> + <tr><td><code>ai-config</code></td><td>View/set AI loop config (max-turns, max-context, max-context-tokens, confirm-timeout(0=disabled), chat-lock-timeout, skill-debounce, trivial-optimization)</td><td><code>ai-config max-turns 12</code></td></tr> <tr><td><code>debug</code></td><td>Toggle AI debug mode</td><td><code>debug</code></td></tr> </table></div> @@ -640,7 +640,7 @@ diff a.txt b.txt</pre> <div class="callout callout-warning"> <span class="callout-title">โ ๏ธ Destructive Tools</span> - <code>write_file</code>, <code>delete_file</code>, <code>run_command</code>, <code>make_directory</code>, <code>edit_module</code> all require confirmation before executing. + <code>write_file</code>, <code>delete_file</code>, <code>make_directory</code>, <code>edit_module</code> require confirmation before executing. <code>run_command</code> only requires confirmation for destructive commands (rm, kill, format) โ safe-read commands like <code>ls</code>, <code>find</code>, <code>tree</code> are auto-approved. </div> <h3>Available Tools</h3> @@ -655,7 +655,7 @@ diff a.txt b.txt</pre> <tr><td><code>get_help</code></td><td>List commands</td><td>โ</td></tr> <tr><td><code>web_search</code></td><td>SearXNG + DDG</td><td>โ</td></tr> <tr><td><code>web_fetch</code></td><td>Extract URL content</td><td>โ</td></tr> - <tr><td><code>run_command</code></td><td>Execute terminal cmd</td><td>โ
</td></tr> + <tr><td><code>run_command</code></td><td>Execute terminal cmd (safe-read auto-approved)</td><td>โ (only destructive cmds)</td></tr> <tr><td><code>load_skills</code></td><td>Search relevant skills</td><td>โ</td></tr> <tr><td><code>create/patch/list_skills</code></td><td>Skill management</td><td>โ</td></tr> <tr><td><code>view/edit/test/reload</code></td><td>Module operations</td><td>edit โ
</td></tr> @@ -1178,7 +1178,9 @@ vfs-import</pre> <tr><td><code>nvfs-status</code></td><td>Show index stats and last scan time</td></tr> <tr><td><code>nvfs-resolve <concept></code></td><td>Find files related to a concept (exact + fuzzy)</td></tr> <tr><td><code>nvfs-tree [concept]</code></td><td>Show concept-to-file mapping tree</td></tr> - <tr><td><code>nvfs-config [key] [value]</code></td><td>View/set display limits (fuzzy-limit, tree-files, tree-concepts, resolve-files)</td></tr> +<tr><td><code>nvfs-config [key] [value]</code></td><td>View/set config (fuzzy-limit, tree-files, tree-concepts, resolve-files, deep-index, deep-model)</td></tr> + <tr><td><code>nvfs-alias list|add|remove|clear</code></td><td>Manage concept aliases</td></tr> + <tr><td><code>nvfs-sync [--deep]</code></td><td>Incremental background sync dirty files</td></tr> </table></div> </section> @@ -1330,7 +1332,7 @@ listmods</pre> <section id="ref"> <h2>โก Quick Reference</h2> <div class="quick-ref"> - <div class="quick-ref-title">WebX11 v4.5.9</div> + <div class="quick-ref-title">WebX11 v4.5.11</div> <div class="quick-ref-grid"> <div class="quick-ref-item"><span class="cmd">help</span><span class="desc">Show all commands</span></div> <div class="quick-ref-item"><span class="cmd">cls</span><span class="desc">Clear terminal</span></div> @@ -1360,6 +1362,7 @@ listmods</pre> <div class="quick-ref-item"><span class="cmd">base64</span><span class="desc">Encode/decode</span></div> <div class="quick-ref-item"><span class="cmd">nvfs-index</span><span class="desc">Build semantic index</span></div> <div class="quick-ref-item"><span class="cmd">nvfs-resolve</span><span class="desc">Find files by concept</span></div> + <div class="quick-ref-item"><span class="cmd">nvfs-sync</span><span class="desc">Sync dirty files</span></div> <div class="quick-ref-item"><span class="cmd">vfs-size</span><span class="desc">VFS size report</span></div> <div class="quick-ref-item"><span class="cmd">vfs-clean</span><span class="desc">Nuke system dirs</span></div> <div class="quick-ref-item"><span class="cmd">mindmap svg</span><span class="desc">Render mindmap as SVG</span></div> @@ -1386,17 +1389,38 @@ listmods</pre> <div class="version-item"> <div class="v-header"><span class="v-num">v4.5 โ June 2026</span><span class="v-date">2026-06-06</span></div> <ul> - <li><strong>Security Config CLI</strong> โ <code>security-destructive</code>, <code>security-system</code>, <code>security-whitelist</code>, <code>security-flag</code> add|remove|list with localStorage persistence and boot loading</li> +<li><strong>Security Config CLI</strong> โ <code>security-destructive</code>, <code>security-system</code>, <code>security-whitelist</code>, <code>security-flag</code> add|remove|list with localStorage persistence and boot loading</li> <li><strong>Enhanced security command</strong> โ Shows all sets (destructive, system, whitelist, flags) plus registered command count</li> <li><strong>AI loop config</strong> โ <code>ai-config max-turns|max-context|trivial-optimization</code> with persistence across sessions</li> <li><strong>Unified config CLI</strong> โ <code>config</code> command with 4 terminal tuning keys (max-lines, history, pipe-buffer), all persisted in localStorage</li> <li><strong>Extended ai-config</strong> โ New keys: <code>max-context-tokens</code>, <code>confirm-timeout</code>, <code>chat-lock-timeout</code>, <code>skill-debounce</code> with range validation</li> + <li><strong>Safe-read run_command</strong> โ <code>run_command</code> now command-aware: <code>ls</code>, <code>find</code>, <code>tree</code>, <code>cat</code> auto-approved; only mutating commands require confirmation</li> + <li><strong>Whitelist expanded (50+ cmds)</strong> โ find, grep, tree, head, tail, wc, sort, du, diff, which, hostname, and all BusyBox read-only utils now whitelisted</li> + <li><strong>Security-bypass fix</strong> โ <code>run_command</code> AI tool now routes through <code>checkAndRun()</code> security layer instead of bypassing</li> + <li><strong>list_files root fallback</strong> โ relative paths retry from root if cwd-relative lookup fails</li> + <li><strong>Confirm timeout configurable</strong> โ <code>ai-config confirm-timeout <seconds></code> accepts 0+ (0 = no confirm / auto-approve all, no upper cap). Dynamic label on confirm bar shows actual timeout.</li> + <li><strong>Auto-sync Neural VFS</strong> โ <code>write_file</code>/<code>mkdir</code>/<code>rm</code>/<code>mv</code>/<code>cp</code> automatically mark index dirty; auto-reindexes after 10s debounce. <code>nvfs-alias</code> command for concept aliases. Hybrid discovery fallback in <code>nvfs-resolve</code> (live VFS scan when index empty). Staleness warning in <code>nvfs-status</code> if index >4h old.</li> + <li><strong>5 modules whitelisted</strong> โ system-health, server, servers, server-check, monitor run directly</li> <li><strong>OllamaModule consolidated</strong> โ Removed duplicate <code>ollamamodels</code> code path (redirects to <code>models</code>), centralized endpoint via <code>ollamaManager.endpoint</code></li> <li><strong>Neural VFS async indexing</strong> โ Chunked processing with progress percentage, no UI freeze on large VFS. Improved <code>_summarize</code> (section heading โ paragraph โ sentence โ fallback)</li> <li><strong>Neural VFS display-limit config</strong> โ <code>nvfs-config</code> command with 4 keys (fuzzy-limit, tree-files, tree-concepts, resolve-files), persisted</li> <li><strong>Orchestrator event bus</strong> โ In-memory pub/sub for instant A2A delivery (VFS polling remains as fallback). Improved <code>_deriveCapabilities</code> with 14 keyword mappings</li> <li><strong>Orchestrator extended config</strong> โ 4 new interval keys (a2a-poll-interval, heartbeat-check, idle-sweep-interval, dead-purge-timeout) + all config now persists across sessions</li> - <li>Cache: <code>2026-06-06-v4.5.9</code> โ hard refresh required</li> + <li>Cache: <code>2026-06-06-v4.5.11</code> โ hard refresh required</li> + </ul> + </div> + + <div class="version-item"> + <div class="v-header"><span class="v-num">v4.5.12 โ June 2026</span><span class="v-date">2026-06-06</span></div> + <ul> + <li><strong>Tiered Indexing toggle</strong> โ <code>nvfs-config deep-index on|off</code> switches between Fast (metadata-only, instant) and Deep (LLM-assisted, semantic) modes. Off by default. <code>nvfs-index --deep</code> or <code>nvfs-sync --deep</code> forces deep mode per-run.</li> + <li><strong>Configurable deep model</strong> โ <code>nvfs-config deep-model <name></code> selects the LLM for deep indexing. Default: <code>ministral-3:3b-cloud</code> for speed. Example: <code>nvfs-config deep-model gemma4:31b-cloud</code> for deeper analysis.</li> + <li><strong>Status bar indicator</strong> โ Real-time ๐ง status in terminal bar: concept count + ๐ wave for deep mode, ๐ก amber for pending auto-sync, ๐ง Deep Indexing progress percentage during LLM processing.</li> + <li><strong>nvfs-sync command</strong> โ <code>nvfs-sync [--deep]</code> for incremental background sync of dirty files.</li> + <li><strong>Deep concept extraction</strong> โ LLM generates 2-5 semantic concepts per file (configurable model via <code>nvfs-config deep-model</code>, default <code>ministral-3:3b-cloud</code>, 2000 chars, 3-file batches with UI yield). Fast filename-concepts stored immediately, LLM concepts augment in background queue.</li> + <li><strong>nvfs-status mode display</strong> โ Shows ๐ Deep or โก Fast mode.</li> + <li><strong>LLM self-healing</strong> โ If Ollama disconnected, deep mode silently falls back to fast metadata-only.</li> + <li>Cache: <code>2026-06-06-v4.5.12</code> โ hard refresh required</li> </ul> </div> @@ -1414,13 +1438,13 @@ listmods</pre> </div> <div class="version-item"> - <div class="v-header"><span class="v-num">v4.5.10 โ June 2026</span><span class="v-date">2026-06-06</span></div> + <div class="v-header"><span class="v-num">v4.5.11 โ June 2026</span><span class="v-date">2026-06-06</span></div> <ul> <li><strong>init(terminal) fix</strong> โ <code>loadModuleFromCode</code> and <code>loadModuleFromURL</code> now pass <code>this.terminal</code> to <code>init()</code>, fixing PredictorModule fatal error</li> <li><strong>Save & Test returns value</strong> โ <code>saveCodeEditor</code> checks <code>loadModuleFromCode</code> return before printing success</li> <li><strong>StateSyncModule recovered</strong> โ Extracted from backup, saved to <code>~/.hermes/scripts/</code></li> <li><strong>PredictorModule hasSync</strong> โ Feature detection for sync/recall tasks with tuned duration/turn prediction</li> - <li>Cache: <code>2026-06-06-v4.5.9</code> โ hard refresh required</li> + <li>Cache: <code>2026-06-06-v4.5.11</code> โ hard refresh required</li> </ul> </div> @@ -219,7 +219,7 @@ max-height: 200px; overflow-y: auto; display: none; - z-index: 100; + z-index: 1001; } .autocomplete-item { padding: 8px 12px; @@ -1095,7 +1095,7 @@ </script> <!-- Async dependency-ordered module loader (replaces 26 synchronous script tags) --> - <script src="modules/core/module-loader.js?v=2026-06-06-v4.5.9"></script> + <script src="modules/core/module-loader.js?v=2026-06-06-v4.5.12"></script> <script> // โโโ ONE TRUE VERSION SOURCE โโโ diff --git a/modules/core/module-aiassist.js b/modules/core/module-aiassist.js index 5a4341a..ed76a8a 100644 --- a/modules/core/module-aiassist.js +++ b/modules/core/module-aiassist.js @@ -49,9 +49,20 @@ 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\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 == + 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: @@ -235,7 +246,7 @@ RULES: type: 'function', function: { name: 'run_command', - description: 'Run terminal command', + description: 'Run terminal command (safe-read commands auto-approved; mutating ones require confirmation)', parameters: { type: 'object', properties: { command: { type: 'string', description: 'Command to run' } }, required: ['command'] } } }, { @@ -506,7 +517,7 @@ RULES: 'ai-ignore': 'ai-ignore โ Suppress validator warnings for this session', 'ai-stop': 'ai-stop โ Cancel current AI operation', 'ai-model': 'ai-model [model] โ Show or set AI model', - 'ai-config': 'ai-config [key] [value] โ View or set AI loop config (max-turns, max-context)' + 'ai-config': 'ai-config [key] [value] โ View or set AI loop config (max-turns, max-context, confirm-timeout(0=disabled))' }; } registerCommands() { @@ -776,8 +787,8 @@ RULES: this.terminal.print(`๐ง AI wants ${res.tool_calls.length} tool(s) โ turn ${toolTurns}/${this.MAX_TOOL_TURNS} (${thinkTime}s)`, 'info'); messages.push(res.message); let pendingText = res.message?.content || null; - const destructiveTools = res.tool_calls.filter(tc => this.isDestructive(tc.function.name)); - const safeTools = res.tool_calls.filter(tc => !this.isDestructive(tc.function.name)); + const destructiveTools = res.tool_calls.filter(tc => this.isDestructive(tc.function.name, tc.function.arguments)); + const safeTools = res.tool_calls.filter(tc => !this.isDestructive(tc.function.name, tc.function.arguments)); // Execute non-destructive tools in parallel (independent read-only operations) const safeResults = await Promise.all(safeTools.map(async (tc) => { const fn = tc.function; @@ -969,10 +980,18 @@ RULES: } } async confirmActionMulti(toolCalls) { + // No-confirm mode: auto-approve + if (this._confirmTimeoutMs === 0) { + this.terminal.print(`โก No-confirm mode: auto-approving ${toolCalls.map(tc => tc.function.name).join(', ')}`, 'info'); + return true; + } const names = toolCalls.map(tc => tc.function.name).join(', '); const args = toolCalls.map(tc => typeof tc.function.arguments === 'string' ? tc.function.arguments : JSON.stringify(tc.function.arguments)).join(' | '); const truncatedArgs = args.length > 60 ? args.substring(0, 60) + '...' : args; - this.terminal.printHTML(`โ ๏ธ AI wants to: ${names}(${truncatedArgs}). <span class="cmd-link" onclick="webx11.executeCommand('ai-allow')" style="color:var(--success-color);cursor:pointer;text-decoration:underline;">ai-allow</span> to confirm, <span class="cmd-link" onclick="webx11.executeCommand('ai-deny')" style="color:var(--error-color);cursor:pointer;text-decoration:underline;">ai-deny</span> to refuse. (Auto-denies in 120s)`); + const timeoutLabel = this._confirmTimeoutMs === 0 + ? '' + : ` (Auto-denies in ${this._confirmTimeoutMs / 1000}s)`; + this.terminal.printHTML(`โ ๏ธ AI wants to: ${names}(${truncatedArgs}). <span class="cmd-link" onclick="webx11.executeCommand('ai-allow')" style="color:var(--success-color);cursor:pointer;text-decoration:underline;">ai-allow</span> to confirm, <span class="cmd-link" onclick="webx11.executeCommand('ai-deny')" style="color:var(--error-color);cursor:pointer;text-decoration:underline;">ai-deny</span> to refuse.${timeoutLabel}`); // Show confirm bar OUTSIDE scrollable output (always visible) const area = document.getElementById('ai-confirm-area'); if (!area) { @@ -1390,8 +1409,19 @@ RULES: } } // ๐ OVERRIDES for isDestructive โ adds runTool intercept - isDestructive(name) { - return name === 'write_file' || name === 'delete_file' || name === 'run_command' || name === 'make_directory' || name === 'edit_module'; + // Now command-aware: run_command is only destructive for mutating commands + isDestructive(name, args = {}) { + if (name === 'run_command') { + const cmdStr = typeof args === 'object' ? (args?.command || '') : String(args); + const cmd = cmdStr.split(' ')[0].toLowerCase(); + const safeRead = new Set(['ls','cat','pwd','echo','date','env','whoami','which','hostname', + 'cal','tree','find','grep','head','tail','sort','wc','du','diff','basename','dirname', + 'yes','seq','cut','uniq','expand','printf','base64','help','clear','set','export', + 'unalias','history','ll','h','mem','status','df','free','top','ps','health-check', + 'system-health','modules','listmods','modules-list','get-help-info','alias']); + return !safeRead.has(cmd); + } + return ['write_file','delete_file','make_directory','edit_module'].includes(name); } async runTool(name, argsStr) { let a = {}; @@ -1420,10 +1450,26 @@ RULES: const parts = p.split('/'); const dir = this.terminal.fs.root; let cur = dir; + let found = true; for (const part of parts) { if (part === '..') { saved.pop(); continue; } if (part && cur.content[part] && cur.content[part].type === 'dir') cur = cur.content[part]; - else return JSON.stringify({ error: `Directory not found: ${p}` }); + else { found = false; break; } + } + if (!found) { + // Retry from root โ cwd may be stale + this.terminal.fs.currentPath = []; + cur = this.terminal.fs.root; + found = true; + for (const part of parts) { + if (part === '..') continue; + if (part && cur.content[part] && cur.content[part].type === 'dir') cur = cur.content[part]; + else { found = false; break; } + } + if (!found) { + this.terminal.fs.currentPath = saved; + return JSON.stringify({ error: `Directory not found: ${p}` }); + } } this.terminal.fs.currentPath = saved.concat(parts.filter(x => x !== '..')); } @@ -1541,7 +1587,7 @@ RULES: const originalPrint = this.terminal.print.bind(this.terminal); let output = ''; this.terminal.print = (text, type) => { output += text + '\n'; }; - try { await this.terminal.dispatchCommand(cmd.split(' ')[0].toLowerCase(), cmd.split(' ').slice(1)); } finally { this.terminal.print = originalPrint; } + try { await this.terminal.security.checkAndRun(cmd); } finally { this.terminal.print = originalPrint; } // ๐ธ Diff and append shadow report const shadowDiff = this._diffShadowVFS(); if (shadowDiff && shadowDiff.length > 0) { @@ -2104,11 +2150,11 @@ RULES: this.terminal.print(`max-context: ${this.maxContext} (default: 10)`, ''); this.terminal.print(`max-context-tokens: ${this.maxContextTokens} (default: 2000)`, ''); this.terminal.print(`trivial-optimization: ${(this.terminal?.ollamaManager?.optimizationEnabled !== false) ? 'on' : 'off'}`, ''); - this.terminal.print(`confirm-timeout: ${this._confirmTimeoutMs / 1000}s (default: 120s)`, ''); + this.terminal.print(`confirm-timeout: ${this._confirmTimeoutMs > 0 ? this._confirmTimeoutMs / 1000 + 's' : 'disabled (no auto-deny)'} (default: 120s)`, ''); this.terminal.print(`chat-lock-timeout: ${this._chatLockTimeoutMs / 1000}s (default: 10s)`, ''); this.terminal.print(`skill-debounce: ${this._skillDebounceMs / 60000}min (default: 30min)`, ''); this.terminal.print('Usage: ai-config <key> <value>', 'info'); - this.terminal.print('Keys: max-turns, max-context, max-context-tokens, confirm-timeout, chat-lock-timeout, skill-debounce, trivial-optimization', 'info'); + this.terminal.print('Keys: max-turns, max-context, max-context-tokens, confirm-timeout(0=disabled), chat-lock-timeout, skill-debounce, trivial-optimization', 'info'); return; } const key = args[0]; @@ -2127,7 +2173,7 @@ RULES: this.maxContextTokens = n; } else if (key === 'confirm-timeout') { const n = parseInt(val); - if (isNaN(n) || n < 5 || n > 600) return this.terminal.print('confirm-timeout must be 5-600 seconds', 'warning'); + if (isNaN(n) || n < 0) return this.terminal.print('confirm-timeout must be 0+ seconds (0 = no confirm / auto-approve all)', 'warning'); this._confirmTimeoutMs = n * 1000; } else if (key === 'chat-lock-timeout') { const n = parseInt(val); diff --git a/modules/core/module-loader.js b/modules/core/module-loader.js index f4bd20b..1d1cbda 100644 --- a/modules/core/module-loader.js +++ b/modules/core/module-loader.js @@ -105,7 +105,7 @@ window.__moduleLoaderReload = reloadWithSelection; -const LATEST_VER = '2026-06-06-v4.5.9'; +const LATEST_VER = '2026-06-06-v4.5.12'; const CACHE = '?v=' + LATEST_VER; const stored = localStorage.getItem('webx11_cache_ver'); if (stored !== LATEST_VER) { diff --git a/modules/core/module-neural-vfs.js b/modules/core/module-neural-vfs.js index 2644e85..5c30718 100644 --- a/modules/core/module-neural-vfs.js +++ b/modules/core/module-neural-vfs.js @@ -12,6 +12,26 @@ class NeuralVFSModule { this._treeConcepts = 15; this._resolveFiles = 20; this._loadConfig(); + // Auto-sync state + this._dirtyPaths = new Set(); + this._autoSyncTimer = null; + this._aliasMap = {}; + this._loadAliases(); + // Deep Indexing toggle + this._deepIndexEnabled = localStorage.getItem('webx11_nvfs_deep') === 'true'; + this._deepModel = localStorage.getItem('webx11_nvfs_deep_model') || 'ministral-3:3b-cloud'; + this._deepIndexing = false; + this._deepQueue = []; + this._deepProcessed = 0; + this._deepTotal = 0; + // Status bar polling โ updates #status-text with indexing indicator + if (typeof window !== 'undefined') { + this._statusInterval = setInterval(() => this._updateStatusBar(), 2000); + } + // Register as window-level event listener for mutation events from VirtualFS + if (typeof window !== 'undefined') { + window.__nvfsMarkDirty = (path) => this.markDirty(path); + } } _loadConfig() { try { const c = localStorage.getItem('webx11_nvfs_config'); if (c) { const p = JSON.parse(c); if (p.fuzzyLimit) this._fuzzyLimit = p.fuzzyLimit; if (p.treeFiles) this._treeFiles = p.treeFiles; if (p.treeConcepts) this._treeConcepts = p.treeConcepts; if (p.resolveFiles) this._resolveFiles = p.resolveFiles; } } catch (e) {} @@ -22,29 +42,34 @@ class NeuralVFSModule { getHelpInfo() { return { - 'nvfs-index': 'nvfs-index โ Scan VFS and build semantic concept index', + 'nvfs-index': 'nvfs-index [--deep] โ Scan VFS and build semantic concept index', 'nvfs-status': 'nvfs-status โ Show semantic index stats and last scan time', 'nvfs-resolve': 'nvfs-resolve <concept> โ Find files related to a concept', 'nvfs-tree': 'nvfs-tree [concept] โ Show concept-to-file mapping as a tree', - 'nvfs-config': 'nvfs-config [key] [value] โ View or set Neural VFS display limits (fuzzy-limit, tree-files, tree-concepts, resolve-files)' + 'nvfs-config': 'nvfs-config [key] [value] โ View or set Neural VFS config (fuzzy-limit, tree-files, tree-concepts, resolve-files, deep-index, deep-model)', + 'nvfs-alias': 'nvfs-alias list|add|remove|clear โ Manage concept aliases', + 'nvfs-sync': 'nvfs-sync [--deep] โ Incremental background sync (auto-sync dirty files)' }; } registerCommands() { - this.commands['nvfs-index'] = (args) => this.buildIndex(); + this.commands['nvfs-index'] = (args) => this.buildIndex(args); this.commands['nvfs-status'] = (args) => this.showStatus(); this.commands['nvfs-resolve'] = (args) => this.resolve(args); this.commands['nvfs-tree'] = (args) => this.tree(args); this.commands['nvfs-config'] = (args) => this.nvfsConfig(args); + this.commands['nvfs-alias'] = (args) => this.nvfsAlias(args); + this.commands['nvfs-sync'] = async (args) => this.nvfsSync(args); } // โโ Core Index Builder (async, chunked) โโ - async buildIndex() { + async buildIndex(args) { + const useDeep = this._deepIndexEnabled || (args && args.includes('--deep')); const fs = this.terminal.fs; if (!fs) return this.terminal.print('VFS not available', 'error'); - this.terminal.print('๐ Scanning VFS for semantic concepts...', 'info'); + this.terminal.print(`๐ Scanning VFS for semantic concepts${useDeep ? ' (deep mode)' : ''}...`, 'info'); const start = Date.now(); let newIndex = {}; @@ -99,13 +124,13 @@ class NeuralVFSModule { cachedCount++; if (!livePaths.has(path)) { // File was in cache but somehow missing from prevIndex - this._reindexFile(path, content, newIndex); + this._reindexFile(path, content, newIndex, useDeep); } continue; } // Changed or new file โ extract concepts fresh - this._reindexFile(path, content, newIndex); + this._reindexFile(path, content, newIndex, useDeep); this._hashCache[path] = { hash }; } i = end; @@ -133,7 +158,22 @@ class NeuralVFSModule { processChunk(); } - _reindexFile(path, content, index) { + _reindexFile(path, content, index, useDeep) { + if (useDeep) { + // Deep mode: queue for async LLM concept extraction โ fast-path filename concepts first + const fastConcepts = this._extractConcepts(path, ''); + for (const concept of fastConcepts) { + if (!index[concept]) index[concept] = []; + const exists = index[concept].some(e => e.path === path); + if (!exists) index[concept].push({ path, summary: this._summarize(content), source: 'filename' }); + } + this._deepQueue.push({ path, content }); + if (!this._deepIndexing) { + this._deepIndexing = true; + this._processDeepQueue(index).catch(e => console.error('Deep index error:', e)); + } + return; + } const concepts = this._extractConcepts(path, content); // Remove old entries for this path for (const entries of Object.values(index)) { @@ -170,6 +210,15 @@ class NeuralVFSModule { if (!args[0]) return this.terminal.print('Usage: nvfs-resolve <concept>', 'warning'); const concept = args.join(' ').toLowerCase(); + + // Check aliases first + if (this._aliasMap[concept]) { + const targets = this._aliasMap[concept]; + this.terminal.print(`๐ Alias "${concept}" โ ${targets.length} target(s):`, 'info'); + for (const t of targets) this.terminal.print(` ${t}`, 'success'); + return; + } + const matches = this.index[concept] || []; // Fuzzy fallback โ find partial matches @@ -178,6 +227,18 @@ class NeuralVFSModule { ).filter(k => k !== concept); if (matches.length === 0 && fuzzy.length === 0) { + // Hybrid discovery: fall back to live VFS scan + const shellResult = this._hybridShellCheck(concept); + if (shellResult.length > 0) { + this.terminal.print(`๐ Index empty for "${concept}" but found ${shellResult.length} file(s) via live scan. Run nvfs-index to refresh.`, 'warning'); + for (const f of shellResult.slice(0, this._resolveFiles)) { + this.terminal.print(` ${f.path}${f.summary ? ' โ ' + f.summary : ''}`, 'success'); + } + if (shellResult.length > this._resolveFiles) { + this.terminal.print(` ... and ${shellResult.length - this._resolveFiles} more`, 'info'); + } + return; + } return this.terminal.print(`No files found for concept "${concept}"`, 'warning'); } @@ -223,6 +284,31 @@ class NeuralVFSModule { this.terminal.print(` Concepts: ${Object.keys(this.index).length}`, ''); this.terminal.print(` Files indexed: ${totalFiles.size}`, ''); this.terminal.print(` Last scan: ${this.lastScan || 'never'}`, ''); + this.terminal.print(` Mode: ${this._deepIndexEnabled ? '๐ Deep (LLM-assisted)' : 'โก Fast (metadata-only)'}`, ''); + + // Staleness check + if (this.lastScan && this.lastScan !== '(loaded from disk)') { + const ageMs = Date.now() - new Date(this.lastScan).getTime(); + const ageHours = Math.floor(ageMs / 3600000); + const ageMinutes = Math.floor((ageMs % 3600000) / 60000); + if (ageHours >= 1) { + this.terminal.print(` Age: ${ageHours}h${ageMinutes > 0 ? ` ${ageMinutes}m` : ''}`, ''); + } + if (ageHours >= 4) { + this.terminal.print(` โ ๏ธ Index is ${ageHours} hours old โ structural changes may be missing. Run nvfs-index to refresh.`, 'warning'); + } + } + + // Dirty file warning + if (this._dirtyPaths && this._dirtyPaths.size > 0) { + this.terminal.print(` โ ๏ธ ${this._dirtyPaths.size} file(s) changed since last scan (auto-sync pending)`, 'warning'); + } + + // Alias count + const aliasCount = Object.keys(this._aliasMap).length; + if (aliasCount > 0) { + this.terminal.print(` Aliases: ${aliasCount}`, ''); + } } // โโ Tree View โโ @@ -260,22 +346,89 @@ class NeuralVFSModule { this.terminal.print(`tree-files: ${this._treeFiles} (default: 5)`, ''); this.terminal.print(`tree-concepts: ${this._treeConcepts} (default: 15)`, ''); this.terminal.print(`resolve-files: ${this._resolveFiles} (default: 20)`, ''); + this.terminal.print(`deep-index: ${this._deepIndexEnabled ? 'on' : 'off'} (LLM-assisted concept extraction)`, ''); + this.terminal.print(`deep-model: ${this._deepModel} (model for deep indexing)`, ''); this.terminal.print('Usage: nvfs-config <key> <value>', 'info'); - this.terminal.print('Keys: fuzzy-limit, tree-files, tree-concepts, resolve-files', 'info'); + this.terminal.print('Keys: fuzzy-limit, tree-files, tree-concepts, resolve-files, deep-index (on|off), deep-model <name>', 'info'); return; } const key = args[0]; + if (key === 'deep-index') { + const val = args[1]; + if (val !== 'on' && val !== 'off') return this.terminal.print('deep-index must be "on" or "off"', 'warning'); + this._deepIndexEnabled = val === 'on'; + localStorage.setItem('webx11_nvfs_deep', this._deepIndexEnabled ? 'true' : 'false'); + // If turning off mid-queue, abort pending deep work + if (!this._deepIndexEnabled && this._deepIndexing) { + this._deepQueue = []; + this._deepIndexing = false; + this._deepProcessed = 0; + this._deepTotal = 0; + this._updateStatusBar(); + this.terminal.print(' Deep index queue aborted', 'warning'); + } + this.terminal.print(`nvfs-config deep-index set to ${val}`, 'success'); + if (this._deepIndexEnabled) this.terminal.print(` Deep mode uses ${this._deepModel} for semantic concept extraction`, 'info'); + return; + } + if (key === 'deep-model') { + const val = args.slice(1).join(' '); + if (!val) return this.terminal.print('Usage: nvfs-config deep-model <model-name>', 'warning'); + this._deepModel = val; + localStorage.setItem('webx11_nvfs_deep_model', val); + this.terminal.print(`nvfs-config deep-model set to ${val}`, 'success'); + return; + } const val = parseInt(args[1]); if (isNaN(val) || val < 1 || val > 200) return this.terminal.print('Value must be 1-200', 'warning'); if (key === 'fuzzy-limit') { this._fuzzyLimit = val; } else if (key === 'tree-files') { this._treeFiles = val; } else if (key === 'tree-concepts') { this._treeConcepts = val; } else if (key === 'resolve-files') { this._resolveFiles = val; } - else { return this.terminal.print(`Unknown key: ${key}. Valid: fuzzy-limit, tree-files, tree-concepts, resolve-files`, 'warning'); } + else { return this.terminal.print(`Unknown key: ${key}. Valid: fuzzy-limit, tree-files, tree-concepts, resolve-files, deep-index (on|off), deep-model <name>`, 'warning'); } this._saveConfig(); this.terminal.print(`nvfs-config ${key} set to ${val}`, 'success'); } + // โโ Nvfs Alias โโ + + nvfsAlias(args) { + const action = args[0]; + if (!action || action === 'list') { + const keys = Object.keys(this._aliasMap); + if (keys.length === 0) return this.terminal.print('No aliases defined. Use nvfs-alias add <name> <target,paths>', 'info'); + this.terminal.print('๐ Concept Aliases:', 'info'); + for (const [name, targets] of Object.entries(this._aliasMap)) { + this.terminal.print(` ${name} โ ${targets.join(', ')}`, 'success'); + } + return; + } + if (action === 'add') { + const name = args[1]; + if (!name || !args[2]) return this.terminal.print('Usage: nvfs-alias add <name> <target1,target2,...>', 'warning'); + const targets = args.slice(2).join(' ').split(',').map(t => t.trim()).filter(Boolean); + this._aliasMap[name] = targets; + this._saveAliases(); + this.terminal.print(`Alias "${name}" added (${targets.length} target(s))`, 'success'); + return; + } + if (action === 'remove') { + const name = args[1]; + if (!name || !this._aliasMap[name]) return this.terminal.print('Alias not found', 'warning'); + delete this._aliasMap[name]; + this._saveAliases(); + this.terminal.print(`Alias "${name}" removed`, 'success'); + return; + } + if (action === 'clear') { + this._aliasMap = {}; + this._saveAliases(); + this.terminal.print('All aliases cleared', 'success'); + return; + } + this.terminal.print('Usage: nvfs-alias list|add <name> <targets>|remove <name>|clear', 'warning'); + } + // โโ Private Helpers โโ _walk(path, fs, index, cb) { @@ -292,7 +445,7 @@ class NeuralVFSModule { } _isSystemFile(path) { - const sysPrefixes = ['/.trash', '/.semantic_index', '/_snapshots', '/_sessions']; + const sysPrefixes = ['/.trash', '/.semantic_index', '/.memory_graph', '/_snapshots', '/_sessions', '/logs', '/memory', '/system', '/_mods', '/backups']; return sysPrefixes.some(p => path.startsWith(p) || path.includes('/node_modules')); } @@ -371,6 +524,22 @@ class NeuralVFSModule { return [...concepts]; } + _hybridShellCheck(concept) { + const fs = this.terminal?.fs; + if (!fs) return []; + const words = concept.split(/\s+/).filter(w => w.length > 2); + if (words.length === 0) return []; + const results = []; + this._walk('/', fs, {}, (path, node) => { + if (this._isSystemFile(path)) return; + const lower = path.toLowerCase(); + if (words.some(w => lower.includes(w))) { + results.push({ path, summary: this._summarize(node.content || '') }); + } + }); + return results; + } + _summarize(content) { if (!content || content.length < 10) return ''; // Strip YAML frontmatter @@ -432,6 +601,185 @@ class NeuralVFSModule { console.error('NeuralVFS save error:', e); } } + + // โโ Auto-Sync โโ + + markDirty(path) { + this._dirtyPaths.add(path); + // Debounced auto-sync: wait 10s after last mutation + if (this._autoSyncTimer) clearTimeout(this._autoSyncTimer); + this._autoSyncTimer = setTimeout(() => this._reindexDirtyPaths(), 10000); + } + + async _reindexDirtyPaths() { + if (this._dirtyPaths.size === 0) return; + const fs = this.terminal?.fs; + if (!fs) { this._dirtyPaths.clear(); return; } + + const start = Date.now(); + let reindexed = 0; + this._loadHashCache(fs); + + for (const path of [...this._dirtyPaths]) { + const content = fs.readFile(path); + if (content !== null) { + const hash = this._contentHash(content); + const cached = this._getCached(path, hash); + if (!cached) { + this._reindexFile(path, content, this.index); + this._hashCache[path] = { hash }; + reindexed++; + } + } else { + // File was deleted โ remove from index + for (const entries of Object.values(this.index)) { + const idx = entries.findIndex(e => e.path === path); + if (idx !== -1) entries.splice(idx, 1); + } + delete this._hashCache[path]; + } + } + this._dirtyPaths.clear(); + this._autoSyncTimer = null; + this.lastScan = new Date().toISOString(); + this._saveIndex(fs); + const elapsed = ((Date.now() - start) / 1000).toFixed(1); + if (reindexed > 0) { + this.terminal?.print(`๐ Auto-sync: re-indexed ${reindexed} changed file(s) in ${elapsed}s`, 'success'); + } + } + + // โโ Aliases โโ + + _loadAliases() { + try { const r = localStorage.getItem('webx11_nvfs_aliases'); if (r) this._aliasMap = JSON.parse(r); } catch (e) {} + } + _saveAliases() { + localStorage.setItem('webx11_nvfs_aliases', JSON.stringify(this._aliasMap)); + } + + // โโ Status Bar Polling โโ + + _updateStatusBar() { + if (!this.terminal || !this.terminal.statusBar) return; + const el = this.terminal.statusBar; + if (this._deepIndexing) { + const pct = this._deepTotal > 0 ? Math.round(this._deepProcessed / this._deepTotal * 100) : 0; + el.textContent = `๐ง Deep Indexing... ${this._deepProcessed}/${this._deepTotal} (${pct}%)`; + el.style.color = 'var(--accent, #00ff88)'; + } else if (this._autoSyncTimer !== null || this._dirtyPaths.size > 0) { + el.textContent = `๐ Auto-sync pending (${this._dirtyPaths.size} dirty)`; + el.style.color = 'var(--warning, #ffaa00)'; + } else { + const count = Object.keys(this.index).length; + el.textContent = `๐ง ${count} concepts${this._deepIndexEnabled ? ' ๐' : ''}`; + el.style.color = ''; + } + } + + // โโ Deep Indexing (LLM-assisted) โโ + + _parseLLMConcepts(llmResponse) { + // LLM returns markdown bullet list: "- concept: description" + const concepts = []; + if (!llmResponse) return concepts; + const lines = llmResponse.split('\n'); + for (const line of lines) { + const trimmed = line.replace(/^[-*\s]+/, '').trim(); + if (!trimmed) continue; + // Split on first colon or comma or dash + const colonIdx = trimmed.indexOf(':'); + const commaIdx = trimmed.indexOf(','); + const delimIdx = colonIdx > 0 ? colonIdx : (commaIdx > 0 ? commaIdx : -1); + const concept = delimIdx > 0 ? trimmed.slice(0, delimIdx).trim().toLowerCase() : trimmed.toLowerCase(); + if (concept && concept.length > 2 && concept.length < 60) { + concepts.push(concept); + } + } + return [...new Set(concepts)]; + } + + async _callLLM(prompt, context) { + const om = this.terminal?.ollamaManager; + if (!om || !om.connected) return null; + const fullPrompt = `Extract 2-5 key searchable concepts from this file. Return one per line as lowercase keywords (1-3 words). + +File: ${context.path} +Content: +${context.content.slice(0, 2000)} + +Concepts:`; + try { + const result = await om._chatWithProvider(om.currentProvider, fullPrompt, this._deepModel); + return result; + } catch (e) { + console.warn('LLM concept extraction failed:', e); + return null; + } + } + + async _processDeepQueue(index) { + this._deepProcessed = 0; + this._deepTotal = this._deepQueue.length; + const batchSize = 3; + const results = []; + + while (this._deepQueue.length > 0) { + const batch = this._deepQueue.splice(0, batchSize); + for (const item of batch) { + const context = { path: item.path, content: item.content }; + const llmResponse = await this._callLLM('', context); + if (llmResponse) { + const concepts = this._parseLLMConcepts(llmResponse); + results.push({ path: item.path, concepts }); + for (const concept of concepts) { + if (!index[concept]) index[concept] = []; + const exists = index[concept].some(e => e.path === item.path); + if (!exists) index[concept].push({ + path: item.path, + summary: this._summarize(item.content), + source: 'llm' + }); + } + } + this._deepProcessed++; + // Update status bar live + this._updateStatusBar(); + } + // Yield to UI between batches + await new Promise(r => setTimeout(r, 50)); + } + + this._deepIndexing = false; + this._deepQueue = []; + this._deepProcessed = 0; + this._deepTotal = 0; + this._updateStatusBar(); + + if (results.length > 0) { + const llmConcepts = results.reduce((s, r) => s + r.concepts.length, 0); + this.terminal?.print(`๐ง Deep pass complete: ${llmConcepts} LLM-generated concepts across ${results.length} files`, 'success'); + } + } + + // โโ Nvfs Sync (incremental background) โโ + + async nvfsSync(args) { + const useDeep = args && args.includes('--deep'); + if (this._dirtyPaths.size === 0) { + this.terminal.print('No dirty files to sync', 'info'); + return; + } + this.terminal.print(`๐ Syncing ${this._dirtyPaths.size} dirty file(s)${useDeep ? ' (deep mode)' : ''}...`, 'info'); + const fs = this.terminal?.fs; + if (!fs) return; + await this._reindexDirtyPaths(); + // After reindex, if deep mode, queue remaining queued items for LLM processing + if (useDeep && this._deepQueue.length > 0) { + this._deepIndexing = true; + await this._processDeepQueue(this.index); + } + } } // Required for module system diff --git a/modules/core/terminal.js b/modules/core/terminal.js index 8f1ad19..2825340 100644 --- a/modules/core/terminal.js +++ b/modules/core/terminal.js @@ -10,7 +10,7 @@ this.destructiveCmds = new Set(['delete','del','write','save','overwrite','format','kill','stop','shutdown','reboot']); this.systemCmds = new Set(['reboot','shutdown','format','kill','halt','poweroff']); this.suspiciousFlags = new Set(['--dump','--execute','--eval','--exec','--shell','--script']); - this.whitelist = new Set(['help','clear','ls','cat','pwd','whoami','df','free','top','ps','echo','date','env','history']); + this.whitelist = new Set(['help','clear','ls','cat','pwd','whoami','df','free','top','ps','echo','date','env','history','find','grep','tree','head','tail','wc','sort','du','diff','which','hostname','cal','basename','dirname','yes','seq','cut','uniq','expand','printf','base64','sleep','ll','h','mem','status','health-check','system-health','alias','unalias','modules','listmods','modules-list']); // Load persisted overrides from localStorage try { const d = localStorage.getItem('webx11_security_destructive'); if(d){JSON.parse(d).forEach(c=>this.destructiveCmds.add(c));} }catch(e){} try { const s = localStorage.getItem('webx11_security_system'); if(s){JSON.parse(s).forEach(c=>this.systemCmds.add(c));} }catch(e){} diff --git a/modules/core/virtual-fs.js b/modules/core/virtual-fs.js index 59ab18b..12bb7f1 100644 --- a/modules/core/virtual-fs.js +++ b/modules/core/virtual-fs.js @@ -83,6 +83,14 @@ class VirtualFileSystem { } catch (e) { console.error('FS save error:', e); } }, 2000); } + _emitChange(path) { + // Notify NeuralVFS via window event โ zero coupling + // Skip internal index/system files to prevent re-trigger loops + if (path && (path.startsWith('/.semantic_index') || path.startsWith('/.memory_graph') || path.startsWith('/.trash') || path.startsWith('/_snapshots') || path.startsWith('/_sessions') || path.startsWith('/logs') || path.startsWith('/memory') || path.startsWith('/system') || path.startsWith('/_mods') || path.startsWith('/backups'))) return; + if (typeof window !== 'undefined' && window.__nvfsMarkDirty) { + window.__nvfsMarkDirty(path); + } + } getCurrentPath() { return '/' + this.currentPath.join('/'); } @@ -192,6 +200,7 @@ class VirtualFileSystem { if (resolved.dir.content[name] && resolved.dir.content[name].type === 'dir') return false; resolved.dir.content[name] = { type: 'file', content: content, modified: new Date() }; this.saveToStorage(); + this._emitChange(path); return true; } readFile(path) { @@ -242,6 +251,7 @@ class VirtualFileSystem { const resolved = this._resolvePath(path); if (resolved) delete resolved.dir.content[resolved.name]; this.saveToStorage(); + this._emitChange(path); } catch (e) { console.error('Trash error:', e); } } trashDir(path) { @@ -272,6 +282,7 @@ class VirtualFileSystem { flatten(node, ts + '-dir-' + name); delete resolved.dir.content[resolved.name]; this.saveToStorage(); + this._emitChange(path); return true; } catch (e) { console.error('Trash dir error:', e); } } @@ -290,6 +301,7 @@ class VirtualFileSystem { dir = dir.content[seg]; } this.saveToStorage(); + this._emitChange(path); return true; } rename(oldPath, newPath) { @@ -302,6 +314,8 @@ class VirtualFileSystem { dst.dir.content[dst.name] = node; delete src.dir.content[src.name]; this.saveToStorage(); + this._emitChange(oldPath); + this._emitChange(newPath); return true; } copy(srcPath, dstPath) { @@ -313,6 +327,7 @@ class VirtualFileSystem { if (!node) return false; dst.dir.content[dst.name] = JSON.parse(JSON.stringify(node)); this.saveToStorage(); + this._emitChange(dstPath); return true; } move(srcPath, dstPath) { @@ -347,6 +362,7 @@ class VirtualFileSystem { if (Object.keys(node.content).length > 0) return false; delete resolved.dir.content[resolved.name]; this.saveToStorage(); + this._emitChange(path); return true; } return false; |