diff options
| author | Agando <roduccomm@gmail.com> | 2026-06-14 06:00:14 +0200 |
|---|---|---|
| committer | Agando <roduccomm@gmail.com> | 2026-06-14 06:00:14 +0200 |
| commit | 3dda77c60c19ebe810ac86e35a5c510c737b7b39 (patch) | |
| tree | bfa0ac2c5e96249f7a5010df8bde039e416915bf /modules/core | |
| parent | de713efd619360e29b9bfc4b99d30d2a86b96caf (diff) | |
auto: 2026-06-14_06:00 changes
Diffstat (limited to 'modules/core')
| -rw-r--r-- | modules/core/core-bundle.js | 194 | ||||
| -rw-r--r-- | modules/core/module-loader.js | 4 | ||||
| -rw-r--r-- | modules/core/module-neural-vfs.js | 76 | ||||
| -rw-r--r-- | modules/core/module-system.js | 2 | ||||
| -rw-r--r-- | modules/core/terminal.js | 111 |
5 files changed, 302 insertions, 85 deletions
diff --git a/modules/core/core-bundle.js b/modules/core/core-bundle.js index e242230..8b73403 100644 --- a/modules/core/core-bundle.js +++ b/modules/core/core-bundle.js @@ -1,4 +1,5 @@ -// Core module bundle — generated for v4.5.49// Loaded synchronously in main.html before module-loader.js. +// Core module bundle — generated for v4.5.54 + /* === virtual-fs.js === */ class VirtualFileSystem { constructor() { @@ -2150,7 +2151,7 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm this._registerBuiltIn(); } _registerBuiltIn() { - const internals = ['clear','help','ls','cat','df','free','env','history','cd','pwd','touch','mkdir','rm','cp','mv','edit','cat','run','font-size','load-local','loadlocal','mem','security','security-level','security-destructive','security-system','security-whitelist','security-flag','config','ai-config','startup-modules','smart-prompt','cmd-allow','cmd-deny','system-health','server','servers','server-check','monitor','vfs','vfs-clean','vfs-size','empty-trash']; + const internals = ['clear','help','ls','cat','df','free','env','history','cd','pwd','touch','mkdir','rm','cp','mv','edit','cat','run','font-size','load-local','loadlocal','mem','security','security-level','security-destructive','security-system','security-whitelist','security-flag','config','ai-config','startup-modules','smart-prompt','cmd-allow','cmd-deny','system-health','server','servers','server-check','monitor','vfs','vfs-clean','vfs-size','empty-trash','expand','sysmsg']; internals.forEach(c => this.registerCommand(c, { category: 'builtin' })); } setLevel(n) { @@ -2358,16 +2359,18 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm this._configSetters = {}; this._loadTerminalConfig(); // Register built-in terminal config keys - this.registerConfigItem('terminal-max-lines', 200, (v) => { this.MAX_OUTPUT_LINES = v; }); + this.registerConfigItem('terminal-max-lines', 100, (v) => { this.MAX_OUTPUT_LINES = v; }); this.registerConfigItem('terminal-hist-output', 50, (v) => { this._historyMaxLines = v; }); this.registerConfigItem('terminal-cmd-history', 200, (v) => { /* applied on next save */ }); this.registerConfigItem('terminal-pipe-buffer', 5000, (v) => { this._pipeBufferSize = v; }); + this.registerConfigItem('terminal-sysmsg', true, (v) => { this._showSystemMessages = !!v; }); this._pipeInput = null; this._pipeBufferSize = 5000; this._historyBuffer = []; this._historyMaxLines = 50; this._historySavePending = false; - this.MAX_OUTPUT_LINES = 200; + this.MAX_OUTPUT_LINES = 100; + this._showSystemMessages = true; this._historyCap = 200; // Apply history cap from localStorage on startup (was applied "on next save" before — broken) try { @@ -2468,14 +2471,7 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm } else if (e.ctrlKey && (e.key === 'l' || e.key === 'L')) { // Ctrl+L — clear output (standard terminal) e.preventDefault(); - // Preserve welcome/loading indicators — only clear actual output lines - if (this.outputContainer) { - for (const child of [...this.outputContainer.children]) { - if (!child.id || (!child.id.includes('welcome') && !child.id.includes('loading'))) { - child.remove(); - } - } - } + this.clearOutput(); } else if (e.ctrlKey && (e.key === 'c' || e.key === 'C') && this.inputField.value) { // Ctrl+C — cancel current input line (standard terminal) e.preventDefault(); @@ -2585,6 +2581,13 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm label.textContent = prefix + (path === '/' ? '$' : path + ' $'); } print(text, type = '') { + if (['error','warning','success','info','text-muted'].includes(type) && !this._showSystemMessages && !type.startsWith('command')) { + // Still log to history buffer so it can be shown again when toggled on, + // but don't create DOM nodes while system messages are hidden. + this._historyBuffer.push({ l: text, t: type }); + if (this._historyBuffer.length > this._historyMaxLines) this._historyBuffer.shift(); + return; + } const line = document.createElement('div'); // Use card layout for typed messages (error/warning/info/success/text-muted), plain for bare text const isTyped = ['error','warning','success'].includes(type); @@ -2595,6 +2598,13 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm } else { line.className = 'output-line'; line.textContent = text; + // Plain lines get a lightweight inline copy button + const copyBtn = document.createElement('button'); + copyBtn.className = 'line-copy-btn'; + copyBtn.textContent = 'Copy'; + copyBtn.setAttribute('onclick', 'webx11.copyLineToClipboard(this)'); + copyBtn.setAttribute('aria-label', 'Copy line'); + line.appendChild(copyBtn); } this.outputContainer.appendChild(line); this._historyBuffer.push({ l: text, t: type }); @@ -2731,17 +2741,15 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm const nd = btn.nextElementSibling; if (nd) text = nd.innerText || nd.textContent; } + this._copyText(text, btn); + } + copyLineToClipboard(btn) { + const line = btn.parentElement; + const text = line ? line.childNodes[0].textContent : ''; + this._copyText(text, btn); + } + _copyText(text, btn=null) { if (!text) return; - btn.textContent = 'Copied!'; - setTimeout(function(){ btn.textContent = 'Copy'; }, 2000); - // Use navigator.clipboard on secure contexts (iOS 14+, modern browsers). - // This is the preferred API — NOTHING blocks it on HTTPS pages. - if (navigator.clipboard) { - navigator.clipboard.writeText(text).catch(function(){}); - } - // Also try textarea+execCommand synchronously for older browsers. - // DO NOT wrap in setTimeout — iOS Safari requires execCommand to run - // in the same microtask as the user's click event. try { var ta = document.createElement('textarea'); ta.value = text; @@ -2752,7 +2760,11 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm ta.setSelectionRange(0, text.length); document.execCommand('copy'); document.body.removeChild(ta); - } catch(e) {} + if (btn) { btn.textContent = 'Copied!'; setTimeout(function(){ btn.textContent = 'Copy'; }, 2000); } + this.print('📋 Copied to clipboard', 'success'); + } catch (e) { + this.print('Copy failed: ' + e.message, 'error'); + } } printClickable(items) { const line = document.createElement('div'); @@ -2891,10 +2903,9 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm } } _saveHistory() { - if (this._historySavePending) return; - this._historySavePending = true; - setTimeout(() => { - this._historySavePending = false; + if (this._historySaveTimer) clearTimeout(this._historySaveTimer); + this._historySaveTimer = setTimeout(() => { + this._historySaveTimer = null; try { const slice = this._historyBuffer.slice(-this._historyMaxLines); localStorage.setItem('webx11_terminal_output', JSON.stringify(slice)); @@ -2916,6 +2927,33 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm container.removeChild(container.firstChild); } } + toggleExpand() { + const container = document.getElementById('terminal-container'); + if (!container) return; + container.classList.toggle('terminal-expanded'); + const on = container.classList.contains('terminal-expanded'); + this.print(on ? 'Terminal expanded' : 'Terminal compact', 'text-muted'); + } + toggleSystemMessages(arg) { + let desired; + if (arg === 'on') desired = true; + else if (arg === 'off') desired = false; + else desired = !this._showSystemMessages; + this._showSystemMessages = desired; + this._saveTerminalConfig(); + this.print('System messages ' + (desired ? 'on' : 'off'), 'text-muted'); + } + clearOutput() { + if (this.outputContainer) { + this.outputContainer.innerHTML = ''; + } + this._historyBuffer = []; + if (this._historySaveTimer) { + clearTimeout(this._historySaveTimer); + this._historySaveTimer = null; + } + try { localStorage.removeItem('webx11_terminal_output'); } catch (e) {} + } markDirty() { this._dirty = true; } @@ -3369,6 +3407,7 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm if (parsed['terminal-hist-output'] !== undefined) this._historyMaxLines = parsed['terminal-hist-output']; if (parsed['terminal-cmd-history'] !== undefined) this._historyCap = parsed['terminal-cmd-history']; if (parsed['terminal-pipe-buffer'] !== undefined) this._pipeBufferSize = parsed['terminal-pipe-buffer']; + this._trimOutput(); } catch (e) {} } async _loadExtModules(filterKeys) { @@ -3478,7 +3517,9 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm return trimmed; } async dispatchCommand(cmd, args) { - if (cmd === 'clear') { this.outputContainer.innerHTML = ''; this._historyBuffer = []; localStorage.removeItem('webx11_terminal_output'); return; } + if (cmd === 'clear') { this.clearOutput(); return; } + if (cmd === 'expand') { this.toggleExpand(); return; } + if (cmd === 'sysmsg') { this.toggleSystemMessages(args[0]); return; } if (cmd === 'startup-modules') { this.openStartupModules(); return; } if (cmd === 'font-size' || cmd === 'setfont') { this.setFontSize(args[0]); return; } if (cmd === 'load-local' || cmd === 'loadlocal') { this.loadLocalModule(args[0]); return; } @@ -3487,24 +3528,31 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm if (cmd === 'config') { if (args.length === 0) { this.print('=== Terminal Config ===', 'info'); - this.print(`terminal-max-lines: ${this.MAX_OUTPUT_LINES} (default: 200)`, ''); + this.print(`terminal-max-lines: ${this.MAX_OUTPUT_LINES} (default: 100)`, ''); this.print(`terminal-hist-output: ${this._historyMaxLines} (default: 50)`, ''); this.print(`terminal-cmd-history: ${this._historyCap} (default: 200)`, ''); this.print(`terminal-pipe-buffer: ${this._pipeBufferSize} (default: 5000)`, ''); + this.print(`terminal-sysmsg: ${this._showSystemMessages ? 'on' : 'off'} (default: on)`, ''); this.print('Usage: config <key> <value>', 'info'); - this.print('Keys: terminal-max-lines, terminal-hist-output, terminal-cmd-history, terminal-pipe-buffer', 'info'); + this.print('Keys: terminal-max-lines, terminal-hist-output, terminal-cmd-history, terminal-pipe-buffer, terminal-sysmsg', 'info'); return; } const key = args[0]; const val = args[1]; if (val === undefined) { this.print('Usage: config <key> <value>', 'warning'); return; } const n = parseInt(val); + if (key === 'terminal-sysmsg') { + this._showSystemMessages = val !== 'off'; + this._saveTerminalConfig(); + this.print(`config ${key} set to ${this._showSystemMessages ? 'on' : 'off'}`, 'success'); + return; + } if (isNaN(n) || n < 1) { this.print('Value must be a positive number', 'warning'); return; } if (key === 'terminal-max-lines') { this.MAX_OUTPUT_LINES = n; } else if (key === 'terminal-hist-output') { this._historyMaxLines = n; } else if (key === 'terminal-cmd-history') { this._historyCap = n; } else if (key === 'terminal-pipe-buffer') { this._pipeBufferSize = n; } - else { this.print(`Unknown key: ${key}. Valid: terminal-max-lines, terminal-hist-output, terminal-cmd-history, terminal-pipe-buffer`, 'warning'); return; } + else { this.print(`Unknown key: ${key}. Valid: terminal-max-lines, terminal-hist-output, terminal-cmd-history, terminal-pipe-buffer, terminal-sysmsg`, 'warning'); return; } this._saveTerminalConfig(); this.print(`config ${key} set to ${val}`, 'success'); return; @@ -4538,6 +4586,8 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm printMemoryStats() { const pm = typeof performance !== 'undefined' && performance.memory; let heapInfo = ''; + let domNodes = 0; + try { domNodes = document.getElementsByTagName('*').length; } catch (e) {} if (pm) { const used = (pm.usedJSHeapSize / 1048576).toFixed(1); const total = (pm.totalJSHeapSize / 1048576).toFixed(1); @@ -4554,9 +4604,11 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm const output = [ `📊 Memory Stats`, ` ${heapInfo}`, + ` DOM nodes: ${domNodes}`, ` Terminal lines in DOM: ${this.outputContainer.children.length}`, ` History buffer: ${this._historyBuffer.length} entries`, ` MAX_OUTPUT_LINES: ${this.MAX_OUTPUT_LINES}`, + ` History-backed output lines: ${this._historyBuffer.length}`, ` Commands in history: ${this.history.length}` ].join('\n'); this.print(output, 'info'); @@ -4631,6 +4683,8 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm 'font-size': 'font-size <8-32> — Set terminal font size (persists across sessions)', 'setfont': 'setfont <8-32> — Alias for font-size', 'smart-prompt': 'smart-prompt [on|off] — Toggle context-aware prompt (shows name, skills, sessions, memory, edits)', + 'expand': 'expand — Toggle full-screen terminal output', + 'sysmsg': 'sysmsg [on|off] — Toggle system/warning/error/info message visibility', 'checkpoint': 'checkpoint [name] — Create a VFS snapshot (name defaults to ISO timestamp)', 'checkpoint-list': 'checkpoint-list — List all VFS checkpoints', 'rollback': 'rollback <name> — Restore VFS from a checkpoint', @@ -14370,6 +14424,10 @@ class NeuralVFSModule { this._treeFiles = 5; this._treeConcepts = 15; this._resolveFiles = 20; + this._excludePatterns = []; + this._excludeDirs = []; + this._excludeFiles = []; + this._includeOnly = []; this._loadConfig(); // Auto-sync state this._dirtyPaths = new Set(); @@ -14395,10 +14453,10 @@ class NeuralVFSModule { } } _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) {} + 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; if (Array.isArray(p.excludePatterns)) this._excludePatterns = p.excludePatterns; if (Array.isArray(p.excludeDirs)) this._excludeDirs = p.excludeDirs; if (Array.isArray(p.excludeFiles)) this._excludeFiles = p.excludeFiles; if (Array.isArray(p.includeOnly)) this._includeOnly = p.includeOnly; } } catch (e) {} } _saveConfig() { - localStorage.setItem('webx11_nvfs_config', JSON.stringify({ fuzzyLimit: this._fuzzyLimit, treeFiles: this._treeFiles, treeConcepts: this._treeConcepts, resolveFiles: this._resolveFiles })); + localStorage.setItem('webx11_nvfs_config', JSON.stringify({ fuzzyLimit: this._fuzzyLimit, treeFiles: this._treeFiles, treeConcepts: this._treeConcepts, resolveFiles: this._resolveFiles, excludePatterns: this._excludePatterns, excludeDirs: this._excludeDirs, excludeFiles: this._excludeFiles, includeOnly: this._includeOnly })); } getHelpInfo() { @@ -14407,7 +14465,7 @@ class NeuralVFSModule { '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 config (fuzzy-limit, tree-files, tree-concepts, resolve-files, deep-index, deep-model)', + 'nvfs-config': 'nvfs-config [key] [value] — View or set Neural VFS config (fuzzy-limit, tree-files, tree-concepts, resolve-files, deep-index, deep-model, exclude-patterns, exclude-dirs, exclude-files, include-only)', 'nvfs-alias': 'nvfs-alias list|add|remove|clear — Manage concept aliases', 'nvfs-sync': 'nvfs-sync [--deep] — Incremental background sync (auto-sync dirty files)', 'nvfs-save': 'nvfs-save — Force-save current in-memory index to VFS (survives refresh)', @@ -14582,6 +14640,7 @@ class NeuralVFSModule { if (!entries) return; for (const entry of entries) { const fullPath = path === '/' ? '/' + entry.name : path + '/' + entry.name; + if (this._isExcluded(fullPath)) continue; if (entry.type === 'file') { if (this._isSystemFile(fullPath)) continue; list.push({ path: fullPath, node: { type: 'file', content: fs.readFile(fullPath) || '' } }); @@ -14734,7 +14793,7 @@ class NeuralVFSModule { // ── Nvfs Config ── nvfsConfig(args) { - if (args.length === 0) { + const listConfig = () => { this.terminal.print('=== Neural VFS Config ===', 'info'); this.terminal.print(`fuzzy-limit: ${this._fuzzyLimit} (default: 10)`, ''); this.terminal.print(`tree-files: ${this._treeFiles} (default: 5)`, ''); @@ -14742,17 +14801,21 @@ class NeuralVFSModule { 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(`exclude-patterns: ${this._excludePatterns.length ? this._excludePatterns.join(', ') : '(none)'}`, ''); + this.terminal.print(`exclude-dirs: ${this._excludeDirs.length ? this._excludeDirs.join(', ') : '(none)'}`, ''); + this.terminal.print(`exclude-files: ${this._excludeFiles.length ? this._excludeFiles.join(', ') : '(none)'}`, ''); + this.terminal.print(`include-only: ${this._includeOnly.length ? this._includeOnly.join(', ') : '(all)'}`, ''); this.terminal.print('Usage: nvfs-config <key> <value>', 'info'); - this.terminal.print('Keys: fuzzy-limit, tree-files, tree-concepts, resolve-files, deep-index (on|off), deep-model <name>', 'info'); - return; - } + this.terminal.print('Keys: fuzzy-limit, tree-files, tree-concepts, resolve-files, deep-index (on|off), deep-model, exclude-patterns, exclude-dirs, exclude-files, include-only', 'info'); + this.terminal.print('Lists are comma-separated. Use "+item" to append, "-item" to remove, or a plain comma list to replace.', 'info'); + }; + if (args.length === 0) { listConfig(); 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; @@ -14773,13 +14836,38 @@ class NeuralVFSModule { this.terminal.print(`nvfs-config deep-model set to ${val}`, 'success'); return; } + // List-type config keys + const listKeys = { + 'exclude-patterns': '_excludePatterns', + 'exclude-dirs': '_excludeDirs', + 'exclude-files': '_excludeFiles', + 'include-only': '_includeOnly' + }; + if (listKeys[key]) { + const raw = args.slice(1).join(' ').trim(); + if (!raw) { this.terminal.print(`${key}: ${this[listKeys[key]].join(', ') || '(empty)'}`, 'info'); return; } + let current = this[listKeys[key]]; + if (raw.startsWith('+')) { + const add = raw.slice(1).split(',').map(s => s.trim()).filter(Boolean); + current = [...new Set([...current, ...add])]; + } else if (raw.startsWith('-')) { + const rem = raw.slice(1).split(',').map(s => s.trim()).filter(Boolean); + current = current.filter(c => !rem.includes(c)); + } else { + current = raw.split(',').map(s => s.trim()).filter(Boolean); + } + this[listKeys[key]] = current; + this._saveConfig(); + this.terminal.print(`${key}: ${current.join(', ')}`, '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, deep-index (on|off), deep-model <name>`, 'warning'); } + else { return this.terminal.print(`Unknown key: ${key}. Valid: fuzzy-limit, tree-files, tree-concepts, resolve-files, deep-index (on|off), deep-model, exclude-patterns, exclude-dirs, exclude-files, include-only`, 'warning'); } this._saveConfig(); this.terminal.print(`nvfs-config ${key} set to ${val}`, 'success'); } @@ -14846,6 +14934,28 @@ class NeuralVFSModule { return false; } + _isExcluded(path) { + const normalized = path.startsWith('/') ? path : '/' + path; + // Explicit file or directory names + const excludeNames = ['agents', 'leases', 'agent-registry', 'lease-system']; + const parts = normalized.split('/').filter(Boolean); + for (const part of parts) { + if (excludeNames.includes(part.toLowerCase())) return true; + if (this._excludeDirs.includes(part)) return true; + if (this._excludeFiles.includes(part)) return true; + } + if (this._excludePatterns.some(p => { + try { return new RegExp(p).test(normalized); } catch (e) { return normalized.includes(p); } + })) return true; + if (this._includeOnly.length > 0) { + const isIncluded = this._includeOnly.some(p => { + try { return new RegExp(p).test(normalized); } catch (e) { return normalized.includes(p); } + }); + if (!isIncluded) return true; + } + return false; + } + _contentHash(str) { if (!str) return ''; let hash = 5381; @@ -15062,7 +15172,7 @@ class NeuralVFSModule { // ── Enum helpers for autocomplete ── _getNvfsConfigKeys() { - return ['fuzzy-limit', 'tree-files', 'tree-concepts', 'resolve-files', 'deep-index', 'deep-model']; + return ['fuzzy-limit', 'tree-files', 'tree-concepts', 'resolve-files', 'deep-index', 'deep-model', 'exclude-patterns', 'exclude-dirs', 'exclude-files', 'include-only']; } _getNvfsAliasActions() { @@ -15343,5 +15453,3 @@ Concepts:`; if (typeof window !== 'undefined') { window.NeuralVFSModule = NeuralVFSModule; } - -/* === end core bundle === */ diff --git a/modules/core/module-loader.js b/modules/core/module-loader.js index e0fa8e1..cd5ed27 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-13-v4.5.49'; + const LATEST_VER = '2026-06-13-v4.5.54'; 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') { - const extKeys = ['calc', 'voice', 'cron', 'theme', 'wpm', 'mindmap', 'webdav', 'skill-pack-core', 'cryptogzip', 'tutorial', 'floating', 'console']; + let extKeys = ['calc', 'voice', '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/module-neural-vfs.js b/modules/core/module-neural-vfs.js index 2e5b9ac..f9bc7f4 100644 --- a/modules/core/module-neural-vfs.js +++ b/modules/core/module-neural-vfs.js @@ -21,6 +21,10 @@ class NeuralVFSModule { this._treeFiles = 5; this._treeConcepts = 15; this._resolveFiles = 20; + this._excludePatterns = []; + this._excludeDirs = []; + this._excludeFiles = []; + this._includeOnly = []; this._loadConfig(); // Auto-sync state this._dirtyPaths = new Set(); @@ -46,10 +50,10 @@ class NeuralVFSModule { } } _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) {} + 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; if (Array.isArray(p.excludePatterns)) this._excludePatterns = p.excludePatterns; if (Array.isArray(p.excludeDirs)) this._excludeDirs = p.excludeDirs; if (Array.isArray(p.excludeFiles)) this._excludeFiles = p.excludeFiles; if (Array.isArray(p.includeOnly)) this._includeOnly = p.includeOnly; } } catch (e) {} } _saveConfig() { - localStorage.setItem('webx11_nvfs_config', JSON.stringify({ fuzzyLimit: this._fuzzyLimit, treeFiles: this._treeFiles, treeConcepts: this._treeConcepts, resolveFiles: this._resolveFiles })); + localStorage.setItem('webx11_nvfs_config', JSON.stringify({ fuzzyLimit: this._fuzzyLimit, treeFiles: this._treeFiles, treeConcepts: this._treeConcepts, resolveFiles: this._resolveFiles, excludePatterns: this._excludePatterns, excludeDirs: this._excludeDirs, excludeFiles: this._excludeFiles, includeOnly: this._includeOnly })); } getHelpInfo() { @@ -58,7 +62,7 @@ class NeuralVFSModule { '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 config (fuzzy-limit, tree-files, tree-concepts, resolve-files, deep-index, deep-model)', + 'nvfs-config': 'nvfs-config [key] [value] — View or set Neural VFS config (fuzzy-limit, tree-files, tree-concepts, resolve-files, deep-index, deep-model, exclude-patterns, exclude-dirs, exclude-files, include-only)', 'nvfs-alias': 'nvfs-alias list|add|remove|clear — Manage concept aliases', 'nvfs-sync': 'nvfs-sync [--deep] — Incremental background sync (auto-sync dirty files)', 'nvfs-save': 'nvfs-save — Force-save current in-memory index to VFS (survives refresh)', @@ -233,6 +237,7 @@ class NeuralVFSModule { if (!entries) return; for (const entry of entries) { const fullPath = path === '/' ? '/' + entry.name : path + '/' + entry.name; + if (this._isExcluded(fullPath)) continue; if (entry.type === 'file') { if (this._isSystemFile(fullPath)) continue; list.push({ path: fullPath, node: { type: 'file', content: fs.readFile(fullPath) || '' } }); @@ -385,7 +390,7 @@ class NeuralVFSModule { // ── Nvfs Config ── nvfsConfig(args) { - if (args.length === 0) { + const listConfig = () => { this.terminal.print('=== Neural VFS Config ===', 'info'); this.terminal.print(`fuzzy-limit: ${this._fuzzyLimit} (default: 10)`, ''); this.terminal.print(`tree-files: ${this._treeFiles} (default: 5)`, ''); @@ -393,17 +398,21 @@ class NeuralVFSModule { 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(`exclude-patterns: ${this._excludePatterns.length ? this._excludePatterns.join(', ') : '(none)'}`, ''); + this.terminal.print(`exclude-dirs: ${this._excludeDirs.length ? this._excludeDirs.join(', ') : '(none)'}`, ''); + this.terminal.print(`exclude-files: ${this._excludeFiles.length ? this._excludeFiles.join(', ') : '(none)'}`, ''); + this.terminal.print(`include-only: ${this._includeOnly.length ? this._includeOnly.join(', ') : '(all)'}`, ''); this.terminal.print('Usage: nvfs-config <key> <value>', 'info'); - this.terminal.print('Keys: fuzzy-limit, tree-files, tree-concepts, resolve-files, deep-index (on|off), deep-model <name>', 'info'); - return; - } + this.terminal.print('Keys: fuzzy-limit, tree-files, tree-concepts, resolve-files, deep-index (on|off), deep-model, exclude-patterns, exclude-dirs, exclude-files, include-only', 'info'); + this.terminal.print('Lists are comma-separated. Use "+item" to append, "-item" to remove, or a plain comma list to replace.', 'info'); + }; + if (args.length === 0) { listConfig(); 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; @@ -424,13 +433,38 @@ class NeuralVFSModule { this.terminal.print(`nvfs-config deep-model set to ${val}`, 'success'); return; } + // List-type config keys + const listKeys = { + 'exclude-patterns': '_excludePatterns', + 'exclude-dirs': '_excludeDirs', + 'exclude-files': '_excludeFiles', + 'include-only': '_includeOnly' + }; + if (listKeys[key]) { + const raw = args.slice(1).join(' ').trim(); + if (!raw) { this.terminal.print(`${key}: ${this[listKeys[key]].join(', ') || '(empty)'}`, 'info'); return; } + let current = this[listKeys[key]]; + if (raw.startsWith('+')) { + const add = raw.slice(1).split(',').map(s => s.trim()).filter(Boolean); + current = [...new Set([...current, ...add])]; + } else if (raw.startsWith('-')) { + const rem = raw.slice(1).split(',').map(s => s.trim()).filter(Boolean); + current = current.filter(c => !rem.includes(c)); + } else { + current = raw.split(',').map(s => s.trim()).filter(Boolean); + } + this[listKeys[key]] = current; + this._saveConfig(); + this.terminal.print(`${key}: ${current.join(', ')}`, '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, deep-index (on|off), deep-model <name>`, 'warning'); } + else { return this.terminal.print(`Unknown key: ${key}. Valid: fuzzy-limit, tree-files, tree-concepts, resolve-files, deep-index (on|off), deep-model, exclude-patterns, exclude-dirs, exclude-files, include-only`, 'warning'); } this._saveConfig(); this.terminal.print(`nvfs-config ${key} set to ${val}`, 'success'); } @@ -497,6 +531,28 @@ class NeuralVFSModule { return false; } + _isExcluded(path) { + const normalized = path.startsWith('/') ? path : '/' + path; + // Explicit file or directory names + const excludeNames = ['agents', 'leases', 'agent-registry', 'lease-system']; + const parts = normalized.split('/').filter(Boolean); + for (const part of parts) { + if (excludeNames.includes(part.toLowerCase())) return true; + if (this._excludeDirs.includes(part)) return true; + if (this._excludeFiles.includes(part)) return true; + } + if (this._excludePatterns.some(p => { + try { return new RegExp(p).test(normalized); } catch (e) { return normalized.includes(p); } + })) return true; + if (this._includeOnly.length > 0) { + const isIncluded = this._includeOnly.some(p => { + try { return new RegExp(p).test(normalized); } catch (e) { return normalized.includes(p); } + }); + if (!isIncluded) return true; + } + return false; + } + _contentHash(str) { if (!str) return ''; let hash = 5381; @@ -713,7 +769,7 @@ class NeuralVFSModule { // ── Enum helpers for autocomplete ── _getNvfsConfigKeys() { - return ['fuzzy-limit', 'tree-files', 'tree-concepts', 'resolve-files', 'deep-index', 'deep-model']; + return ['fuzzy-limit', 'tree-files', 'tree-concepts', 'resolve-files', 'deep-index', 'deep-model', 'exclude-patterns', 'exclude-dirs', 'exclude-files', 'include-only']; } _getNvfsAliasActions() { diff --git a/modules/core/module-system.js b/modules/core/module-system.js index 72735a7..f7da981 100644 --- a/modules/core/module-system.js +++ b/modules/core/module-system.js @@ -25,6 +25,8 @@ 'font-size': 'font-size <8-32> — Set terminal font size (persists across sessions)', 'setfont': 'setfont <8-32> — Alias for font-size', 'smart-prompt': 'smart-prompt [on|off] — Toggle context-aware prompt (shows name, skills, sessions, memory, edits)', + 'expand': 'expand — Toggle full-screen terminal output', + 'sysmsg': 'sysmsg [on|off] — Toggle system/warning/error/info message visibility', 'checkpoint': 'checkpoint [name] — Create a VFS snapshot (name defaults to ISO timestamp)', 'checkpoint-list': 'checkpoint-list — List all VFS checkpoints', 'rollback': 'rollback <name> — Restore VFS from a checkpoint', diff --git a/modules/core/terminal.js b/modules/core/terminal.js index 1aad5bb..c416b8b 100644 --- a/modules/core/terminal.js +++ b/modules/core/terminal.js @@ -19,7 +19,7 @@ this._registerBuiltIn(); } _registerBuiltIn() { - const internals = ['clear','help','ls','cat','df','free','env','history','cd','pwd','touch','mkdir','rm','cp','mv','edit','cat','run','font-size','load-local','loadlocal','mem','security','security-level','security-destructive','security-system','security-whitelist','security-flag','config','ai-config','startup-modules','smart-prompt','cmd-allow','cmd-deny','system-health','server','servers','server-check','monitor','vfs','vfs-clean','vfs-size','empty-trash']; + const internals = ['clear','help','ls','cat','df','free','env','history','cd','pwd','touch','mkdir','rm','cp','mv','edit','cat','run','font-size','load-local','loadlocal','mem','security','security-level','security-destructive','security-system','security-whitelist','security-flag','config','ai-config','startup-modules','smart-prompt','cmd-allow','cmd-deny','system-health','server','servers','server-check','monitor','vfs','vfs-clean','vfs-size','empty-trash','expand','sysmsg']; internals.forEach(c => this.registerCommand(c, { category: 'builtin' })); } setLevel(n) { @@ -227,16 +227,18 @@ this._configSetters = {}; this._loadTerminalConfig(); // Register built-in terminal config keys - this.registerConfigItem('terminal-max-lines', 200, (v) => { this.MAX_OUTPUT_LINES = v; }); + this.registerConfigItem('terminal-max-lines', 100, (v) => { this.MAX_OUTPUT_LINES = v; }); this.registerConfigItem('terminal-hist-output', 50, (v) => { this._historyMaxLines = v; }); this.registerConfigItem('terminal-cmd-history', 200, (v) => { /* applied on next save */ }); this.registerConfigItem('terminal-pipe-buffer', 5000, (v) => { this._pipeBufferSize = v; }); + this.registerConfigItem('terminal-sysmsg', true, (v) => { this._showSystemMessages = !!v; }); this._pipeInput = null; this._pipeBufferSize = 5000; this._historyBuffer = []; this._historyMaxLines = 50; this._historySavePending = false; - this.MAX_OUTPUT_LINES = 200; + this.MAX_OUTPUT_LINES = 100; + this._showSystemMessages = true; this._historyCap = 200; // Apply history cap from localStorage on startup (was applied "on next save" before — broken) try { @@ -337,14 +339,7 @@ } else if (e.ctrlKey && (e.key === 'l' || e.key === 'L')) { // Ctrl+L — clear output (standard terminal) e.preventDefault(); - // Preserve welcome/loading indicators — only clear actual output lines - if (this.outputContainer) { - for (const child of [...this.outputContainer.children]) { - if (!child.id || (!child.id.includes('welcome') && !child.id.includes('loading'))) { - child.remove(); - } - } - } + this.clearOutput(); } else if (e.ctrlKey && (e.key === 'c' || e.key === 'C') && this.inputField.value) { // Ctrl+C — cancel current input line (standard terminal) e.preventDefault(); @@ -454,6 +449,13 @@ label.textContent = prefix + (path === '/' ? '$' : path + ' $'); } print(text, type = '') { + if (['error','warning','success','info','text-muted'].includes(type) && !this._showSystemMessages && !type.startsWith('command')) { + // Still log to history buffer so it can be shown again when toggled on, + // but don't create DOM nodes while system messages are hidden. + this._historyBuffer.push({ l: text, t: type }); + if (this._historyBuffer.length > this._historyMaxLines) this._historyBuffer.shift(); + return; + } const line = document.createElement('div'); // Use card layout for typed messages (error/warning/info/success/text-muted), plain for bare text const isTyped = ['error','warning','success'].includes(type); @@ -464,6 +466,13 @@ } else { line.className = 'output-line'; line.textContent = text; + // Plain lines get a lightweight inline copy button + const copyBtn = document.createElement('button'); + copyBtn.className = 'line-copy-btn'; + copyBtn.textContent = 'Copy'; + copyBtn.setAttribute('onclick', 'webx11.copyLineToClipboard(this)'); + copyBtn.setAttribute('aria-label', 'Copy line'); + line.appendChild(copyBtn); } this.outputContainer.appendChild(line); this._historyBuffer.push({ l: text, t: type }); @@ -600,17 +609,15 @@ const nd = btn.nextElementSibling; if (nd) text = nd.innerText || nd.textContent; } + this._copyText(text, btn); + } + copyLineToClipboard(btn) { + const line = btn.parentElement; + const text = line ? line.childNodes[0].textContent : ''; + this._copyText(text, btn); + } + _copyText(text, btn=null) { if (!text) return; - btn.textContent = 'Copied!'; - setTimeout(function(){ btn.textContent = 'Copy'; }, 2000); - // Use navigator.clipboard on secure contexts (iOS 14+, modern browsers). - // This is the preferred API — NOTHING blocks it on HTTPS pages. - if (navigator.clipboard) { - navigator.clipboard.writeText(text).catch(function(){}); - } - // Also try textarea+execCommand synchronously for older browsers. - // DO NOT wrap in setTimeout — iOS Safari requires execCommand to run - // in the same microtask as the user's click event. try { var ta = document.createElement('textarea'); ta.value = text; @@ -621,7 +628,11 @@ ta.setSelectionRange(0, text.length); document.execCommand('copy'); document.body.removeChild(ta); - } catch(e) {} + if (btn) { btn.textContent = 'Copied!'; setTimeout(function(){ btn.textContent = 'Copy'; }, 2000); } + this.print('📋 Copied to clipboard', 'success'); + } catch (e) { + this.print('Copy failed: ' + e.message, 'error'); + } } printClickable(items) { const line = document.createElement('div'); @@ -760,10 +771,9 @@ } } _saveHistory() { - if (this._historySavePending) return; - this._historySavePending = true; - setTimeout(() => { - this._historySavePending = false; + if (this._historySaveTimer) clearTimeout(this._historySaveTimer); + this._historySaveTimer = setTimeout(() => { + this._historySaveTimer = null; try { const slice = this._historyBuffer.slice(-this._historyMaxLines); localStorage.setItem('webx11_terminal_output', JSON.stringify(slice)); @@ -785,6 +795,33 @@ container.removeChild(container.firstChild); } } + toggleExpand() { + const container = document.getElementById('terminal-container'); + if (!container) return; + container.classList.toggle('terminal-expanded'); + const on = container.classList.contains('terminal-expanded'); + this.print(on ? 'Terminal expanded' : 'Terminal compact', 'text-muted'); + } + toggleSystemMessages(arg) { + let desired; + if (arg === 'on') desired = true; + else if (arg === 'off') desired = false; + else desired = !this._showSystemMessages; + this._showSystemMessages = desired; + this._saveTerminalConfig(); + this.print('System messages ' + (desired ? 'on' : 'off'), 'text-muted'); + } + clearOutput() { + if (this.outputContainer) { + this.outputContainer.innerHTML = ''; + } + this._historyBuffer = []; + if (this._historySaveTimer) { + clearTimeout(this._historySaveTimer); + this._historySaveTimer = null; + } + try { localStorage.removeItem('webx11_terminal_output'); } catch (e) {} + } markDirty() { this._dirty = true; } @@ -1238,6 +1275,7 @@ if (parsed['terminal-hist-output'] !== undefined) this._historyMaxLines = parsed['terminal-hist-output']; if (parsed['terminal-cmd-history'] !== undefined) this._historyCap = parsed['terminal-cmd-history']; if (parsed['terminal-pipe-buffer'] !== undefined) this._pipeBufferSize = parsed['terminal-pipe-buffer']; + this._trimOutput(); } catch (e) {} } async _loadExtModules(filterKeys) { @@ -1347,7 +1385,9 @@ return trimmed; } async dispatchCommand(cmd, args) { - if (cmd === 'clear') { this.outputContainer.innerHTML = ''; this._historyBuffer = []; localStorage.removeItem('webx11_terminal_output'); return; } + if (cmd === 'clear') { this.clearOutput(); return; } + if (cmd === 'expand') { this.toggleExpand(); return; } + if (cmd === 'sysmsg') { this.toggleSystemMessages(args[0]); return; } if (cmd === 'startup-modules') { this.openStartupModules(); return; } if (cmd === 'font-size' || cmd === 'setfont') { this.setFontSize(args[0]); return; } if (cmd === 'load-local' || cmd === 'loadlocal') { this.loadLocalModule(args[0]); return; } @@ -1356,24 +1396,31 @@ if (cmd === 'config') { if (args.length === 0) { this.print('=== Terminal Config ===', 'info'); - this.print(`terminal-max-lines: ${this.MAX_OUTPUT_LINES} (default: 200)`, ''); + this.print(`terminal-max-lines: ${this.MAX_OUTPUT_LINES} (default: 100)`, ''); this.print(`terminal-hist-output: ${this._historyMaxLines} (default: 50)`, ''); this.print(`terminal-cmd-history: ${this._historyCap} (default: 200)`, ''); this.print(`terminal-pipe-buffer: ${this._pipeBufferSize} (default: 5000)`, ''); + this.print(`terminal-sysmsg: ${this._showSystemMessages ? 'on' : 'off'} (default: on)`, ''); this.print('Usage: config <key> <value>', 'info'); - this.print('Keys: terminal-max-lines, terminal-hist-output, terminal-cmd-history, terminal-pipe-buffer', 'info'); + this.print('Keys: terminal-max-lines, terminal-hist-output, terminal-cmd-history, terminal-pipe-buffer, terminal-sysmsg', 'info'); return; } const key = args[0]; const val = args[1]; if (val === undefined) { this.print('Usage: config <key> <value>', 'warning'); return; } const n = parseInt(val); + if (key === 'terminal-sysmsg') { + this._showSystemMessages = val !== 'off'; + this._saveTerminalConfig(); + this.print(`config ${key} set to ${this._showSystemMessages ? 'on' : 'off'}`, 'success'); + return; + } if (isNaN(n) || n < 1) { this.print('Value must be a positive number', 'warning'); return; } if (key === 'terminal-max-lines') { this.MAX_OUTPUT_LINES = n; } else if (key === 'terminal-hist-output') { this._historyMaxLines = n; } else if (key === 'terminal-cmd-history') { this._historyCap = n; } else if (key === 'terminal-pipe-buffer') { this._pipeBufferSize = n; } - else { this.print(`Unknown key: ${key}. Valid: terminal-max-lines, terminal-hist-output, terminal-cmd-history, terminal-pipe-buffer`, 'warning'); return; } + else { this.print(`Unknown key: ${key}. Valid: terminal-max-lines, terminal-hist-output, terminal-cmd-history, terminal-pipe-buffer, terminal-sysmsg`, 'warning'); return; } this._saveTerminalConfig(); this.print(`config ${key} set to ${val}`, 'success'); return; @@ -2407,6 +2454,8 @@ printMemoryStats() { const pm = typeof performance !== 'undefined' && performance.memory; let heapInfo = ''; + let domNodes = 0; + try { domNodes = document.getElementsByTagName('*').length; } catch (e) {} if (pm) { const used = (pm.usedJSHeapSize / 1048576).toFixed(1); const total = (pm.totalJSHeapSize / 1048576).toFixed(1); @@ -2423,9 +2472,11 @@ const output = [ `📊 Memory Stats`, ` ${heapInfo}`, + ` DOM nodes: ${domNodes}`, ` Terminal lines in DOM: ${this.outputContainer.children.length}`, ` History buffer: ${this._historyBuffer.length} entries`, ` MAX_OUTPUT_LINES: ${this.MAX_OUTPUT_LINES}`, + ` History-backed output lines: ${this._historyBuffer.length}`, ` Commands in history: ${this.history.length}` ].join('\n'); this.print(output, 'info'); |