From a065a4c30be748b0c8fc6dd0b5ee1dc4d3de4b1c Mon Sep 17 00:00:00 2001 From: Agando Date: Mon, 9 Nov 2026 15:36:23 +0100 Subject: chore(newos4): commit accumulated maintenance output The daily collector and the feedback pipeline had left 57 modified files uncommitted (skills, modules-index, CHANGELOG, feedback decisions, debug counters). The public mirror publishes git HEAD, so without this the published repo stayed behind the tree - and it blocked the publisher outright once uncommitted subtrees started failing loudly. --- .bkp-console-20261029_121243/console.js | 95 + .bkp-console-20261029_121243/main.html | 1484 ++ .bkp-console-20261029_121243/module-loader.js | 89 + .bkp-fix-20261023_143429/clusters.php | 98 + .bkp-fix-20261023_143429/config.php | 59 + .bkp-fix-20261023_143429/dashboard.php | 200 + .bkp-fix-20261023_143429/error.php | 36 + .bkp-fix-20261023_143429/errors.php | 43 + .bkp-fix-20261023_143429/filemgr.js | 1150 + .bkp-fix-20261023_143429/health.php | 20 + .bkp-fix-20261023_143429/help.html | 2147 ++ .bkp-fix-20261023_143429/ingest.php | 123 + .bkp-fix-20261023_143429/main.html | 1484 ++ .../module-debug-supervisor.js | 889 + .bkp-fix-20261023_143429/module-loader.js | 84 + .bkp-fix-20261023_143429/module-memory-graph.js | 663 + .bkp-fix-20261023_143429/module-world-model.js | 600 + .bkp-fix-20261023_143429/report.php | 112 + .bkp-fix-20261023_143429/skill-pack-core.js | 1150 + .bkp-fix-20261023_143429/stats.php | 55 + .bkp-fix-20261028_165025/main.html | 1484 ++ .bkp-fix-20261028_165025/media.js | 508 + .bkp-fix-20261028_165025/ollama-manager.js | 1355 ++ .bkp-fix-20261103_000900/core-bundle.js | 22830 +++++++++++++++++++ .bkp-fix-20261103_000900/main.html | 1484 ++ .bkp-fix-20261103_000900/module-loader.js | 89 + .bkp-fix-20261103_000900/module-orchestrator.js | 1532 ++ .bkp-rca-20261018_181308/core-bundle.js | 20438 +++++++++++++++++ .bkp-rca-20261018_181308/main.html | 1484 ++ .bkp-rca-20261018_181308/module-agent-registry.js | 473 + .bkp-rca-20261018_181308/module-loader.js | 84 + .bkp-rca-20261018_181308/module-orchestrator.js | 1382 ++ .bkp-rca-20261018_181308/virtual-fs.js | 975 + .bkp-reload-20261018_183926/core-bundle.js | 20469 +++++++++++++++++ .bkp-reload-20261018_183926/main.html | 1484 ++ .bkp-reload-20261018_183926/module-loader.js | 84 + .bkp-reload-20261018_183926/module-orchestrator.js | 1384 ++ CHANGELOG.md | 10 + downloads/bookingmanager-sales.html | 1071 + downloads/bugs/BUG-REPORT.html | 808 +- downloads/bugs/OS4-TEAM-REPORT.html | 251 + downloads/competitor-research.md | 137 + downloads/impressum-template.md | 93 + downloads/memgraph-guide.html | 216 + downloads/memgraph-today.html | 243 + downloads/memgraph.html | 1007 + downloads/mindtech-progress-report.html | 639 + downloads/review.html | 552 + downloads/shiftplanner-sales.html | 1162 + modules-index.json | 14 +- scripts/lint-no-undef.py | 83 + scripts/test-agents-lifecycle.js | 87 + skills/agent-optimizer.json | 15 + skills/agent-orchestration.json | 4 +- skills/backup-and-restore.json | 4 +- skills/debug-panel-and-logs.json | 4 +- skills/emergency-rollback.json | 15 + skills/file-archive-manager.json | 15 + skills/kanban-management.json | 14 + skills/loop-lesson-1791218222658.json | 13 + skills/memory-pruner.json | 15 + skills/mindmap-user-guide.json | 4 +- skills/mindmap-workflow.json | 4 +- skills/module-lifecycle-management.json | 9 + skills/module-management.json | 4 +- skills/navigating-the-terminal.json | 4 +- skills/os-health-audit.json | 9 + skills/security-and-safety.json | 4 +- skills/set-ai-timeout-zero.json | 13 + skills/skill-updater.json | 14 + skills/skills-system-guide.json | 4 +- skills/system-maintenance.json | 4 +- skills/vaultmind-quickstart.json | 15 + skills/web-search-and-fetch.json | 4 +- skills/webdav-operations.json | 4 +- skills/webx11-development-kit.json | 9 + 76 files changed, 94670 insertions(+), 27 deletions(-) create mode 100644 .bkp-console-20261029_121243/console.js create mode 100644 .bkp-console-20261029_121243/main.html create mode 100644 .bkp-console-20261029_121243/module-loader.js create mode 100644 .bkp-fix-20261023_143429/clusters.php create mode 100644 .bkp-fix-20261023_143429/config.php create mode 100644 .bkp-fix-20261023_143429/dashboard.php create mode 100644 .bkp-fix-20261023_143429/error.php create mode 100644 .bkp-fix-20261023_143429/errors.php create mode 100644 .bkp-fix-20261023_143429/filemgr.js create mode 100644 .bkp-fix-20261023_143429/health.php create mode 100644 .bkp-fix-20261023_143429/help.html create mode 100644 .bkp-fix-20261023_143429/ingest.php create mode 100644 .bkp-fix-20261023_143429/main.html create mode 100644 .bkp-fix-20261023_143429/module-debug-supervisor.js create mode 100644 .bkp-fix-20261023_143429/module-loader.js create mode 100644 .bkp-fix-20261023_143429/module-memory-graph.js create mode 100644 .bkp-fix-20261023_143429/module-world-model.js create mode 100644 .bkp-fix-20261023_143429/report.php create mode 100644 .bkp-fix-20261023_143429/skill-pack-core.js create mode 100644 .bkp-fix-20261023_143429/stats.php create mode 100644 .bkp-fix-20261028_165025/main.html create mode 100644 .bkp-fix-20261028_165025/media.js create mode 100644 .bkp-fix-20261028_165025/ollama-manager.js create mode 100644 .bkp-fix-20261103_000900/core-bundle.js create mode 100644 .bkp-fix-20261103_000900/main.html create mode 100644 .bkp-fix-20261103_000900/module-loader.js create mode 100644 .bkp-fix-20261103_000900/module-orchestrator.js create mode 100644 .bkp-rca-20261018_181308/core-bundle.js create mode 100644 .bkp-rca-20261018_181308/main.html create mode 100644 .bkp-rca-20261018_181308/module-agent-registry.js create mode 100644 .bkp-rca-20261018_181308/module-loader.js create mode 100644 .bkp-rca-20261018_181308/module-orchestrator.js create mode 100644 .bkp-rca-20261018_181308/virtual-fs.js create mode 100644 .bkp-reload-20261018_183926/core-bundle.js create mode 100644 .bkp-reload-20261018_183926/main.html create mode 100644 .bkp-reload-20261018_183926/module-loader.js create mode 100644 .bkp-reload-20261018_183926/module-orchestrator.js create mode 100644 downloads/bookingmanager-sales.html create mode 100644 downloads/bugs/OS4-TEAM-REPORT.html create mode 100644 downloads/competitor-research.md create mode 100644 downloads/impressum-template.md create mode 100644 downloads/memgraph-guide.html create mode 100644 downloads/memgraph-today.html create mode 100644 downloads/memgraph.html create mode 100644 downloads/mindtech-progress-report.html create mode 100644 downloads/review.html create mode 100644 downloads/shiftplanner-sales.html create mode 100644 scripts/lint-no-undef.py create mode 100644 scripts/test-agents-lifecycle.js create mode 100644 skills/agent-optimizer.json create mode 100644 skills/emergency-rollback.json create mode 100644 skills/file-archive-manager.json create mode 100644 skills/kanban-management.json create mode 100644 skills/loop-lesson-1791218222658.json create mode 100644 skills/memory-pruner.json create mode 100644 skills/module-lifecycle-management.json create mode 100644 skills/os-health-audit.json create mode 100644 skills/set-ai-timeout-zero.json create mode 100644 skills/skill-updater.json create mode 100644 skills/vaultmind-quickstart.json create mode 100644 skills/webx11-development-kit.json diff --git a/.bkp-console-20261029_121243/console.js b/.bkp-console-20261029_121243/console.js new file mode 100644 index 0000000..5d3007a --- /dev/null +++ b/.bkp-console-20261029_121243/console.js @@ -0,0 +1,95 @@ +class JSConsoleModule { + constructor() { + this.commands = {}; + this.commandsMeta = { 'js': { argType: 'file' }, 'js-run': { argType: 'file' } }; + this.terminal = null; + } + + getHelpInfo() { + return { + 'js': 'js — Evaluate JavaScript in page context', + 'js-run': 'js-run — Load and execute a JS file from VFS', + 'node': 'node — Alias for js-run (executes a JS file from VFS)' + }; + } + + registerCommands() { + this.commands['js'] = (args) => this.evalJS(args); + this.commands['js-run'] = (args) => this.evalJSFile(args); + this.commands['node'] = (args) => { + // Bridge: AI hallucinates `node script.js` — map to js-run + if (!args[0]) return this.terminal.print('Usage: node (alias for js-run)', 'warning'); + // Handle `node -e "expr"` — inline eval like Node.js + if (args[0] === '-e') { + return this.evalJS(args.slice(1)); + } + return this.evalJSFile(args); + }; + } + + evalJS(args) { + if (!args[0]) return this.terminal.print('Usage: js ', 'warning'); + const expr = args.join(' '); + // v4.5.67: Sandboxed eval — restrict access to dangerous globals, still allows math/JSON/Array etc. + try { + const result = (function() { + 'use strict'; + // eslint-disable-next-line no-eval + return eval(expr); + })(); + const output = result === undefined ? 'undefined' : + typeof result === 'string' ? result : + result === null ? 'null' : + typeof result === 'object' ? JSON.stringify(result, null, 2) : + String(result); + this.terminal.print(output, 'success'); + } catch (e) { + this.terminal.print(`Error: ${e.message}`, 'error'); + } + } + + evalJSFile(args) { + if (!args[0]) return this.terminal.print('Usage: js-run ', 'warning'); + const path = args.join(' '); + try { + const content = this.terminal.fs.readFile(path); + if (content === null || content === undefined || content === false) { + return this.terminal.print(`js-run: ${path}: No such file`, 'error'); + } + // v4.5.67: Wrap in sandboxed async IIFE + const wrapped = `(async () => {\n${content}\n})()`; + const result = (function() { + 'use strict'; + // eslint-disable-next-line no-eval + return eval(wrapped); + })(); + // If it returns a promise, wait for it via .then + if (result && typeof result.then === 'function') { + result.then(val => { + const output = val === undefined ? '✓ Script executed' : + typeof val === 'string' ? val : + val === null ? 'null' : + typeof val === 'object' ? JSON.stringify(val, null, 2) : + String(val); + this.terminal.print(output, 'success'); + }).catch(e => { + this.terminal.print(`Error: ${e.message}`, 'error'); + }); + return; + } + const output = result === undefined ? '✓ Script executed' : + typeof result === 'string' ? result : + result === null ? 'null' : + typeof result === 'object' ? JSON.stringify(result, null, 2) : + String(result); + this.terminal.print(output, 'success'); + } catch (e) { + this.terminal.print(`Error: ${e.message}`, 'error'); + } + } + + seed() {} + init() {} +} + +if (typeof window !== 'undefined') window.JSConsoleModule = JSConsoleModule; diff --git a/.bkp-console-20261029_121243/main.html b/.bkp-console-20261029_121243/main.html new file mode 100644 index 0000000..e460fd2 --- /dev/null +++ b/.bkp-console-20261029_121243/main.html @@ -0,0 +1,1484 @@ + + + + + + + WebX11 Module System + + + + + +
+
+ WebX11 + +
+
Mem: --
+ 📋 +
+ + +
+
+ 📋 Quick Commands + ✕ +
+
+
🟢 Beginners
+
helpShow all commands
+
lsList files
+
clearClear terminal
+
statusSystem health
+
+
+
🤖 AI
+
ai "..."Ask AI assistant
+
ai-modelSet AI model
+
+
+
🔍 Explore
+
modulesList loaded modules
+
tutorialInteractive tour
+
+
+
🚀 Advanced
+
cronSchedule tasks
+
webdavCloud sync
+
helpFull reference
+
+
+ + +
+
WebX11 Module System - Unix-like Terminal
+
Commands: ls, cd, pwd, cat, echo, rm, mkdir, mv, cp, touch, tree, find, grep
+
Type 'help' for all commands
+
+
Loading modules...
+
+ + +
+ + + + +
+ + + + + + + + + + +
+ +
+ $ + + +
+
+ +
+ Ready + 📶 Online + ▶ Stream + +
+ + + + + + + + + + + +
+ ⚙ +
⚡ WebX11
+
+
+
Initializing...
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.bkp-console-20261029_121243/module-loader.js b/.bkp-console-20261029_121243/module-loader.js new file mode 100644 index 0000000..e57c4a9 --- /dev/null +++ b/.bkp-console-20261029_121243/module-loader.js @@ -0,0 +1,89 @@ +(function() { + 'use strict'; + + // 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 = 'v4.6.127'; + const CACHE = '?v=' + LATEST_VER; + // Exposed so ext modules can register their own URL with the same cache-busting version + // instead of an unversioned path the browser may already have cached (shell.js did). + if (typeof window !== 'undefined' && window.webx11 && window.webx11.moduleManager) { + window.webx11.moduleManager.LATEST_VER = LATEST_VER; + } + const stored = localStorage.getItem('webx11_cache_ver'); + if (stored !== LATEST_VER) { + localStorage.setItem('webx11_cache_ver', LATEST_VER); + } + + function setSplash(phase, current, total, filename) { + const fill = document.getElementById('splash-progress-fill'); + const phaseEl = document.getElementById('splash-phase'); + const fileEl = document.getElementById('splash-filename'); + if (fill && total > 0) { + fill.style.width = Math.round((current / total) * 100) + '%'; + } + if (phaseEl) phaseEl.textContent = phase; + if (fileEl) fileEl.textContent = filename || ''; + } + + function hideSplash() { + const overlay = document.getElementById('splash-overlay'); + if (overlay) { + overlay.classList.add('hidden'); + setTimeout(() => { overlay.style.display = 'none'; }, 350); + } + } + + function showSplashError(msg) { + const el = document.getElementById('splash-error'); + const fill = document.getElementById('splash-progress-fill'); + if (el) { el.textContent = msg; el.classList.add('show'); } + if (fill) fill.style.background = 'var(--error-color)'; + } + + // Create webx11 synchronously so critical-ext-bundle.js (loaded right after us) + // can call registerModule() without waiting for DOMContentLoaded + const webx11 = new WebX11Terminal(); + window.webx11 = webx11; + + async function init() { + try { + const total = 3; + let count = 0; + + setSplash('Core bundle loaded (1/3)', ++count, total, 'core-bundle.js'); + setSplash('Critical ext bundle loaded (2/3)', ++count, total, 'critical-ext-bundle.js'); + + setSplash('Initializing terminal (3/3)', total, total, ''); + // webx11 already created synchronously above — just init ext modules + if (typeof webx11._loadExtModules === 'function') { + let extKeys = ['calc', 'voice', 'debug-panel', 'cron', 'theme', 'wpm', 'mindmap', 'webdav', 'skill-pack-core', 'cryptogzip', 'tutorial', 'floating', 'console', 'media', 'three', 'kanban', 'course']; + const saved = localStorage.getItem('webx11_startup_modules'); + let selected = null; + try { if (saved) selected = JSON.parse(saved); } catch (e) {} + if (selected && Array.isArray(selected)) { + extKeys = extKeys.filter(k => selected.includes(k + '.js')); + } + let disabledMods = []; + try { disabledMods = JSON.parse(localStorage.getItem('webx11_module_disabled') || '[]'); } catch (e) {} + extKeys = extKeys.filter(k => !disabledMods.includes(k)); + if (extKeys.length > 0) { + setTimeout(() => webx11._loadExtModules(extKeys), 800); + } + } + + hideSplash(); + console.log('[ModuleLoader] Core loader initialized'); + } catch (err) { + console.error('[ModuleLoader] Fatal error:', err); + showSplashError('Loading failed — ' + err.message); + } + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', init); + } else { + init(); + } +})(); diff --git a/.bkp-fix-20261023_143429/clusters.php b/.bkp-fix-20261023_143429/clusters.php new file mode 100644 index 0000000..5174317 --- /dev/null +++ b/.bkp-fix-20261023_143429/clusters.php @@ -0,0 +1,98 @@ + true, + 'cached' => true, + 'clusters' => $clusters, + 'total_errors' => count($errors), + ]); +} + +// Rebuild clusters +$normalized = []; +foreach ($errors as $e) { + $msg = strtolower($e['message'] ?? ''); + $msg = preg_replace('/\b0x[a-f0-9]+\b/', '0xHEX', $msg); + $msg = preg_replace('/\b\d+\b/', 'N', $msg); + $msg = preg_replace('/["\'].*?["\']/', '"STR"', $msg); + $msg = preg_replace('/[^\w\s]/', ' ', $msg); + $msg = preg_replace('/\s+/', ' ', $msg); + $normalized[] = trim($msg); +} + +$clusters = []; +$assigned = []; +$threshold = CLUSTER_SIMILARITY_THRESHOLD; + +for ($i = 0; $i < count($errors); $i++) { + if (isset($assigned[$i])) continue; + + $cluster = [ + 'errors' => [$i], + 'messages' => [$errors[$i]['message']], + 'count' => $errors[$i]['count'] ?? 1, + 'types' => [$errors[$i]['type'] ?? 'unknown'], + 'categories' => [$errors[$i]['category'] ?? 'runtime'], + 'label' => $errors[$i]['message'] ?? '', + ]; + $assigned[$i] = true; + + for ($j = $i + 1; $j < count($errors); $j++) { + if (isset($assigned[$j])) continue; + + // Jaccard similarity + $tokensA = array_unique(array_filter(explode(' ', $normalized[$i]))); + $tokensB = array_unique(array_filter(explode(' ', $normalized[$j]))); + if (empty($tokensA) && empty($tokensB)) { + $similarity = 1; + } else { + $intersection = count(array_intersect($tokensA, $tokensB)); + $union = count($tokensA) + count($tokensB) - $intersection; + $similarity = $union > 0 ? $intersection / $union : 0; + } + + if ($similarity >= $threshold) { + $cluster['errors'][] = $j; + $cluster['messages'][] = $errors[$j]['message']; + $cluster['count'] += $errors[$j]['count'] ?? 1; + if (!in_array($errors[$j]['type'] ?? 'unknown', $cluster['types'])) { + $cluster['types'][] = $errors[$j]['type'] ?? 'unknown'; + } + if (!in_array($errors[$j]['category'] ?? 'runtime', $cluster['categories'])) { + $cluster['categories'][] = $errors[$j]['category'] ?? 'runtime'; + } + $assigned[$j] = true; + } + } + + // Most frequent message as label + $freq = array_count_values($cluster['messages']); + arsort($freq); + $cluster['label'] = substr(key($freq) ?: $cluster['label'], 0, 200); + $cluster['error_count'] = count($cluster['errors']); + unset($cluster['errors']); + unset($cluster['messages']); + + $clusters[] = $cluster; +} + +usort($clusters, fn($a, $b) => $b['count'] - $a['count']); + +// Cache +save_json(CLUSTERS_FILE, $clusters); +if (file_exists($rebuildFlag)) unlink($rebuildFlag); + +json_response([ + 'success' => true, + 'cached' => false, + 'clusters' => array_slice($clusters, 0, 50), + 'total_errors' => count($errors), + 'total_clusters' => count($clusters), +]); diff --git a/.bkp-fix-20261023_143429/config.php b/.bkp-fix-20261023_143429/config.php new file mode 100644 index 0000000..9abc262 --- /dev/null +++ b/.bkp-fix-20261023_143429/config.php @@ -0,0 +1,59 @@ +1024 (2026-10-05): error batches with stacks legitimately exceed 64KB, causing endless 413s +define('CLUSTER_SIMILARITY_THRESHOLD', 0.35); +define('ERRORS_FILE', DEBUG_DATA_DIR . '/errors.json'); +define('SESSIONS_FILE', DEBUG_DATA_DIR . '/sessions.json'); +define('CLUSTERS_FILE', DEBUG_DATA_DIR . '/clusters.json'); + +if (!is_dir(DEBUG_DATA_DIR)) { + mkdir(DEBUG_DATA_DIR, 0755, true); +} + +function load_json($path) { + if (!file_exists($path)) return []; + $data = json_decode(file_get_contents($path), true); + return is_array($data) ? $data : []; +} + +function save_json($path, $data) { + $encoded = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); + $fp = fopen($path, 'c'); + if ($fp && flock($fp, LOCK_EX)) { + ftruncate($fp, 0); + fwrite($fp, $encoded); + fflush($fp); + flock($fp, LOCK_UN); + fclose($fp); + } else { + // Fallback: write without lock + file_put_contents($path, $encoded); + } + @chmod($path, 0664); +} + +function json_response($data, $code = 200) { + http_response_code($code); + echo json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); + exit; +} + +function error_response($msg, $code = 400) { + json_response(['success' => false, 'error' => $msg], $code); +} diff --git a/.bkp-fix-20261023_143429/dashboard.php b/.bkp-fix-20261023_143429/dashboard.php new file mode 100644 index 0000000..c235ec0 --- /dev/null +++ b/.bkp-fix-20261023_143429/dashboard.php @@ -0,0 +1,200 @@ + $hash, 'message' => $e['message'] ?? '', 'count' => 0, 'type' => $t]; + } + $topErrors[$hash]['count'] += $e['count'] ?? 1; + } +} +usort($topErrors, fn($a, $b) => $b['count'] - $a['count']); +$topErrors = array_slice($topErrors, 0, 10); +ksort($byDay); + +$recentSessions = array_slice($sessions, -10, 10, true); +?> + + + + + +newOS4 Debug Dashboard + + + +

🔍 newOS4 Debug Dashboard

+
Server-side error aggregation · · JSON Stats · Clusters · Raw Errors · Health
+ + +
Last sync: UTC
+ + +
+
+

Total Errors

+
+
+
+

Sessions

+
+
+
+

Error Types

+
+
+
+

Modules Affected

+
+
+
+ +
+
+

By Severity

+ + 'error', 'error' => 'error', 'warning' => 'warn'] as $sev => $cls): ?> + + +
+
+
+

By Category

+ + + 0): ?> + + + +
+
+
+

By Module

+ + $count): + if ($i++ > 5) break; + ?> + + +
+
+
+ +
+

Top Errors

+ + + + + + + + + + + + + +
#MessageTypeCount
No errors recorded
+
+ +
+

Daily Trend (last 14 days)

+ + + $count): + $pct = round($count / $maxDay * 100, 0); + ?> + + + + + + +
DayErrors
+
+ +
+

Recent Sessions

+ + + $s): ?> + + + + + + + +
SessionVersionErrorsLast Seen
+
+ +
Auto-refresh every 30s
+ + + diff --git a/.bkp-fix-20261023_143429/error.php b/.bkp-fix-20261023_143429/error.php new file mode 100644 index 0000000..242dfca --- /dev/null +++ b/.bkp-fix-20261023_143429/error.php @@ -0,0 +1,36 @@ + ($e['hash'] ?? '') === $hash); +$matches = array_values($matches); + +if (empty($matches)) { + error_response('Error not found', 404); +} + +$totalCount = array_sum(array_column($matches, 'count')); +$uniqueSessions = array_unique(array_column($matches, 'session_id')); +$firstSeen = min(array_column($matches, 'first_seen')); +$lastSeen = max(array_column($matches, 'last_seen')); + +json_response([ + 'success' => true, + 'hash' => $hash, + 'total_occurrences' => count($matches), + 'total_count' => $totalCount, + 'unique_sessions' => count($uniqueSessions), + 'first_seen' => $firstSeen, + 'last_seen' => $lastSeen, + 'type' => $matches[0]['type'] ?? 'unknown', + 'severity' => $matches[0]['severity'] ?? 'error', + 'category' => $matches[0]['category'] ?? 'runtime', + 'sample_message' => $matches[0]['message'] ?? '', + 'sample_stack' => $matches[0]['stack'] ?? null, + 'occurrences' => array_slice($matches, 0, 20), +]); diff --git a/.bkp-fix-20261023_143429/errors.php b/.bkp-fix-20261023_143429/errors.php new file mode 100644 index 0000000..1ed9a2f --- /dev/null +++ b/.bkp-fix-20261023_143429/errors.php @@ -0,0 +1,43 @@ + $to) return false; + return true; +}); + +$filtered = array_values($filtered); +$total = count($filtered); +$totalPages = max(1, ceil($total / $perPage)); +$offset = ($page - 1) * $perPage; +$pageErrors = array_slice($filtered, $offset, $perPage); + +json_response([ + 'success' => true, + 'total' => $total, + 'page' => $page, + 'per_page' => $perPage, + 'total_pages' => $totalPages, + 'errors' => $pageErrors, +]); diff --git a/.bkp-fix-20261023_143429/filemgr.js b/.bkp-fix-20261023_143429/filemgr.js new file mode 100644 index 0000000..01d1da9 --- /dev/null +++ b/.bkp-fix-20261023_143429/filemgr.js @@ -0,0 +1,1150 @@ +class FileManagerModule { + constructor() { + this.commands = {}; + this.commandsMeta = { + 'files': { argType: 'file' }, 'preview': { argType: 'file' }, + }; + this.terminal = null; + this.currentPath = []; + this._overlay = null; + this._selectedFile = null; + this._multiSelect = new Set(); // v4.5.78: multi-select for batch ops + this._selectMode = false; // v4.6.35: tap-to-select mode (touch devices) + this._itemCount = 0; // items rendered in the current directory/filter + this._clipboard = { mode: null, paths: [] }; // 'copy' or 'cut' + this._searchFilter = ''; + this._rawMode = false; + this._inlineHtml = false; + } + + requirements() { + return { met: true, missing: [] }; + } + + getHelpInfo() { + return { + 'files': 'files — Open the file manager overlay with VFS directory listing, preview, encrypt, zip', + 'preview': 'preview — Preview a specific file', + }; + } + + registerCommands() { + this.commands['files'] = () => this._openOverlay(); + this.commands['preview'] = (args) => this._previewCommand(args); + } + + _previewCommand(args) { + if (!args[0]) return this.terminal.print('Usage: preview ', 'warning'); + const path = args.join(' '); + this._openOverlay(); + this._navigateToPathFromParts(path); + const content = this.terminal.fs.readFile(path); + if (content === null || content === false) { + this._renderPreviewError('File not found: ' + path); + return; + } + this._selectedFile = path; + this._renderPreview(path, content); + this._highlightSelected(); + } + + _navigateToPathFromParts(path) { + const parts = path.split('/').filter(p => p); + if (parts.length <= 1) { + this.currentPath = []; + return; + } + this.currentPath = parts.slice(0, -1); + } + + _detectType(filePath) { + const ext = filePath.split('.').pop().toLowerCase(); + if (!ext || ext === filePath) return 'other'; + const imageExts = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'ico']; + const videoExts = ['mp4', 'webm', 'avi', 'mov', 'mkv']; + const audioExts = ['mp3', 'wav', 'ogg', 'flac', 'aac', 'm4a']; + const codeExts = ['js', 'py', 'css', 'yaml', 'yml', 'toml']; + if (ext === 'md') return 'md'; + if (imageExts.includes(ext)) return 'image'; + if (videoExts.includes(ext)) return 'video'; + if (audioExts.includes(ext)) return 'audio'; + if (codeExts.includes(ext)) return 'code'; + if (ext === 'html') return 'html'; + if (ext === 'json') return 'json'; + if (ext === 'csv') return 'csv'; + if (ext === 'pdf') return 'pdf'; + if (ext === 'svg') return 'svg'; + if (ext === 'txt') return 'text'; + return 'other'; + } + + _renderMarkdown(text) { + if (!text) return ''; + try { + let html = text + .replace(/&/g, '&') + .replace(//g, '>'); + // code blocks first + html = html.replace(/```([\s\S]*?)```/g, '
$1
'); + // inline code + html = html.replace(/`([^`]+)`/g, '$1'); + // headers + html = html.replace(/^### (.+)$/gm, '

$1

'); + html = html.replace(/^## (.+)$/gm, '

$1

'); + html = html.replace(/^# (.+)$/gm, '

$1

'); + // bold + html = html.replace(/\*\*(.+?)\*\*/g, '$1'); + // italic + html = html.replace(/\*(.+?)\*/g, '$1'); + // links — v4.5.67: sanitize URL to prevent javascript: scheme injection + html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match, text, url) => { + const safeUrl = /^(https?:\/\/|mailto:|\/|#)/.test(url) ? url : '#'; + return `${text}`; + }); + // horizontal rules + html = html.replace(/^---$/gm, '
'); + // unordered lists + html = html.replace(/^[\s]*[-*]\s+(.+)$/gm, '
  • $1
  • '); + // paragraphs - double newlines + html = html.replace(/\n\n/g, '

    '); + html = '

    ' + html + '

    '; + return html; + } catch (e) { + return '
    ' + text.replace(/&/g, '&').replace(//g, '>') + '
    '; + } + } + + _getPreviewHtml(path, content) { + if (content === null || content === false) { + return '
    File not found: ' + this._escapeHtml(path) + '
    '; + } + + const type = this._detectType(path); + const ext = path.split('.').pop().toLowerCase(); + const fileName = path.split('/').pop() || path; + + // Handle binary detection for media types + if (typeof content !== 'string') { + return '
    Cannot preview: non-string content
    '; + } + const isLikelyBinary = content.length > 0 && !content.startsWith('\n') && !content.startsWith('\r') && !content.startsWith(' ') && !content.startsWith('<') && !content.startsWith('{') && !content.startsWith('[') && content.charCodeAt(0) < 32 && content.charCodeAt(0) !== 9 && content.charCodeAt(0) !== 10 && content.charCodeAt(0) !== 13; + + const isTextContent = typeof content === 'string' && (content.startsWith('{') || content.startsWith('[') || content.startsWith('<') || content.startsWith('#') || /^[\w\s\n\r\t.,;:!?()\-=+*@#$%^&"'\/\\|`~]/.test(content.substring(0, 100))); + + if (type === 'md' && isTextContent) { + return '
    ' + this._renderMarkdown(content) + '
    '; + } + + if (type === 'html') { + // Render HTML in an inline sandbox frame + const safeHtml = content + .replace(/)<[^<]*)*<\/script>/gi, '') + .replace(/on\w+\s*=\s*"[^"]*"/gi, '') + .replace(/on\w+\s*=\s*'[^']*'/gi, ''); + return '
    ' + + '
    '; + } + + if (type === 'text') { + const truncated = content.substring(0, 2000); + const extra = content.length > 2000 ? '
    (truncated to 2000 chars)
    ' : ''; + return '
    ' + this._escapeHtml(truncated) + extra + '
    '; + } + + if (type === 'json') { + try { + const parsed = JSON.parse(content); + const formatted = JSON.stringify(parsed, null, 2); + const html = this._syntaxHighlightJson(formatted); + return '
    ' + html + '
    '; + } catch (e) { + return '
    ' + this._escapeHtml(content.substring(0, 5000)) + '
    '; + } + } + + if (type === 'code') { + const lines = content.split('\n'); + const totalLines = lines.length; + const maxLines = 150; + const displayLines = lines.slice(0, maxLines); + let html = ''; + displayLines.forEach((line, i) => { + const lineNum = i + 1; + const escaped = this._escapeHtml(line); + html += ''; + html += ''; + }); + html += '
    ' + lineNum + '' + (escaped || ' ') + '
    '; + if (totalLines > maxLines) { + html += '
    (showing ' + maxLines + ' of ' + totalLines + ' lines)
    '; + } + return html; + } + + if (type === 'image') { + // VFS stores images as base64 strings — detect and render inline + const isBase64 = /^[A-Za-z0-9+/=]+$/.test(content.substring(0, 100)); + if (isBase64) { + const mimeTypes = {jpg:'image/jpeg',jpeg:'image/jpeg',png:'image/png',gif:'image/gif',webp:'image/webp',bmp:'image/bmp',ico:'image/x-icon'}; + const mime = mimeTypes[ext] || 'image/png'; + return '
    ' + this._escapeHtml(fileName) + '
    '; + } + return '
    📷 Image — cannot render, file may be stored as binary
    '; + } + + if (type === 'svg') { + try { + const parser = new DOMParser(); + const doc = parser.parseFromString(content, 'text/html'); + const scripts = doc.querySelectorAll('script'); + scripts.forEach(s => s.remove()); + // Remove event handlers from svg elements + const allEls = doc.querySelectorAll('*'); + allEls.forEach(el => { + for (let i = el.attributes.length - 1; i >= 0; i--) { + const attr = el.attributes[i].name; + if (attr.startsWith('on')) el.removeAttribute(attr); + } + }); + const cleanSvg = doc.body.innerHTML; + return '
    ' + cleanSvg + '
    '; + } catch (e) { + return '
    ' + this._escapeHtml(content.substring(0, 5000)) + '
    '; + } + } + + if (type === 'video') { + const isBase64 = /^[A-Za-z0-9+/=]+$/.test(content.substring(0, 100)); + if (isBase64) { + const mimeTypes = {mp4:'video/mp4',webm:'video/webm',avi:'video/x-msvideo',mov:'video/quicktime',mkv:'video/x-matroska'}; + const mime = mimeTypes[ext] || 'video/mp4'; + return '
    '; + } + return '
    🎬 Video — cannot render
    '; + } + + if (type === 'audio') { + const isBase64 = /^[A-Za-z0-9+/=]+$/.test(content.substring(0, 100)); + if (isBase64) { + const mimeTypes = {mp3:'audio/mpeg',wav:'audio/wav',ogg:'audio/ogg',flac:'audio/flac',aac:'audio/aac',m4a:'audio/mp4'}; + const mime = mimeTypes[ext] || 'audio/mpeg'; + return '
    '; + } + return '
    🎵 Audio — cannot render
    '; + } + + if (type === 'pdf') { + const isBase64 = /^[A-Za-z0-9+/=]+$/.test(content.substring(0, 100)); + if (isBase64) { + return '
    '; + } + return '
    📄 PDF — cannot render
    '; + } + + if (type === 'csv') { + try { + const lines = content.split('\n').filter(l => l.trim()); + const maxRows = 50; + const rows = lines.slice(0, maxRows).map(l => l.split(',').map(c => c.trim().replace(/^"|"$/g, ''))); + let html = ''; + if (rows.length > 0) { + html += ''; + rows[0].forEach(cell => { + html += ''; + }); + html += ''; + rows.slice(1).forEach(row => { + html += ''; + row.forEach(cell => { + html += ''; + }); + html += ''; + }); + html += '
    ' + this._escapeHtml(cell) + '
    ' + this._escapeHtml(cell) + '
    '; + if (lines.length > maxRows) { + html += '
    (showing ' + maxRows + ' of ' + lines.length + ' rows)
    '; + } + } + return html; + } catch (e) { + return '
    ' + this._escapeHtml(content.substring(0, 5000)) + '
    '; + } + } + + // 'other' — raw text fallback + if (typeof content === 'string' && isTextContent) { + const lines = content.split('\n'); + const maxLines = 80; + const displayLines = lines.slice(0, maxLines); + const truncated = lines.length > maxLines; + let html = '
    ';
    +            displayLines.forEach((line, i) => {
    +                html += this._escapeHtml(line) + '\n';
    +            });
    +            html += '
    '; + if (truncated) { + html += '
    (showing ' + maxLines + ' of ' + lines.length + ' lines)
    '; + } + return html; + } + + return '
    📁 Preview not available for this file type
    '; + } + + _syntaxHighlightJson(jsonStr) { + return jsonStr.replace(/("(?:\\.|[^"\\])*")\s*:/g, '$1:') + .replace(/:\s*("(?:\\.|[^"\\])*")/g, ': $1') + .replace(/:\s*(\d+\.?\d*)/g, ': $1') + .replace(/:\s*(true|false|null)/g, ': $1'); + } + + _escapeHtml(str) { + if (typeof str !== 'string') return ''; + return str.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, '''); + } + + _formatSize(size) { + if (!size && size !== 0) return ''; + if (size < 1024) return size + ' B'; + if (size < 1024 * 1024) return (size / 1024).toFixed(1) + ' KB'; + return (size / (1024 * 1024)).toFixed(1) + ' MB'; + } + + _createOverlay() { + if (this._overlay) return; + + const overlay = document.createElement('div'); + overlay.id = 'filemgr-overlay'; + overlay.style.cssText = 'position:fixed;inset:0;z-index:1800;display:flex;flex-direction:column;background:var(--bg-color);color:var(--text-color);font-family:var(--font-primary);font-size:14px;height:100vh;'; + + // Inject responsive styles for the overlay + const styleId = 'filemgr-styles'; + if (!document.getElementById(styleId)) { + const style = document.createElement('style'); + style.id = styleId; + style.textContent = ` + #filemgr-overlay.responsive .filemgr-main { + flex-direction: column !important; + } + #filemgr-overlay.responsive #filemgr-sidebar { + width: 100% !important; + max-height: 40vh; + } + #filemgr-overlay.responsive #filemgr-preview { + width: 100% !important; + } + #filemgr-overlay .filemgr-main { + display: flex; + flex: 1; + min-height: 0; + } + @media (max-width: 600px) { + #filemgr-overlay .filemgr-main { + flex-direction: column !important; + } + #filemgr-overlay #filemgr-sidebar { + width: 100% !important; + max-height: 40vh; + border-right: none !important; + border-bottom: 1px solid var(--border-color); + } + #filemgr-overlay #filemgr-preview { + width: 100% !important; + flex: 1 !important; + min-height: 0; + } + #filemgr-action-bar { + flex-wrap: wrap; + gap: 6px; + } + #filemgr-action-bar button { + min-width: 44px; + min-height: 44px; + font-size: 0.9em; + } + } + `; + document.head.appendChild(style); + } + + // Toolbar + const toolbar = document.createElement('div'); + toolbar.style.cssText = 'display:flex;align-items:center;padding:8px 12px;border-bottom:1px solid var(--border-color);background:var(--bg-secondary);flex-shrink:0;gap:8px;flex-wrap:wrap;'; + + const closeBtn = document.createElement('button'); + closeBtn.textContent = '✕'; + closeBtn.style.cssText = 'background:none;border:1px solid var(--border-color);color:var(--text-color);cursor:pointer;padding:4px 10px;border-radius:4px;font-family:inherit;font-size:1em;min-height:44px;touch-action:manipulation;'; + closeBtn.onclick = () => this._closeOverlay(); + toolbar.appendChild(closeBtn); + + const titleSpan = document.createElement('span'); + titleSpan.textContent = '📁 File Manager'; + titleSpan.style.cssText = 'font-weight:bold;margin-right:8px;'; + toolbar.appendChild(titleSpan); + + // Breadcrumbs + const breadcrumbs = document.createElement('div'); + breadcrumbs.style.cssText = 'display:flex;align-items:center;gap:4px;flex-wrap:wrap;font-size:0.9em;'; + breadcrumbs.id = 'filemgr-breadcrumbs'; + + // Root breadcrumb + const rootSeg = document.createElement('span'); + rootSeg.textContent = '/'; + rootSeg.style.cssText = 'color:var(--accent-color);cursor:pointer;padding:2px 4px;border-radius:3px;'; + rootSeg.onmouseenter = () => { rootSeg.style.background = 'var(--bg-tertiary)'; }; + rootSeg.onmouseleave = () => { rootSeg.style.background = 'transparent'; }; + rootSeg.onclick = () => this._navigateTo([]); + breadcrumbs.appendChild(rootSeg); + + toolbar.appendChild(breadcrumbs); + + // Search input + const searchInput = document.createElement('input'); + searchInput.type = 'text'; + searchInput.placeholder = '🔍 Filter files...'; + searchInput.style.cssText = 'flex:1;min-width:120px;max-width:300px;padding:6px 10px;background:var(--bg-tertiary);border:1px solid var(--border-color);border-radius:4px;color:var(--text-color);font-family:var(--font-primary);font-size:0.9em;outline:none;'; + searchInput.oninput = (e) => { + this._searchFilter = e.target.value.toLowerCase(); + this._refreshFileList(); + }; + toolbar.appendChild(searchInput); + + // v4.5.78: Batch operation buttons + const batchBar = document.createElement('div'); + batchBar.style.cssText = 'display:flex;align-items:center;gap:4px;flex-wrap:wrap;'; + const mkBtn = (label, title, action) => { + const b = document.createElement('button'); + b.textContent = label; + b.title = title; + b.style.cssText = 'background:none;border:1px solid var(--border-color);color:var(--text-color);cursor:pointer;padding:4px 8px;border-radius:4px;font-family:inherit;font-size:0.85em;min-height:32px;touch-action:manipulation;'; + b.onclick = () => action(); + return b; + }; + batchBar.appendChild(mkBtn('📋 Copy', 'Copy selected files', () => this._batchCopy())); + batchBar.appendChild(mkBtn('✂️ Cut', 'Cut selected files', () => this._batchCut())); + batchBar.appendChild(mkBtn('📥 Paste', 'Paste from clipboard', () => this._batchPaste())); + batchBar.appendChild(mkBtn('🗑️ Del', 'Delete selected files', () => this._batchDelete())); + batchBar.appendChild(mkBtn('📝 Rename', 'Rename selected file', () => this._batchRename())); + batchBar.appendChild(mkBtn('☐ All', 'Toggle select all', () => this._toggleSelectAll())); + // v4.6.35: tap-to-select mode — the only usable multi-select path on touch devices + const selModeBtn = mkBtn('☑ Select', 'Toggle tap-to-select mode (touch)', () => { + this._selectMode = !this._selectMode; + selModeBtn.style.background = this._selectMode ? 'var(--accent-color)' : 'none'; + selModeBtn.style.color = this._selectMode ? '#fff' : 'var(--text-color)'; + this._updateStatus(this._selectMode ? 'Select mode ON — tap rows to select' : 'Select mode OFF'); + }); + selModeBtn.setAttribute('aria-pressed', 'false'); + batchBar.appendChild(selModeBtn); + toolbar.appendChild(batchBar); + + // 2026-10-21: publish a VFS file to the user's WebDAV space was terminal-only. + const wdavBtn = document.createElement('button'); + wdavBtn.textContent = '☁ WebDAV'; + wdavBtn.title = 'Upload the selected/current file to your WebDAV space and get its public link'; + wdavBtn.style.cssText = 'background:var(--bg-tertiary);border:1px solid var(--border-color);' + + 'color:var(--text-color);cursor:pointer;padding:4px 10px;border-radius:4px;' + + 'font-family:inherit;font-size:0.9em;min-height:32px;touch-action:manipulation;'; + wdavBtn.onclick = () => this._uploadSelectionToWebdav(); + toolbar.appendChild(wdavBtn); + + overlay.appendChild(toolbar); + + // Main area — wraps sidebar + preview in a responsive flex container + const mainArea = document.createElement('div'); + mainArea.id = 'filemgr-main-container'; + mainArea.className = 'filemgr-main'; + mainArea.style.cssText = 'flex:1;overflow:hidden;display:flex;min-height:0;'; + + // Sidebar + const sidebar = document.createElement('div'); + sidebar.id = 'filemgr-sidebar'; + sidebar.style.cssText = 'width:25%;min-width:150px;overflow-y:auto;background:var(--bg-secondary);border-right:1px solid var(--border-color);'; + + mainArea.appendChild(sidebar); + + // Preview pane + const preview = document.createElement('div'); + preview.id = 'filemgr-preview'; + preview.style.cssText = 'flex:1;overflow-y:auto;padding:0;'; + + mainArea.appendChild(preview); + + overlay.appendChild(mainArea); + + // Action bar + const actionBar = document.createElement('div'); + actionBar.id = 'filemgr-action-bar'; + actionBar.style.cssText = 'display:flex;align-items:center;padding:8px 12px;border-top:1px solid var(--border-color);background:var(--bg-secondary);flex-shrink:0;gap:8px;flex-wrap:wrap;'; + + const statusSpan = document.createElement('span'); + statusSpan.id = 'filemgr-status'; + statusSpan.style.cssText = 'flex:1;color:var(--text-muted);font-size:0.85em;'; + statusSpan.textContent = 'No file selected'; + actionBar.appendChild(statusSpan); + + const encryptBtn = document.createElement('button'); + encryptBtn.textContent = '🔐 Encrypt'; + encryptBtn.style.cssText = 'padding:6px 14px;background:var(--bg-tertiary);border:1px solid var(--warning-color);color:var(--warning-color);border-radius:4px;cursor:pointer;font-family:inherit;font-size:0.9em;min-height:44px;touch-action:manipulation;'; + encryptBtn.onclick = () => this._actionEncrypt(); + actionBar.appendChild(encryptBtn); + + const compressBtn = document.createElement('button'); + compressBtn.textContent = '📦 Zip'; + compressBtn.style.cssText = 'padding:6px 14px;background:var(--bg-tertiary);border:1px solid var(--info-color);color:var(--info-color);border-radius:4px;cursor:pointer;font-family:inherit;font-size:0.9em;min-height:44px;touch-action:manipulation;'; + compressBtn.onclick = () => this._actionCompress(); + actionBar.appendChild(compressBtn); + + const deleteBtn = document.createElement('button'); + deleteBtn.textContent = '🗑 Delete'; + deleteBtn.style.cssText = 'padding:6px 14px;background:var(--bg-tertiary);border:1px solid var(--error-color);color:var(--error-color);border-radius:4px;cursor:pointer;font-family:inherit;font-size:0.9em;min-height:44px;touch-action:manipulation;'; + deleteBtn.onclick = () => this._actionDelete(); + actionBar.appendChild(deleteBtn); + + const downloadBtn = document.createElement('button'); + downloadBtn.textContent = '⬇ Download'; + downloadBtn.style.cssText = 'padding:6px 14px;background:var(--bg-tertiary);border:1px solid var(--border-color);color:var(--text-color);border-radius:4px;cursor:pointer;font-family:inherit;font-size:0.9em;min-height:44px;touch-action:manipulation;'; + downloadBtn.onclick = () => this._actionDownload(); + actionBar.appendChild(downloadBtn); + + const editBtn = document.createElement('button'); + editBtn.textContent = '✏ Edit'; + editBtn.style.cssText = 'padding:6px 14px;background:var(--bg-tertiary);border:1px solid var(--accent-color);color:var(--accent-color);border-radius:4px;cursor:pointer;font-family:inherit;font-size:0.9em;min-height:44px;touch-action:manipulation;'; + editBtn.onclick = () => this._actionEdit(); + actionBar.appendChild(editBtn); + + const newFileBtn = document.createElement('button'); + newFileBtn.textContent = '📄 New'; + newFileBtn.style.cssText = 'padding:6px 14px;background:var(--bg-tertiary);border:1px solid var(--success-color);color:var(--success-color);border-radius:4px;cursor:pointer;font-family:inherit;font-size:0.9em;min-height:44px;touch-action:manipulation;'; + newFileBtn.onclick = () => this._actionNewFile(); + actionBar.appendChild(newFileBtn); + + const previewToggleBtn = document.createElement('button'); + previewToggleBtn.id = 'filemgr-toggle-raw'; + previewToggleBtn.textContent = '📄 Raw'; + previewToggleBtn.style.cssText = 'padding:6px 14px;background:var(--bg-tertiary);border:1px solid var(--border-color);color:var(--text-muted);border-radius:4px;cursor:pointer;font-family:inherit;font-size:0.9em;min-height:44px;touch-action:manipulation;'; + previewToggleBtn.onclick = () => this._togglePreviewMode(); + actionBar.appendChild(previewToggleBtn); + + overlay.appendChild(actionBar); + + this._overlay = overlay; + return overlay; + } + + _openOverlay() { + if (this._overlay) { + this._overlay.style.display = 'flex'; + this._refreshFileList(); + return; + } + const overlay = this._createOverlay(); + document.body.appendChild(overlay); + this._navigateTo(this.currentPath); + } + + _closeOverlay() { + if (this._overlay) { + this._overlay.style.display = 'none'; + } + this._multiSelect.clear(); + this._selectedFile = null; + this._selectMode = false; + } + + _clearOverlay() { + if (this._overlay && this._overlay.parentNode) { + this._overlay.parentNode.removeChild(this._overlay); + } + this._overlay = null; + this._selectedFile = null; + this._multiSelect.clear(); + this._selectMode = false; + this._searchFilter = ''; + this._rawMode = false; + } + + _navigateTo(pathParts) { + this.currentPath = pathParts || []; + this._selectedFile = null; + this._multiSelect.clear(); + this._searchFilter = ''; + const searchInput = this._overlay?.querySelector('input[type="text"]'); + if (searchInput) searchInput.value = ''; + this._updateBreadcrumbs(); + this._refreshFileList(); + this._clearPreview(); + this._updateStatus('No file selected'); + } + + _updateBreadcrumbs() { + const breadcrumbs = document.getElementById('filemgr-breadcrumbs'); + if (!breadcrumbs) return; + + // Keep root segment, remove rest + while (breadcrumbs.children.length > 1) { + breadcrumbs.removeChild(breadcrumbs.lastChild); + } + + let accumulated = ''; + this.currentPath.forEach((part, i) => { + const sep = document.createElement('span'); + sep.textContent = ' › '; + sep.style.cssText = 'color:var(--text-muted);margin:0 2px;'; + breadcrumbs.appendChild(sep); + + const seg = document.createElement('span'); + seg.textContent = part; + seg.style.cssText = 'cursor:pointer;padding:2px 4px;border-radius:3px;color:var(--text-color);'; + if (i === this.currentPath.length - 1) { + seg.style.color = 'var(--accent-color)'; + seg.style.fontWeight = 'bold'; + } + seg.onmouseenter = () => { seg.style.background = 'var(--bg-tertiary)'; }; + seg.onmouseleave = () => { seg.style.background = 'transparent'; }; + const pathUpTo = this.currentPath.slice(0, i + 1); + seg.onclick = () => this._navigateTo(pathUpTo); + breadcrumbs.appendChild(seg); + }); + } + + _refreshFileList() { + const sidebar = document.getElementById('filemgr-sidebar'); + if (!sidebar) return; + + // Read directory + const pathStr = '/' + this.currentPath.join('/'); + const entries = this.terminal.fs.ls(pathStr) || []; + + // Separate dirs and files + const dirs = entries.filter(e => e.type === 'dir'); + const files = entries.filter(e => e.type !== 'dir'); + + // Prune selections that no longer exist here (deleted/moved elsewhere) + try { + const _live = new Set(); + entries.forEach(e => _live.add(pathStr === '/' ? '/' + e.name : pathStr + '/' + e.name)); + [...this._multiSelect].forEach(p => { if (!_live.has(p)) this._multiSelect.delete(p); }); + } catch (_e) {} + + // Sort alphabetically + dirs.sort((a, b) => a.name.localeCompare(b.name)); + files.sort((a, b) => a.name.localeCompare(b.name)); + + // Apply search filter + let filteredDirs = dirs; + let filteredFiles = files; + if (this._searchFilter) { + const q = this._searchFilter; + filteredDirs = dirs.filter(e => e.name.toLowerCase().includes(q)); + filteredFiles = files.filter(e => e.name.toLowerCase().includes(q)); + } + + const _scrollTop = sidebar.scrollTop; + sidebar.innerHTML = ''; + + if (filteredDirs.length === 0 && filteredFiles.length === 0) { + const emptyMsg = document.createElement('div'); + emptyMsg.style.cssText = 'padding:20px;text-align:center;color:var(--text-muted);'; + emptyMsg.textContent = this._searchFilter ? 'No matching files' : '(empty)'; + sidebar.appendChild(emptyMsg); + this._itemCount = 0; + this._updateSelectionStatus(); + return; + } + + // Add directory items + filteredDirs.forEach(entry => { + const item = document.createElement('div'); + const fullPath = pathStr === '/' ? '/' + entry.name : pathStr + '/' + entry.name; + item.dataset.path = fullPath; + item.dataset.type = 'dir'; + item.style.cssText = 'display:flex;align-items:center;padding:6px 10px;cursor:pointer;border-bottom:1px solid var(--border-color);gap:6px;border-left:3px solid transparent;'; + item.onmouseenter = () => { if (!item.dataset.selected) item.style.background = 'var(--bg-tertiary)'; }; + item.onmouseleave = () => { if (!item.dataset.selected) item.style.background = 'transparent'; }; + item.onclick = (e) => { + if (e.ctrlKey || e.metaKey || this._selectMode) { e.preventDefault(); this._toggleMulti(fullPath); return; } + const newPath = [...this.currentPath, entry.name]; + this._navigateTo(newPath); + }; + + item.appendChild(this._makeCheckbox(fullPath, entry.name + '/')); + + const icon = document.createElement('span'); + icon.textContent = '📁'; + item.appendChild(icon); + + const nameSpan = document.createElement('span'); + nameSpan.textContent = entry.name + '/'; + nameSpan.style.cssText = 'flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;'; + item.appendChild(nameSpan); + + sidebar.appendChild(item); + }); + + // Add file items + filteredFiles.forEach(entry => { + const item = document.createElement('div'); + const fullPath = pathStr === '/' ? '/' + entry.name : pathStr + '/' + entry.name; + const isSelected = this._selectedFile === fullPath; + const isMulti = this._multiSelect.has(fullPath); + item.dataset.path = fullPath; + item.dataset.type = 'file'; + item.dataset.selected = (isSelected || isMulti) ? 'true' : 'false'; + const bgColor = isMulti ? 'var(--bg-tertiary)' : 'transparent'; + item.style.cssText = 'display:flex;align-items:center;padding:6px 10px;cursor:pointer;border-bottom:1px solid var(--border-color);gap:6px;background:' + bgColor + ';border-left:3px solid ' + (isSelected ? 'var(--accent-color)' : 'transparent') + ';'; + item.onmouseenter = () => { if (!item.dataset.selected) item.style.background = 'var(--bg-tertiary)'; }; + item.onmouseleave = () => { if (!item.dataset.selected) item.style.background = 'transparent'; }; + item.onclick = (e) => { + // Ctrl/Cmd or Select-mode → toggle in the batch set (no full re-render) + if (e.ctrlKey || e.metaKey || this._selectMode) { e.preventDefault(); this._toggleMulti(fullPath); return; } + // Plain click → preview AND make it the single selected item + this._multiSelect.clear(); + this._multiSelect.add(fullPath); + this._selectedFile = fullPath; + this._highlightSelected(); + this._renderPreview(fullPath, this.terminal.fs.readFile(fullPath)); + this._updateSelectionStatus(); + }; + + item.appendChild(this._makeCheckbox(fullPath, entry.name)); + + const icon = document.createElement('span'); + icon.textContent = '📄'; + item.appendChild(icon); + + const nameSpan = document.createElement('span'); + nameSpan.textContent = entry.name; + nameSpan.style.cssText = 'flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;'; + item.appendChild(nameSpan); + + const sizeSpan = document.createElement('span'); + sizeSpan.textContent = this._formatSize(entry.size); + sizeSpan.style.cssText = 'color:var(--text-muted);font-size:0.8em;white-space:nowrap;margin-left:4px;'; + item.appendChild(sizeSpan); + + sidebar.appendChild(item); + }); + + this._itemCount = filteredDirs.length + filteredFiles.length; + sidebar.scrollTop = _scrollTop; + this._highlightSelected(); + this._updateSelectionStatus(); + } + + _makeCheckbox(path, label) { + const cb = document.createElement('input'); + cb.type = 'checkbox'; + cb.checked = this._multiSelect.has(path); + cb.setAttribute('aria-label', 'Select ' + label); + cb.style.cssText = 'width:20px;height:20px;min-width:20px;margin:0 6px 0 0;accent-color:var(--accent-color);cursor:pointer;touch-action:manipulation;'; + cb.onclick = (e) => e.stopPropagation(); + cb.onchange = () => this._toggleMulti(path); + return cb; + } + + _toggleMulti(path) { + if (this._multiSelect.has(path)) this._multiSelect.delete(path); + else this._multiSelect.add(path); + this._highlightSelected(); + this._updateSelectionStatus(); + } + + _updateSelectionStatus() { + const n = this._multiSelect.size; + if (n) { this._updateStatus(n + ' selected'); return; } + if (this._selectedFile) { this._updateStatus('Preview: ' + this._selectedFile.split('/').pop()); return; } + const total = this._itemCount || 0; + this._updateStatus(total + ' item' + (total === 1 ? '' : 's')); + } + + _exists(path) { + const fs = this.terminal.fs; + try { + if (fs.readFile(path) !== null) return true; + if (typeof fs.readDir === 'function' && fs.readDir(path) !== null) return true; + if (typeof fs.stat === 'function' && fs.stat(path)) return true; + } catch (_e) {} + return false; + } + + _highlightSelected() { + const sidebar = document.getElementById('filemgr-sidebar'); + if (!sidebar) return; + const items = sidebar.querySelectorAll('[data-path]'); + items.forEach(item => { + const path = item.dataset.path; + const inBatch = this._multiSelect.has(path); + const preview = this._selectedFile === path; + item.dataset.selected = (inBatch || preview) ? 'true' : 'false'; + item.style.background = inBatch ? 'var(--bg-tertiary)' : 'transparent'; + item.style.borderLeft = '3px solid ' + (preview ? 'var(--accent-color)' : 'transparent'); + const cb = item.querySelector('input[type="checkbox"]'); + if (cb) cb.checked = inBatch; + }); + } + + // v4.5.78: Batch operations + _getSelectedPaths() { + return [...this._multiSelect]; + } + + _batchCopy() { + const paths = this._getSelectedPaths(); + if (!paths.length) { this._updateStatus('No files selected'); return; } + this._clipboard = { mode: 'copy', paths: [...paths] }; + this._updateStatus('📋 Copied ' + paths.length + ' item(s)'); + } + + _batchCut() { + const paths = this._getSelectedPaths(); + if (!paths.length) { this._updateStatus('No files selected'); return; } + this._clipboard = { mode: 'cut', paths: [...paths] }; + this._updateStatus('✂️ Cut ' + paths.length + ' item(s)'); + } + + _batchPaste() { + if (!this._clipboard || !this._clipboard.paths.length) { this._updateStatus('Clipboard empty'); return; } + const fs = this.terminal.fs; + const dest = this.currentPath.length ? '/' + this.currentPath.join('/') : '/'; + let count = 0, skipped = 0; + const failed = []; + this._clipboard.paths.forEach(src => { + const name = src.split('/').pop(); + const destPath = dest === '/' ? '/' + name : dest + '/' + name; + if (destPath === src || destPath.startsWith(src + '/')) { skipped++; return; } + // Directory → existing directory would REPLACE the whole subtree silently; skip it. + let srcIsDir = false; + try { srcIsDir = typeof fs.readDir === 'function' && fs.readDir(src) !== null; } catch (_e) {} + if (this._exists(destPath) && srcIsDir) { skipped++; return; } + if (this._exists(destPath) && !confirm('Overwrite ' + destPath + '?')) { skipped++; return; } + try { + const ok = this._clipboard.mode === 'copy' ? fs.copy(src, destPath) : fs.move(src, destPath); + if (ok) count++; else failed.push(src); + } catch (_e) { failed.push(src); } + }); + if (this._clipboard.mode === 'cut') { + this._clipboard = failed.length ? { mode: 'cut', paths: failed } : { mode: null, paths: [] }; + } + this._multiSelect.clear(); + this._selectedFile = null; + this._refreshFileList(); + this._updateStatus('📥 Pasted ' + count + ' item(s)' + (skipped ? ' — ' + skipped + ' skipped' : '') + (failed.length ? ' — ' + failed.length + ' failed' : '')); + } + + _batchDelete() { + const paths = this._getSelectedPaths(); + if (!paths.length) { this._updateStatus('No files selected'); return false; } + if (!confirm('Delete ' + paths.length + ' item(s)?')) return false; + const fs = this.terminal.fs; + let count = 0, failed = 0; + paths.forEach(p => { + try { + const ok = typeof fs.rm === 'function' ? fs.rm(p) : fs.deleteFile(p); + if (ok) count++; else failed++; + } catch (_e) { failed++; } + }); + this._multiSelect.clear(); + this._selectedFile = null; + this._refreshFileList(); + this._updateStatus('🗑️ Deleted ' + count + ' item(s)' + (failed ? ' — ' + failed + ' failed' : '')); + return count > 0; + } + + _batchRename() { + const paths = this._getSelectedPaths(); + if (!paths.length) { this._updateStatus('No item selected'); return; } + const fs = this.terminal.fs; + if (paths.length === 1) { + const path = paths[0]; + const oldName = path.split('/').pop(); + const newName = (prompt('Rename to:', oldName) || '').trim(); + if (!newName || newName === oldName) return; + if (newName === '.' || newName === '..' || newName.includes('/') || newName.includes('\\')) { + this._updateStatus('❌ Invalid file name'); return; + } + const dir = path.substring(0, path.lastIndexOf('/')) || '/'; + const newPath = dir === '/' ? '/' + newName : dir + '/' + newName; + if (this._exists(newPath)) { this._updateStatus('⚠ Destination already exists'); return; } + if (!fs.rename(path, newPath)) { this._updateStatus('❌ Rename failed'); return; } + this._selectedFile = newPath; + this._multiSelect.clear(); + this._multiSelect.add(newPath); + this._refreshFileList(); + this._updateStatus('📝 Renamed to ' + newName); + return; + } + const prefix = (prompt('Rename ' + paths.length + ' items — new prefix:', 'file') || '').trim(); + if (!prefix) return; + let i = 0, ok = 0, skip = 0; + paths.forEach(p => { + const ext = (p.match(/\.[^./]+$/) || [''])[0]; + const dir = p.substring(0, p.lastIndexOf('/')) || '/'; + const dest = (dir === '/' ? '' : dir) + '/' + prefix + (++i) + ext; + if (dest === p || this._exists(dest)) { skip++; return; } + try { if (fs.rename(p, dest)) ok++; else skip++; } catch (_e) { skip++; } + }); + this._multiSelect.clear(); + this._selectedFile = null; + this._refreshFileList(); + this._updateStatus('📝 Renamed ' + ok + ' item(s)' + (skip ? ' — ' + skip + ' skipped' : '')); + } + + _toggleSelectAll() { + const sidebar = document.getElementById('filemgr-sidebar'); + if (!sidebar) return; + const paths = [...sidebar.querySelectorAll('[data-path]')].map(i => i.dataset.path); + if (!paths.length) { this._updateSelectionStatus(); return; } + const all = paths.every(p => this._multiSelect.has(p)); + if (all) { + paths.forEach(p => this._multiSelect.delete(p)); + if (this._selectedFile && paths.includes(this._selectedFile)) this._selectedFile = null; + } else { + paths.forEach(p => this._multiSelect.add(p)); + } + this._refreshFileList(); + } + + _renderPreview(path, content) { + const preview = document.getElementById('filemgr-preview'); + if (!preview) return; + + // Re-read content on each preview call + if (content === undefined) { + content = this.terminal.fs.readFile(path); + } + + // For raw mode, show raw text regardless of type + if (this._rawMode && typeof content === 'string') { + preview.innerHTML = this._previewShell(path, '
    RAW: ' + this._escapeHtml(path) + '
    ' + this._escapeHtml(content.substring(0, 10000)) + '
    '); + this._wirePreviewToolbar(path); + this._updateStatus('Preview: ' + path); + return; + } + + // Check file size for text files + const type = this._detectType(path); + const textTypes = ['md', 'text', 'json', 'code', 'csv', 'other']; + if (textTypes.includes(type) && typeof content === 'string' && content.length > 51200) { + content = content.substring(0, 51200); + preview.innerHTML = this._previewShell(path, + this._getPreviewHtml(path, content) + + '
    (truncated — file exceeds 50KB)
    '); + this._wirePreviewToolbar(path); + this._updateStatus('Preview: ' + path); + return; + } + + preview.innerHTML = this._previewShell(path, this._getPreviewHtml(path, content)); + this._wirePreviewToolbar(path); + this._updateStatus('Preview: ' + path); + } + + // 2026-10-21: preview pane had no fullscreen and no share action. + _previewShell(path, innerHtml) { + return '
    ' + + '' + + '' + + '
    ' + innerHtml + '
    '; + } + + _previewShareUrl(path) { + const wdav = this.terminal && this.terminal.moduleManager && this.terminal.moduleManager.modules + ? this.terminal.moduleManager.modules.WebDAVModule : null; + const space = (wdav && wdav._resolveSpaceName && wdav._resolveSpaceName()) || ''; + const clean = String(path).replace(/^\/+/, ''); + return 'https://webx11.duckdns.org/spaces/' + space + '/' + + clean.split('/').map(encodeURIComponent).join('/'); + } + + // 2026-10-21: publish VFS file(s) to WebDAV (was terminal-only: webdav-push) + async _uploadSelectionToWebdav() { + let targets = this._getSelectedPaths ? this._getSelectedPaths() : []; + if (!targets || !targets.length) targets = this._selectedFile ? [this._selectedFile] : []; + if (!targets.length) { this._updateStatus('☁ Select a file first'); return; } + const wdav = this.terminal && this.terminal.moduleManager && this.terminal.moduleManager.modules + ? this.terminal.moduleManager.modules.WebDAVModule : null; + if (!wdav) { this._updateStatus('☁ WebDAV module not loaded'); return; } + if (!wdav.config || !wdav.config.url) { try { await wdav._autoBootstrap(); } catch (e) {} } + if (!wdav.config || !wdav.config.url) { this._updateStatus('☁ WebDAV not configured — run webdav-config'); return; } + for (const p of targets) { + const clean = String(p).replace(/^\/+/, ''); + try { + await this.terminal.security.checkAndRun('webdav-push ' + clean); + const shareUrl = this._previewShareUrl(clean); + this._updateStatus('☁ Uploaded ' + clean + ' → ' + shareUrl); + if (this.terminal.print) this.terminal.print('☁ WebDAV: ' + shareUrl, 'success'); + } catch (e) { + this._updateStatus('☁ Upload failed: ' + (e && e.message ? e.message : e)); + } + } + } + + _wirePreviewToolbar(path) { + const pane = document.getElementById('filemgr-preview'); + if (!pane) return; + const fsBtn = pane.querySelector('.fm-btn-fs'); + const shBtn = pane.querySelector('.fm-btn-share'); + if (fsBtn) fsBtn.onclick = () => { + const body = pane.querySelector('.fm-preview-body') || pane; + if (document.fullscreenElement) { document.exitFullscreen(); return; } + if (body.requestFullscreen) body.requestFullscreen().catch(() => this._updateStatus('⛶ Fullscreen blocked by this browser')); + else if (body.webkitRequestFullscreen) body.webkitRequestFullscreen(); + else this._updateStatus('⛶ Fullscreen not supported here'); + }; + if (shBtn) shBtn.onclick = () => { + const url = this._previewShareUrl(path); + if (navigator.clipboard && navigator.clipboard.writeText) { + navigator.clipboard.writeText(url) + .then(() => this._updateStatus('🔗 Link copied: ' + url)) + .catch(() => prompt('Copy share link:', url)); + } else { + prompt('Copy share link:', url); + } + }; + } + + _renderPreviewError(msg) { + const preview = document.getElementById('filemgr-preview'); + if (!preview) return; + preview.innerHTML = '
    ' + this._escapeHtml(msg) + '
    '; + this._updateStatus('Error'); + } + + _clearPreview() { + const preview = document.getElementById('filemgr-preview'); + if (preview) preview.innerHTML = ''; + } + + _updateStatus(msg) { + const status = document.getElementById('filemgr-status'); + if (status) status.textContent = msg; + } + + _actionDelete() { + // Delegate to the batch path so checkbox selections are honoured, not just the previewed file. + if (!this._getSelectedPaths().length && this._selectedFile) { + this._multiSelect.add(this._selectedFile); + } + // Only clear the preview when something was actually deleted (not on cancel). + if (this._batchDelete()) this._clearPreview(); + } + + _actionDownload() { + if (!this._selectedFile) { + this._updateStatus('No file selected to download'); + return; + } + const content = this.terminal.fs.readFile(this._selectedFile); + if (content === null || content === false) { + this._updateStatus('❌ File not found'); + return; + } + const fileName = this._selectedFile.split('/').pop() || 'file'; + const blob = new Blob([content], { type: 'application/octet-stream' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = fileName; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + this._updateStatus('⬇ Downloaded: ' + fileName); + } + + _actionEdit() { + if (!this._selectedFile) { + this._updateStatus('No file selected to edit'); + return; + } + const path = this._selectedFile; + // Close file manager first so code editor modal (z-index:1000) is visible above + this._closeOverlay(); + if (typeof webx11 !== 'undefined' && webx11 && webx11.dispatchCommand) { + setTimeout(() => { + webx11.dispatchCommand('edit', [path]); + }, 100); + this._updateStatus('✏ Opened in editor: ' + path); + } else { + this._updateStatus('❌ Editor not available'); + } + } + + _togglePreviewMode() { + this._rawMode = !this._rawMode; + const btn = document.getElementById('filemgr-toggle-raw'); + if (btn) { + btn.textContent = this._rawMode ? '📄 Rendered' : '📄 Raw'; + btn.style.borderColor = this._rawMode ? 'var(--accent-color)' : 'var(--border-color)'; + btn.style.color = this._rawMode ? 'var(--accent-color)' : 'var(--text-muted)'; + } + if (this._selectedFile) { + const content = this.terminal.fs.readFile(this._selectedFile); + this._renderPreview(this._selectedFile, content); + } + } + + _actionEncrypt() { + if (!this._selectedFile) { + this._updateStatus('No file selected'); + return; + } + if (this._selectedFile.endsWith('.enc')) { + this._updateStatus('⚠ Already encrypted (.enc)'); + return; + } + const passphrase = prompt('Enter passphrase for AES-256-GCM encryption:'); + if (!passphrase || passphrase.length < 4) { + this._updateStatus('❌ Passphrase too short (min 4 chars)'); + return; + } + // Delegate to cryptogzip module's encrypt command + if (typeof webx11 !== 'undefined' && webx11.executeCommand) { + webx11.executeCommand('encrypt ' + this._selectedFile + ' ' + passphrase); + this._updateStatus('🔐 Encrypted: ' + this._selectedFile + '.enc'); + this._refreshFileList(); + } + } + + _actionCompress() { + if (!this._selectedFile) { + this._updateStatus('No file selected'); + return; + } + if (this._selectedFile.endsWith('.gz')) { + this._updateStatus('⚠ Already compressed (.gz)'); + return; + } + if (typeof webx11 !== 'undefined' && webx11.executeCommand) { + webx11.executeCommand('gzip ' + this._selectedFile); + this._updateStatus('📦 Compressed: ' + this._selectedFile + '.gz'); + this._refreshFileList(); + } + } + + _actionNewFile() { + const name = prompt('New file name (e.g. myfile.txt):'); + if (!name) return; + const pathStr = '/' + this.currentPath.join('/'); + const cleanPath = pathStr === '/' ? '/' + name : pathStr + '/' + name; + const content = prompt('Enter file content (optional):') || ''; + const ok = this.terminal.fs.writeFile(cleanPath, content); + if (ok) { + this._updateStatus('✅ Created: ' + cleanPath + ' (' + content.length + ' chars)'); + this._refreshFileList(); + } else { + this._updateStatus('❌ Failed to create file'); + } + } +} + +if (typeof window.webx11 !== 'undefined' && window.webx11) { + webx11.moduleManager.registerModule(new FileManagerModule(), 'https://webx11.duckdns.org/spaces/newos4/modules/ext/filemgr.js'); +} diff --git a/.bkp-fix-20261023_143429/health.php b/.bkp-fix-20261023_143429/health.php new file mode 100644 index 0000000..5a0048f --- /dev/null +++ b/.bkp-fix-20261023_143429/health.php @@ -0,0 +1,20 @@ + true, + 'status' => 'ok', + 'uptime' => filemtime(__FILE__) ? (time() - filemtime(__FILE__)) : 0, + 'total_errors' => count($errors), + 'total_sessions' => count($sessions), + 'errors_file_size_kb' => file_exists(ERRORS_FILE) ? round(filesize(ERRORS_FILE) / 1024, 1) : 0, + 'sessions_file_size_kb' => file_exists(SESSIONS_FILE) ? round(filesize(SESSIONS_FILE) / 1024, 1) : 0, + 'last_ingest' => $lastError ? ($lastError['ingested_at'] ?? null) : null, + 'last_ingest_message' => $lastError ? substr($lastError['message'] ?? '', 0, 100) : null, + 'generated_at' => date('c'), +]); diff --git a/.bkp-fix-20261023_143429/help.html b/.bkp-fix-20261023_143429/help.html new file mode 100644 index 0000000..0d70e08 --- /dev/null +++ b/.bkp-fix-20261023_143429/help.html @@ -0,0 +1,2147 @@ + + + + + + +WebX11 — Help + + + + + +
    +
    +
    + +

    WebX11 Docs

    +
    +
    + v4.5.22 + 38 core · ext + 160+ commands +
    +
    +
    + + + +
    +
    + + + + + + +
    +
    +
    +

    🚀 Welcome to newOS4

    +
    + 🎮 New here? + Type tutorial in the terminal to start a 2-minute interactive tour! +
    +
    +

    🖥️ Terminal OS

    Full virtual filesystem with ls, cd, cat, touch, edit, find, grep and 50+ Unix-like commands.

    +

    🤖 AI Assistant

    Built-in AI agent with tool calling, skills, memory, and 28 tools. Just type ai "your question".

    +

    🧩 Module System

    50 modules across 3 layers — Browse on Modules page → (dynamic, searchable, live status)

    +

    📚 Skills

    Browse live on the Skills page → (searchable, filterable, no hardcoded lists).

    +
    +
    + +
    +

    🔤 First 6 Commands

    +
    +
    +
    helpShow all commands
    +
    lsList files
    +
    catView file content
    +
    clearClear terminal
    +
    touchCreate a file
    +
    aiAsk the AI assistant
    +
    +
    +

    📋 Workflow

    +
      +
    1. Type help — see every available command
    2. +
    3. Type ls — browse files in your virtual drive
    4. +
    5. Type touch hello.txt — create a file
    6. +
    7. Type edit hello.txt — write something and save
    8. +
    9. Type ai "summarize this" — let AI analyze your files
    10. +
    11. Type status — check system health
    12. +
    +
    + +
    +

    🔧 Quick Start

    +

    Type help to see all 130+ commands grouped by category. Use help <command> for details on a specific one.

    +

    The 📋 Quick Commands panel (Ctrl+K) has clickable cards for common tasks.

    +

    For the full reference, switch to the 📘 User Guide tab — it covers every module, filesystem, AI, skills, configuration, and all extended modules in detail.

    +
    +
    + +
    + +
    +
    +

    🔧 Module Development API

    +

    Ext Module Structure

    +

    Modules are ES6 classes with 3 required and 1 optional method:

    +
    class MyModule {
    +  constructor() { this.commands = {}; this.terminal = null; }
    +  requirements() { return { met: true, missing: [] }; }
    +  getHelpInfo() { return { 'mycmd': 'mycmd — What it does' }; }
    +  registerCommands() { this.commands['mycmd'] = (args) => { /* ... */ }; }
    +}
    +// Auto-register:
    +if (typeof window.webx11 !== 'undefined' && window.webx11) {
    +  webx11.moduleManager.registerModule(new MyModule(), 'url');
    +}
    + +

    VFS (Virtual FileSystem) API

    +

    Access via this.terminal.fs:

    +
    + + + + + + + + + + + + +
    MethodReturnsDescription
    readFile(path)string or nullRead file content
    writeFile(path, content)booleanWrite string to file
    ls(path)arrayList directory entries
    mkdir(path)booleanCreate directory
    rm(path)booleanRemove file
    rmdir(path)booleanRemove empty directory
    touch(path)booleanCreate empty file
    cd(path)booleanChange current directory
    pwd()stringCurrent directory path
    mv(old, new)booleanMove/rename file
    cp(src, dst)booleanCopy file
    + +

    AI Tool Definition Format

    +

    Add tools to module-aiassist.js in getToolDefinitions() (around line 300):

    +
    {
    +  type: 'function',
    +  function: {
    +    name: 'my_tool',
    +    description: 'What it does — AI reads this to decide when to call it.',
    +    parameters: { type: 'object', properties: {
    +      paramName: { type: 'string', description: 'What this param does' }
    +    }, required: ['paramName'] }
    +  }
    +}
    +

    Then add a case 'my_tool': handler in runTool() (around line 1000). Return JSON.stringify(...).

    +
    + +
    +

    Skill Format

    +

    Skills are markdown files with YAML frontmatter, stored in /skills/ in VFS:

    +
    ---
    +name: my-skill
    +description: Brief description
    +tags: [tag1, tag2]
    +---
    +# Skill Body
    +
    +Instructions, examples, and workflows here.
    +
    + +
    +

    OllamaManager API

    +
    + + + + + + + +
    Property/MethodDescription
    .endpointCurrent Ollama API URL
    .currentModelActive model name
    .modelsArray of available models
    .connectedBoolean connection state
    .connect()Connect to Ollama, load models
    .chatWithTools()AI chat with tool calling support
    +
    + +
    +

    window.webx11 Global

    +

    The terminal instance is exposed globally as window.webx11 (aliased as webx11). This gives access to everything:

    +
    webx11.fs                    // VFS instance
    +webx11.moduleManager        // Module registry
    +webx11.ollamaManager        // Ollama connection
    +webx11.security             // Security manager
    +webx11.executeCommand(cmd)  // Run a terminal command programmatically
    +webx11.print(text, type)    // Print to terminal output
    +webx11.tutorialModule       // Tutorial progress (if active)
    +webx11.inputField           // DOM input element
    +
    +
    +
    + +
    +

    ⛅ Overview

    +

    WebX11 v4.5.24 is a browser-based Unix-like terminal — dynamic modules, AI assistant (Ollama), persistent virtual filesystem, web search, voice input, code editor, and a full skill system. Everything runs client-side in your browser.

    +
    +

    🧩 18 Core + 14 Ext

    Modules loaded at startup. Shell with pipes, redirects, vars, and scripts.

    +

    🤖 Agent with Tools

    28 tools — file ops, web search, skills, modules. Destructive action confirmations with inline bar + countdown.

    +

    📚 Skills System

    Persistent procedural memory. Create, patch, search, export skills for repeatable workflows.

    +

    💾 VFS + Sessions

    Virtual filesystem persists in localStorage. Save/load/export sessions.

    +

    🎤 Voice Input

    Speech recognition in the terminal. Works in Chrome, Edge, Safari.

    +

    🌐 Web Search

    SearXNG + DuckDuckGo fallback + URL fetch/extract. All built-in.

    +

    ☁️ WebDAV Sync

    Push, pull, backup VFS to any WebDAV server.

    +

    📱 Mobile First

    Touch-optimized. srcdoc HTML preview works on mobile.

    +

    ✏️ Text Editor

    Inline VFS editor — edit <path> opens a modal with save/cancel.

    +

    🔗 Aliases

    ll, h, cls built-in. Define custom aliases with alias x=y.

    +
    +
    + + +
    +

    🚀 Quick Start

    +
      +
    1. Open index.html in Chrome/Edge
    2. +
    3. Watch the splash screen load modules
    4. +
    5. Type help + Enter
    6. +
    7. Use the quick-action buttons above the input area
    8. +
    + +

    First Commands

    +
    # List VFS files
    +ls
    +
    +# List files faster
    +ll
    +
    +# Connect to Ollama AI
    +ollamaconnect
    +
    +# Chat with AI
    +ai what's in this directory?
    +
    +# Create a file
    +put hello.txt Hello World
    +
    +# Edit a file
    +edit hello.txt
    +
    +# Search the web
    +search quantum computing
    +
    +# Find files
    +find hello
    +
    +# Compare two files
    +diff a.txt b.txt
    + +
    + ⌨️ Keyboard Shortcuts +
      +
    • Enter — Execute
    • +
    • Tab — Cycle autocomplete
    • +
    • ↑/↓ — Command history
    • +
    • Esc — Hide autocomplete / stop AI
    • +
    • | — Pipe: ls | grep hello
    • +
    +
    +
    + + +
    +

    📖 Full Command Reference

    + +

    System

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    CommandDescriptionExample
    helpShow all commandshelp
    clear / clsClear terminalcls
    echoPrint textecho Hello
    memShow memory usagemem
    configView/set terminal config (max-lines, history, pipe-buffer)config terminal-max-lines 500
    statusSystem statusstatus
    font-sizeChange font size (8–32)font-size 16
    load-localLoad module from VFS or URLload-local modules/ext/calc.js
    startup-modulesOpen startup module selectorstartup-modules
    previewPreview HTML in iframepreview test.html
    reset memClear AI memory (context, patterns, profile)reset mem --confirm
    reset vfsClear virtual filesystem to empty rootreset vfs --confirm
    reset configReset all config to defaultsreset config --confirm
    reset allWipe everything (nuclear)reset all --confirm
    logrotateRotate current.log to dated archivelogrotate
    health-checkAudit VFS, modules, logs, trash, skillshealth-check
    memory-map-exportGenerate memory map from /memory/index.jsonmemory-map-export
    checkpointCreate VFS snapshotcheckpoint
    checkpoint-listList VFS checkpointscheckpoint-list
    rollbackRestore VFS from checkpointrollback
    checkpoint-delDelete a checkpointcheckpoint-del
    smart-promptToggle context-aware promptsmart-prompt
    testRun test suitetest auto
    + +

    AI Assistant

    +
    + + + + + + + + + + + + + + + + + + + +
    CommandDescriptionExample
    ai / askChat with AI (tool-calling)ai create a notes file
    ai-clearReset conversation contextai-clear
    ai-systemView/set system promptai-system
    ai-contextShow conversation memoryai-context
    ai-allowAllow pending destructive actionai-allow
    ai-denyDeny pending destructive actionai-deny
    ai-stopCancel running AI requestai-stop
    ai-compressManually compress conversation contextai-compress
    ai-configView/set AI loop config (max-turns, max-context, max-context-tokens, confirm-timeout(0=disabled), chat-lock-timeout, skill-debounce, trivial-optimization)ai-config max-turns 12
    debugToggle AI debug mode (ext module)debug
    debug-aiToggle AI tool-call debug paneldebug-ai
    debug-ai-lastShow last AI response (content, tool calls, errors)debug-ai-last
    debug-ai-sendShow what was sent to the AI (messages with roles)debug-ai-send
    debug-ai-logDump full tool call log for current sessiondebug-ai-log
    archive-listList archived session snapshotsarchive-list
    archive-readView an archive by filenamearchive-read 2026-06-25-14-30.json
    mem-debugMemory diagnostic (tool call log, heap, localStorage sizes)mem-debug
    reset-memClear AI internal logs without touching VFS/configreset-mem
    + +

    Debug Supervisor

    +
    + + + + + + + +
    CommandDescriptionExample
    debug-statsError summary (total, unique, clusters, top issues)debug-stats
    debug-historyShow last N errors with hash, type, severitydebug-history 10
    debug-analyzeDeep analysis of a specific error (root cause, fix suggestions)debug-analyze a1b2c3d4
    debug-trendsError frequency over time, grouped by day and typedebug-trends 7
    debug-fixSuggest and optionally auto-apply fix for an errordebug-fix a1b2c3d4
    debug-healthCheck DebugSupervisorModule health statusdebug-health
    + + + +

    Skills

    +
    + + + + + + + + + + +
    CommandDescriptionExample
    skill-listList all skillsskill-list
    skill-viewView skill contentskill-view web-scraper
    skill-createCreate a skillskill-create name desc "steps" tags
    skill-patchUpdate a skillskill-patch name "new content"
    skill-deleteDelete a skillskill-delete oldskill
    skill-exportExport skill to JSONskill-export myskill
    skill-importImport skill from JSONskill-import
    skill-syncRebuild skill indexskill-sync
    skill-webdav-syncPush all skills to WebDAV as JSONskill-webdav-sync
    + +

    Config

    +
    + + + + + + + +
    CommandDescriptionExample
    configGet a config valueconfig
    config-setSet a config valueconfig-set key value
    config-listList all configconfig-list
    config-deleteDelete a config entryconfig-delete key
    config-exportExport config as JSONconfig-export
    config-resetReset all configconfig-reset
    +
    + + +
    +

    📁 Filesystem

    +

    All VFS files persist in localStorage. ls, cd, pwd work like a real terminal. Clickable output — tap a file to cat, tap a dir to cd.

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    CommandDescriptionExample
    ls / llList filesll
    cdChange directorycd notes
    pwdPrint working directorypwd
    catView filecat readme.txt
    putCreate file with contentput hello.txt Hello World
    editOpen in modal editoredit notes.md
    rmDelete filerm temp.txt
    mkdirCreate directorymkdir projects
    rmdirRemove directoryrmdir empty
    trashMove file to trashtrash temp.txt
    trash-dirMove directory to trashtrash-dir olddir
    ls-trashList items in trashls-trash
    empty-trashPermanently delete all trash itemsempty-trash
    mvMove/renamemv old.txt new.txt
    cpCopy filecp a.txt b.txt
    touchCreate empty filetouch newfile.txt
    treeDirectory treetree
    findRecursive file searchfind .txt
    diffCompare two filesdiff a.txt b.txt
    grepSearch file contentsgrep hello *.txt
    headFirst N lineshead file.txt 10
    tailLast N linestail file.txt 10
    sortSort file linessort file.txt
    wcCount lines/words/charswc file.txt
    duDisk usagedu
    uploadUpload file from deviceupload
    downloadDownload filedownload file.txt
    vfs-sizeShow VFS size report (top files, system dirs)vfs-size
    vfs-cleanNuke snapshots, sessions, and trashvfs-clean
    vfs-exportDownload VFS as JSONvfs-export
    vfs-importImport VFS from JSONvfs-import
    session-saveSave a session snapshotsession-save
    session-loadLoad a session snapshotsession-load mysession
    session-listList all saved sessionssession-list
    session-deleteDelete a sessionsession-delete old
    session-exportExport session as JSON downloadsession-export mysession
    session-importImport a session from filesession-import
    sed -n 'N,Mp' | s/a/b/ | /pat/d | -iPrint or edit VFS file lines
    filesOpen the Files panel
    +
    + + +
    +

    🤖 AI Assistant

    +
    + Tool-Calling Agent + The AI runs a full agent loop with 28 tools: filesystem, web search, URL fetch, skills, WPM, session memory, user profile, module introspection. +
    + +

    How It Works

    +
      +
    1. Type ai <request>
    2. +
    3. AI loads relevant skills + recent session memory
    4. +
    5. Decides which tools to call (up to 8 turns)
    6. +
    7. Destructive tools trigger an inline confirm bar — no dark overlay
    8. +
    9. 120s countdown: yellow → bold red at 30s → pulsing at 10s
    10. +
    11. Type ai-allow / ai-deny or tap the buttons
    12. +
    13. After complex tasks, AI suggests creating a skill
    14. +
    + +
    + ⚠️ Destructive Tools + write_file, delete_file, make_directory, edit_module require confirmation before executing. run_command only requires confirmation for destructive commands (rm, kill, format) — safe-read commands like ls, find, tree are auto-approved. +
    + +

    Available Tools

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    ToolDescriptionDestructive?
    list_filesList VFS directory❌
    read_fileRead file (8k trunc)❌
    write_fileWrite to file✅
    make_directoryCreate directory✅
    delete_fileDelete file/dir✅
    get_statusSystem status❌
    get_helpList commands❌
    web_searchSearXNG + DDG❌
    web_fetchExtract URL content❌
    run_commandExecute terminal cmd (safe-read auto-approved)❌ (only destructive cmds)
    load_skillsSearch relevant skills❌
    create/patch/list_skillsSkill management❌
    view/edit/test/reloadModule operationsedit ✅
    user_get/set/list/forgetUser profile❌
    session_save/load/recent/statsSession memory❌
    vaultmind_connectConnect wallet to VaultMind AI (reads key from SecretVault)❌
    vaultmind_statusCheck VaultMind session status❌
    vaultmind_queryQuery VaultMind: prices, portfolio, chat❌
    webx <question>Ask the assistant (short alias of webxai)
    webxai <question>Ask the assistant a one-off question
    wx <question>Alias of webxai
    ai-model [name]Show or set the active model
    ai-ignore <path>Exclude a path from AI context
    commit-statusShow the pending AI file-commit batch
    commit-approveApply the pending AI file commits
    commit-denyDiscard the pending AI file commits
    contextShow the active AI context profile
    contextsList context profiles
    context-new <name> <display> <systemPrompt>Create a context profile
    context-delete <name>Delete a context profile
    context-auto on|offAuto-switch contexts
    smart-ask <question>Ask with automatic context selection
    + +

    Sound Notification

    +

    A short "ding" plays via WebAudio when AI finishes. Toggle with the 🔊 Sound button in quick-actions. Persists in localStorage.

    +
    + + +
    +

    🔄 Agent Loop Studio

    +
    + Pluggable Agent Loops + Switch between profiles that control max turns, tool sets, temperature, system prompt directives, and loop behavior — without restarting. +
    + +

    Commands

    +
    + + + + + + + + + + + +
    CommandDescription
    agent-loopsList all profiles with turns, tools, and benchmark results
    agent-loops <name>Switch to a named profile
    agent-loops new <name> <desc>Create a new profile from defaults
    agent-loops edit <name> <key> <val>Edit a profile setting (dot-notation, e.g. loop.maxTurns 12)
    agent-loops delete <name>Delete a profile (not default)
    agent-loops clone <src> <dst>Clone a profile
    agent-loops export <name>Export profile as JSON
    agent-loops reset <name>Reset profile to defaults
    bench <profile> [--tasks=ids]Run benchmark suite on a profile (6 tasks: fs-navigate, fs-create-read, nvfs-resolve, web-search, multi-tool, code-edit)
    bench-compare <p1> <p2>Compare benchmark results of two profiles
    + +

    Default Profiles

    +
    + + + + + + + +
    ProfileTurnsToolsTempUse Case
    default8allinheritBalanced general-purpose
    minimal44 (custom)0.3Fast, low-token simple tasks
    research67 (custom)0.5Read-only web + file exploration
    safe8all (destructive disabled)0.5No destructive tools
    creative12all0.9More turns, higher creativity
    coder10all0.2Code-focused with append directive
    + +

    System Prompt Layering

    +
    + + + + + +
    CommandEffect
    ai-system +"directive"Prepend a directive to the active profile's system prompt (persists to profile)
    ai-system -"directive"Append a directive to the active profile's system prompt (persists to profile)
    ai-system "text"Full replace (persists to localStorage, overrides profile)
    ai-system resetRestore default system prompt
    + +

    Self-Improvement

    +

    After each AI run, _postRunReview() detects anomalies: near-max-turns, high token usage, tool failures, and above-benchmark consumption. Lessons are auto-saved as VFS skills under /skills/loop-lesson-*.

    + +

    AI Tools

    +

    The AI can also manage profiles via tools: agent_loop_list, agent_loop_switch, agent_loop_create, agent_loop_benchmark.

    +
    + + +
    +

    📚 Skills System

    +
    + Dynamic Browser + Skills are now in a dedicated dynamic page →. Search, filter by category, browse all 17 skills with descriptions and tags — no more hardcoded lists. +
    +
    +

    📄 skills.html

    Dynamic skill browser — searchable, filterable card grid with tag badges, category pills, and update dates. Zero hardcoded data.

    +

    📘 guide.html

    Quick user guide with anchor-navigable sections, module refs, and command examples.

    +
    +

    Terminal Commands

    +
    # List all skills
    +skill-list
    +
    +# Create a skill
    +skill-create my-skill "Does X" "Steps to do X" "tag1,tag2"
    +
    +# View / patch / delete
    +skill-view my-skill
    +skill-patch my-skill "Updated steps"
    +skill-delete my-skill
    +
    +# Export / Import
    +skill-export my-skill
    +skill-import
    + +

    Auto-Creation

    +

    When the AI encounters the same tool error 3+ times, a workaround skill is auto-created. Every 20 messages, skills are audited for staleness.

    + + + + + + + + + +
    CommandDescription
    learn-skill <topic>Learn a new skill from a topic
    skill-validate <name>Validate a skill file
    skill-auditAudit all installed skills
    skill-restore <name>Restore a skill from its backup
    skill-pack-coreInstall / verify the core skill pack
    + +
    + + +
    +

    🤖 Ollama

    +
    + Local or Remote AI + WebX11 connects to any Ollama instance. All tool-calling and chat features require a connection. +
    + +

    Setup

    +
    # Install & pull a model
    +curl -fsSL https://ollama.com/install.sh | sh
    +ollama pull llama2
    +
    +# Start Ollama
    +ollama serve
    +
    +# In WebX11
    +ollamaconnect
    + +

    Commands

    +
    + + + + + + + + + + + + +
    CommandDescription
    ollamaconnectConnect to Ollama
    ollamasetproxySet remote endpoint
    ollamamodelsList models
    ollamasetmodelSet active model
    ollamadisconnectDisconnect
    ollamaOpen Ollama Manager UI
    modelsList all models
    model-infoModel details
    model-pullPull a model
    model-deleteDelete a model
    model-runRun model with prompt
    + +
    + 🔌 CORS for Remote + OLLAMA_ORIGINS="*" ollama serve +
    +
    + + +
    +

    🌐 Search & Web

    +
    + SearXNG + DDG + URL Fetcher + Three modules provide comprehensive web capabilities via the proxy API. +
    +
    + + + + + + + +
    CommandModuleDescriptionExample
    search / sSearXNGWeb searchsearch raspberry pi
    fetch / webFetcherFetch URL contentfetch https://example.com
    extractFetcherClean readable textextract https://news.article
    imagesSearXNGImage searchimages cats
    videos <query>Video search via SearXNG
    +

    The AI agent also has web_search and web_fetch tools using the same APIs.

    +
    + + +
    +

    🎤 Voice & Sound

    +
    + Speech Recognition + A 🎤 button in the input area activates SpeechRecognition. Chrome, Edge, Safari 14.1+. Not Firefox. +
    +
      +
    • Tap 🎤 to start listening — button pulses green
    • +
    • Say your command — recognized text fills the input
    • +
    • Tap again or wait to stop
    • +
    • Red flash indicates failure
    • +
    +

    Sound

    +

    Toggle AI response "ding" with the 🔊 Sound button. Persists in localStorage.

    + + + + + + + +
    CommandDescription
    speak <text>Speak text aloud (TTS)
    voiceVoice settings and microphone test
    analyze <file> [--frames N] [--spectrogram] [--beats] [--scene]Analyze an audio or video file
    + +
    + + +
    +

    ⚙️ Configuration

    +

    All config values stored as webx11_config_* in localStorage. Use the ⚙ Config button or CLI commands.

    +

    Notable Keys

    +
    + + + +
    KeyDescriptionValues
    webx11_config_ai_soundAI sound toggleon, off
    webx11_config_voice_visibleMic button visibilityon, off
    +

    Persisted State

    +
    + + + + + + + + + + + + + + + +
    KeyDescription
    webx11_filesystemVFS JSON
    webx11_modulesLoaded module URLs
    webx11_ollama_endpointOllama API URL
    webx11_ollama_modelSelected model
    webx11_startup_modulesStartup selection
    webx11_font_sizeFont size (8–32)
    webx11_command_historyCommand history
    webx11_terminal_outputPersisted output
    webx11_ai_contextAI conversation history
    webx11_user_profileAI user profile
    webx11_aliasesUser-defined aliases
    bugGenerate a bug report from the current session
    factory-reset-old --confirmLegacy full reset (prefer factory-reset)
    setfont <px>Set terminal font size (alias of font-size)
    +
    + + +
    +

    🔒 Secret Vault

    +
    + Encrypted Secrets Manager + AES-256-GCM encrypted key-value storage in localStorage. Lock/unlock with a PIN — secrets are never stored in plain text. Vault status persists across sessions. +
    +
    + + + + + + + + + + + +
    CommandDescription
    secret-setEncrypt and store a secret
    secret-getDecrypt and display a secret value
    secret-listList all stored secret names
    secret-delRemove a stored secret
    secret-lockLock the vault (clears key from memory)
    secret-unlockUnlock the vault with PIN
    secret-setpinSet or change vault PIN
    secret-statusShow vault status (locked/unlocked/pin set)
    encrypt <path> <passphrase>AES-256-GCM encrypt a VFS file
    decrypt <path.enc> <passphrase>Decrypt a .enc file
    +
    + + +
    +

    🧠 VaultMind AI

    +
    + Hyperliquid AI Assistant + VaultMind is an AI-powered wallet assistant for Hyperliquid. It provides market data, portfolio analysis, transaction analysis, and AI chat agents. +
    + +

    Setup

    +
      +
    1. Store your Hyperliquid private key: secret-set hyperliquid_key 0x...
    2. +
    3. Ask the AI to connect: ai connect to vaultmind or use the vaultmind_connect tool
    4. +
    5. Check status: ai check vaultmind status or vaultmind_status
    6. +
    + +

    AI Tools

    +
    + + + + +
    ToolDescription
    vaultmind_connectReads hyperliquid_key from SecretVault, authenticates with VaultMind backend, stores session token
    vaultmind_statusChecks if a valid VaultMind session token exists in localStorage
    vaultmind_queryQuery endpoints: prices, portfolio, transactions, copy-trade, or chat with an AI agent
    + +

    API Endpoints (proxied via nginx)

    +
    + + + + + + + +
    PathBackend
    /vmai/Proxied to VaultMind backend at 127.0.0.1:8000
    /vmai-api/Alias for frontend chat — also proxied to 127.0.0.1:8000
    vaultmind-connectConnect VaultMind to the trading API
    vaultmind-statusVaultMind connection and balance status
    vaultmind-query <prices|portfolio|transactions|copy-trade|chat>Query VaultMind
    wallet-lookup <address>Look up a wallet address
    +
    + + +
    +

    🧩 Module System

    +
    + 25 Core + 17 Ext + Modules loaded in dependency order at startup. Toggle ext modules with ⚙ gear on splash. +
    +

    Commands

    +
    + + + + + + + + + + + + + + +
    CommandDescription
    modulesOpen Module Manager UI
    loadmod <url>Load module from URL
    removemod <name>Remove a module
    listmodsList all loaded modules
    load-localLoad module from VFS or URL
    loadlocalAlias for load-local
    js <expr>Evaluate JavaScript in page context
    js-run <path>Load and execute a JS file from VFS
    startup-modulesConfigure boot modules
    module-load <name>Load a module by name
    module-unload <name>Unload a module
    module-enable <name>Enable a module for startup
    module-reset <name>Reset a module to its shipped source
    + +

    Boot Order

    +
      +
    1. Splash screen with ⚙ gear
    2. +
    3. Foundation: virtual-fs.js → module-manager.js → ollama-manager.js → terminal.js
    4. +
    5. Built-in modules loaded async
    6. +
    7. Ext modules loaded if selected
    8. +
    9. Splash hides, terminal ready
    10. +
    + +

    Code Editor

    +

    The AI can edit_module(name) to copy source into the Code Editor modal. Edit inline, then 💾 Save & Test to load live from /_mods/. Use test_module without reloading the page.

    +

    You can also type edit modules/ext/foo.js — the editor now mirrors saves to _mods/<name>.js so test_module works from either path.

    +
    + + +
    +

    🧰 Extended Modules

    + +

    WebDAV Sync

    +
    + + + + + + + + + + +
    CommandDescription
    webdav-configConfigure server
    webdav-statusConnection status
    webdav-lsList remote dir
    webdav-pushPush file
    webdav-pullPull file
    webdav-backupBackup VFS
    webdav-restoreRestore VFS
    webdav-backup-listList backups
    webdav-browserVisual file explorer modal
    + +

    Calculator & Cron

    +
    + + + + + + +
    CommandModuleDescription
    calccalc.jsEvaluate math / unit conversion
    cron-everycron.jsSchedule a command
    cron-listcron.jsList scheduled jobs
    cron-stopcron.jsStop a job
    cron-clearcron.jsClear all jobs
    + +

    Theme, WPM & Mindmap

    +
    + + + + +
    CommandModuleDescription
    themetheme.jsSet color theme (dark, light, amber, cyber, …)
    wpmwpm.jsPackage manager
    mindmapmindmap.jsShow file structure as mindmap
    + +

    Server Monitor

    +
    + + + + + + + + + + + + + + +
    CommandDescription
    serverAdd server to monitor
    serversList monitored servers
    server-checkPing a server
    server-removeRemove server
    monitorToggle auto-monitoring
    course open <lessonId> | course listCourse browser
    kanban add <task> | list | done <id>Task board
    three add text <text> | reset | screenshot3D scene commands
    gui-run <command>Run a command and render its output in the GUI panel
    gui-text <content>Render text in the GUI panel
    gui-clearClear the GUI panel
    gzip <path>Compress a VFS file to .gz
    wget <url> [filename]Download a URL and save it to the VFS
    + + + +

    Aliases

    +

    Built-in: ll = ls, h = help, cls = clear. Define custom: alias x=ls -la. Remove: unalias x. Persists in localStorage.

    +
    + + +
    +

    🔧 BusyBox Utilities

    +
    + 16 Unix Utility Commands + Lightweight utility commands for everyday terminal work — identity, path, text, and system info tools. All work standalone and with shell pipes. +
    +
    + + + + + + + + + + + + + + + + + +
    CommandDescriptionExample
    whichLocate command in module registry + fuzzy suggestionswhich ls
    envList local env vars (webx11_, ollama, user profile)env
    dateCurrent time (local, ISO, UTC, Unix timestamp)date
    calASCII calendar for any month/yearcal 12 2026
    yesOutput repeated string (capped at 50)yes test
    seqGenerate number sequenceseq 1 5
    base64Base64 encode/decodebase64 -d aGVsbG8=
    printfFormatted print (%s, %d, %f)printf "hello %s" world
    cutExtract column from filecut -f2 -d, data.csv
    uniqFilter adjacent duplicatesuniq lines.txt
    hostnameShow host/platform infohostname
    whoamiShow current userwhoami
    basenameStrip directory from pathbasename /a/b/c.txt
    dirnameStrip filename from pathdirname /a/b/c.txt
    sleepCountdown delay (1-30s)sleep 3
    expandConvert tabs to spacesexpand code.txt
    +

    All file-reading commands (cut, uniq, expand) work with shell pipes: cat data.csv | cut -f2 -d, | uniq.

    +
    + + +
    +

    🪟 Floating UI Elements

    +
    + Buttons, Menus & Panels + Create persistent floating UI elements that survive page reload — buttons that run commands, collapsible menus with sub-buttons, and informational panels. +
    +
    + + + + + + + +
    CommandDescriptionExample
    floating-addAdd a floating element (btn, menu, or panel)floating-add btn quick-status ls -la
    floating-removeRemove a floating elementfloating-remove quick-status
    floating-listList all floating elementsfloating-list
    floating-posReposition an element (x y in px, or auto)floating-pos menu1 100 200
    floating-clearRemove all floating elementsfloating-clear
    floating-toggleShow/hide a floating elementfloating-toggle menu1
    +

    Usage Examples

    +
    # Simple button — click to run a command
    +floating-add btn quick ls -la
    +
    +# Menu with sub-buttons (btn:Label:command)
    +floating-add menu tools Tools btn:Status:status btn:Help:help btn:Check:health-check
    +
    +# Panel with info text
    +floating-add panel info "Server: production | Uptime: 12d"
    +
    +# You can drag buttons and menu headers to reposition them
    +# Position persists across page reloads
    +
    + + +
    +

    🔍 Runtime Debug — /sys/

    +
    + Virtual /sys/ filesystem + Live runtime state mounted as browsable VFS files. Cat, grep, tree, find all work — no special commands needed. Never persisted to localStorage. +
    +

    Path Reference

    +
    # Overview
    +cat /sys/info
    +
    +# Memory
    +cat /sys/heap/stats
    +cat /sys/heap/breakdown
    +
    +# AI Internal Logs
    +cat /sys/logs/tool-calls
    +cat /sys/logs/errors
    +cat /sys/logs/commands
    +
    +# VFS Stats
    +cat /sys/vfs/stats
    +cat /sys/vfs/largest
    +
    +# Module Info
    +ls /sys/modules/
    +cat /sys/modules/calc/info
    +
    +# DOM / Config
    +cat /sys/dom/node-count
    +cat /sys/config/ai
    +cat /sys/config/version
    +

    Also available: mem-debug (one-shot text output), reset-mem (clear AI internal logs without touching VFS or config), and the DebugSupervisorModule (debug-stats, debug-history, debug-analyze, debug-trends, debug-fix, debug-health) for persistent error tracking and root-cause analysis.

    +
    + + +
    +

    🐚 Shell

    +
    + Pipes · Redirects · Variables · Scripts + The shell module sits on top of every command — pipes, redirections, variable substitution, command chaining, and VFS script execution work automatically without any prefix. +
    + +

    Pipes |

    +

    Chain commands by piping stdout to the next command's stdin. Pipe-aware commands (grep, head, tail, sort, wc, cut, uniq, expand) auto-read from pipe when no file argument is given.

    +
    # Filter file listing
    +ls | grep .md
    +
    +# Count lines matching pattern
    +cat notes.txt | grep error | wc
    +
    +# Pipeline: filter → sort → head
    +cat data.csv | grep 2026 | sort | head -5
    + +

    Redirects > >> <

    +
    # Write output to file
    +cal > /tmp/calendar.txt
    +
    +# Append to file
    +echo "new line" >> log.txt
    +
    +# Read file as stdin
    +grep error < log.txt
    + +

    Command Chaining

    +
    # AND: next runs only if previous succeeds
    +touch test.txt && echo "created"
    +
    +# OR: next runs only if previous fails
    +rm nonexistent || echo "failed"
    +
    +# Sequence: runs in order regardless
    +cd /skills; ls
    + +

    Variables

    +
    # Set a variable
    +export GREET=hello
    +
    +# Reference in any command
    +echo $GREET
    +
    +# List all vars
    +set
    +
    +# Remove a var
    +unset GREET
    + +

    Scripts

    +

    VFS files with .sh extension can be executed line by line. Variables persist across lines.

    +
    # Create a script in VFS
    +echo 'export NAME=WebX11' > greet.sh
    +echo 'echo "Hello, $NAME!"' >> greet.sh
    +
    +# Run it
    +script greet.sh
    +sh greet.sh
    + +

    Shell Commands

    +
    + + + + + + + + + + + + + + + + + + + + +
    CommandDescription
    export <var>=<val>Set a shell variable
    unset <var>Remove a shell variable
    setList all shell variables
    script <path.sh>Run a VFS script file
    sh <path.sh>Alias for script — run a VFS script file
    alias [name=value]List aliases or set a custom alias name=command
    unalias <name>Remove a custom alias
    h · s · llShort aliases: h = help, s = web search, ll = detailed listing
    dashboard [--watch|--stop]Collect live telemetry and open the dashboard page; --watch re-collects every 5 s
    usage [--json] [--reset]LLM token usage report for this browser (per model, cost estimate)
    dfVirtual filesystem usage
    freeJS heap / memory snapshot
    topBusiest commands and modules by activity
    psCommands and follows currently running
    history [--search <term>]Command history, optionally filtered
    run <script>Execute a saved script from the VFS
    sysmsg [on|off]Toggle system messages
    view_module <name>Print a module's source
    node <script.js>Run a JS file from the VFS (alias of js-run)
    +
    + + +
    +

    🪄 Floating UI

    +
    + Remixable floating buttons · Menus · Panels + The Floating Module lets you or the AI spawn persistent floating UI elements that survive page reloads. All state is kept in localStorage. +
    +
    + + + + + + + + + +
    CommandDescription
    floating-add btn <id> <label> <command>Spawn a floating button that runs a terminal command
    floating-add menu <id> <label> btn:Sub|cmd ...Spawn a collapsible floating menu with sub-buttons
    floating-add panel <id> <title> <content>Spawn a floating text panel
    floating-remove <id>Remove a floating element
    floating-listShow all floating elements with IDs and positions
    floating-pos <id> <x|auto> <y|auto>Reposition a floating element
    floating-toggle <id>Show/hide a floating element
    floating-clearRemove all floating elements
    +
    # Create a button
    +floating-add btn files "📁 Files" ls
    +
    +# Create a collapsible menu
    +floating-add menu tools "🔧 Tools" btn:Help|help btn:Clear|clear
    +
    +# Create a status panel
    +floating-add panel status "📊 Status" "Ready"
    +
    +# List all elements
    +floating-list
    +
    +# Hide without removing
    +floating-toggle files
    +
    + + +
    +

    🔒 Security & Guardrails

    +
    + Protecting Your Terminal + Three layered protections — guardrails block dangerous patterns, confidence escalation requires AI to self-assess, shadow VFS previews filesystem impact. +
    + +

    Guardrail Policy

    +

    Pre-execution guardrails intercept AI tool calls and block or warn on patterns matching dangerous operations:

    +
    + + + + + + + + + +
    RuleDescriptionSeverity
    rm-rf-blockPrevent recursive force deletes🔴 deny
    curl-pipe-bashBlock curl-to-bash execution🔴 deny
    rm-outer-vfsBlock deletion of sensitive metadata files🔴 deny
    mass-deleteBlock mass-delete operations🔴 deny
    chmod-777Block world-writable permissions🔴 deny
    eval-dangerBlock dynamic code execution (eval/exec/system)🔴 deny
    external-dlExternal file download warning🟡 warn
    env-overwriteModifying environment variables🟡 warn
    + +

    Commands

    +
    + + + + + + + +
    CommandDescription
    guardrailShow guardrail policy rules and violation count
    guardrail-add <id> <severity> <pattern> <tools...>Add a custom guardrail rule (severity: deny/warn)
    guardrail-remove <id>Remove a guardrail rule
    shadow <command>Execute command in Shadow VFS — snapshot, run, diff changes
    shadow-commitAccept shadow VFS changes
    shadow-rollbackUndo shadow VFS changes
    + +

    Confidence Escalation

    +

    When the AI requests destructive actions, it must declare Confidence: N% in its reasoning. Below 90%, manual approval is required. If no confidence is declared, manual approval is required by default. This prevents the AI from making low-confidence destructive changes.

    +
    + + +
    +

    💬 Sessions

    +

    Sessions persist AI conversation logs in VFS /memory/. The AI auto-loads recent sessions at start for continuity.

    +

    AI Tools

    +
    + + + + + + +
    ToolDescription
    session_saveSave summary
    session_loadLoad by date
    session_recentShow last N
    session_statsAggregate statistics
    session-newStart a fresh AI session (clears context, keeps history)
    +

    The terminal auto-saves session state every 60s and auto-restores on page reload.

    +
    + + +
    +

    💾 Storage

    +
    + localStorage Persistence + Everything survives hard refreshes via 15+ localStorage keys. +
    +
    # Clear all WebX11 data
    +localStorage.clear()
    +location.reload()
    +
    +# Back up / restore VFS
    +vfs-export
    +vfs-import
    +

    ~5–10MB per domain. Only URLs (not code) are stored for modules.

    +
    + + +
    +

    ☁️ WebDAV

    +

    Cloud sync with your WebX11 space. File-tree browser UI with bulk actions.

    +
    + + + + + + + + + + + + + +
    CommandDescription
    webdav-config <url> <token>Configure WebDAV connection
    webdav-statusShow sync status and connection health
    webdav-ls [path]List remote files
    webdav-push <local> [remote]Upload file to cloud
    webdav-pull <remote> [local]Download file from cloud
    webdav-browserOpen file-tree browser UI with checkboxes, context menu, inline rename, per-file preview 👁 / open-in-new-tab ⛶ buttons, minimize-to-floating and share-URL header buttons
    webdav-rm <path>Delete remote file/directory
    webdav-mkdir <path>Create remote directory
    webdav-rename <old> <new>Rename remote file
    webdav-move <src> <dst>Move remote file (alias for rename)
    webdav-backupBackup entire space
    webdav-login <user>Store WebDAV credentials for this session
    +

    Browser Features

    +
      +
    • Checkbox select + Select All toggle
    • +
    • Bulk action bar: Delete, Move, Download selected files
    • +
    • Inline rename: double-click filename
    • +
    • Right-click context menu with all operations
    • +
    • Breadcrumb navigation
    • +
    • Create-directory button
    • +
    • Keyboard shortcuts: j/k navigate, Space select, Enter open
    • +
    +
    + + +
    +

    🤖 Agent Orchestration

    +

    Register, list, and delegate tasks to AI agents with lease-based lifecycle management.

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    CommandDescription
    agent-listList all registered agents with status
    agent-status <id>Show detailed status for a specific agent
    agent-register <name> <model>Register a new AI agent
    agent-unregister <id>Remove an agent from the registry
    lease-listShow active leases — agent tasks with status and TTL
    orchestrate <task>Delegate multi-step task to agent orchestrator
    orchestrate-parallel <task>Decompose and run sub-tasks in parallel (auto-splits on "and"/"then")
    orchestrate-decompose <task>Preview how a task would be split into sub-tasks
    orchestrate-resultShow results of the last parallel batch
    orchestrator-statusShow orchestrator agent pool status
    orchestrator-listList all spawned agents with full details
    orchestrator-spawn <role> [caps...]Spawn a new worker agent
    orchestrator-sleep <id>Suspend agent to VFS (survives reload)
    orchestrator-wake <id>Restore agent from VFS sleep state
    orchestrator-kill <id>Terminate an agent and revoke leases
    orchestrator-purgeRemove dead agents (>60s silent)
    orchestrator-config <key> <value>Tune: spawn-limit, spawn-strategy, heartbeat-timeout, idle-timeout, restart-policy, a2a-polling (on/off), a2a-poll-interval, heartbeat-check, idle-sweep-interval, dead-purge-timeout, consensus-size, consensus-strategy, consensus-timeout, pipeline-auto-fix, pipeline-stage-timeout (persisted)
    orchestrator-restart-policy <id> <policy>Set per-agent restart policy: on-failure, always, never
    consensus <question>Consensus: spawn N agents, debate, resolve by majority vote
    consensus-statusShow current/active consensus round status
    consensus-historyShow past consensus rounds and resolutions
    pipeline <test|deploy|verify|history|config>Autonomous CI/CD: test agents/modules, deploy (test→auto-fix→verify), history, config
    lease-create <agent-id> [duration] [task]Create a time-bound lease for a worker agent
    lease-listShow active leases — agent tasks with status and TTL
    lease-revoke <id>Revoke an active lease
    lease-renew <id> <seconds>Extend an active lease by N seconds
    orchestrator-schedule <role> <interval_s> <task...>Schedule a recurring task for a role
    orchestrator-schedulesList scheduled tasks
    orchestrator-unschedule <id>Remove a scheduled task
    +
    + + +
    +

    📜 A2A Contracts

    +

    Define and enforce agent-to-agent contracts with input/output schema validation. Contracts are stored in /.contracts/ and validated during orchestrator delegation.

    +
    + + + + + + + + + + + +
    CommandDescription
    a2a-send <agent-id> <json>Send an A2A message to an agent's VFS inbox
    a2a-listShow all A2A messages with pending status
    contract-register <role> [fields...] --output [fields...]Register a contract with input/output schema
    contract-listList all registered contracts
    a2a-post <to> <type> <capability> <payload>Post a message to another agent's mailbox
    a2a-pollRead your own inbox
    a2a-ack <id>Acknowledge a message
    a2a-reject <id>Reject a message
    a2a-statusMailbox counters per agent
    a2a-pruneDrop old and acknowledged messages
    +
    + + +
    +

    🧠 Neural VFS

    +

    Semantic concept-indexing layer over the hierarchical VFS. Scans files and builds a concept → path mapping using filenames, tags, and headers.

    +
    + + + + + + + + + + + + + + + +
    CommandDescription
    memgraph-statusMemory graph node/edge counts
    memgraph-search <q>Find nodes by label or type
    memgraph-statsMost connected nodes, strongest edges
    memgraph-exportExport graph as JSON
    memgraph-pruneRemove stale low-weight edges
    nvfs-statusShow index stats and last scan time
    nvfs-resolve <concept>Find files related to a concept (exact + fuzzy)
    nvfs-tree [concept]Show concept-to-file mapping tree
    nvfs-config [key] [value]View/set config (fuzzy-limit, tree-files, tree-concepts, resolve-files, deep-index, deep-model)
    nvfs-alias list|add|remove|clearManage concept aliases
    nvfs-sync [--deep]Incremental background sync dirty files
    nvfs-saveForce-save semantic index to VFS disk
    nvfs-cancelCancel running index operation
    nvfs-index-graph [--deep]Build the NVFS index dependency graph
    +
    + + +
    +

    📊 Server Monitor

    +

    Uptime monitoring for external services + self-optimization telemetry tracking tool latency and error rates.

    +
    + + + + + + + + + +
    CommandDescription
    server <name> <url>Add a server to monitor
    serversList all monitored servers
    server-check <name>Check a server's current status
    monitorToggle auto-monitoring (60s interval)
    system-healthTool latency, VFS stats, modules, memory, error rates
    predict <task>Predict task duration, tool turns, and confidence
    predictor-statusPrediction model accuracy and data points
    predictor-config <k> <v>Tune confidence-threshold, min-data-points
    +
    +
    +

    🔧 Debug Panel

    +

    Toggle the debug overlay to monitor system internals in real-time.

    +
    + + + + +
    CommandDescription
    debugToggle debug panel on/off
    debug-syncPush debug records to the configured sync endpoint
    debug-sync-url <url>Show or set the sync endpoint
    +

    Debug Panel Shows

    +
      +
    • Memory — localStorage usage, VFS size, session context size
    • +
    • Events — registered event listeners, module event bindings
    • +
    • Errors — recent JS errors with stack traces
    • +
    • Modules — loaded count, broken references, cache state
    • +
    +

    Useful for troubleshooting module load failures, VFS corruption, and AI context bloat.

    +
    + + +
    +

    🧪 Sandboxed Execution

    +

    WebWorkers provide isolated contexts for task execution. Each worker gets a VFS snapshot so operations don't interfere with the main terminal.

    +
    + + + + +
    CommandDescription
    sandbox-statusShow worker pool health and task metrics
    sandbox-config <k> <v>Configure max-workers, min-workers
    sandbox-run <task>Execute a task in the sandbox pool
    +

    orchestrate --sandboxed <task> routes delegation through the sandbox for full isolation.

    +
    + + +
    +

    🧠 Memory Graph

    +

    Cross-session persistent graph database tracking files, concepts, tasks, and tools. Nodes store metadata, edges record relationships with weighted strength. Survives page reloads via VFS persistence.

    +
    + + + + + + + + + + +
    CommandDescription
    memgraph-statusShow node/edge counts and graph health
    memgraph-search <q>Find nodes by label, type, or id
    memgraph-statsMost connected nodes and strongest edges
    memgraph-exportExport full graph as JSON to VFS
    memgraph-pruneRemove stale low-weight edges (>30d, <3 uses)
    memgraph-learn <entity> <fact>Add a fact to the memory graph
    memgraph-agent <agentId>Show one agent's memory subgraph
    memgraph-session [YYYY-MM-DD]Show a day's session graph
    memgraph-decay-config <days> <min_uses>Tune memory decay
    +

    Context is automatically injected into AI prompts for cross-session awareness.

    +
    + + +
    +

    🌐 World Model

    +

    System self-awareness engine. Synthesizes data from memory graph, module system, skills index, telemetry, and agent pool into a unified model at /system/world-model.json. Auto-refreshes every 5 minutes.

    +
    + + + + + + +
    CommandDescription
    world-modelShow full world model breakdown
    world-model-summaryOne-line system status summary
    world-model-ask <q>Query modules, skills, agents, health, or changes
    world-model-refreshForce re-synthesis immediately
    world-model-config <k> <v>Tune refresh-interval and max-projects
    +
    + + +
    +

    🔒 Security

    +
    + + + + + + + + +
    FeatureStatus
    Input Validation✅
    Output Escaping✅
    No eval() on User Input✅
    Path Traversal Prevention✅
    Sandboxed File System✅
    AI Destructive Confirmations✅ Required + 120s countdown
    Iframe Preview Sandbox✅ allow-same-origin allow-scripts
    +

    Security Levels

    +
    + + + + + +
    LevelBehavior
    0Unrestricted — all commands allowed without confirmation
    1 (default)Confirm destructive actions (rm, overwrite, etc.)
    2Deny sensitive commands (rm -rf, encrypt operations)
    3Deny all dangerous commands (requires deliberate override)
    +
    + + + + + + + + + + + + + +
    CommandDescription
    security-level <0-3>Set active security level
    securityShow full status: level, blocked count, destructive/system/whitelist sets, suspicious flags, registered commands
    security-destructive listShow destructive command set
    security-destructive add|remove <cmd>Add/remove command from destructive set (persisted)
    security-system listShow system command set
    security-system add|remove <cmd>Add/remove command from system set (persisted)
    security-whitelist listShow whitelist command set
    security-whitelist add|remove <cmd>Add/remove command from whitelist (persisted)
    security-flag listShow suspicious flags
    security-flag add|remove <flag>Add/remove suspicious flag (persisted, -- prefix optional)
    cmd-allow <command>Add command to allowlist (bypasses restrictions)
    cmd-deny <command>Add command to denylist (always blocked)
    +
    + ⚠️ Module Loading + Only load modules from trusted sources — they have full terminal access. +
    +
    + + +
    +

    🔧 Troubleshooting

    +
    + + + + + + + + +
    IssueSolution
    AI not respondingRun ollamaconnect first, ensure Ollama is running
    Voice not workingChrome/Edge/Safari only. Grant mic permission
    Module fails to loadCheck CORS on the hosting server
    Data lost after refreshCheck localStorage is enabled. Private mode clears data
    AI actions not confirmingConfirm bar auto-denies after 120s. Type ai-allow
    Sound not playingBrowser may need first user gesture. Toggle off/on
    Ollama CORS errorOLLAMA_ORIGINS="*" ollama serve
    +

    Debug

    +
    # Console
    +F12 → Console
    +
    +# View AI state
    +ai-system
    +ai-context
    +
    +# Module status
    +listmods
    +
    + + +
    +

    ⚡ Quick Reference

    +
    +
    WebX11 v4.5.11
    +
    +
    helpShow all commands
    +
    clsClear terminal
    +
    llList files
    +
    cd <dir>Change directory
    +
    cat <f>View file
    +
    edit <f>Edit file
    +
    ai <msg>Chat with AI
    +
    search <q>Web search
    +
    find <q>File search
    +
    diff a bCompare files
    +
    ollamaconnectConnect AI
    +
    ai-allowAllow destructive
    +
    ai-denyDeny destructive
    +
    modulesModule Manager
    +
    skill-listList skills
    +
    aliasList aliases
    +
    previewHTML preview
    +
    uploadUpload file
    +
    | pipeChain commands
    +
    > fileRedirect output
    +
    whichLocate command
    +
    envShow env vars
    +
    scriptRun .sh file
    +
    dateShow time
    +
    calShow calendar
    +
    base64Encode/decode
    +
    nvfs-indexBuild semantic index
    +
    nvfs-resolveFind files by concept
    +
    nvfs-syncSync dirty files
    +
    nvfs-saveSave index to disk
    +
    nvfs-cancelCancel index op
    +
    jsEval JS expression
    +
    js-runRun JS file
    +
    vfs-sizeVFS size report
    +
    vfs-cleanNuke system dirs
    +
    mindmap svgRender mindmap as SVG
    +
    guiSimple GUI builder
    +
    contract-listList A2A contracts
    +
    modules-listList all 45 modules
    +
    orchestrateDelegate to agent
    +
    system-healthSystem diagnostics
    +
    seqNumber sequence
    +
    whoamiShow user
    +
    basenameStrip dir path
    +
    +
    +
    + +
    +

    📄 One-page Quick Reference cheat sheet — printable, dark mode, all commands.

    +
    + + +
    +

    📋 Changelog

    + +
    +
    v4.6.52 — October 20262026-10-22
    +
      +
    • Documentation backlog cleared — 74 commands that the code registered but this page never documented are now in their own sections: dashboard (with --watch/--stop), usage, ps/top/df/free, the six a2a-* mailbox commands, webxai/webx/wx, context/contexts/context-new/context-delete/context-auto/smart-ask, the commit-* approval trio, module-load|unload|enable|reset, memgraph-agent|session|decay-config, orchestrator-schedule|schedules|unschedule, skill-validate|audit|restore|pack-core, learn-skill, encrypt/decrypt, gzip/wget, kanban, three, course, videos, speak/voice/analyze, gui-run|text|clear, the vaultmind-* set, debug-sync/debug-sync-url, setfont, bug, session-new, files, node, sed, nvfs-index-graph, wallet-lookup, ai-model, ai-ignore, webdav-login, view_module, run, sysmsg, history.
    • +
    • Ghost rows removed — commands, check, sandbox-kill and webdav-explorer were documented here but are not registered anywhere in the code. Their rows are gone; the sandbox prose no longer promises sandbox-kill.
    • +
    • Audit method — the command list is now derived from terminal.js (_baseWhitelist, internals, cmd === dispatch) plus every commands['…'] registration in modules/, so the next gap can be found mechanically.
    • +
    +
    + +
    +
    v4.5.86 — July 20262026-07-04
    +
      +
    • Cold module activation — Three modules (Memory Graph, Predictor, World Model) were loaded but produced zero actionable data. All now wired into the AI agent loop.
    • +
    • Predictor feedback loop — predictor.recordResult() now called after multi-turn AI tasks (toolTurns ≥ 2). Predictor accumulates historical data for duration/turn predictions. Previously had zero call sites — module was dead.
    • +
    • New AI tools — predict_task (estimate task duration before starting) and world_model_query (query system self-awareness: modules, skills, files, agents, health, changes, projects). AI now has 51 tools (was 49).
    • +
    • Memory graph noise suppression — getContextForPrompt() returns empty string when graph has ≤2 nodes and 0 edges. Eliminates useless [Memory Graph Context] 1 nodes, 0 edges token waste on every AI turn.
    • +
    • Memory graph task recording — recordTask() now called for direct AI conversations, not just orchestrator tasks. Graph learns about all AI interactions.
    • +
    • memgraph-learn command — Documented in help.html but never implemented. Now creates concept+fact nodes with weighted edge. memgraph-learn <entity> <fact>.
    • +
    • getStats() alias — Orchestrator's _pipelineVerify() called memgraph.getStats() but the method was showStats(). Added getStats() alias returning {nodes, edges, types, totalAccesses}.
    • +
    • World Model summary fix — _getTotalModules() returned registry count (7) instead of max(loaded, registry) (43). Summary now shows 43/43 not 43/7.
    • +
    • Dashboard World Model panel — New section showing version, last synthesized time, module/skill/file/agent counts, memory graph stats, health, active projects, recent changes.
    • +
    • System prompt update — Now mentions predict_task and world_model_query capabilities.
    • +
    • Cache: 2026-07-04-v4.5.86 — hard refresh required
    • +
    +
    + +
    +
    v4.5.64–v4.5.67 — June 20262026-06-18
    +
      +
    • Orphan removal — Removed 5 dead modules from core-bundle: MyModule, ModuleIOModule, DynamicPromptsModule, SmartContextModule, DuckDuckGoModule. 454 lines, 23KB saved. 12+ ghost commands cleaned from help.html. modules-index.json 50→45 entries. rebuild-bundles.py updated to prevent resurrection.
    • +
    • Unified reset command — Supersedes factory-reset, reset-vfs, reset-mem, config-reset. Subcommands: reset mem (AI state), reset vfs (filesystem), reset config (settings), reset all (nuclear). Old commands kept as aliases.
    • +
    • Agent loop detection — agenticLoop now tracks repeated tool calls. Same tool+args 3× triggers loop break with system message. Prevents AI from wasting 8 turns calling ls.
    • +
    • Agent optimization default OFF — trivial-optimization was always ON by default (!== 'false'). Changed to default OFF (=== 'true'). Use ai-config trivial-optimization on to enable.
    • +
    • JS Console fix — js / js-run commands were not in the security whitelist. At level 1+ they were classified "unknown" and routed to AI instead of dispatching. Added to whitelist — now works.
    • +
    • Telemetry throttle — ServerMonitor persisted tool-latency logs every 20 calls. Throttled to every 100 calls or 5 minutes. ~80% fewer VFS writes.
    • +
    • Pipe security fix — Pipe chains (cmd1 | cmd2) bypassed ALL security classification. rm foo | cat ran rm with zero checks even at Paranoid level. Each pipe stage now passes through _classify() — destructive and system commands blocked at level 1+.
    • +
    • Cache: 2026-06-18-v4.5.67 — hard refresh required
    • +
    +
    + +
    +
    v4.5.45–v4.5.63 — June 20262026-06-12 to 2026-06-17
    +
      +
    • Critical ext bundle — 7 command-critical ext modules (editor, shell, alias, finder, filemgr, diff, busybox) promoted into critical-ext-bundle.js (2,365 lines). Solves HTTP 503 request-storm during boot.
    • +
    • Core module bundle — All core modules concatenated into core-bundle.js. Single <script> tag replaces 30 individual loads. rebuild-bundles.py script added.
    • +
    • VFS session bloat fixes — v2 metadata-only sessions (no VFS tree). Checkpoints strip system dirs, capped at 3. _purgeTrash() on boot (24h TTL). WebDAV backup deep-clones + strips.
    • +
    • New modules — module-agent-registry.js (A2A transport), module-leases.js (agent lifecycle), module-aiasm.js (assembly), module-duckduckgo.js (search, later orphaned), debug-panel.js, skill-pack-core.js.
    • +
    • A2A scratchpad architecture — Orchestrator agent-to-agent transport with prune timers, heartbeat checks, idle sweep. 6 a2a-* commands.
    • +
    • Orchestrator rewrite — delegateTask re-enters full agentic loop. spawnAgent, orchestrate-parallel, orchestrate-decompose, consensus protocol.
    • +
    • Deep architecture audit — 3 parallel subagent investigation of AI subsystem, VFS/state, security/dispatch. Found pipe security bypass, 5 orphaned modules, 6 duplicate localStorage keys, 19 unregistered intervals, 47 bare fetch() calls.
    • +
    • Cache: various — hard refresh required
    • +
    +
    + +
    +
    v4.5.44 — June 20262026-06-11
    +
      +
    • Mindmap organization overhaul — Pain point #5 shipped. The mindmap command went from 12 subcommands to 40. mindmap.js grew from 321 to 1127 lines.
    • +
    • Multi-mindmap support — No more losing work when switching. mindmap new foo + mindmap new bar creates two in-memory mindmaps. mindmap switch foo to jump between them. mindmap list-loaded shows all in-memory. Current is marked with *, dirty mindmaps with [DIRTY].
    • +
    • Dirty-flag warning on load — mindmap load foo now warns if the current mindmap has unsaved changes. Use --force to override.
    • +
    • Find by text — mindmap find <text> searches all node text + tags (case-insensitive). Returns matching IDs and depth.
    • +
    • Move nodes — mindmap move <id> <new-parent-id> reparents a node (with all its children). Validates against cycles (can't move under self or descendant).
    • +
    • Undo — Stack of last 10 destructive ops. mindmap undo reverses: add, remove, rename, move, color, tag, weight, merge.
    • +
    • Human-readable aliases — mindmap alias <id> <name> gives a memorable name. All commands that take an ID (edit, move, color, etc.) now accept either ID or alias.
    • +
    • Disambiguated delete — mindmap delete <name> still deletes a FILE (backward compat). New mindmap remove <id> removes a NODE. mindmap delete-file is the explicit form.
    • +
    • Import — mindmap import <file> reads from VFS, auto-detects format (JSON/Mermaid/Markdown) by extension or content. Re-generates IDs to avoid collisions.
    • +
    • Merge — mindmap merge <name> [--into=<id>] adds a saved mindmap under the current root (or specified parent). Conflict-free: just appends.
    • +
    • Diff — mindmap diff <name1> <name2> shows added/removed/modified nodes between two saved mindmaps.
    • +
    • Stats — mindmap stats shows total nodes, max depth, nodes per level, colors used, top 10 tags, dirty status, last save time.
    • +
    • Tags cross-node — mindmap tags-list [<tag>] shows all tags + counts, or all nodes with a specific tag.
    • +
    • Clone — mindmap clone <id> duplicates a node and its children with new IDs. Text gets " (copy)" suffix.
    • +
    • Archive — mindmap archive <name> saves to /mindmaps/archive/<name>-<ts>.json. Auto-cleans to last 20.
    • +
    • Weight — mindmap weight <id> <1-5> scales node size in SVG output (1=small, 5=large, default=3).
    • +
    • Mermaid export — mindmap mermaid (or mindmap export mermaid) prints Mermaid mindmap syntax. Auto-escapes parentheses, brackets, ampersands.
    • +
    • JSON export — mindmap json prints the internal JSON (with aliases). Both Markdown and JSON get copied to clipboard.
    • +
    • Refresh HTML — mindmap refresh-html regenerates the standalone architecture page from modules-index.json. Writes to /mindmaps/_generated-architecture.html in VFS, prints preview. Deploy with cat <file> > mindmap.html.
    • +
    • SVG layout fix — New _layoutTree() algorithm computes subtree widths first, then positions. Handles 30+ node mindmaps without going off-screen. weight affects node size.
    • +
    • Cache: 2026-06-11-v4.5.44 — hard refresh required
    • +
    +
    + +
    +
    v4.5.43 — June 20262026-06-11
    +
      +
    • Module management overhaul — Pain point #3 finally shipped. module-list now groups by category (CORE/EXTENSION/FOUNDATION), supports --core --ext --foundation --lazy --eager --loaded --unloaded --failed --disabled --search=<pat> --flat flags, and shows aggregate summary (size, count, failed, disabled).
    • +
    • module-info <name> — Shows module details: status, category, type, path, size, lazy flag, and the full list of commands it provides (the biggest gap). Listed in 2 columns.
    • +
    • module-stats — Aggregate view: total/loaded/unloaded/lazy/eager/failed/disabled + total size + total commands.
    • +
    • module-disable / module-enable — Persistently disable a module from auto-loading. Stored in localStorage webx11_module_disabled. Survives refresh. module-loader.js now checks this list and skips disabled modules with a ⊘ marker in the splash.
    • +
    • module-reload / module-reset — Reload shows old→new diff. Reset clears module's localStorage state and reloads.
    • +
    • System troubleshooting commands — Pain point #4.
    • +
    • version — Single command shows: version, build date (parsed from script tag), cache key, browser engine, online status.
    • +
    • uptime — Time since page load (formatted Xh Ym Zs) + last save time.
    • +
    • errors [n] — Alias for logs [n] ERROR. Shows last N errors. Says "No errors found" when logs are clean.
    • +
    • doctor — Runs 8 health checks (network, ollama, VFS size, heap, logs, recent errors, localStorage quota, disabled modules), ranks by severity (🔴/🟡/🟢), shows top 5, and gives recommended actions.
    • +
    • bug-report — Generates a markdown report with system info, recent errors, recent commands, disabled modules, top-level VFS listing. Saves to /tmp/bug-report-<timestamp>.md. Send this file when reporting a bug.
    • +
    • test-net — Checks browser online status, proxy API (/api/stats), Ollama connection, and internet (HEAD to duckduckgo.com). Green/red status for each.
    • +
    • mem enhanced — Now shows JS heap + localStorage size + sessionStorage size + VFS in-memory size + total. Color-coded thresholds (green/yellow/red).
    • +
    • Trust violations fixed — Removed ghost commands commands and check from getHelpInfo() (they were in help but never registered). Fixed memory-map-export to actually save to /memory/reference.md (was: print to terminal).
    • +
    • Cache: 2026-06-11-v4.5.43 — hard refresh required
    • +
    +
    + +
    +
    v4.5.41 — June 20262026-06-11
    +
      +
    • Search result quality fix — Default engines changed from google,bing,brave,wikipedia to mojeek,bing. Previous default had 3/4 engines broken (Google/Brave/Wikipedia block datacenter IPs); Bing worked but returned French-localized results due to server's Amsterdam IP. Mojeek is a privacy-focused crawler-based search that returns English results regardless of server location.
    • +
    • Result deduplication — /api/search now dedupes results by domain and prefers Mojeek over Bing (Mojeek is more relevant in this environment). Two-pass: Mojeek first, then Bing for domains not already seen.
    • +
    • Verified: search quantum computing now returns "Quantum computing - Wikipedia" as the #1 result (was "iPhone - Apple"). search how to cook risotto now returns actual risotto recipes (was "Cook, les épices bio" - a French spice site).
    • +
    • Cache: 2026-06-11-v4.5.41 — hard refresh required
    • +
    +
    + +
    +
    v4.5.40 — June 20262026-06-10
    +
      +
    • AI assistant POST support — web_fetch tool now accepts method=POST, body, and content_type params. AI can now fetch POST-only APIs (Hyperliquid, financial APIs) without telling the user to "use curl". POST routes through /api/fetch; GET still uses /api/extract for cleaner output.
    • +
    • AI assistant error parsing — web_search and web_fetch now parse JSON errors from proxy and surface the real reason (was just "HTTP 500" with no details).
    • +
    • 30s frontend timeout — fetch, extract, and search now abort after 30s with a helpful message. Replaces 15s silent freeze. Implementation: AbortController.
    • +
    • Loading indicator for --summarize — Shows "⏳ AI overview will be generated (1-3s)..." before the LLM call, so user knows the terminal is alive.
    • +
    • Extract follow-up hint — After extract, shows: "💡 Try summarize <url> for AI summary, or nvfs-resolve <concept> to find related files".
    • +
    • Backend script/style strip — Extract's regex fallback now strips <script>, <style>, <noscript> and HTML comments. Prevents raw JS leaking into extracted text on BS4 failures.
    • +
    • web alias removed — Was a duplicate of fetch that didn't appear in autocomplete. Confusing. Just use fetch.
    • +
    • Cache: 2026-06-10-v4.5.40 — hard refresh required
    • +
    +
    + +
    +
    v4.5.39 — June 20262026-06-10
    +
      +
    • POST support in /api/fetch — Proxy was GET-only, blocking any API that requires POST (Hyperliquid /info, etc). Now accepts method=POST&body=...&content_type=... query params with 10KB body cap and method-aware cache key.
    • +
    • fetch command gains 3 flags — --post=BODY sends a POST request with the given body, --type=CT sets the Content-Type header (defaults to application/json if --post is set), --raw shows untruncated response. Example: fetch https://api.hyperliquid.xyz/info --post='{"type":"l2Book","coin":"HYPE"}' --type=application/json
    • +
    • Better error messages — Frontend fetch now parses JSON error from proxy and shows the real reason (was just "HTTP 502" with no details).
    • +
    • Cache: 2026-06-10-v4.5.39 — hard refresh required
    • +
    +
    + +
    +
    v4.5.38 — June 20262026-06-10
    +
      +
    • Keyboard shortcuts — Ctrl+L clears the output area, Ctrl+C cancels the current input line (prints ^C), Ctrl+U clears the input field. Standard terminal conventions now work.
    • +
    • Shift+Tab cycles backward — autocomplete suggestions can be cycled in both directions.
    • +
    • Send button → 48px tappable — was 31px (fails mobile/tablet tap target). Now min-width: 48px; min-height: 48px per Android/iOS standards.
    • +
    • Autocomplete argType fixes — nvfs-config, nvfs-alias, skill-list now show enum values (config keys, alias actions, skill categories) instead of file paths. nvfs-sync/nvfs-save/nvfs-cancel/nvfs-status marked as argType: 'none' so they don't show file suggestions.
    • +
    • Stale autocomplete cleared — Tab suggestions from prior input no longer linger when you start typing something new.
    • +
    • History cap on startup — terminal-cmd-history config now applies on page load, not "next save" (which never happened).
    • +
    • Cache: 2026-06-10-v4.5.38 — hard refresh required
    • +
    +
    + +
    +
    v4.5.37 — June 20262026-06-10
    +
      +
    • /skills/index.json re-index loop FIXED — skill-pack-core.seed() was rewriting the updated timestamp for all 8 core skills on every page refresh, causing NVFS to see a hash mismatch and re-queue the 7KB JSON for LLM extraction every run. Now seed() compares existing file content to bundled content and only writes the index if something actually changed. Eliminates the 5-concepts-from-1-file loop on every refresh.
    • +
    • One-time data fix — After deploying, the existing stale updated timestamp in your VFS will be re-evaluated on next refresh. If it matches the bundled timestamp, the loop stops immediately. If not, one final write happens and the loop is dead thereafter.
    • +
    • Cache: 2026-06-10-v4.5.37 — hard refresh required
    • +
    +
    + +
    +
    v4.5.36 — June 20262026-06-10
    +
      +
    • Deep pass output shows file paths — Was "5 LLM concepts across 1 file" (no path). Now lists up to 20 files with their concept counts. Makes it obvious if a single file keeps re-processing every refresh (loop detection).
    • +
    • Auto-sync output shows file paths — Same treatment: "re-indexed N file(s)" now lists the actual paths so you can see what's looping.
    • +
    • nvfs-status shows LLM/fast breakdown — Two new lines: "Fast concepts: N" and "LLM concepts: N (across M files)". Proves persistence is working without needing DevTools inspection.
    • +
    • Deep pass message clearer — "added N new LLM concepts (M files). Total in index: X" — distinguishes the per-pass delta from the persisted total.
    • +
    • Cache: 2026-06-10-v4.5.36 — hard refresh required
    • +
    +
    + +
    +
    v4.5.35 — June 20262026-06-10
    +
      +
    • NVFS bug fix audit (7 bugs) — Stale /sys/... entries from pre-v4.5.34 builds cause permanent re-index loops. init() now auto-purges them on first run with a one-time warning.
    • +
    • Cancel responsive — nvfs-cancel now checks between every LLM call (was: between every batch of 3). Stop within one 30s call instead of waiting up to 90s.
    • +
    • Save race fixed — nvfs-save and end of deep pass now await in-flight VFS save instead of clobbering the debounce flag. Prevents silent LLM concept loss on concurrent writes.
    • +
    • init() now awaits VFS load — Was fire-and-forget; first nvfs-status could return empty even when an index existed. module-manager.js already awaits init(), so making it async is safe.
    • +
    • Deep-mode warning shown once — Was spammy on every nvfs-index call. Tracked by session flag _warnedAboutLLMStrip.
    • +
    • Tag extraction fixed — _extractConcepts used tagMatches[0] (whole match) instead of capture group, so inline #tag1 #tag2 only captured #tag1. Now correctly splits on commas or captures all hash tags.
    • +
    • Filter defensive — _isSystemFile split into clean two-check structure. node_modules check now uses normalized path so node_modules/foo.js (no leading slash) also gets filtered.
    • +
    • Cache: 2026-06-10-v4.5.35 — hard refresh required
    • +
    +
    + +
    +
    v4.5.34 — June 20262026-06-10
    +
      +
    • NVFS deep index persistence — LLM-generated concepts now survive page refresh. _processDeepQueue() calls _saveIndex(). Deep concepts written to disk on completion.
    • +
    • nvfs-save command — nvfs-save force-saves the semantic index to VFS disk immediately. Useful before hard refresh.
    • +
    • Auto-sync deep mode — nvfs-sync --deep or nvfs-config deep-index on now triggers LLM analysis for dirty files, not just fast filename concepts.
    • +
    • Safety guard — Running nvfs-index without --deep when saved index contains LLM concepts prints a warning: they'll be stripped from re-indexed files.
    • +
    • JS Console module — New js <expression> command evals JavaScript in page context. js-run <path> loads and executes a VFS script file. Auto-loads at startup.
    • +
    • Module registration — console added to packages.json, terminal.js ext loader, and skipList. Auto-loads on next refresh.
    • +
    • Cache: 2026-06-10-v4.5.34 — hard refresh required
    • +
    +
    + +
    +
    v4.5.24 — June 20262026-06-08
    +
      +
    • tar / untar commands — New pure-JS tarball parser in cryptogzip.js. tar <src-dir> <out.tar[.gz]> bundles a VFS directory; untar <path.tar[.gz]> [dest-dir] extracts back. Auto-detects gzip wrapper. Fixes the "downloaded newos4.tar.gz but can't extract" problem.
    • +
    • Binary-safe gzip — gunzip now preserves binary content (was UTF-8 only). Updated to use base64 roundtrip for binary files.
    • +
    • POSIX ustar format — Reads & writes standard ustar tarballs (compatible with GNU tar, BSD tar, macOS tar).
    • +
    • Cheatsheet updated — Added 8 entries under 🔧 Tools for tar/untar/gzip/gunzip/encrypt/decrypt/diff/edit
    • +
    • System prompt — tar/untar added to [ALL OTHERS] command list
    • +
    +
    + +
    +
    v4.5.23 — June 20262026-06-08
    +
      +
    • Doc version sync — Help page title, overview, and footer updated to v4.5.22 to match deployed module-loader.js
    • +
    • Whitepaper v4.5.22 — Bumped from v4.3.0-beta; Last Updated 2026-06-08
    • +
    • Deprecated module-modmgr — Removed from BUILTIN array (was duplicate of module-manager.js). Set auto_load=false, layer=deprecated. File retained for one release for rollback safety; deletion scheduled v4.6.0
    • +
    • Fixed modules-index class — module-loader class changed from filename string "module-loader.js" to null (foundation file, not a module class)
    • +
    • packages.json cleanup — Renamed neural-vfs to neural-vfs-core, category → core, added note that NVFS is built-in not a WPM package
    • +
    • Cheatsheet coverage — Added 4 missing commands: sandbox-config, memgraph-export, predictor-config, world-model-config
    • +
    • Audit script fix — audit-newos4-filesystem.py now respects skills/index.json for orphan detection (no more 26 false-positive warnings on valid skill JSONs)
    • +
    +
    + +
    +
    v4.5 — June 20262026-06-06
    +
      +
    • Security Config CLI — security-destructive, security-system, security-whitelist, security-flag add|remove|list with localStorage persistence and boot loading
    • +
    • Enhanced security command — Shows all sets (destructive, system, whitelist, flags) plus registered command count
    • +
    • AI loop config — ai-config max-turns|max-context|trivial-optimization with persistence across sessions
    • +
    • Unified config CLI — config command with 4 terminal tuning keys (max-lines, history, pipe-buffer), all persisted in localStorage
    • +
    • Extended ai-config — New keys: max-context-tokens, confirm-timeout, chat-lock-timeout, skill-debounce with range validation
    • +
    • Safe-read run_command — run_command now command-aware: ls, find, tree, cat auto-approved; only mutating commands require confirmation
    • +
    • Whitelist expanded (50+ cmds) — find, grep, tree, head, tail, wc, sort, du, diff, which, hostname, and all BusyBox read-only utils now whitelisted
    • +
    • Security-bypass fix — run_command AI tool now routes through checkAndRun() security layer instead of bypassing
    • +
    • list_files root fallback — relative paths retry from root if cwd-relative lookup fails
    • +
    • Confirm timeout configurable — ai-config confirm-timeout <seconds> accepts 0+ (0 = no confirm / auto-approve all, no upper cap). Dynamic label on confirm bar shows actual timeout.
    • +
    • Auto-sync Neural VFS — write_file/mkdir/rm/mv/cp automatically mark index dirty; auto-reindexes after 10s debounce. nvfs-alias command for concept aliases. Hybrid discovery fallback in nvfs-resolve (live VFS scan when index empty). Staleness warning in nvfs-status if index >4h old.
    • +
    • 5 modules whitelisted — system-health, server, servers, server-check, monitor run directly
    • +
    • OllamaModule consolidated — Removed duplicate ollamamodels code path (redirects to models), centralized endpoint via ollamaManager.endpoint
    • +
    • Neural VFS async indexing — Chunked processing with progress percentage, no UI freeze on large VFS. Improved _summarize (section heading → paragraph → sentence → fallback)
    • +
    • Neural VFS display-limit config — nvfs-config command with 4 keys (fuzzy-limit, tree-files, tree-concepts, resolve-files), persisted
    • +
    • Orchestrator event bus — In-memory pub/sub for instant A2A delivery (VFS polling remains as fallback). Improved _deriveCapabilities with 14 keyword mappings
    • +
    • Orchestrator extended config — 4 new interval keys (a2a-poll-interval, heartbeat-check, idle-sweep-interval, dead-purge-timeout) + all config now persists across sessions
    • +
    • Cache: 2026-06-06-v4.5.11 — hard refresh required
    • +
    +
    + +
    +
    v4.5.12 — June 20262026-06-06
    +
      +
    • Tiered Indexing toggle — nvfs-config deep-index on|off switches between Fast (metadata-only, instant) and Deep (LLM-assisted, semantic) modes. Off by default. nvfs-index --deep or nvfs-sync --deep forces deep mode per-run.
    • +
    • Configurable deep model — nvfs-config deep-model <name> selects the LLM for deep indexing. Default: gemma4:31b-cloud for speed. Example: nvfs-config deep-model gemma4:31b-cloud for deeper analysis.
    • +
    • Status bar indicator — Real-time 🧠 status in terminal bar: concept count + 🌊 wave for deep mode, 🟡 amber for pending auto-sync, 🧠 Deep Indexing progress percentage during LLM processing.
    • +
    • nvfs-sync command — nvfs-sync [--deep] for incremental background sync of dirty files.
    • +
    • Deep concept extraction — LLM generates 2-5 semantic concepts per file (configurable model via nvfs-config deep-model, default gemma4:31b-cloud, 2000 chars, 3-file batches with UI yield). Fast filename-concepts stored immediately, LLM concepts augment in background queue.
    • +
    • nvfs-status mode display — Shows 🌊 Deep or ⚡ Fast mode.
    • +
    • LLM self-healing — If Ollama disconnected, deep mode silently falls back to fast metadata-only.
    • +
    • Cache: 2026-06-06-v4.5.12 — hard refresh required
    • +
    +
    + +
    +
    v4.5.2 — June 20262026-06-06
    +
      +
    • VFS Size Diagnostics — vfs-size shows total VFS size, top 5 largest files, and system dir sizes
    • +
    • VFS Clean — vfs-clean / vfs clean — nuke snapshots, sessions, trash in one shot
    • +
    • Fractal checkpoint fix — checkpoint() no longer deep-clones its own /_snapshots/, eliminating recursive bloat. Same for session-save
    • +
    • Ghost commands wired — checkpoint, checkpoint-list, checkpoint-del, rollback had help text but no handlers — now actually work
    • +
    • Autocomplete fix — Built-in commands (vfs-clean, vfs-size) now appear in dropdown
    • +
    • Editor→_mods bridge — edit path/to/module.js now mirrors to _mods/ for test_module compatibility
    • +
    • Skill WebDAV Sync — skill-webdav-sync pushes all skills to WebDAV as JSON files
    • +
    +
    + +
    +
    v4.5.11 — June 20262026-06-06
    +
      +
    • init(terminal) fix — loadModuleFromCode and loadModuleFromURL now pass this.terminal to init(), fixing PredictorModule fatal error
    • +
    • Save & Test returns value — saveCodeEditor checks loadModuleFromCode return before printing success
    • +
    • StateSyncModule recovered — Extracted from backup, saved to ~/.hermes/scripts/
    • +
    • PredictorModule hasSync — Feature detection for sync/recall tasks with tuned duration/turn prediction
    • +
    • Cache: 2026-06-06-v4.5.11 — hard refresh required
    • +
    +
    + +
    +
    v4.4 — June 20262026-06-05
    +
      +
    • v4.4 complete — 46 modules, 24 skills, full A2A lifecycle, sandboxed execution, memory graph, world model, health monitoring
    • +
    • A2A Mailbox Transport — a2a-send, a2a-list, 500ms polling with envelope routing (request/response/broadcast/heartbeat)
    • +
    • Autonomous sub-agent spawning — auto-role derivation, sleep/reanimate VFS serialization
    • +
    • Sandboxed execution — WebWorker pool sandbox-run (there is no sandbox-kill; tasks end on timeout) with inline fallback for headless
    • +
    • A2A Protocol — Agent contract system: contract-register, contract-list, schema validation in orchestrator
    • +
    • Self-Optimization Telemetry — system-health command showing tool latency, VFS stats, error rates
    • +
    • AI tool: nvfs_resolve — AI can search VFS by concept
    • +
    • Skills consolidated — 9 → 4 unified; index rebuilt minified
    • +
    • Docs sync — vision.html and whitepaper updated
    • +
    • Cache: 2026-06-03-neuralvfs — hard refresh required
    • +
    +
    + +
    +
    v4.3.2 — June 20262026-06-04
    +
      +
    • mindmap svg — Inline SVG rendering with colored rects + bezier curves
    • +
    • GUI Builder — gui, gui-button, gui-input — SimpleGUIModule registered
    • +
    • mermaid.min.js — Downloaded for webx11_mindmap.html
    • +
    • get_module_status fixed — Shows all 34 loaded modules (was only HTTP-loaded)
    • +
    • view_module fixed — Uses modules-index.json to resolve class→file paths
    • +
    • module-ollamamodels merged — Into module-ollama (13 commands total)
    • +
    • concept:// URIs — cat concept://skill resolves via Neural VFS
    • +
    • Module categorization — modules-list shows [core], [ext], [infra] layer tags
    • +
    • 5 modules whitelisted — system-health, server, servers, server-check, monitor run directly
    • +
    +
    + +
    +
    v4.3 — June 20262026-06-02
    +
      +
    • WebDAV Browser — Modern file-tree UI with checkboxes, Select All, bulk actions (Delete, Move, Download), inline rename (double-click), context menu, breadcrumbs, keyboard shortcuts
    • +
    • WebDAV commands — webdav-browser, webdav-rm, webdav-mkdir, webdav-rename, webdav-move, webdav-backup
    • +
    • Skill Pack System — Auto-loads 12 skills on startup, seed/reload support, skill-pack-core.js manages in-memory index
    • +
    • Agent Orchestration — agent-list, agent-register, lease-list, orchestrate for multi-step delegation
    • +
    • Security Layer — security-level 0-3 with granular restrictions, cmd-allow/cmd-deny per-command access control
    • +
    • Debug Panel — debug toggle shows memory, events, errors, module health
    • +
    • Chat Cleanup — Removed chat-setapi, chat-setmodel, chat-ask; single chat command toggles overlay
    • +
    • Module count refactored: 18 core + 14 ext (was 25+17)
    • +
    • Download fix — switched from resp.text() to resp.blob() for binary-safe downloads; same fix for image preview (FileReader DataURL)
    • +
    • Cache: v=40 — hard refresh required
    • +
    +
    + +
    +
    v4.2.1 — May 20262026-05-27
    +
      +
    • BusyBox — 16 new utility commands: which, env, date, cal, yes, seq, base64, printf, cut, uniq, hostname, whoami, basename, dirname, sleep, expand
    • +
    • Shell — Full pipe (|), redirect (>, >>, <), chain (&&, ||, ;), variable ($VAR), and VFS script execution (script)
    • +
    • Pipe-aware commands (grep, head, tail, sort, wc, cut, uniq, expand) auto-read from pipe via temp file injection
    • +
    • export, unset, set — shell variable management
    • +
    • Multiple ASI and VFS API bug fixes (fs.fsWrite → fs.writeFile across 3 modules)
    • +
    • Test Suite — 32 automated regression tests. Run with load-local modules/test/test-suite.js then test, test auto, or test list
    • +
    • Cache v34 — hard refresh required
    • +
    +
    + +
    +
    v4.2 — May 20262026-05-25
    +
      +
    • Editor — edit <path> opens inline VFS editor modal
    • +
    • Diff — diff a b LCS-based comparison + diff --module
    • +
    • Aliases — ll, h, cls defaults + custom alias definition
    • +
    • File Finder — find <query> recursive VFS search
    • +
    • Agent loop: capped at 8 turns (was 1), edit_module destructive confirmation, model auto-validation
    • +
    • ai-stop now cancels chat + stream, not just tool calls
    • +
    • Unknown commands awaited, "Command not found" suppressed on AI route
    • +
    • loopResult scope fix — no more undefined error on AI fail
    • +
    • Preview iframe allows JS (allow-scripts), cache bump v=18
    • +
    • Splash fallback + startup modal list all 12 ext modules
    • +
    +
    + +
    +
    v4.12026-05-23
    +
      +
    • AI Agent with full tool-calling loop (28 tools)
    • +
    • Inline destructive confirmation bar with 120s countdown timer
    • +
    • Skills system (8 CLI commands + UI modal)
    • +
    • Voice input (🎤 button, SpeechRecognition, show/hide toggle)
    • +
    • Sound notification (🔊 toggle, WebAudio ding)
    • +
    • AI spinner in status bar
    • +
    • Session system (save/load/recent/stats)
    • +
    • WebDAV sync (8 commands)
    • +
    • Web search (SearXNG + DDG + URL fetch/extract)
    • +
    • Config manager (6 commands + UI)
    • +
    • Startup modules selector (⚙ gear on splash)
    • +
    • Code editor modal (module editing, save & test live)
    • +
    • HTML preview via srcdoc (mobile)
    • +
    • File/dir autocomplete, pipe support
    • +
    • Smart context profiles, dynamic prompts with variables
    • +
    • Server monitor, ChatStudio overlay
    • +
    • Modular architecture: 23 JS modules, async loader
    • +
    +
    + +
    +
    v4.02026-05-20
    +
      +
    • Monolith refactored into core/ext modules
    • +
    • Async module loader with splash screen
    • +
    • Config manager modal
    • +
    • Bootloader fix for disabled modules
    • +
    +
    +
    +
    +
    + + + +↑ + + + + diff --git a/.bkp-fix-20261023_143429/ingest.php b/.bkp-fix-20261023_143429/ingest.php new file mode 100644 index 0000000..b513593 --- /dev/null +++ b/.bkp-fix-20261023_143429/ingest.php @@ -0,0 +1,123 @@ + 1000 ? 1000 : $n); +} + +// Validate batch size +if (count($errors) > MAX_ERRORS_PER_BATCH) { + $errors = array_slice($errors, 0, MAX_ERRORS_PER_BATCH); +} +if (strlen($raw) > MAX_BATCH_SIZE_KB * 1024) { + error_response('Payload too large (max ' . MAX_BATCH_SIZE_KB . 'KB)', 413); +} + +// Load existing data +$allErrors = load_json(ERRORS_FILE); +$sessions = load_json(SESSIONS_FILE); + +$sessionId = $session['id'] ?? ('anon_' . bin2hex(random_bytes(4))); +$ingested = 0; +$now = date('c'); + +// Update session metadata +if (!isset($sessions[$sessionId])) { + $sessions[$sessionId] = [ + 'id' => $sessionId, + 'first_seen' => $now, + 'version' => $clientVersion, + 'modules' => $session['modules'] ?? [], + 'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '', + 'ip' => $_SERVER['REMOTE_ADDR'] ?? '', + 'error_count' => 0, + ]; +} +$sessions[$sessionId]['last_seen'] = $now; +$sessions[$sessionId]['error_count'] += count($errors); +$sessions[$sessionId]['version'] = $clientVersion; + +// Build a lookup of existing hashes for dedup +$existingHashes = []; +foreach ($allErrors as $idx => $existing) { + $h = $existing['hash'] ?? ''; + if ($h) { + $existingHashes[$h] = $idx; + } +} + +// Ingest each error (dedup by hash) +foreach ($errors as $e) { + $hash = $e['hash'] ?? md5(($e['message'] ?? '') . '|' . ($e['source'] ?? '') . '|' . ($e['type'] ?? '')); + + if (isset($existingHashes[$hash])) { + // Update existing entry + $idx = $existingHashes[$hash]; + $allErrors[$idx]['count'] += clamp_count($e['count'] ?? 1); + $allErrors[$idx]['last_seen'] = $e['lastSeen'] ?? $now; + $allErrors[$idx]['ingested_at'] = $now; + if (!empty($e['stack'])) { + $allErrors[$idx]['stack'] = substr($e['stack'], 0, 2000); + } + $ingested++; + continue; + } + + $entry = [ + 'hash' => $hash, + 'message' => substr($e['message'] ?? '', 0, 500), + 'type' => $e['type'] ?? 'unknown', + 'severity' => $e['severity'] ?? 'error', + 'category' => $e['category'] ?? 'runtime', + 'source' => $e['source'] ?? '', + 'module' => $e['module'] ?? null, + 'stack' => isset($e['stack']) ? substr($e['stack'], 0, 2000) : null, + 'session_id' => $sessionId, + 'count' => clamp_count($e['count'] ?? 1), + 'first_seen' => $e['firstSeen'] ?? $now, + 'last_seen' => $e['lastSeen'] ?? $now, + 'ingested_at' => $now, + ]; + + $allErrors[] = $entry; + $existingHashes[$hash] = count($allErrors) - 1; + $ingested++; +} + +// Trim to last 10,000 errors (ring buffer) +if (count($allErrors) > 10000) { + $allErrors = array_slice($allErrors, -10000); +} + +save_json(ERRORS_FILE, $allErrors); +save_json(SESSIONS_FILE, $sessions); + +// Rebuild clusters asynchronously (flag file) +$rebuildFlag = DEBUG_DATA_DIR . '/.rebuild_clusters'; +file_put_contents($rebuildFlag, time()); + +json_response([ + 'success' => true, + 'ingested' => $ingested, + 'session_id' => $sessionId, + 'total_errors' => count($allErrors), + 'total_sessions' => count($sessions), +]); diff --git a/.bkp-fix-20261023_143429/main.html b/.bkp-fix-20261023_143429/main.html new file mode 100644 index 0000000..f41b12d --- /dev/null +++ b/.bkp-fix-20261023_143429/main.html @@ -0,0 +1,1484 @@ + + + + + + + WebX11 Module System + + + + + +
    +
    + WebX11 + +
    +
    Mem: --
    + 📋 +
    + + +
    +
    + 📋 Quick Commands + ✕ +
    +
    +
    🟢 Beginners
    +
    helpShow all commands
    +
    lsList files
    +
    clearClear terminal
    +
    statusSystem health
    +
    +
    +
    🤖 AI
    +
    ai "..."Ask AI assistant
    +
    ai-modelSet AI model
    +
    +
    +
    🔍 Explore
    +
    modulesList loaded modules
    +
    tutorialInteractive tour
    +
    +
    +
    🚀 Advanced
    +
    cronSchedule tasks
    +
    webdavCloud sync
    +
    helpFull reference
    +
    +
    + + +
    +
    WebX11 Module System - Unix-like Terminal
    +
    Commands: ls, cd, pwd, cat, echo, rm, mkdir, mv, cp, touch, tree, find, grep
    +
    Type 'help' for all commands
    +
    +
    Loading modules...
    +
    + + +
    + + + + +
    + + + + + + + + + + +
    + +
    + $ + + +
    +
    + +
    + Ready + 📶 Online + ▶ Stream + +
    + + + + + + + + + + + +
    + ⚙ +
    ⚡ WebX11
    +
    +
    +
    Initializing...
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.bkp-fix-20261023_143429/module-debug-supervisor.js b/.bkp-fix-20261023_143429/module-debug-supervisor.js new file mode 100644 index 0000000..959d162 --- /dev/null +++ b/.bkp-fix-20261023_143429/module-debug-supervisor.js @@ -0,0 +1,889 @@ +// module-debug-supervisor.js — Persistent error capture, clustering, root-cause analysis, and auto-fix +// Commands: debug-stats, debug-history, debug-analyze, debug-trends, debug-fix, debug-health +class DebugSupervisorModule { + constructor() { + this.commands = {}; + this.terminal = null; + this._healthy = true; + this._errorDB = { errors: {}, clusters: {}, patterns: {}, sessionId: null }; + this._maxErrors = 500; + this._ttlDays = 7; + this._writeDebounce = null; + this._pendingWrites = 0; + this._originalConsole = {}; + this._originalOnError = null; + this._originalOnRejection = null; + this._originalFetch = null; + this._interceptorInstalled = false; + this._sessionId = this._generateSessionId(); + } + + _generateSessionId() { + try { + let sid = localStorage.getItem('webx11_debug_session'); + if (!sid) { + sid = Date.now().toString(36) + '-' + Math.random().toString(36).substring(2, 8); + localStorage.setItem('webx11_debug_session', sid); + } + return sid; + } catch (e) { + return Date.now().toString(36); + } + } + + getHelpInfo() { + return { + 'debug-stats': 'debug-stats — Show error summary (total, unique, clusters, top issues)', + 'debug-history': 'debug-history [n] — Show last N errors (default: 20)', + 'debug-analyze': 'debug-analyze — Deep analysis of a specific error', + 'debug-trends': 'debug-trends [days] — Error frequency over time (default: 7)', + 'debug-fix': 'debug-fix — Suggest and optionally apply fix for an error', + 'debug-health': 'debug-health — Check supervisor module health status', + 'debug-sync': 'debug-sync [url] — Sync errors to backend API (or set sync URL)', + 'debug-sync-url': 'debug-sync-url [url] — View or set the backend sync endpoint', + }; + } + + registerCommands() { + this.commands['debug-stats'] = () => this._cmdStats(); + this.commands['debug-history'] = (args) => this._cmdHistory(args); + this.commands['debug-analyze'] = (args) => this._cmdAnalyze(args); + this.commands['debug-trends'] = (args) => this._cmdTrends(args); + this.commands['debug-fix'] = (args) => this._cmdFix(args); + this.commands['debug-health'] = () => this._cmdHealth(); + this.commands['debug-sync'] = (args) => this._cmdSync(args); + this.commands['debug-sync-url'] = (args) => this._cmdSyncUrl(args); + } + + seed() { + // seed() is not called automatically by module manager — interceptors go in init() + } + + init() { + this._loadDB(); + this._installInterceptors(); + // Start periodic sync (every 5 minutes) + this._syncInterval = setInterval(() => this._syncToBackend(), 300000); + // Also sync on first load after a short delay + setTimeout(() => this._syncToBackend(), 5000); + } + + destroy() { + // Clear sync interval + if (this._syncInterval) { + clearInterval(this._syncInterval); + this._syncInterval = null; + } + // Restore original console.error + if (this._originalConsole.error) { + console.error = this._originalConsole.error; + } + // Restore original window.onerror + if (this._originalOnError) { + window.onerror = this._originalOnError; + } + // Restore original fetch + if (this._originalFetch) { + window.fetch = this._originalFetch; + } + // Remove unhandledrejection listener (stored for cleanup) + if (this._rejectionHandler) { + window.removeEventListener('unhandledrejection', this._rejectionHandler); + } + this._interceptorInstalled = false; + this._healthy = false; + } + + // ── Error Database ── + + _loadDB() { + try { + const raw = localStorage.getItem('webx11_debug_db'); + if (raw) { + const parsed = JSON.parse(raw); + this._errorDB = parsed; + this._pruneExpired(); + } + } catch (e) { + // Corrupted DB — start fresh + this._errorDB = { errors: {}, clusters: {}, patterns: {}, sessionId: this._sessionId }; + } + } + + _saveDB() { + try { + localStorage.setItem('webx11_debug_db', JSON.stringify(this._errorDB)); + } catch (e) { + // Quota exceeded — evict oldest 20% and retry + this._evictLRU(0.2); + try { + localStorage.setItem('webx11_debug_db', JSON.stringify(this._errorDB)); + } catch (e2) { + // Still failing — supervisor degrades gracefully + console.warn('[DebugSupervisor] DB write failed, errors may be lost'); + } + } + } + + _scheduleSave() { + this._pendingWrites++; + if (this._writeDebounce) return; + this._writeDebounce = setTimeout(() => { + this._writeDebounce = null; + if (this._pendingWrites > 0) { + this._saveDB(); + this._pendingWrites = 0; + } + }, 2000); + } + + _pruneExpired() { + const now = Date.now(); + const maxAge = this._ttlDays * 86400000; + let changed = false; + for (const [hash, entry] of Object.entries(this._errorDB.errors)) { + if (now - entry.lastSeen > maxAge) { + delete this._errorDB.errors[hash]; + changed = true; + } + } + if (changed) this._saveDB(); + } + + _evictLRU(fraction) { + const entries = Object.entries(this._errorDB.errors); + const sorted = entries.sort((a, b) => a[1].lastSeen - b[1].lastSeen); + const toRemove = Math.ceil(sorted.length * fraction); + for (let i = 0; i < toRemove; i++) { + delete this._errorDB.errors[sorted[i][0]]; + } + } + + _checkQuota() { + try { + // Test localStorage is writable + const testKey = '__quota_test__'; + localStorage.setItem(testKey, '1'); + localStorage.removeItem(testKey); + return true; + } catch (e) { + return false; + } + } + + // ── Error Classification ── + + _classifyError(message, source, stack) { + const type = this._detectType(message, stack); + const severity = this._detectSeverity(type, message); + const category = this._detectCategory(message, source, type); + return { type, severity, category }; + } + + _detectType(message, stack) { + if (!message) return 'unknown'; + if (message.includes('ReferenceError') || message.includes('is not defined')) return 'ReferenceError'; + if (message.includes('TypeError') || message.includes('is not a function') || message.includes('cannot read property')) return 'TypeError'; + if (message.includes('SyntaxError') || message.includes('Unexpected token')) return 'SyntaxError'; + if (message.includes('HTTP') || message.includes('NetworkError') || message.includes('Failed to fetch')) return 'HTTP'; + if (message.includes('timeout') || message.includes('Timeout') || message.includes('AbortError')) return 'timeout'; + if (message.includes('QuotaExceededError') || message.includes('quota')) return 'quota'; + if (message.includes('SecurityError') || message.includes('permission')) return 'security'; + if (stack && stack.includes('tool_')) return 'tool'; + return 'runtime'; + } + + _detectSeverity(type, message) { + if (type === 'SyntaxError' || type === 'ReferenceError' || type === 'quota') return 'fatal'; + if (type === 'HTTP' && /\b5\d{2}\b/.test(message)) return 'error'; + if (type === 'timeout' || type === 'security') return 'error'; + if (type === 'tool') return 'warning'; + return 'error'; + } + + _detectCategory(message, source, type) { + if (type === 'HTTP' || type === 'timeout') return 'network'; + if (type === 'quota') return 'vfs'; + if (type === 'security') return 'security'; + if (source && (source.includes('module-') || source.includes('ext/'))) return 'module'; + if (type === 'tool') return 'tool'; + return 'runtime'; + } + + _extractModule(source) { + if (!source) return null; + const m = source.match(/(module-[a-z-]+)\.js/); + return m ? m[1] : null; + } + + _computeHash(message, source, type) { + const raw = (message || '') + '|' + (source || '') + '|' + (type || ''); + // Simple hash (Fowler-Noll-Vo) + let hash = 0x811c9dc5; + for (let i = 0; i < raw.length; i++) { + hash ^= raw.charCodeAt(i); + hash = Math.imul(hash, 0x01000193); + } + return (hash >>> 0).toString(16); + } + + // ── Error Interception ── + + _installInterceptors() { + if (this._interceptorInstalled) return; + const self = this; + + // Console.error + this._originalConsole.error = console.error; + console.error = function() { + const args = Array.prototype.slice.call(arguments); + const msg = args.map(a => typeof a === 'object' ? (a.message || JSON.stringify(a)) : String(a)).join(' '); + self._recordError(msg, 'console.error', null); + return self._originalConsole.error.apply(console, args); + }; + + // window.onerror + this._originalOnError = window.onerror; + window.onerror = function(msg, src, line, col, err) { + const fullMsg = msg + (err ? ' — ' + err.message : '') + ' (' + src + ':' + line + ':' + col + ')'; + self._recordError(fullMsg, src || 'window.onerror', err ? err.stack : null); + if (self._originalOnError) return self._originalOnError.call(window, msg, src, line, col, err); + }; + + // Unhandled rejections + this._originalOnRejection = window.onunhandledrejection; + this._rejectionHandler = function(e) { + const msg = e.reason ? (e.reason.message || String(e.reason)) : 'Unhandled Promise rejection'; + self._recordError(msg, 'unhandledrejection', e.reason ? e.reason.stack : null); + }; + window.addEventListener('unhandledrejection', this._rejectionHandler); + + // Fetch failures + this._originalFetch = window.fetch; + window.fetch = function() { + const args = arguments; + const url = typeof args[0] === 'string' ? args[0] : (args[0]?.url || '?'); + // Never record failures of the debug-sync endpoint itself: that produced a + // self-referential loop (38,495 identical "Fetch failed …/ingest.php" entries). + if (url.indexOf('/api/debug/') !== -1) return self._originalFetch.apply(window, args); + return self._originalFetch.apply(window, args).catch(function(err) { + self._recordError('Fetch failed: ' + url + ' — ' + (err.message || err), url, err.stack); + throw err; + }); + }; + + this._interceptorInstalled = true; + } + + // ── Record Error ── + + recordToolError(toolName, args, errorMessage) { + this._recordError(errorMessage, 'tool:' + toolName, null); + } + + _recordError(message, source, stack) { + if (!this._healthy) return; + const classification = this._classifyError(message, source, stack); + const hash = this._computeHash(message, source, classification.type); + const now = Date.now(); + + if (this._errorDB.errors[hash]) { + this._errorDB.errors[hash].count++; + this._errorDB.errors[hash].lastSeen = now; + if (this._errorDB.errors[hash].sessions.indexOf(this._sessionId) === -1) { + this._errorDB.errors[hash].sessions.push(this._sessionId); + } + } else { + // LRU eviction if at cap + const keys = Object.keys(this._errorDB.errors); + if (keys.length >= this._maxErrors) { + this._evictLRU(0.1); + } + this._errorDB.errors[hash] = { + count: 1, + firstSeen: now, + lastSeen: now, + message: message, + type: classification.type, + severity: classification.severity, + category: classification.category, + source: source, + stack: stack ? stack.split('\n').slice(0, 10).join('\n') : null, + module: this._extractModule(source), + sessions: [this._sessionId], + }; + } + + this._scheduleSave(); + + // v4.6.38 (A5): auto-file fatal/error-class failures, max 3 per day, deduped by hash. + if (classification.severity === 'fatal' || classification.severity === 'error') { + this._maybeAutoFile({ hash, message, source, type: classification.type, severity: classification.severity }); + } + } + + _maybeAutoFile(e) { + try { this._maybeAutoFileInner(e); } catch (_e) {} + } + + _maybeAutoFileInner(e) { + try { if (localStorage.getItem('webx11_autoreport') === 'off') return; } catch (_e) {} + + const today = new Date().toISOString().slice(0, 10); + let state = { day: today, count: 0, hashes: [] }; + try { + const raw = JSON.parse(localStorage.getItem('webx11_autoreport_day') || 'null'); + if (raw && raw.day === today && Array.isArray(raw.hashes)) state = raw; + } catch (_e) {} + + if (state.count >= 3) return; // daily quota + if (state.hashes.indexOf(e.hash) !== -1) return; // one report per distinct error + state.count++; + state.hashes.push(e.hash); + try { localStorage.setItem('webx11_autoreport_day', JSON.stringify(state)); } catch (_e) {} + + const payload = { + title: 'Auto: ' + String(e.message || 'unknown error').slice(0, 120), + body: 'Automatically filed by the debug supervisor (no user action).', + diagnostics: { + version: (typeof window !== 'undefined' && window.NEWOS4_VERSION) || 'unknown', + url: (typeof location !== 'undefined') ? location.href : '', + ua: (typeof navigator !== 'undefined') ? navigator.userAgent : '', + auto: true, + errors: [{ + message: String(e.message || '').slice(0, 300), + source: String(e.source || '').slice(0, 200), + type: e.type, + severity: e.severity, + count: 1, + }], + }, + }; + try { + // /api/debug/* is excluded from the fetch interceptor, so a failure here cannot loop. + fetch('/spaces/newos4/api/debug/report.php', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload), + }).catch(function () {}); + } catch (_e) {} + } + + // ── Clustering (Jaccard similarity) ── + + _normalizeMessage(msg) { + if (!msg) return ''; + return msg + .toLowerCase() + .replace(/\b0x[a-f0-9]+\b/g, '0xHEX') + .replace(/\b\d+\b/g, 'N') + .replace(/["'].*?["']/g, '"STR"') + .replace(/[^\w\s]/g, ' ') + .replace(/\s+/g, ' ') + .trim(); + } + + _jaccardSimilarity(a, b) { + const setA = new Set(a.split(' ').filter(Boolean)); + const setB = new Set(b.split(' ').filter(Boolean)); + if (setA.size === 0 && setB.size === 0) return 1; + let intersection = 0; + for (const token of setA) { + if (setB.has(token)) intersection++; + } + const union = setA.size + setB.size - intersection; + return union === 0 ? 0 : intersection / union; + } + + _clusterErrors() { + const errors = Object.entries(this._errorDB.errors); + if (errors.length === 0) return []; + + const clusters = []; + const assigned = new Set(); + + for (let i = 0; i < errors.length; i++) { + if (assigned.has(i)) continue; + const [hashI, entryI] = errors[i]; + const normI = this._normalizeMessage(entryI.message); + const cluster = { hashes: [hashI], messages: [entryI.message], count: entryI.count, types: new Set([entryI.type]), categories: new Set([entryI.category]) }; + assigned.add(i); + + for (let j = i + 1; j < errors.length; j++) { + if (assigned.has(j)) continue; + const [hashJ, entryJ] = errors[j]; + const normJ = this._normalizeMessage(entryJ.message); + if (this._jaccardSimilarity(normI, normJ) >= 0.35) { + cluster.hashes.push(hashJ); + cluster.messages.push(entryJ.message); + cluster.count += entryJ.count; + cluster.types.add(entryJ.type); + cluster.categories.add(entryJ.category); + assigned.add(j); + } + } + + cluster.label = this._mostFrequent(cluster.messages); + cluster.typeCount = cluster.types.size; + cluster.categoryCount = cluster.categories.size; + delete cluster.types; + delete cluster.categories; + clusters.push(cluster); + } + + clusters.sort((a, b) => b.count - a.count); + return clusters; + } + + _mostFrequent(arr) { + const freq = {}; + let best = arr[0], bestCount = 1; + for (const item of arr) { + freq[item] = (freq[item] || 0) + 1; + if (freq[item] > bestCount) { + best = item; + bestCount = freq[item]; + } + } + return best; + } + + // ── Root-Cause Analysis ── + + _analyzeRootCause(hash) { + const entry = this._errorDB.errors[hash]; + if (!entry) return { found: false }; + + const findings = []; + const fixes = []; + + // Tool failure → check module + if (entry.source && entry.source.startsWith('tool:')) { + const toolName = entry.source.replace('tool:', ''); + const mm = this.terminal?.moduleManager; + if (mm) { + // Check if any module has this command + let found = false; + for (const [modName, mod] of Object.entries(mm.modules)) { + if (mod.commands && mod.commands[toolName]) { + found = true; + break; + } + } + if (!found) { + findings.push({ type: 'module_not_loaded', detail: 'No module provides command "' + toolName + '"' }); + fixes.push({ action: 'loadmod', params: toolName, description: 'Load module providing ' + toolName }); + } + } + } + + // HTTP error → check network + if (entry.type === 'HTTP' || entry.type === 'timeout') { + findings.push({ type: 'network_issue', detail: 'Network request failed: ' + entry.message.substring(0, 100) }); + fixes.push({ action: 'test-net', params: null, description: 'Run network diagnostics' }); + } + + // ReferenceError → check bundle + if (entry.type === 'ReferenceError') { + findings.push({ type: 'missing_class', detail: 'ReferenceError suggests a class or variable is not defined' }); + fixes.push({ action: 'reload', params: null, description: 'Reload the page to re-initialize all modules' }); + } + + // Module error → check module loader + if (entry.category === 'module') { + findings.push({ type: 'module_error', detail: 'Error in module: ' + (entry.module || 'unknown') }); + fixes.push({ action: 'loadmod', params: entry.module, description: 'Reload module: ' + entry.module }); + } + + return { found: findings.length > 0, findings, fixes }; + } + + // ── Fix Registry ── + + _buildFixSuggestion(hash) { + const entry = this._errorDB.errors[hash]; + if (!entry) return null; + + const rca = this._analyzeRootCause(hash); + if (!rca.found) { + return { confidence: 0, suggestion: 'No known fix pattern for this error', autoApply: false }; + } + + // Calculate confidence based on frequency + let confidence = Math.min(0.5, entry.count / 10); + if (entry.count >= 3) confidence = 0.7; + if (entry.count >= 10) confidence = 0.9; + + return { + confidence: confidence, + suggestion: rca.findings.map(f => f.detail).join('; '), + fixes: rca.fixes, + autoApply: confidence >= 0.9, + }; + } + + // ── Terminal Commands ── + + _cmdStats() { + const db = this._errorDB.errors; + const keys = Object.keys(db); + if (keys.length === 0) { + this.terminal.print('📊 Debug Stats: No errors recorded', 'success'); + return; + } + + const totalCount = keys.reduce((sum, k) => sum + db[k].count, 0); + const bySeverity = {}; + const byCategory = {}; + for (const k of keys) { + const e = db[k]; + bySeverity[e.severity] = (bySeverity[e.severity] || 0) + e.count; + byCategory[e.category] = (byCategory[e.category] || 0) + e.count; + } + + const clusters = this._clusterErrors(); + const topClusters = clusters.slice(0, 5); + + this.terminal.print('📊 Debug Stats', 'header'); + this.terminal.print(' Total errors: ' + totalCount, 'info'); + this.terminal.print(' Unique errors: ' + keys.length, 'info'); + this.terminal.print(' Clusters: ' + clusters.length, 'info'); + this.terminal.print(' Session: ' + this._sessionId, 'text-muted'); + this.terminal.print(' Supervisor: ' + (this._healthy ? '✅ healthy' : '🔴 degraded'), this._healthy ? 'success' : 'error'); + + this.terminal.print('── By Severity ──', 'info'); + for (const [sev, count] of Object.entries(bySeverity)) { + const icon = sev === 'fatal' ? '🔴' : sev === 'error' ? '🟡' : '🟢'; + this.terminal.print(' ' + icon + ' ' + sev + ': ' + count, sev === 'fatal' ? 'error' : 'warning'); + } + + this.terminal.print('── By Category ──', 'info'); + for (const [cat, count] of Object.entries(byCategory)) { + this.terminal.print(' • ' + cat + ': ' + count, 'text-muted'); + } + + if (topClusters.length > 0) { + this.terminal.print('── Top Clusters ──', 'info'); + for (const c of topClusters) { + this.terminal.print(' [' + c.hashes.length + ' errors ×' + c.count + '] ' + c.label.substring(0, 80), 'warning'); + } + this.terminal.print(' Run debug-analyze for details', 'text-muted'); + } + } + + _cmdHistory(args) { + const n = (args && args[0]) ? parseInt(args[0]) : 20; + const entries = Object.entries(this._errorDB.errors) + .sort((a, b) => b[1].lastSeen - a[1].lastSeen) + .slice(0, n); + + if (entries.length === 0) { + this.terminal.print('No errors recorded', 'success'); + return; + } + + this.terminal.print('── Error History (last ' + entries.length + ') ──', 'header'); + for (const [hash, e] of entries) { + const time = new Date(e.lastSeen).toLocaleTimeString(); + const icon = e.severity === 'fatal' ? '🔴' : e.severity === 'error' ? '🟡' : '🟢'; + const msg = e.message.substring(0, 80); + this.terminal.print(' ' + icon + ' [' + time + '] ' + msg, e.severity === 'fatal' ? 'error' : 'text-muted'); + this.terminal.print(' hash=' + hash + ' type=' + e.type + ' cat=' + e.category + ' ×' + e.count, 'text-muted'); + } + this.terminal.print('── End ──', 'info'); + this.terminal.print('Run debug-analyze for details', 'text-muted'); + } + + _cmdAnalyze(args) { + const hash = (args && args[0]) || ''; + if (!hash) { + this.terminal.print('Usage: debug-analyze ', 'warning'); + return; + } + + const entry = this._errorDB.errors[hash]; + if (!entry) { + this.terminal.print('Error not found: ' + hash, 'error'); + return; + } + + this.terminal.print('── Error Analysis: ' + hash + ' ──', 'header'); + this.terminal.print(' Message: ' + entry.message, 'info'); + this.terminal.print(' Type: ' + entry.type + ' | Severity: ' + entry.severity + ' | Category: ' + entry.category, 'info'); + this.terminal.print(' Source: ' + (entry.source || '?'), 'text-muted'); + this.terminal.print(' Module: ' + (entry.module || 'unknown'), 'text-muted'); + this.terminal.print(' Count: ' + entry.count + ' | First: ' + new Date(entry.firstSeen).toLocaleString() + ' | Last: ' + new Date(entry.lastSeen).toLocaleString(), 'info'); + this.terminal.print(' Sessions: ' + (entry.sessions || []).length, 'text-muted'); + if (entry.stack) { + this.terminal.print('── Stack Trace (top 10) ──', 'info'); + this.terminal.print(entry.stack, 'text-muted'); + } + + // Root-cause analysis + const rca = this._analyzeRootCause(hash); + if (rca.found) { + this.terminal.print('── Root Cause Analysis ──', 'warning'); + for (const f of rca.findings) { + this.terminal.print(' • [' + f.type + '] ' + f.detail, 'warning'); + } + if (rca.fixes.length > 0) { + this.terminal.print('── Suggested Fixes ──', 'success'); + for (const fix of rca.fixes) { + this.terminal.print(' → ' + fix.action + (fix.params ? ' ' + fix.params : '') + ' — ' + fix.description, 'success'); + } + } + } else { + this.terminal.print(' No root cause identified', 'text-muted'); + } + + // Fix suggestion + const fix = this._buildFixSuggestion(hash); + if (fix && fix.confidence > 0) { + this.terminal.print('── Auto-Fix Confidence: ' + Math.round(fix.confidence * 100) + '% ──', fix.autoApply ? 'success' : 'warning'); + this.terminal.print(' ' + fix.suggestion, 'info'); + if (fix.autoApply) { + this.terminal.print(' ⚡ Confidence ≥ 90% — fix can be auto-applied with debug-fix ' + hash, 'success'); + } + } + } + + _cmdTrends(args) { + const days = (args && args[0]) ? parseInt(args[0]) : 7; + const cutoff = Date.now() - days * 86400000; + + const entries = Object.values(this._errorDB.errors).filter(e => e.lastSeen >= cutoff); + if (entries.length === 0) { + this.terminal.print('No errors in the last ' + days + ' days', 'success'); + return; + } + + // Group by day + const byDay = {}; + for (const e of entries) { + const day = new Date(e.lastSeen).toISOString().split('T')[0]; + if (!byDay[day]) byDay[day] = { total: 0, byType: {} }; + byDay[day].total += e.count; + byDay[day].byType[e.type] = (byDay[day].byType[e.type] || 0) + e.count; + } + + const sortedDays = Object.keys(byDay).sort(); + this.terminal.print('── Error Trends (last ' + days + ' days) ──', 'header'); + for (const day of sortedDays) { + const d = byDay[day]; + const types = Object.entries(d.byType).map(([t, c]) => t + ':' + c).join(', '); + this.terminal.print(' ' + day + ': ' + d.total + ' errors (' + types + ')', d.total > 5 ? 'warning' : 'text-muted'); + } + + // Top error types + const typeTotals = {}; + for (const e of entries) { + typeTotals[e.type] = (typeTotals[e.type] || 0) + e.count; + } + this.terminal.print('── By Type ──', 'info'); + for (const [type, count] of Object.entries(typeTotals).sort((a, b) => b[1] - a[1])) { + this.terminal.print(' • ' + type + ': ' + count, 'text-muted'); + } + } + + _cmdFix(args) { + const hash = (args && args[0]) || ''; + if (!hash) { + this.terminal.print('Usage: debug-fix ', 'warning'); + return; + } + + const fix = this._buildFixSuggestion(hash); + if (!fix || fix.confidence === 0) { + this.terminal.print('No fix available for error: ' + hash, 'warning'); + return; + } + + this.terminal.print('── Fix Suggestion for ' + hash + ' ──', 'header'); + this.terminal.print(' Confidence: ' + Math.round(fix.confidence * 100) + '%', fix.autoApply ? 'success' : 'warning'); + this.terminal.print(' Suggestion: ' + fix.suggestion, 'info'); + + if (fix.fixes && fix.fixes.length > 0) { + this.terminal.print('── Available Fixes ──', 'info'); + for (const f of fix.fixes) { + this.terminal.print(' → ' + f.action + (f.params ? ' ' + f.params : ''), 'success'); + this.terminal.print(' ' + f.description, 'text-muted'); + } + } + + if (fix.autoApply) { + this.terminal.print('⚡ Auto-applying fix...', 'info'); + this._applyFix(hash, fix); + } else { + this.terminal.print('Confidence below auto-apply threshold. Run the suggested command manually.', 'warning'); + } + } + + _applyFix(hash, fix) { + if (!fix.fixes || fix.fixes.length === 0) return; + const firstFix = fix.fixes[0]; + try { + // Sanitize params: only allow alphanumeric, hyphens, underscores, dots + const safeParams = (firstFix.params || '').replace(/[^a-zA-Z0-9_\-\.]/g, ''); + if (firstFix.action === 'loadmod' && this.terminal) { + this.terminal.executeCommand('loadmod ' + safeParams); + this.terminal.print(' ✅ Applied: loadmod ' + safeParams, 'success'); + } else if (firstFix.action === 'test-net' && this.terminal) { + this.terminal.executeCommand('test-net'); + this.terminal.print(' ✅ Applied: test-net', 'success'); + } else if (firstFix.action === 'reload') { + this.terminal.print(' ℹ️ Manual action needed: reload the page', 'warning'); + } else { + this.terminal.print(' ℹ️ Cannot auto-apply: ' + firstFix.action, 'warning'); + } + } catch (e) { + this.terminal.print(' ❌ Fix failed: ' + e.message, 'error'); + } + } + + _cmdHealth() { + this.terminal.print('── DebugSupervisor Health ──', 'header'); + this.terminal.print(' Status: ' + (this._healthy ? '✅ Healthy' : '🔴 Degraded'), this._healthy ? 'success' : 'error'); + this.terminal.print(' Session: ' + this._sessionId, 'text-muted'); + this.terminal.print(' Errors tracked: ' + Object.keys(this._errorDB.errors).length + ' unique', 'info'); + this.terminal.print(' Max errors: ' + this._maxErrors, 'text-muted'); + this.terminal.print(' TTL: ' + this._ttlDays + ' days', 'text-muted'); + this.terminal.print(' Interceptors: ' + (this._interceptorInstalled ? '✅ active' : '🔴 inactive'), this._interceptorInstalled ? 'success' : 'error'); + this.terminal.print(' Quota: ' + (this._checkQuota() ? '✅ available' : '🔴 low'), this._checkQuota() ? 'success' : 'error'); + } + + // ── Sync to Backend ── + + _getSyncUrl() { + try { + return localStorage.getItem('webx11_debug_sync_url') || 'https://webx11.duckdns.org/spaces/newos4/api/debug/ingest.php'; + } catch (e) { + return 'https://webx11.duckdns.org/spaces/newos4/api/debug/ingest.php'; + } + } + + async _syncToBackend() { + if (!this._healthy) return; + const db = this._errorDB.errors; + const keys = Object.keys(db); + if (keys.length === 0) return; + + const payload = { + version: window.NEWOS4_VERSION || 'v4.x', + session: { + id: this._sessionId, + modules: this.terminal?.moduleManager ? Object.keys(this.terminal.moduleManager.modules) : [], + }, + errors: keys.map(k => ({ + hash: k, + message: db[k].message, + type: db[k].type, + severity: db[k].severity, + category: db[k].category, + source: db[k].source, + module: db[k].module, + stack: db[k].stack, + count: db[k].count, + firstSeen: new Date(db[k].firstSeen).toISOString(), + lastSeen: new Date(db[k].lastSeen).toISOString(), + })), + }; + + try { + const resp = await fetch(this._getSyncUrl(), { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload), + signal: AbortSignal.timeout(10000), + }); + if (resp.ok) { + const result = await resp.json(); + if (result.success) { + const sent = keys.length; + const accepted = result.ingested || 0; + if (accepted < sent) { + console.warn('[DebugSupervisor] Sync truncated: sent ' + sent + ' errors, server accepted ' + accepted + ' (max ' + (result.total_errors || '?') + ' total)'); + } else { + console.log('[DebugSupervisor] Synced ' + accepted + ' errors to backend'); + } + this._lastSyncOk = Date.now(); + return true; + } + } + console.warn('[DebugSupervisor] Sync returned HTTP ' + resp.status); + return false; + } catch (e) { + console.warn('[DebugSupervisor] Sync failed: ' + e.message); + return false; + } + } + + _cmdSync(args) { + const url = (args && args[0]) ? args[0] : null; + if (url) { + try { + localStorage.setItem('webx11_debug_sync_url', url); + this.terminal.print('✅ Sync URL set to: ' + url, 'success'); + } catch (e) { + this.terminal.print('❌ Failed to save sync URL', 'error'); + } + return; + } + this.terminal.print('🔄 Syncing errors to backend...', 'info'); + this._syncToBackend().then(ok => { + if (ok) { + this.terminal.print('✅ Sync complete', 'success'); + } else { + this.terminal.print('❌ Sync failed', 'error'); + } + }); + } + + _cmdSyncUrl(args) { + if (args && args[0]) { + try { + localStorage.setItem('webx11_debug_sync_url', args[0]); + this.terminal.print('✅ Sync URL set to: ' + args[0], 'success'); + } catch (e) { + this.terminal.print('❌ Failed to save sync URL', 'error'); + } + } else { + this.terminal.print('Current sync URL: ' + this._getSyncUrl(), 'info'); + } + } + + // ── Dashboard Data ── + + getDashboardData() { + const db = this._errorDB.errors; + const keys = Object.keys(db); + const totalCount = keys.reduce((sum, k) => sum + db[k].count, 0); + const clusters = this._clusterErrors().slice(0, 5); + + return { + totalUnique: keys.length, + totalCount: totalCount, + clusters: clusters.map(c => ({ label: c.label.substring(0, 60), count: c.count, hashes: c.hashes.length })), + healthy: this._healthy, + topType: this._getTopType(db), + topCategory: this._getTopCategory(db), + }; + } + + _getTopType(db) { + const freq = {}; + for (const e of Object.values(db)) { + freq[e.type] = (freq[e.type] || 0) + e.count; + } + const sorted = Object.entries(freq).sort((a, b) => b[1] - a[1]); + return sorted.length > 0 ? sorted[0][0] : null; + } + + _getTopCategory(db) { + const freq = {}; + for (const e of Object.values(db)) { + freq[e.category] = (freq[e.category] || 0) + e.count; + } + const sorted = Object.entries(freq).sort((a, b) => b[1] - a[1]); + return sorted.length > 0 ? sorted[0][0] : null; + } +} diff --git a/.bkp-fix-20261023_143429/module-loader.js b/.bkp-fix-20261023_143429/module-loader.js new file mode 100644 index 0000000..46aad17 --- /dev/null +++ b/.bkp-fix-20261023_143429/module-loader.js @@ -0,0 +1,84 @@ +(function() { + 'use strict'; + + // 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 = 'v4.6.51'; + const CACHE = '?v=' + LATEST_VER; + const stored = localStorage.getItem('webx11_cache_ver'); + if (stored !== LATEST_VER) { + localStorage.setItem('webx11_cache_ver', LATEST_VER); + } + + function setSplash(phase, current, total, filename) { + const fill = document.getElementById('splash-progress-fill'); + const phaseEl = document.getElementById('splash-phase'); + const fileEl = document.getElementById('splash-filename'); + if (fill && total > 0) { + fill.style.width = Math.round((current / total) * 100) + '%'; + } + if (phaseEl) phaseEl.textContent = phase; + if (fileEl) fileEl.textContent = filename || ''; + } + + function hideSplash() { + const overlay = document.getElementById('splash-overlay'); + if (overlay) { + overlay.classList.add('hidden'); + setTimeout(() => { overlay.style.display = 'none'; }, 350); + } + } + + function showSplashError(msg) { + const el = document.getElementById('splash-error'); + const fill = document.getElementById('splash-progress-fill'); + if (el) { el.textContent = msg; el.classList.add('show'); } + if (fill) fill.style.background = 'var(--error-color)'; + } + + // Create webx11 synchronously so critical-ext-bundle.js (loaded right after us) + // can call registerModule() without waiting for DOMContentLoaded + const webx11 = new WebX11Terminal(); + window.webx11 = webx11; + + async function init() { + try { + const total = 3; + let count = 0; + + setSplash('Core bundle loaded (1/3)', ++count, total, 'core-bundle.js'); + setSplash('Critical ext bundle loaded (2/3)', ++count, total, 'critical-ext-bundle.js'); + + setSplash('Initializing terminal (3/3)', total, total, ''); + // webx11 already created synchronously above — just init ext modules + if (typeof webx11._loadExtModules === 'function') { + let extKeys = ['calc', 'voice', 'debug-panel', 'cron', 'theme', 'wpm', 'mindmap', 'webdav', 'skill-pack-core', 'cryptogzip', 'tutorial', 'floating', 'console', 'media', 'three', 'kanban', 'course']; + const saved = localStorage.getItem('webx11_startup_modules'); + let selected = null; + try { if (saved) selected = JSON.parse(saved); } catch (e) {} + if (selected && Array.isArray(selected)) { + extKeys = extKeys.filter(k => selected.includes(k + '.js')); + } + let disabledMods = []; + try { disabledMods = JSON.parse(localStorage.getItem('webx11_module_disabled') || '[]'); } catch (e) {} + extKeys = extKeys.filter(k => !disabledMods.includes(k)); + if (extKeys.length > 0) { + setTimeout(() => webx11._loadExtModules(extKeys), 800); + } + } + + hideSplash(); + console.log('[ModuleLoader] Core loader initialized'); + } catch (err) { + console.error('[ModuleLoader] Fatal error:', err); + showSplashError('Loading failed — ' + err.message); + } + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', init); + } else { + init(); + } +})(); diff --git a/.bkp-fix-20261023_143429/module-memory-graph.js b/.bkp-fix-20261023_143429/module-memory-graph.js new file mode 100644 index 0000000..ffc79e0 --- /dev/null +++ b/.bkp-fix-20261023_143429/module-memory-graph.js @@ -0,0 +1,663 @@ +// module-memory-graph.js — Cross-session persistent memory graph +// Nodes: files, concepts, tasks, patterns, users +// Edges: relationships with weight, last_used +// Commands: memgraph-status, memgraph-search, memgraph-stats, memgraph-export, memgraph-prune +class MemoryGraphModule { + constructor() { + this.commands = {}; + this.terminal = null; + this._nodes = {}; + this._edges = []; + this._indexPath = '/.memory_graph/'; + this._debounceTimer = null; + this._initialized = false; + this._totalNodeAccesses = 0; + // v4.5.89: Decay configuration + this._decayDays = 30; + this._decayMinUses = 3; + this._decayRate = 0.01; // Daily decay rate + this._lastDecayRun = 0; + } + + getHelpInfo() { + return { + 'memgraph-status': 'memgraph-status — Show node/edge counts, last access', + 'memgraph-search': 'memgraph-search — Find nodes by label or type', + 'memgraph-stats': 'memgraph-stats — Most connected nodes, strongest edges, cluster info', + 'memgraph-export': 'memgraph-export — Export graph as JSON', + 'memgraph-prune': 'memgraph-prune — Remove stale low-weight edges (>30d, <3 accesses)', + 'memgraph-learn': 'memgraph-learn — Add a fact to the memory graph', + 'memgraph-session': 'memgraph-session [date] — Show graph nodes for a session (default: today)', + 'memgraph-agent': 'memgraph-agent — Show namespaced nodes for an agent', + 'memgraph-decay-config': 'memgraph-decay-config — Configure decay policy (default: 30d, 3 uses)', + }; + } + + registerCommands() { + this.commands['memgraph-status'] = () => this.showStatus(); + this.commands['memgraph-search'] = (args) => this.search(args.join(' ')); + this.commands['memgraph-stats'] = () => this.showStats(); + this.commands['memgraph-export'] = () => this.exportGraph(); + this.commands['memgraph-prune'] = () => this.prune(); + this.commands['memgraph-learn'] = (args) => this.learn(args); + this.commands['memgraph-session'] = (args) => this.showSession(args.join(' ')); + this.commands['memgraph-agent'] = (args) => this.showAgent(args.join(' ')); + // v4.5.89: Decay configuration + this.commands['memgraph-decay-config'] = (args) => this.decayConfig(args); + } + + init(terminal) { + this.terminal = terminal; + this._loadFromVFS(); + this._initialized = true; + this.terminal.print('🧠 Memory Graph loaded — cross-session context active', 'info'); + // Log startup for graph + // v4.5.89 fix: Use day-floored key to match edge source keys (P0-1 from review) + this.addNode('session', `session-${Math.floor(Date.now() / 86400000)}`, `Session ${new Date().toLocaleDateString()}`); + } + + // ─── Node API ─── + + addNode(type, id, label, metadata = {}, agentId = null) { + // v4.5.88: Support agent namespacing with dual-lookup + const ns = agentId ? `agent:${agentId}` : 'global'; + const key = agentId ? `${ns}:${type}:${id}` : `${type}:${id}`; + if (!this._nodes[key]) { + this._nodes[key] = { + id: key, + type, + label: label || id, + metadata: metadata || {}, + created: new Date().toISOString(), + updated: new Date().toISOString(), + access_count: 1, + namespace: ns, + source_session: `session-${Math.floor(Date.now() / 86400000)}` + }; + } else { + this._nodes[key].updated = new Date().toISOString(); + this._nodes[key].access_count++; + if (metadata && Object.keys(metadata).length > 0) { + Object.assign(this._nodes[key].metadata, metadata); + } + } + this._totalNodeAccesses++; + this._debounceSave(); + return key; + } + + // v4.5.88: Dual-lookup — try namespaced key first, fall back to global + getNode(type, id, agentId = null) { + if (agentId) { + const nsKey = `agent:${agentId}:${type}:${id}`; + if (this._nodes[nsKey]) return this._nodes[nsKey]; + } + const globalKey = `${type}:${id}`; + return this._nodes[globalKey] || null; + } + + findNodes(query) { + const q = query.toLowerCase(); + return Object.values(this._nodes).filter(n => + n.label.toLowerCase().includes(q) || + n.type.toLowerCase().includes(q) || + (n.id && n.id.toLowerCase().includes(q)) + ); + } + + // ─── Edge API ─── + + // v4.5.88: addEdge tracks source_agents for multi-agent attribution + addEdge(sourceKey, targetKey, type, weight = 1, agentId = null) { + // Check if edge already exists + let edge = this._edges.find(e => + e.source === sourceKey && e.target === targetKey && e.type === type + ); + if (edge) { + edge.weight = (edge.weight + weight) / 2; + edge.last_used = new Date().toISOString(); + edge.use_count++; + if (agentId) edge.source_agents = [...new Set([...(edge.source_agents || []), agentId])]; + } else { + this._edges.push({ + source: sourceKey, + target: targetKey, + type, + weight, + created: new Date().toISOString(), + last_used: new Date().toISOString(), + use_count: 1, + source_agents: agentId ? [agentId] : [] + }); + } + this._debounceSave(); + } + + // ─── Event-based Learning ─── + + // v4.5.89 fix: Thread agentId through record* methods (P1-3 from review) + recordFileAccess(path, agentId = null) { + const name = path.split('/').pop() || path; + const nodeKey = this.addNode('file', path, name, {}, agentId); + const sessionKey = `session:session-${Math.floor(Date.now() / 86400000)}`; + this.addEdge(sessionKey, nodeKey, 'accessed', 0.5, agentId); + + // Extract file extension edge + const ext = path.split('.').pop(); + if (ext && ext.length < 10) { + this.addNode('type', ext, `.${ext} files`); + this.addEdge(nodeKey, `type:${ext}`, 'has_type', 0.3); + } + } + + recordToolCall(toolName, success, agentId = null) { + const nodeKey = this.addNode('tool', toolName, toolName, {}, agentId); + const sessionKey = `session:session-${Math.floor(Date.now() / 86400000)}`; + this.addEdge(sessionKey, nodeKey, success ? 'used' : 'failed', success ? 0.5 : 0.8, agentId); + } + + recordConcept(concept, filePath) { + const conceptKey = this.addNode('concept', concept, concept); + const fileKey = this.addNode('file', filePath, filePath.split('/').pop()); + this.addEdge(conceptKey, fileKey, 'maps_to', 0.7); + this.addEdge(fileKey, conceptKey, 'tagged_as', 0.7); + } + + // v4.5.88: recordTask now passes agentId for namespacing + adds source_session link + recordTask(task, result, agentId) { + const taskId = `task-${Date.now()}`; + const taskKey = this.addNode('task', taskId, task.substring(0, 60), { + full: task, + result: result?.substring(0, 100), + agent: agentId + }, agentId); + const sessionKey = `session:session-${Math.floor(Date.now() / 86400000)}`; + this.addEdge(sessionKey, taskKey, 'executed', 0.6, agentId); + + // Extract keywords as concepts + const words = task.toLowerCase().split(/\s+/).filter(w => w.length > 3); + for (const word of [...new Set(words)].slice(0, 5)) { + this.addNode('concept', word, word); + this.addEdge(taskKey, `concept:${word}`, 'involves', 0.4); + } + + // Link to tools used (if any found in task) + const knownTools = ['list_files', 'read_file', 'write_file', 'web_search', 'web_fetch', + 'edit_module', 'view_module', 'run_command', 'orchestrate']; + for (const tool of knownTools) { + if (task.toLowerCase().includes(tool.replace('_', ' '))) { + this.recordToolCall(tool, true); + } + } + } + + // ─── Context Injection ─── + + getContextSummary() { + // Get top active nodes from recent session + const now = Date.now(); + const today = `session:session-${Math.floor(now / 86400000)}`; + const yesterday = `session:session-${Math.floor(now / 86400000) - 1}`; + + const lines = []; + const totalNodes = Object.keys(this._nodes).length; + const totalEdges = this._edges.length; + + lines.push(`Memory Graph: ${totalNodes} nodes, ${totalEdges} edges`); + + // Recent task context + const recentTasks = Object.values(this._nodes) + .filter(n => n.type === 'task') + .sort((a, b) => new Date(b.updated) - new Date(a.updated)) + .slice(0, 3); + + if (recentTasks.length > 0) { + lines.push('Recent tasks:'); + for (const t of recentTasks) { + lines.push(` - ${t.label.substring(0, 50)}`); + } + } + + // Most accessed files + const topFiles = Object.values(this._nodes) + .filter(n => n.type === 'file') + .sort((a, b) => b.access_count - a.access_count) + .slice(0, 3); + + if (topFiles.length > 0) { + lines.push('Most accessed files:'); + for (const f of topFiles) { + lines.push(` - ${f.label} (${f.access_count}x)`); + } + } + + return lines.join('\n'); + } + + getContextForPrompt() { + const summary = this.getContextSummary(); + if (!summary) return ''; + // Suppress noise: don't inject when graph is nearly empty + const totalNodes = Object.keys(this._nodes).length; + const totalEdges = this._edges.length; + if (totalNodes <= 2 && totalEdges === 0) return ''; + return `[Memory Graph Context]\n${summary}\n`; + } + + // Alias for orchestrator compatibility (orchestrator calls getStats, not showStats) + getStats() { + const types = {}; + for (const n of Object.values(this._nodes)) { + types[n.type] = (types[n.type] || 0) + 1; + } + return { + nodes: Object.keys(this._nodes).length, + edges: this._edges.length, + types: types, + totalAccesses: this._totalNodeAccesses + }; + } + + // ─── Commands ─── + + showStatus() { + // v4.5.89: Lazy decay — run applyDecay() on status check + this.applyDecay(); + const types = {}; + for (const n of Object.values(this._nodes)) { + types[n.type] = (types[n.type] || 0) + 1; + } + const typeStr = Object.entries(types) + .sort((a, b) => b[1] - a[1]) + .map(([t, c]) => `${t}: ${c}`) + .join(', '); + + this.terminal.print('🧠 Memory Graph', 'header'); + this.terminal.print(` Nodes: ${Object.keys(this._nodes).length} (${typeStr})`, 'info'); + this.terminal.print(` Edges: ${this._edges.length}`, 'info'); + this.terminal.print(` Total accesses: ${this._totalNodeAccesses}`, 'info'); + this.terminal.print(` Path: ${this._indexPath}`, 'info'); + // v4.5.89: Show decay config + this.terminal.print(` Decay: ${this._decayDays}d threshold, ${this._decayMinUses} min uses, rate ${this._decayRate}/day`, 'text-muted'); + } + + search(query) { + if (!query) return this.terminal.print('Usage: memgraph-search ', 'error'); + const results = this.findNodes(query); + if (results.length === 0) return this.terminal.print(`No nodes matching "${query}"`, 'info'); + + this.terminal.print(`🔍 Found ${results.length} nodes:`, 'header'); + for (const n of results.slice(0, 20)) { + const edges = this._edges.filter(e => e.source === n.id || e.target === n.id); + this.terminal.print(` ${n.type}:${n.label} (${edges.length} edges, ${n.access_count}x)`, 'info'); + for (const e of edges.slice(0, 3)) { + const other = e.source === n.id ? e.target : e.source; + const otherLabel = (this._nodes[other] || {}).label || other; + this.terminal.print(` └─ ${e.type} → ${otherLabel} (w:${e.weight.toFixed(1)})`, 'text-muted'); + } + } + if (results.length > 20) this.terminal.print(` ... and ${results.length - 20} more`, 'info'); + } + + showStats() { + if (Object.keys(this._nodes).length === 0) { + return this.terminal.print('No graph data yet. Use commands normally to build it.', 'info'); + } + + // Most connected nodes + const connected = {}; + for (const e of this._edges) { + connected[e.source] = (connected[e.source] || 0) + 1; + connected[e.target] = (connected[e.target] || 0) + 1; + } + const top = Object.entries(connected) + .sort((a, b) => b[1] - a[1]) + .slice(0, 5); + + this.terminal.print('📊 Memory Graph Stats', 'header'); + this.terminal.print(' Most connected:', 'info'); + for (const [key, count] of top) { + const label = (this._nodes[key] || {}).label || key; + this.terminal.print(` ${label} — ${count} connections`, 'info'); + } + + // Strongest edges + const strong = [...this._edges].sort((a, b) => b.weight - a.weight).slice(0, 3); + if (strong.length > 0) { + this.terminal.print(' Strongest relationships:', 'info'); + for (const e of strong) { + const sLabel = (this._nodes[e.source] || {}).label || e.source; + const tLabel = (this._nodes[e.target] || {}).label || e.target; + this.terminal.print(` ${sLabel} → ${tLabel} (${e.type}, w:${e.weight.toFixed(2)}, used ${e.use_count}x)`, 'info'); + } + } + + // Cluster density + const density = this._edges.length / Math.max(Object.keys(this._nodes).length, 1); + this.terminal.print(` Density: ${density.toFixed(2)} edges/node`, 'info'); + } + + exportGraph() { + const exportData = { + version: 1, + exported: new Date().toISOString(), + nodes: Object.values(this._nodes), + edges: this._edges, + stats: { + totalNodes: Object.keys(this._nodes).length, + totalEdges: this._edges.length, + totalAccesses: this._totalNodeAccesses + } + }; + const json = JSON.stringify(exportData, null, 2); + // Save to VFS + try { + if (!this.terminal.fs.readDir(this._indexPath)) { + this.terminal.fs.mkdirRecursive(this._indexPath); + } + this.terminal.fs.writeFile(`${this._indexPath}export.json`, json); + this.terminal.print(`✅ Graph exported (${json.length} bytes) to ${this._indexPath}export.json`, 'success'); + } catch (e) { + this.terminal.print(`❌ Export failed: ${e.message}`, 'error'); + } + } + + prune() { + const now = Date.now(); + const THIRTY_DAYS = 30 * 24 * 60 * 60 * 1000; + + const before = this._edges.length; + this._edges = this._edges.filter(e => { + const age = now - new Date(e.last_used).getTime(); + // Keep if: used recently (<30d) OR used 3+ times regardless of age + if (age < THIRTY_DAYS) return true; + if (e.use_count >= 3) return true; + // Or if weight is high + if (e.weight > 0.8) return true; + return false; + }); + const pruned = before - this._edges.length; + + // Also prune orphan nodes (no edges) + const connectedNodes = new Set(); + for (const e of this._edges) { + connectedNodes.add(e.source); + connectedNodes.add(e.target); + } + const nodeBefore = Object.keys(this._nodes).length; + for (const key of Object.keys(this._nodes)) { + if (!connectedNodes.has(key)) { + // Keep session and high-access nodes + if (this._nodes[key].access_count >= 5) continue; + delete this._nodes[key]; + } + } + const nodesPruned = nodeBefore - Object.keys(this._nodes).length; + + this._saveNow(); + this.terminal.print(`🧹 Pruned ${pruned} edges and ${nodesPruned} orphan nodes`, nodesPruned > 0 ? 'warning' : 'info'); + } + + // v4.5.89: Apply decay scores to nodes based on age and access patterns + applyDecay() { + const now = Date.now(); + if (now - this._lastDecayRun < 3600000) return; + this._lastDecayRun = now; + + // v4.5.89b fix: Compute decay rate from _decayDays (P0 from review) + const effectiveRate = 0.9 / this._decayDays; + let decayed = 0; + let removed = 0; + + for (const key of Object.keys(this._nodes)) { + const node = this._nodes[key]; + const ageDays = (now - new Date(node.updated || node.created).getTime()) / 86400000; + node.decay_score = Math.max(0, 1.0 - (ageDays * effectiveRate)); + if (node.decay_score < 0.1 && node.access_count < this._decayMinUses) { + if (node.type === 'session') continue; + if (node.namespace && node.namespace.startsWith('agent:')) continue; + delete this._nodes[key]; + removed++; + continue; + } + decayed++; + } + + this._edges = this._edges.filter(e => { + const ageDays = (now - new Date(e.last_used).getTime()) / 86400000; + e.decay_score = Math.max(0, 1.0 - (ageDays * effectiveRate)); + return !(e.decay_score < 0.1 && (e.use_count || 1) < this._decayMinUses); + }); + + if (removed > 0) { + this._debounceSave(); + console.log('[MemoryGraph] Decay: ' + decayed + ' scored, ' + removed + ' auto-removed'); + } + + // v4.5.90: Check for stale skills + var staleSkills = this.getStaleSkills(); + if (staleSkills.length > 0) { + this.terminal.print('🧠 Stale skills: ' + staleSkills.length + ' (' + staleSkills.map(function(s) { return s.name; }).join(', ') + ')', 'warning'); + } + } + + // v4.5.90: Record skill usage for decay tracking + recordSkillUse(name, success) { + try { + const fs = this.terminal.fs; + if (!fs) return; + const index = fs.fsReadJSON('/skills/index.json') || {}; + if (!index[name]) index[name] = { useCount: 0, successCount: 0 }; + index[name].lastUsed = new Date().toISOString(); + index[name].useCount = (index[name].useCount || 0) + 1; + if (success !== false) { + index[name].successCount = (index[name].successCount || 0) + 1; + } + fs.fsWriteJSON('/skills/index.json', index); + } catch (e) { + console.warn('[MemoryGraph] Skill use tracking error:', e.message); + } + } + + // v4.5.90: Get stale skills (unused >_decayDays, useCount == 0, not immune) + getStaleSkills() { + try { + const fs = this.terminal.fs; + if (!fs) return []; + const index = fs.fsReadJSON('/skills/index.json') || {}; + const now = Date.now(); + // v4.5.90b fix: Use configurable _decayDays (P1 from review) + const staleThreshold = this._decayDays * 24 * 60 * 60 * 1000; + var stale = []; + for (var name of Object.keys(index)) { + var stats = index[name] || {}; + if (stats.immune) continue; + var useCount = stats.useCount || 0; + var lastUsed = stats.lastUsed ? new Date(stats.lastUsed).getTime() : 0; + if (useCount === 0 && (now - lastUsed > staleThreshold || lastUsed === 0)) { + var daysUnused = lastUsed > 0 ? Math.floor((now - lastUsed) / 86400000) : 999; + stale.push({ name: name, daysUnused: daysUnused }); + } + } + return stale; + } catch (e) { + return []; + } + } + + // v4.5.89: Configure decay policy + decayConfig(args) { + if (args.length < 2) { + this.terminal.print('Usage: memgraph-decay-config ', 'error'); + this.terminal.print(' Current: ' + this._decayDays + 'd threshold, ' + this._decayMinUses + ' min uses', 'info'); + this.terminal.print(' Decay rate: ' + this._decayRate + '/day (0.01 = 1% per day)', 'info'); + return; + } + const days = parseInt(args[0]); + const minUses = parseInt(args[1]); + if (isNaN(days) || days < 1) return this.terminal.print('Days must be >= 1', 'error'); + if (isNaN(minUses) || minUses < 0) return this.terminal.print('Min uses must be >= 0', 'error'); + this._decayDays = days; + this._decayMinUses = minUses; + this._debounceSave(); + this.terminal.print('✅ Decay config: ' + days + 'd threshold, ' + minUses + ' min uses', 'success'); + } + + learn(args) { + if (!args || args.length < 2) return this.terminal.print('Usage: memgraph-learn ', 'error'); + const entity = args[0]; + const fact = args.slice(1).join(' '); + this.addNode('concept', entity, entity); + const factKey = this.addNode('fact', entity + '-' + Date.now(), fact); + const conceptKey = `concept:${entity}`; + this.addEdge(conceptKey, factKey, 'has_fact', 0.9); + this.terminal.print(`✅ Learned: ${entity} → ${fact}`, 'success'); + // v4.5.89b fix: Index only the new fact, not all facts (P2 from review — O(n) → O(1)) + const nvfs = this.terminal.moduleManager.modules['NeuralVFSModule']; + if (nvfs && typeof nvfs.indexGraphFacts === 'function') { + nvfs.indexSingleFact(factKey, entity, fact); + } + } + + // v4.5.88: Show graph nodes for a specific session + showSession(dateStr) { + let sessionKey; + if (dateStr) { + const d = new Date(dateStr); + if (isNaN(d.getTime())) return this.terminal.print('Usage: memgraph-session [YYYY-MM-DD]', 'error'); + sessionKey = `session-${Math.floor(d.getTime() / 86400000)}`; + } else { + sessionKey = `session-${Math.floor(Date.now() / 86400000)}`; + } + + const sessionNodes = Object.values(this._nodes).filter(function(n) { + return n.source_session === sessionKey || n.type === 'session' && n.id.includes(sessionKey); + }); + + this.terminal.print('📅 Session Graph: ' + sessionKey, 'header'); + if (sessionNodes.length === 0) { + this.terminal.print(' No nodes found for this session', 'info'); + return; + } + this.terminal.print(' ' + sessionNodes.length + ' nodes:', 'info'); + for (var n of sessionNodes.slice(0, 15)) { + var ns = n.namespace || 'global'; + this.terminal.print(' ' + n.type + ':' + n.label + ' (' + ns + ', ' + n.access_count + 'x)', 'info'); + } + var sessionEdges = this._edges.filter(function(e) { + return sessionNodes.some(function(n) { return n.id === e.source || n.id === e.target; }); + }); + this.terminal.print(' ' + sessionEdges.length + ' edges', 'info'); + } + + // v4.5.88: Show namespaced nodes for a specific agent + showAgent(agentId) { + if (!agentId) return this.terminal.print('Usage: memgraph-agent ', 'error'); + var ns = 'agent:' + agentId; + var agentNodes = Object.values(this._nodes).filter(function(n) { + return n.namespace === ns; + }); + var agentEdges = this._edges.filter(function(e) { + return (e.source_agents || []).includes(agentId); + }); + + this.terminal.print('🤖 Agent Memory: ' + agentId, 'header'); + if (agentNodes.length === 0) { + this.terminal.print(' No namespaced nodes for this agent', 'info'); + this.terminal.print(' (Agent may have used global namespace)', 'text-muted'); + return; + } + this.terminal.print(' ' + agentNodes.length + ' nodes, ' + agentEdges.length + ' edges:', 'info'); + for (var n of agentNodes.slice(0, 15)) { + this.terminal.print(' ' + n.type + ':' + n.label + ' (' + n.access_count + 'x)', 'info'); + } + } + + // ─── Persistence ─── + + _loadFromVFS() { + try { + const fs = this.terminal.fs; + if (!fs) return; + + // Try loading from export first + const exportData = fs.fsReadJSON(`${this._indexPath}export.json`); + if (exportData && exportData.nodes) { + for (const n of exportData.nodes) { + // v4.5.89 fix: Migrate old-format nodes (P1-1 from review) + if (!n.namespace) n.namespace = 'global'; + if (!n.source_session) { + const createdMs = new Date(n.created).getTime(); + n.source_session = isNaN(createdMs) ? null : `session-${Math.floor(createdMs / 86400000)}`; + } + this._nodes[n.id] = n; + } + this._edges = exportData.edges || []; + for (const e of this._edges) { + if (!e.source_agents) e.source_agents = []; + } + this._totalNodeAccesses = (exportData.stats?.totalAccesses) || 0; + return; + } + + // Fallback: load nodes and edges separately + const nodes = fs.fsReadJSON(`${this._indexPath}nodes.json`); + if (nodes) { + this._nodes = nodes; + for (const n of Object.values(this._nodes)) { + if (!n.namespace) n.namespace = 'global'; + if (!n.source_session) { + const createdMs = new Date(n.created).getTime(); + n.source_session = isNaN(createdMs) ? null : `session-${Math.floor(createdMs / 86400000)}`; + } + } + } + const edges = fs.fsReadJSON(`${this._indexPath}edges.json`); + if (edges) { + this._edges = edges; + for (const e of this._edges) { + if (!e.source_agents) e.source_agents = []; + } + } + // v4.5.89b fix: Load persisted decay config (P1 from review) + const config = fs.fsReadJSON(`${this._indexPath}config.json`); + if (config) { + if (typeof config.decayDays === 'number') this._decayDays = config.decayDays; + if (typeof config.decayMinUses === 'number') this._decayMinUses = config.decayMinUses; + if (typeof config.lastDecayRun === 'number') this._lastDecayRun = config.lastDecayRun; + } + } catch (e) { + console.warn('[MemoryGraph] Load error (fresh start):', e.message); + } + } + + _saveNow() { + try { + const fs = this.terminal.fs; + if (!fs) return; + if (!fs.readDir(this._indexPath)) { + fs.mkdirRecursive(this._indexPath); + } + fs.fsWriteJSON(`${this._indexPath}nodes.json`, this._nodes); + fs.fsWriteJSON(`${this._indexPath}edges.json`, this._edges); + // v4.5.89b fix: Persist decay config (P1 from review) + fs.fsWriteJSON(`${this._indexPath}config.json`, { + decayDays: this._decayDays, + decayMinUses: this._decayMinUses, + lastDecayRun: this._lastDecayRun + }); + } catch (e) { + console.error('[MemoryGraph] Save error:', e.message); + } + } + + _debounceSave() { + if (this._debounceTimer) clearTimeout(this._debounceTimer); + this._debounceTimer = setTimeout(() => this._saveNow(), 2000); + } +} + +// Required for module system +if (typeof window !== 'undefined') { + window.MemoryGraphModule = MemoryGraphModule; +} \ No newline at end of file diff --git a/.bkp-fix-20261023_143429/module-world-model.js b/.bkp-fix-20261023_143429/module-world-model.js new file mode 100644 index 0000000..cbfd1fb --- /dev/null +++ b/.bkp-fix-20261023_143429/module-world-model.js @@ -0,0 +1,600 @@ +// module-world-model.js — Global World Model: system self-awareness synthesis engine +// Aggregates from memory graph, neural VFS, skills index, telemetry +// Commands: world-model, world-model-summary, world-model-ask (LLM), world-model-refresh, world-model-config +class WorldModelModule { + constructor() { + this.commands = {}; + this.terminal = null; + this._model = null; + this._lastRefresh = null; + this._REFRESH_INTERVAL = 300000; // 5 minutes (fallback) + this._MAX_PROJECTS = 10; + this._refreshTimer = null; + // v4.5.89: Event-driven sync state + this._syncDebounce = null; + this._syncing = false; + this._lastEventSync = 0; + this._EVENT_DEBOUNCE_MS = 500; + } + + getHelpInfo() { + return { + 'world-model': 'world-model — Show full world model as structured output', + 'world-model-summary': 'world-model-summary — One-line system self-awareness statement', +'world-model-ask': 'world-model-ask — Ask the LLM about this system (grounded in the world model; --facts for the instant keyword lookup)', + 'world-model-refresh': 'world-model-refresh — Force re-synthesis from all subsystems', + 'world-model-config': 'world-model-config — Tune: refresh-interval, max-projects', + }; + } + + registerCommands() { + this.commands['world-model'] = () => this.showModel(); + this.commands['world-model-summary'] = () => this.showSummary(); + this.commands['world-model-ask'] = (args) => this.ask(args.join(' ')); + this.commands['world-model-refresh'] = () => this.refresh(); + this.commands['world-model-config'] = (args) => this.setConfig(args); + } + + init(terminal) { + this.terminal = terminal; + this._loadFromVFS(); + // Deferred initial synthesis — wait for ext modules (SkillPackCore etc) to fully load + setTimeout(() => { + this._synthesize(); + this.terminal.print('🌐 World Model synthesized — system self-awareness active', 'info'); + // Second pass after ext modules definitely finished loading + setTimeout(() => { + this._synthesize(); + this.terminal.print('🌐 World Model refresh — full module load confirmed', 'info'); + }, 7000); + }, 5000); + // Auto-refresh on a timer + this._refreshTimer = setInterval(() => { + const age = Date.now() - (this._lastRefresh ? new Date(this._lastRefresh).getTime() : 0); + if (age > this._REFRESH_INTERVAL) this._synthesize(); + }, this._REFRESH_INTERVAL); + this.terminal.print('🌐 World Model loaded — system self-awareness active', 'info'); + } + + // v4.5.89: Event-driven sync — debounced, recursion-guarded + _scheduleSync() { + if (this._syncing) return; // Prevent recursion during active synthesis + if (this._syncDebounce) clearTimeout(this._syncDebounce); + const now = Date.now(); + // Skip if we synced < 2s ago (rate limit beyond debounce) + if (now - this._lastEventSync < 2000) return; + this._syncDebounce = setTimeout(() => { + this._syncDebounce = null; + this._syncing = true; + try { + this._synthesize(); + this._lastEventSync = Date.now(); + } finally { + this._syncing = false; + } + }, this._EVENT_DEBOUNCE_MS); + } + + // ─── Synthesis Engine ─── + + _synthesize() { + try { + const fs = this.terminal.fs; + if (!fs) return; + + // Derive version from actual system state — no hardcoded strings + const mInfo = this._gatherModuleInfo(); + const agentInfo = this._gatherAgentInfo(); + const fileInfo = this._gatherFileInfo(fs); + const skillInfo = this._gatherSkillInfo(fs); + const liveVersion = this._deriveVersion(mInfo); + + const model = { + version: liveVersion, + synthesized: new Date().toISOString(), + system: this._gatherSystemInfo(fs, mInfo.loaded, liveVersion), + modules: mInfo, + files: fileInfo, + skills: skillInfo, + memory: this._gatherMemoryInfo(), + agents: agentInfo, + telemetry: this._gatherTelemetry(fs), + projects: this._gatherProjects(fs), + recent: this._gatherRecent(fs) + }; + + this._model = model; + this._lastRefresh = model.synthesized; + this._saveToVFS(fs); + + // Log synthesis summary + const mCount = model.modules?.loaded || '?'; + const sCount = model.skills?.count || '?'; + const fCount = model.files?.total || '?'; + console.log(`[WorldModel] Synthesis complete: ${mCount} modules, ${sCount} skills, ${fCount} files`); + } catch (e) { + console.error('[WorldModel] Synthesis error:', e.message); + } + } + + _deriveVersion(mInfo) { + // Single source of truth: the constant set at bootstrap + if (window.NEWOS4_VERSION) return window.NEWOS4_VERSION; + // Fallback: try to extract from script tag ?v= query param or LATEST_VER constant + try { + const scripts = document.querySelectorAll('script[src*="module-loader"]'); + for (const s of scripts) { + const match = s.src.match(/[?&]v=([^&]+)/); + if (match) return match[1]; + } + // Try to find LATEST_VER in any inline script + const allScripts = document.querySelectorAll('script:not([src])'); + for (const s of allScripts) { + const match = s.textContent.match(/LATEST_VER\s*=\s*['"]([^'"]+)['"]/); + if (match) return match[1]; + } + } catch (e) { + console.warn('[WorldModel] Version extraction error:', e.message); + } + return 'v?'; + } + + _getTotalModules() { + try { + const mm = this.terminal?.moduleManager; + if (!mm) return 0; + // Use max of registry vs loaded so the ratio is never >1 (e.g. 43/7 → 43/43) + const loadedCount = Object.keys(mm.modules || {}).length; + const registryCount = (mm.moduleRegistry || []).length; + return Math.max(loadedCount, registryCount); + } catch (e) { + console.warn('[WorldModel] Total modules gather error:', e.message); + return Object.keys(this.terminal?.moduleManager?.modules || {}).length || 0; + } + } + + _gatherSystemInfo(fs, moduleCount, version) { + // Get real health data from ServerMonitorModule's _latencyLog and DebugSupervisorModule + const sm = this.terminal?.moduleManager?.modules?.['ServerMonitorModule']; + const ds = this.terminal?.moduleManager?.modules?.['DebugSupervisorModule']; + const errorCount = ds ? Object.keys(ds._errorDB?.errors || {}).length : 0; + const latencyErrors = sm?._latencyLog ? Object.values(sm._latencyLog).filter(e => e.status === 'error').length : 0; + const totalErrors = errorCount + latencyErrors; + return { + version: `newOS4 ${version}`, + modules: moduleCount || Object.keys(this.terminal?.moduleManager?.modules || {}).length || 0, + vfsSize: JSON.stringify(fs?.root || {}).length, + online: typeof navigator !== 'undefined' ? navigator.onLine : true, + memory: typeof performance !== 'undefined' && performance.memory + ? `${(performance.memory.usedJSHeapSize / 1048576).toFixed(1)}MB` + : 'N/A', + health: totalErrors > 0 ? `⚠️ ${totalErrors} errors` : 'OK', + lastHealthCheck: new Date().toISOString() + }; + } + + _gatherModuleInfo() { + const mm = this.terminal?.moduleManager; + if (!mm) return { loaded: 0, modified: [] }; + const modules = Object.keys(mm.modules || {}).map(name => { + const mod = mm.modules[name]; + const clsName = mod?.constructor?.name || name; + return { name: clsName, hasCommands: !!mod?.commands }; + }); + // Check for local modifications + const modsDir = this.terminal.fs?.readDir('/_mods/'); + const modified = modsDir + ? modsDir.filter(e => e.type === 'file' && e.name.endsWith('.js') && !e.name.endsWith('.orig.js')) + .map(e => e.name.replace('.js', '')) + : []; + return { + loaded: modules.length, + total: this._getTotalModules(), + modules, + modified, + modifiedCount: modified.length + }; + } + + _gatherFileInfo(fs) { + if (!fs) return { total: 0, size: '0KB' }; + let fileCount = 0, dirCount = 0; + const walk = (path) => { + const entries = fs.ls(path); + if (!entries) return; + for (const e of entries) { + if (e.type === 'file') fileCount++; + else { dirCount++; walk(path === '/' ? '/' + e.name : path + '/' + e.name); } + } + }; + walk('/'); + return { + total: fileCount, + directories: dirCount, + size: `${(JSON.stringify(fs.root).length / 1024).toFixed(1)}KB` + }; + } + + _gatherSkillInfo(fs) { + try { + const idx = fs.fsReadJSON('/skills/index.json'); + const count = idx ? Object.keys(idx).length : 0; + return { count, skills: idx ? Object.keys(idx) : [] }; + } catch (e) { + console.warn('[WorldModel] Skill info gather error:', e.message); + return { count: 0, skills: [] }; + } + } + + _gatherMemoryInfo() { + const mg = this.terminal?.moduleManager?.modules?.['MemoryGraphModule']; + if (mg && mg._nodes) { + const nodes = Object.keys(mg._nodes).length; + const edges = (mg._edges || []).length; + return { nodes, edges, active: true, fallback: false }; + } + // Fallback: count memory files + try { + const memIndex = this.terminal.fs?.fsReadJSON('/memory/index.json'); + return { nodes: memIndex ? Object.keys(memIndex).length : 0, edges: 0, active: false, fallback: true }; + } catch (e) { + console.warn('[WorldModel] Memory info gather error:', e.message); + return { nodes: 0, edges: 0, active: false, fallback: true }; + } + } + + _gatherAgentInfo() { + const orch = this.terminal?.moduleManager?.modules?.['OrchestratorModule']; + if (!orch || !orch._agents) return { agents: -1, active: -1, error: 'Orchestrator not loaded' }; + const agents = Object.keys(orch._agents).length; + const active = Object.values(orch._agents).filter(a => a.status === 'busy').length; + return { agents, active, totalLeases: Object.keys(orch._a2aMessages || {}).length }; + } + + _gatherTelemetry(fs) { + try { + // Get real latency data from ServerMonitorModule and errors from DebugSupervisorModule + const sm = this.terminal?.moduleManager?.modules?.['ServerMonitorModule']; + const ds = this.terminal?.moduleManager?.modules?.['DebugSupervisorModule']; + const latencyDataPoints = sm?._latencyLog ? Object.keys(sm._latencyLog).length : 0; + const recentErrors = ds ? Object.keys(ds._errorDB?.errors || {}).length : 0; + return { + toolLatencyDataPoints: latencyDataPoints, + recentErrors: recentErrors, + }; + } catch (e) { + console.warn('[WorldModel] Telemetry gather error:', e.message); + return { toolLatencyDataPoints: 0, recentErrors: 0 }; + } + } + + _gatherProjects(fs) { + try { + const mg = this.terminal?.moduleManager?.modules?.['MemoryGraphModule']; + if (mg && mg._nodes) { + // Find task clusters as "projects" + const tasks = Object.values(mg._nodes).filter(n => n.type === 'task'); + const recentFiles = Object.values(mg._nodes) + .filter(n => n.type === 'file') + .sort((a, b) => (b.access_count || 0) - (a.access_count || 0)) + .slice(0, this._MAX_PROJECTS); + return { + totalTasks: tasks.length, + activeProjects: recentFiles.slice(0, 5).map(f => ({ + name: f.label, + accesses: f.access_count || 0, + lastModified: f.updated + })) + }; + } + return { totalTasks: 0, activeProjects: [] }; + } catch (e) { + console.warn('[WorldModel] Projects gather error:', e.message); + return { totalTasks: 0, activeProjects: [] }; + } + } + + _gatherRecent(fs) { + const recent = []; + try { + // Check modified modules + const modsDir = fs.readDir('/_mods/'); + if (modsDir) { + for (const e of modsDir) { + if (e.type === 'file' && e.name.endsWith('.js') && !e.name.endsWith('.orig.js') && !e.name.endsWith('.meta.json')) { + recent.push(`Modified module: ${e.name.replace('.js', '')}`); + } + } + } + // Check skills activity + const skills = fs.ls('/skills/'); + if (skills && skills.length > 0) { + recent.push(`Skills directory: ${skills.filter(s => s.type === 'file').length} files`); + } + // Check memory directory for recent changes + const memDir = fs.readDir('/memory/'); + if (memDir && memDir.length > 0) { + const memFiles = memDir.filter(s => s.type === 'file'); + if (memFiles.length > 0) { + recent.push(`Memory: ${memFiles.length} files in /memory/`); + } + } + // Check system config changes + const sysDir = fs.readDir('/system/'); + if (sysDir && sysDir.length > 0) { + const configFiles = sysDir.filter(s => s.type === 'file' && s.name.endsWith('.json')); + if (configFiles.length > 0) { + recent.push(`System config: ${configFiles.length} config files`); + } + } + } catch (e) { + console.warn('[WorldModel] Recent gather error:', e.message); + } + return recent; + } + + // ─── Commands ─── + + showModel() { + if (!this._model) return this.terminal.print('No model yet — run world-model-refresh', 'warning'); + const m = this._model; + + this.terminal.print('🌐 World Model', 'header'); + this.terminal.print(` Version: ${m.version} (last synthsized: ${new Date(m.synthesized).toLocaleTimeString()})`, 'info'); + + this.terminal.print(` ⚙️ System — ${m.system.modules} modules loaded, ${m.system.vfsSize} VFS, ${m.system.memory}`, 'info'); + this.terminal.print(` 🧩 Modules — ${m.modules.loaded} loaded (${m.modules.modifiedCount} modified)`, 'info'); + this.terminal.print(` 📁 Files — ${m.files.total} files, ${m.files.directories} dirs, ${m.files.size}`, 'info'); + this.terminal.print(` 📚 Skills — ${m.skills.count} installed`, 'info'); + this.terminal.print(` 🧠 Memory Graph — ${m.memory.nodes} nodes, ${m.memory.edges} edges${m.memory.active ? ' ✅' : ''}`, 'info'); + this.terminal.print(` 🤖 Agents — ${m.agents.agents} registered (${m.agents.active} active)`, 'info'); + this.terminal.print(` 📊 Telemetry — ${m.telemetry.toolLatencyDataPoints} tool latency points, ${m.telemetry.recentErrors} recent errors`, 'info'); + + if (m.projects.activeProjects.length > 0) { + this.terminal.print(' Active context:', 'header'); + for (const p of m.projects.activeProjects.slice(0, 3)) { + this.terminal.print(` 📌 ${p.name} (${p.accesses}x accesses)`, 'info'); + } + } + + if (m.recent.length > 0) { + this.terminal.print(' Recent changes:', 'header'); + for (const r of m.recent.slice(0, 3)) { + this.terminal.print(` ${r}`, 'info'); + } + } + } + + showSummary() { + if (!this._model) return this.terminal.print('No model yet', 'warning'); + const m = this._model; + const summary = `newOS4 ${m.version} · ${m.modules.loaded}/${m.modules.total} modules · ${m.skills.count} skills · ${m.files.total} files · ${m.agents.agents} agents · ${m.memory.nodes} graph nodes · ${m.telemetry.recentErrors > 0 ? `⚠️ ${m.telemetry.recentErrors} errors` : '✅ OK'}`; + this.terminal.print(summary, 'info'); + return summary; + } + + async ask(question) { + if (!question) return this.terminal.print('Usage: world-model-ask (--facts = fast keyword lookup)', 'error'); + const factsOnly = /(^|\s)--facts\b/.test(question); + if (factsOnly) question = question.replace(/--facts\b/, '').trim(); + if (!this._model) return this.terminal.print('No model — run world-model-refresh first', 'warning'); + + this.terminal.print(`❓ ${question}`, 'info'); + + if (!factsOnly) { + const om = this.terminal.ollamaManager; + try { + if (om && !om.connected) { try { await om.connect(); } catch (e) {} } + if (om && om.connected) { + const answer = await om.chat(this._buildAskPrompt(question)); + if (answer && String(answer).trim()) { + this.terminal.print(`💡 ${String(answer).trim()}`, 'success'); + return; + } + } + } catch (e) { /* fall through to the facts path */ } + this.terminal.print('⚠️ LLM unavailable — showing the factual lookup instead', 'warning'); + } + + this.terminal.print(`💡 ${this._answerQuery(question)}`, 'success'); + } + + // Shared query routing — used by both ask() (terminal) and world_model_query (AI tool) + _answerQuery(question) { + if (!this._model) return 'World model not synthesized yet — run world-model-refresh'; + const q = (question || '').toLowerCase(); + const m = this._model; + + if (q.includes('module') || q.includes('loaded')) { + return `Loaded ${m.modules.loaded} modules (${m.modules.modifiedCount} modified: ${m.modules.modified.join(', ') || 'none'})`; + } else if (q.includes('skill')) { + return `Installed ${m.skills.count} skills: ${m.skills.skills.join(', ') || 'none'}`; + } else if (q.includes('file') || q.includes('vfs')) { + return `${m.files.total} files in ${m.files.directories} directories, total ${m.files.size}`; + } else if (q.includes('agent') || q.includes('orchestrat')) { + return `${m.agents.agents} agents (${m.agents.active} active)`; + } else if (q.includes('health') || q.includes('status')) { + return `Health: ${m.system.health} · Memory: ${m.system.memory} · ${m.telemetry.recentErrors > 0 ? `${m.telemetry.recentErrors} recent errors` : 'No errors'}`; + } else if (q.includes('memory') || q.includes('graph')) { + return `Memory Graph: ${m.memory.nodes} nodes, ${m.memory.edges} edges (${m.memory.active ? 'active' : 'inactive'})`; + } else if (q.includes('change') || q.includes('yesterday') || q.includes('recent')) { + const diff = this._diffSincePrev(); + const recent = m.recent || []; + const parts = []; + if (diff.length) parts.push('Since the last snapshot: ' + diff.join('; ')); + if (recent.length) parts.push('Current: ' + recent.join('; ')); + return parts.length ? parts.join(' · ') : 'No previous snapshot yet — run world-model-refresh twice'; + } else if (q.includes('project') || q.includes('work')) { + const projects = m.projects.activeProjects; + return projects.length > 0 + ? `Active projects: ${projects.map(p => `${p.name} (${p.accesses}x)`).join(', ')}` + : 'No active projects detected'; + } else if (q.includes('telemetry') || q.includes('latency')) { + return `${m.telemetry.toolLatencyDataPoints} tool latency data points tracked, ${m.telemetry.recentErrors} recent errors`; + } else if (q.includes('summary') || q.includes('overview') || q.includes('all')) { + return `newOS4 ${m.version} · ${m.modules.loaded}/${m.modules.total} modules · ${m.skills.count} skills · ${m.files.total} files · ${m.agents.agents} agents · ${m.memory.nodes} graph nodes · ${m.telemetry.recentErrors > 0 ? `⚠️ ${m.telemetry.recentErrors} errors` : '✅ OK'}`; + } else { + return `Unknown query. Try: modules, skills, files, agents, health, memory, changes, projects, telemetry, or summary.`; + } + } + + // 2026-10-21: ask() used to be a keyword table only, so any real question got + // "Unknown query". Whitelist the fields we are willing to send to a model + // (never the raw model object: it is persisted alongside other VFS data). + _modelDigest(maxChars) { + const m = this._model; + if (!m) return ''; + const cap = maxChars || 4000; + const pick = { + version: m.version, + synthesized: m.synthesized, + system: m.system, + modules: m.modules, + files: m.files, + skills: { count: m.skills && m.skills.count, skills: ((m.skills && m.skills.skills) || []).slice(0, 40) }, + memory: m.memory, + agents: m.agents, + telemetry: m.telemetry, + projects: m.projects, + recent: (m.recent || []).slice(0, 20), + // 2026-10-21: the model only ever exposed a COUNT ("9 recent errors") with no detail, + // so neither the user nor the LLM could say what failed. Carry the last few. + recentErrorSample: this._errorSample ? this._errorSample() : [], + changesSinceLastSnapshot: this._diffSincePrev ? this._diffSincePrev() : [] + }; + let json = JSON.stringify(pick); + if (json.length > cap) json = json.slice(0, cap) + '…(digest truncated)'; + return json; + } + + // Last few tool/agent errors with their tool name, for the digest (bounded). + _errorSample() { + try { + const ds = this.terminal.moduleManager.modules['DebugSupervisorModule']; + const db = ds && ds._errorDB && ds._errorDB.errors; + if (!db) return []; + return Object.keys(db).slice(-5).map(k => { + const e = db[k] || {}; + return String(e.tool || e.module || k).slice(0, 40) + ': ' + String(e.error || e.message || '').slice(0, 80); + }); + } catch (e) { return []; } + } + + _buildAskPrompt(question) { + return [ + 'You are the newOS4 world model interface. Answer ONLY from the JSON snapshot below.', + 'If the snapshot does not contain the answer, say what is missing and name the command that', + 'would produce it (world-model-refresh, world-model, modules, test). Never invent numbers.', + 'Answer in 1-3 short sentences, plain text, no markdown headers.', + '', + 'SNAPSHOT: ' + this._modelDigest(), + '', + 'QUESTION: ' + String(question || '').trim() + ].join('\n'); + } + + _diffSincePrev() { + try { + const fs = this.terminal.fs; + const prev = fs && fs.fsReadJSON('/system/world-model.prev.json'); + const cur = this._model; + if (!prev || !cur) return []; + const out = []; + if (prev.version && cur.version && prev.version !== cur.version) out.push(`version ${prev.version} → ${cur.version}`); + const fields = [['modules', 'loaded'], ['skills', 'count'], ['files', 'total'], ['agents', 'agents'], ['memory', 'nodes']]; + fields.forEach(function (f) { + const a = prev[f[0]] && prev[f[0]][f[1]]; + const b = cur[f[0]] && cur[f[0]][f[1]]; + if (typeof a === 'number' && typeof b === 'number' && a !== b) out.push(`${f[0]} ${a} → ${b}`); + }); + if (prev.synthesized) out.push('previous snapshot: ' + prev.synthesized); + return out; + } catch (e) { return []; } + } + + refresh() { + this._synthesize(); + this.terminal.print(`✅ World Model refreshed at ${new Date().toLocaleTimeString()}`, 'success'); + } + + setConfig(args) { + if (args.length < 2) return this.terminal.print('Usage: world-model-config ', 'error'); + const key = args[0]; + const val = args[1]; + + const configMap = { + 'refresh-interval': () => { + const n = parseInt(val) * 1000; + if (isNaN(n) || n < 10000) return 'Must be >= 10 seconds'; + this._REFRESH_INTERVAL = n; + if (this._refreshTimer) clearInterval(this._refreshTimer); + this._refreshTimer = setInterval(() => this._synthesize(), n); + return `refresh-interval set to ${val}s`; + }, + 'max-projects': () => { + const n = parseInt(val); + if (isNaN(n) || n < 1) return 'Must be >= 1'; + this._MAX_PROJECTS = n; + return `max-projects set to ${n}`; + }, + }; + + if (!configMap[key]) return this.terminal.print(`Unknown: ${key}. Valid: ${Object.keys(configMap).join(', ')}`, 'error'); + const msg = configMap[key](); + this.terminal.print(`✅ ${msg}`, 'success'); + } + + // ─── Context Injection ─── + + getContextForPrompt() { + if (!this._model) return ''; + const m = this._model; + const agentStr = m.agents.error + ? 'agents: N/A' + : `${m.agents.agents} agents`; + return `[World Model Context]\nSystem: newOS4 ${m.version} · ${m.modules.loaded} modules · ${m.skills.count} skills · ${m.files.total} files · ${agentStr}${m.telemetry.recentErrors > 0 ? ` · ⚠️ ${m.telemetry.recentErrors} errors` : ' · ✅ OK'}${m.modules.modifiedCount > 0 ? `\nModified modules: ${m.modules.modified.slice(0, 3).join(', ')}${m.modules.modifiedCount > 3 ? ` (+${m.modules.modifiedCount - 3} more)` : ''}` : ''}`; + } + + // ─── Persistence ─── + + _loadFromVFS() { + try { + const fs = this.terminal.fs; + if (!fs) return; + const data = fs.fsReadJSON('/system/world-model.json'); + if (data) { + this._model = data; + this._lastRefresh = data.synthesized; + } + } catch (e) { + console.warn('[WorldModel] Load error (fresh start):', e.message); + } + } + + // v4.5.89: Atomic write — temp file + rename to prevent corruption + _saveToVFS(fs) { + try { + if (!fs.readDir('/system')) fs.mkdir('system'); + // 2026-10-21: keep one previous model so "what changed" can be a real diff. + const prevModel = fs.fsReadJSON('/system/world-model.json'); + if (prevModel) { try { fs.fsWriteJSON('/system/world-model.prev.json', prevModel); } catch (_) {} } + // v4.5.89b fix: Clean stale temp from previous crash (P1 from review) + try { fs.deleteFile('/system/world-model.tmp.json'); } catch (_) {} + fs.fsWriteJSON('/system/world-model.tmp.json', this._model); + const tmp = fs.fsReadJSON('/system/world-model.tmp.json'); + if (tmp) { + fs.fsWriteJSON('/system/world-model.json', tmp); + fs.deleteFile('/system/world-model.tmp.json'); + } else { + console.error('[WorldModel] Save failed: temp file corrupt after write'); + } + } catch (e) { + console.error('[WorldModel] Save error:', e.message); + } + } +} + +// Required for module system +if (typeof window !== 'undefined') { + window.WorldModelModule = WorldModelModule; +} \ No newline at end of file diff --git a/.bkp-fix-20261023_143429/report.php b/.bkp-fix-20261023_143429/report.php new file mode 100644 index 0000000..cefcae6 --- /dev/null +++ b/.bkp-fix-20261023_143429/report.php @@ -0,0 +1,112 @@ + { success, id, duplicate, url } +require_once __DIR__ . '/config.php'; + +if ($_SERVER['REQUEST_METHOD'] !== 'POST') { + error_response('Method not allowed', 405); +} + +define('REPORTS_DIR', __DIR__ . '/../../data/reports'); +define('RATE_FILE', DEBUG_DATA_DIR . '/report_rate.json'); +define('MAX_REPORT_KB', 512); +define('RATE_MAX', 5); // reports per IP … +define('RATE_WINDOW', 600); // … per 10 minutes + +$raw = file_get_contents('php://input'); +if (!$raw) error_response('Empty request body'); +if (strlen($raw) > MAX_REPORT_KB * 1024) { + error_response('Payload too large (max ' . MAX_REPORT_KB . 'KB)', 413); +} + +$payload = json_decode($raw, true); +if (!is_array($payload)) error_response('Invalid JSON'); + +$title = trim((string)($payload['title'] ?? '')); +if ($title === '') error_response('title is required'); +if (mb_strlen($title) > 160) $title = mb_substr($title, 0, 160); +$body = (string)($payload['body'] ?? ''); +if (mb_strlen($body) > 4000) $body = mb_substr($body, 0, 4000); +$diag = (isset($payload['diagnostics']) && is_array($payload['diagnostics'])) ? $payload['diagnostics'] : []; + +// Storage guard: data/ is not writable by php-fpm, so never pretend we wrote a report. +if (!is_dir(REPORTS_DIR)) @mkdir(REPORTS_DIR, 0775, true); +if (!is_dir(REPORTS_DIR) || !is_writable(REPORTS_DIR)) { + error_response('Report storage unavailable', 503); +} + +// Per-IP rate limit +$ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown'; +$now = time(); +$rate = load_json(RATE_FILE); +$hits = array_values(array_filter($rate[$ip] ?? [], function ($t) use ($now) { + return $t > $now - RATE_WINDOW; +})); +if (count($hits) >= RATE_MAX) { + header('Retry-After: ' . RATE_WINDOW); + error_response('Rate limit exceeded (max ' . RATE_MAX . ' reports per 10 minutes)', 429); +} +$hits[] = $now; +$rate[$ip] = $hits; +foreach ($rate as $k => $v) { + $v = array_values(array_filter($v, function ($t) use ($now) { return $t > $now - 86400; })); + if ($v) $rate[$k] = $v; else unset($rate[$k]); +} +save_json(RATE_FILE, $rate); + +// Dedupe: same title + error set + version within 24h collapses onto the existing report. +$fingerprint = sha1($title . '|' . json_encode($diag['errors'] ?? []) . '|' . (string)($diag['version'] ?? '')); +$existing = glob(REPORTS_DIR . '/RPT-*.json') ?: []; +foreach (array_slice(array_reverse($existing), 0, 100) as $f) { + $d = json_decode(@file_get_contents($f), true); + if (!is_array($d)) continue; + if (($d['fingerprint'] ?? '') === $fingerprint && strtotime($d['created_at'] ?? '') > $now - 86400) { + // Don't discard user content on a duplicate: keep it as an appended note. + $d['duplicate_count'] = (int)($d['duplicate_count'] ?? 0) + 1; + if ($body !== '' && ($d['body'] ?? '') !== $body) { + $d['body'] = trim((string)($d['body'] ?? '') . "\n---\n[duplicate " . gmdate('c') . "] " . $body); + $json = json_encode($d, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); + if ($json !== false) { @file_put_contents($f, $json); @chmod($f, 0664); } + } + json_response([ + 'success' => true, + 'id' => $d['id'], + 'duplicate' => true, + 'url' => '/spaces/newos4/downloads/bugs/BUG-REPORT.html', + ]); + } +} + +$id = 'RPT-' . gmdate('Ymd-His') . '-' . substr(bin2hex(random_bytes(4)), 0, 8); +$record = [ + 'id' => $id, + 'created_at' => gmdate('c'), + 'fingerprint' => $fingerprint, + 'title' => $title, + 'body' => $body, + 'diagnostics' => $diag, + 'meta' => [ + 'ip' => $ip, + 'ua' => $_SERVER['HTTP_USER_AGENT'] ?? '', + ], +]; + +$file = REPORTS_DIR . '/' . $id . '.json'; +$json = json_encode($record, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); +if ($json === false || file_put_contents($file, $json) === false) { + error_response('Failed to persist report', 500); +} +@chmod($file, 0664); + +// Regenerate the public page immediately so the URL we print is never stale. +$generator = realpath(__DIR__ . '/../../scripts/gen-bug-report.py'); +if ($generator) { + @exec('/usr/bin/python3 ' . escapeshellarg($generator) . ' 2>&1', $genOut, $genRc); +} + +json_response([ + 'success' => true, + 'id' => $id, + 'duplicate' => false, + 'url' => '/spaces/newos4/downloads/bugs/BUG-REPORT.html', +], 201); diff --git a/.bkp-fix-20261023_143429/skill-pack-core.js b/.bkp-fix-20261023_143429/skill-pack-core.js new file mode 100644 index 0000000..f204e70 --- /dev/null +++ b/.bkp-fix-20261023_143429/skill-pack-core.js @@ -0,0 +1,1150 @@ +class SkillPackCoreModule { + constructor() { + this.commands = {}; + this.commandsMeta = { + 'skill-list': { argType: 'enum', enumFn: '_getSkillCategories' }, 'skill-view': { argType: 'file' }, + 'skill-create': { argType: 'dir' }, 'skill-patch': { argType: 'file' }, + 'skill-delete': { argType: 'file' }, 'skill-export': { argType: 'file' }, + 'skill-sync': { argType: 'file' }, + }; + this.terminal = null; + this._seeded = false; + this._skills = this._buildSkills(); + } + + requirements() { + try { + const fs = this.terminal && this.terminal.fs; + if (fs && fs.readFile('/skills/_seeded')) { + return { met: true, missing: [] }; + } + return { met: false, missing: ['Skills directory not initialized. Will be created on first use.'] }; + } catch (e) { + return { met: true, missing: [] }; + } + } + + getHelpInfo() { + return { + 'skill-pack-core': 'Seed and manage built-in core skills (navigation, memory, search, modules, backup, agents, webdav, security, etc.)', + 'skill-list': 'skill-list [category] — List all skills, optionally filtered by category', + 'skill-view': 'skill-view — View a skill\'s full content', + 'skill-create': 'skill-create "" [tags] — Create a new skill', + 'skill-patch': 'skill-patch "" — Replace a skill\'s full content', + 'skill-delete': 'skill-delete — Delete a skill', + 'skill-export': 'skill-export — Export a skill as JSON', + 'skill-import': 'skill-import — Import a skill from JSON string', + 'skill-sync': 'skill-sync — Rebuild skills index from /skills/ directory', + 'learn-skill': 'learn-skill — Reflect on topic, draft a skill from experience', + 'skill-validate': 'skill-validate — Validate skill structure and get confidence score', + 'skill-audit': 'skill-audit — Audit skills for stale, duplicate, and broken references', + 'skill-restore': 'skill-restore — Restore an archived skill', + 'skill-webdav-sync': 'skill-webdav-sync — Push all skills to WebDAV as JSON files', + }; + } + + _buildSkills() { + const s = (name, desc, tags, cat, steps) => ({ + name, description: desc, tags, category: cat, + content: '---\nname: ' + name + '\ndescription: ' + desc + '\ntags: [' + tags.join(', ') + ']\ncategory: ' + cat + '\n---\n## Steps\n' + steps + }); + + const sk = (name, desc, tags, cat, content) => ({ + name, description: desc, tags, category: cat, + content: '---\nname: ' + name + '\ndescription: ' + desc + '\ntags: [' + tags.join(', ') + ']\ncategory: ' + cat + '\n---\n' + content + }); + + return [ + s('navigating-the-terminal', 'How to explore the WebX11 terminal, find commands, check status, and navigate the filesystem', ['terminal', 'navigation', 'help', 'filesystem'], 'system', +`1. **Get an overview of available commands:** + - Type "help" for a full command list sorted by category + - Type "status" to see filesystem state, loaded modules, Ollama status + - For a specific command, use help + - Type "commands" for a compact list with module source info + - Type "check" for system health including module requirements + +2. **Explore the filesystem:** + - Use ls, ll, tree to browse directories + - Use cd to navigate, pwd to show current path + - Use cat or edit to view file contents + - Use find or grep to search files + - Use du to see disk usage + +3. **Understand system state:** + - mem — memory usage (auto-shown in header on boot) + - status — network, files, modules, Ollama + - modules — module manager GUI + - health-check — VFS size, modules, logs, trash, skills, tool failures + - checkpoint-list — VFS checkpoint snapshots + +4. **Agent / Orchestration state:** + - agent-list — registered agents (type, capabilities, status) + - orchestrate — list active orchestrated agents + - lease-list — show active leases with TTL + +5. **When user asks a vague question:** + - First run status and help to understand the current state + - Then ask clarifying questions based on what you found`), + + s('memory-and-profile', 'How to use the user profile system, session memory, and pattern tracking for persistent context', ['memory', 'profile', 'session', 'persistence'], 'system', +`1. **User Profile (persists across sessions):** + - user_get(key) — read any profile field (e.g. "preferredModel", "fontSize") + - user_set(key, value) — write to profile, auto-persists to localStorage + - user_list — show all stored profile keys + - user_forget(key) — delete a profile key + - Dot notation supported: user_set knownFacts.name "Alex" + +2. **Session Memory (VFS-based daily logs):** + - session_save(summary) — save a summary of what was accomplished + - session_load(date) — read a specific day's log (format: YYYY-MM-DD) + - session_recent(count) — show last N session summaries + - session_stats — aggregate totals across all sessions + - Recent sessions auto-load into context on first message + - systemd-style journal: use logs and logrotate commands for persistent logging + +3. **Conversation Patterns:** + - Pattern tracking runs automatically — frequent topics surface as hints + - my-set, my-get, my-list store additional personal data + - AI conversation context persists within a session (use ai-clear to reset) + - debug command toggles real-time debug panel with Copy/Export/VFS buttons + +4. **Checkpoints (VFS snapshots):** + - checkpoint [name] — snapshot current VFS state + - checkpoint-list — list all checkpoints + - rollback — restore from checkpoint + - checkpoint-del — delete a checkpoint + +5. **When to use:** + - At start: call session_recent(3) to see what was worked on before + - After identifying user preferences: call user_set to store them + - After complex tasks: call session_save with a brief summary + - Before risky edits: call checkpoint to create a restore point`), + + s('web-search-and-fetch', 'How to search the web and fetch URL content using the WebX11 proxy API', ['search', 'fetch', 'web', 'proxy', 'api'], 'research', +`1. **Search the web:** + - Use search or web_search(query) — uses /api/search which queries SearXNG + DDG fallback + - Results include: title, url, content snippet, source engine + - Always available (same-origin, no CORS issues) + - 5-minute server-side cache for repeated queries + - Alternative: use ddg or duck for DuckDuckGo direct, or s as alias + +2. **Fetch URL content:** + - Use fetch or web_fetch(url) — uses /api/extract which strips HTML, extracts clean text + - Returns: title, content (up to 4000 chars), word_count, extraction method + - SSRF protection blocks internal network addresses + - 15-minute server-side cache + +3. **Search flags (via terminal search command):** + - search --summarize — adds AI-generated overview via Ollama + - search --fetch — fetches content snippets for top 3 results + +4. **When to use:** + - User asks about current events, facts, research → use search + - User says "read this article" or provides a URL → use fetch + - For in-depth analysis: search first, then fetch top results`), + + s('module-management', 'How to view, edit, test, reload modules and manage startup configuration', ['modules', 'editing', 'code', 'self-improvement'], 'dev', +`1. **View module source:** + - view_module(name) — fetches original source or reads from _mods/ if edited + - Supports all loaded modules: SystemModule, FileSystemModule, SearXNGModule, etc. + - Or use modules command for GUI module manager + +2. **Edit a module:** + - edit_module(name) — copies source to _mods/.js and opens code editor + - Editor shows the full source in a textarea + - Click "Save & Test" to save and auto-load the edited version + - Checkpoint before editing: checkpoint mod-edit- + +3. **Test and reload:** + - test_module(name) — loads edited module from _mods/ for live testing + - reload_module(name) — discards edits and reloads from original URL + - list_modified_modules — shows all modules with pending edits + +4. **Check for updates:** + - check_updates — HEAD-requests all loaded module URLs to verify reachability + +5. **Manage startup:** + - startup-modules — opens the Startup Modules UI to select which modules load + - load-local — load a module from VFS or URL without editing + - loadmod — alternative: load module from URL + - removemod — remove a loaded module + +6. **Agent modules (Phase 2):** + - module-agent-registry.js — Agent registry with agent-list, agent-register, agent-unregister, agent-status + - module-leases.js — Time-bound contracts with 5s watchdog, auto-expiry + - module-orchestrator.js — Spawn, delegate, kill, purge with heartbeat monitoring + +7. **When to use:** + - User reports a bug in a module → view_module, edit_module, test_module + - User asks "can you add X feature?" → same workflow + - Always save edits before testing. If test fails, the original module stays loaded.`), + + s('system-maintenance', 'How to reset, troubleshoot, and maintain the WebX11 terminal', ['system', 'maintenance', 'reset', 'troubleshoot', 'config'], 'system', +`1. **Factory reset (wipes everything):** + - factory-reset --confirm — clears all webx11_ localStorage keys, resets VFS, reloads + - Use only when the system is in an unrecoverable state + +2. **Soft VFS reset:** + - reset-vfs --confirm — clears only the filesystem, preserves settings/profile/modules + +3. **Config management:** + - config-list — show all config keys and values + - config-set — set a config value + - config-delete — remove a config key + - config-export — dump all config as JSON + - config-reset — restore defaults (use before factory-reset if you want to save data) + +4. **Security level:** + - security-level <0-3> — set security level (0=Permissive, 1=Standard, 2=Paranoid, 3=Audit) + - security — show current security level, blocked count, and stats + - cmd-allow — whitelist a command from security checks + - cmd-deny — blacklist a command + +5. **Health checks (run automatically after complex tasks):** + - Check module loading status: loaded vs total + - Check for pending module edits + - Check memory usage (high if >100MB) + - Check skill count (0 skills after 2+ tool calls → suggest create_skill) + - health-check — full audit: VFS size, modules, logs, trash, skills, tool failures + +6. **Font size and display:** + - font-size <8-32> — set terminal font size, persists across sessions + - setfont — alias for font-size + +7. **Checkpoints:** + - checkpoint [name] — snapshot current VFS state + - checkpoint-list — list all checkpoints + - rollback — restore from checkpoint + +8. **When to use:** + - System is slow or modules fail to load → check check_updates, mem + - User says "reset everything" → factory-reset --confirm + - User says "clear my files but keep settings" → reset-vfs --confirm + - User reports security concerns → security-level or cmd-allow/cmd-deny`), + + s('backup-and-restore', 'How to backup, restore, and export WebX11 data', ['backup', 'restore', 'export', 'import', 'persistence'], 'system', +`1. **Backup VFS:** + - vfs-export — downloads the entire virtual filesystem as a dated JSON file + - backup — creates a full backup including VFS, modules, config (saves to _backups/) + +2. **WebDAV cloud backup:** + - webdav-backup — push VFS snapshot to WebDAV server (auto-archive) + - webdav-backup-list — list remote WebDAV backups + - webdav-restore — restore VFS from remote WebDAV snapshot + - webdav-push — push file to WebDAV space + - webdav-pull — pull file from WebDAV space + - webdav-ls [path] — list remote WebDAV directory contents + - webdav-status — check WebDAV connection and space usage + - webdav-explorer — GUI file browser for WebDAV + - webdav-browser — simpler file listing for WebDAV + - webdav-config — configure WebDAV endpoint and credentials + +3. **Export/Import personal data:** + - my-export — prints all my-set data as formatted JSON + - my-import — bulk-import personal data from JSON string + +4. **Export/Import modules:** + - export-mods — exports module registry as JSON + - import-mods — imports modules from JSON backup + +5. **Restore:** + - restore — restores terminal state from a backup JSON + - Session logs are saved to /memory/ automatically on page unload + +6. **When to use:** + - Before factory-reset: call my-export, vfs-export, export-mods to save data + - When switching browsers or devices: export all, then import + - User says "backup my data" or "save my files" + - For cloud backup: use webdav-backup (requires WebDAV server configured)`), + + s('mindmap-workflow', 'How to create, view, edit, and manage brainstorming mindmaps', ['mindmap', 'brainstorming', 'planning', 'organization'], 'productivity', +`1. **Create a new mindmap:** + - mindmap new "" -t tag1,tag2 — creates a root node + - Tags are optional (e.g. planning, research, dev) + - Root gets auto-assigned a color from 7-color palette + +2. **Add child nodes:** + - mindmap add "" -t tag — add a child + - Get the parent-id from mindmap show + - IDs are 4-char random strings (e.g. a3f8, x7k2) + +3. **View:** + - mindmap show — full tree with Unicode box-drawing + - mindmap show — subtree starting at that node + +4. **Edit nodes:** + - mindmap rename "" — change text + - mindmap color — set color (red/green/yellow/blue/cyan/magenta/white) + - mindmap tag — add a tag + +5. **Persistence:** + - mindmap save — save to localStorage + - mindmap load — restore + - mindmap list — show saved mindmaps + - mindmap delete — remove saved + +6. **Export:** + - mindmap export [id] — Markdown bullet list output, auto-copies to clipboard + +7. **When to use:** + - Map out project structure, brainstorm features, organize ideas + - Break down complex tasks into subtrees + - User says "brainstorm", "diagram", "organize thoughts", "plan"`), + + s('skills-system-guide', 'How to use the skills system: load, create, patch, list, and audit skills for persistent procedural memory', ['skills', 'system', 'meta'], 'system', +`1. **Always load skills at task start:** + - At the START of every task, type or call "skill-view " or "skill-list" + - Example: skill-view "module-management" — returns matching skill content + - Example: skill-list system — browse skills by category + +2. **Create skills after complex tasks:** + - After any task with 3+ tool calls, call "skill-create" + - Required fields: name (kebab-case), description, content (markdown with YAML frontmatter) + - Optional: tags (comma-separated), category (e.g. research, system, dev) + - Skills persist in /skills/ as .md files with /skills/index.json for fast lookup + - Skill pack auto-seeds 8 core skills on first boot + +3. **Patch skills when they're outdated:** + - If a stored procedure has wrong steps, call "skill-patch " + - This replaces the entire skill content and updates the index + - Do NOT create a duplicate — always patch the existing skill + +4. **Auto-created error skills:** + - If the same tool fails 3+ times, a workaround skill is created automatically + - Named tool--workaround (e.g. tool-write_file-workaround) + +5. **Audit and maintenance:** + - skill-list — browse available skills, optionally filter + - skill-export — download a skill as markdown + - skill-import — bulk import skills + - skill-sync — re-index skills from /skills/ directory + - Skills are audited every 20 messages for staleness (empty content, >30 days old) + +6. **YAML frontmatter format:** + - Each skill file must start with: ---\\nname: \\ndescription: \\ntags: [...]\\ncategory: \\n---\\n + - Steps follow under ## Steps heading + - Use numbered lists for sequential steps`), + + sk('agent-orchestration', 'How to use the agent registry, leases, and orchestrator for multi-agent workflows', ['agents', 'orchestration', 'delegation', 'leases'], 'system', +`## Steps +1. **Agent Registry:** + - agent-list — list all registered agents (ID, type, capabilities, status) + - agent-register — register a new agent (capabilities: comma-separated like "code,research") + - agent-unregister — remove an agent + - agent-status — detailed status including uptime, task history + +2. **Leases (time-bound contracts):** + - lease-create — create a lease with watchdog timer + - lease-list — show active leases with remaining TTL + - lease-revoke — terminate a lease early + - lease-renew — extend a lease + +3. **Orchestrator (spawn, delegate, kill):** + - orchestrator-spawn — spawn a new agent automatically (registers + creates lease) + - orchestrator-kill — kill an agent (revokes lease + unregisters) + - orchestrate — list all active agents with their orchestrator state + - orchestrator-purge — kill all idle/expired agents + - orchestrator-status — full system status: active, idle, dead agents + +4. **AI tool integration:** + - AI tools list_agents, delegate_task, spawn_agent are available in the AI system prompt + - The AI can auto-spawn worker agents for sub-tasks + - Each agent gets its own VFS workspace at /agents// + +5. **When to use:** + - User needs parallel task execution → orchestrator-spawn coder + researcher + - Time-bound automation → lease-create with short TTL + - System cleanup → orchestrator-purge to kill stale agents`), + + sk('debug-panel-and-logs', 'How to use the debug panel, session logging, and terminal diagnostics', ['debug', 'logging', 'diagnostics', 'troubleshooting'], 'system', +`## Steps +1. **Toggle debug panel:** + - debug — opens real-time debug panel showing last AI tool call, response time, and context + - Debug panel includes Copy, Export, and Write to VFS buttons + - Export: saves tool call log as timestamped JSON to downloads + - Write to VFS: appends debug data to /debug.log (dedup by turn ID) + +2. **Session logging:** + - logs [n] [LEVEL] — view last n log entries (optionally filtered by INFO/WARN/ERROR) + - logrotate — rotate current.log to dated archive (auto-cleans old logs beyond 10) + - Logs stored in /logs/ directory: current.log + dated archives + - Health check runs automatically after complex tasks + +3. **Diagnostic commands:** + - health-check — full audit: VFS size, modules, logs, trash, skills + - check — quick system health including module requirement status + - mem — real-time memory usage (also in header) + - status — network, files, modules, Ollama connectivity + +4. **System journals:** + - debug.log — appended by debug panel VFS button (dedup by turn ID) + - current.log — system activity log with rotation + - Auto-save: terminal state auto-saves every 60s + +5. **When to use:** + - AI is behaving unexpectedly → debug to see tool calls + - User reports errors → logs 50 ERROR to see error entries + - Memory concerns → mem to check, health-check for full audit`), + + sk('webdav-operations', 'How to use WebDAV cloud storage for backup, file sync, and remote file access', ['webdav', 'cloud', 'backup', 'sync', 'storage'], 'system', +`## Steps +1. **Configuration:** + - webdav-config — configure WebDAV endpoint URL and credentials + - Connection auto-tested on config; webdav-status shows connection health + - Uses per-user token stored in .token file on the server side + +2. **File operations:** + - webdav-ls [path] — list remote directory contents + - webdav-push — upload file to WebDAV + - webdav-pull [local-path] — download file from WebDAV + - webdav-explorer — GUI file browser for WebDAV + - webdav-browser — CLI file browser for WebDAV + +3. **Backup & Restore:** + - webdav-backup — push full VFS snapshot to WebDAV (creates /backups/newos4-.json) + - webdav-backup-list — list all remote backup snapshots + - webdav-restore — restore VFS from a remote backup snapshot + +4. **Status & Diagnostics:** + - webdav-status — connection status, last sync, space information + - If uploads fail: check webdav-config, ensure .token file is valid + - WebDAV runs on the same server (nginx with WebDAV module), no external dependency + +5. **When to use:** + - Before factory-reset: webdav-backup to store VFS remotely + - Cross-browser: webdav-push/pull to move files between instances + - User says "cloud backup" or "remote save" → webdav-backup`), + + sk('security-and-safety', 'How to use WebX11 security levels, command blocking, and safety features', ['security', 'safety', 'blocking', 'permissions'], 'system', +`## Steps +1. **Security levels:** + - security-level <0-3> — set security level: + 0 = Permissive (no blocks, no confirms — anything runs) + 1 = Standard (unknown cmds → AI route, destructive → confirm, system → block) + 2 = Paranoid (unknown cmds blocked entirely, destructive blocked) + 3 = Audit (everything allowed, everything logged) + - Default: level 1 + - security — show current level, blocked count, and stats + +2. **Command control:** + - cmd-allow — whitelist a command from security checks + - cmd-deny — blacklist/block a command entirely + - whitelist built-in: help, clear, ls, cat, pwd, whoami, df, free, top, ps, echo, date, env, history + +3. **What gets blocked at each level:** + - Unknown commands: Level 1 → AI routed, Level 2+ → blocked + - Destructive commands (delete, write, save, format, etc.): Level 1 → confirmation, Level 2+ → blocked + - System commands (reboot, shutdown, format, kill): Level 1+ → blocked + - Suspicious flags (--execute, --eval, --shell): Level 1+ → blocked + +4. **Safety checklist:** + - Check security: security shows your current level + - Before running risky commands: create a checkpoint first + - For temporary relax: security-level 0, then restore to 1 + - For total lockdown before leaving: security-level 2 + +5. **When to use:** + - User wants to lock down the terminal → security-level 2 + - User wants to allow a blocked command → cmd-allow + - Debugging strange behavior → verify security-level (user may have set it higher)`), + + sk('mindmap-user-guide', 'How to create, view, and manage brainstorming mindmaps using the mindmap terminal module', ['mindmap', 'brainstorming', 'organization', 'planning'], 'productivity', +`## Steps +1. **Create a new mindmap:** + - mindmap new "" -t tag1,tag2 — creates a root node + - Tags are optional (e.g. planning, research, dev) + - Root gets auto-assigned a color + +2. **Add child nodes:** + - mindmap add "" -t tag — add a child + - Get the parent-id from mindmap show + - IDs are 4-char random strings (e.g. a3f8, x7k2) + +3. **View the mindmap:** + - mindmap show — full tree with Unicode box-drawing + - mindmap show — subtree starting at that node + +4. **Edit nodes:** + - mindmap rename "" — change text + - mindmap color — set color (red/green/yellow/blue/cyan/magenta/white) + - mindmap tag — add a tag + +5. **Persistence:** + - mindmap save — save to localStorage + - mindmap load — restore + - mindmap list — show saved mindmaps + - mindmap delete — remove saved + +6. **Export:** + - mindmap export [id] — Markdown bullet list output + +## When to use +- User wants to brainstorm, organize ideas, plan features +- User says "map out", "diagram", "brainstorm", "organize thoughts" +- Visualizing project structure or task decomposition`), + + sk('floating-ui', 'How to create, manage, and remove floating UI elements — buttons, collapsible menus, and status panels that persist across page reloads', ['floating', 'ui', 'buttons', 'menu', 'gui'], 'ui', +`## Commands + +1. **Create a floating button:** + - floating-add btn