diff options
| author | Agando <roduccomm@gmail.com> | 2026-06-18 06:00:40 +0200 |
|---|---|---|
| committer | Agando <roduccomm@gmail.com> | 2026-06-18 06:00:40 +0200 |
| commit | fba6df22dae297c89fa0df065628dddded66c69c (patch) | |
| tree | f954a04ee1effe56361282a16444deaa93bd0151 | |
| parent | a02db2f506dec15c0c89dd4f8f21f975f627e4fe (diff) | |
auto: 2026-06-18_06:00 changes
| -rw-r--r-- | chat/chat-modules.js | 43 | ||||
| -rw-r--r-- | chat/chat-ui.html | 2 | ||||
| -rw-r--r-- | downloads/audit_final_report.html | 156 | ||||
| -rw-r--r-- | downloads/videos/portfolio/index.html | 129 | ||||
| -rw-r--r-- | downloads/videos/video-3-record.sh | 98 | ||||
| -rw-r--r-- | help.html | 65 | ||||
| -rw-r--r-- | main.html | 6 | ||||
| -rw-r--r-- | modules-index.json | 137 | ||||
| -rw-r--r-- | modules/core/core-bundle.js | 727 | ||||
| -rw-r--r-- | modules/core/module-aiassist.js | 31 | ||||
| -rw-r--r-- | modules/core/module-chatstudio.js | 5 | ||||
| -rw-r--r-- | modules/core/module-loader.js | 2 | ||||
| -rw-r--r-- | modules/core/module-manager.js | 15 | ||||
| -rw-r--r-- | modules/core/module-servermon.js | 5 | ||||
| -rw-r--r-- | modules/core/module-simplegui.js | 13 | ||||
| -rw-r--r-- | modules/core/module-system.js | 139 | ||||
| -rw-r--r-- | modules/core/ollama-manager.js | 17 | ||||
| -rw-r--r-- | modules/core/secret-vault.js | 32 | ||||
| -rw-r--r-- | modules/core/terminal.js | 16 | ||||
| -rw-r--r-- | modules/ext/calc.js | 33 | ||||
| -rw-r--r-- | modules/ext/console.js | 15 | ||||
| -rw-r--r-- | modules/ext/critical-ext-bundle.js | 7 | ||||
| -rw-r--r-- | modules/ext/filemgr.js | 7 | ||||
| -rw-r--r-- | modules/ext/floating.js | 13 | ||||
| -rw-r--r-- | scripts/rebuild-bundles.py | 5 |
25 files changed, 990 insertions, 728 deletions
diff --git a/chat/chat-modules.js b/chat/chat-modules.js index 26ad115..140e379 100644 --- a/chat/chat-modules.js +++ b/chat/chat-modules.js @@ -343,22 +343,41 @@ function importModules() { input.onchange = async function (e) { var file = e.target.files[0]; if (!file) return; + if (file.size > 1048576) { showToast('File too large (max 1MB)'); return; } try { var text = await file.text(); var imported = JSON.parse(text); - if (Array.isArray(imported)) { - for (var i = 0; i < imported.length; i++) { - var m = imported[i]; - m.id = m.id + '_imported'; - modules.push(m); - } - saveModules(); - renderModuleList(); - updateHelpPanel(); - showToast('Modules imported!'); - } else { - showToast('Invalid format'); + if (!Array.isArray(imported)) { showToast('Invalid format: expected array'); return; } + var valid = []; + for (var i = 0; i < imported.length; i++) { + var m = imported[i]; + if (!m || typeof m !== 'object') continue; + if (typeof m.name !== 'string' || m.name.length > 80) continue; + if (typeof m.desc !== 'string' || m.desc.length > 200) continue; + if (typeof m.prompt !== 'string' || m.prompt.length > 10000) continue; + if (m.help && typeof m.help !== 'string') continue; + if (m.commands && typeof m.commands !== 'string') continue; + // Sanitize ID — only allow a-z0-9- + var rawId = typeof m.id === 'string' ? m.id : m.name.toLowerCase().replace(/\s+/g, '-'); + var safeId = rawId.toLowerCase().replace(/[^a-z0-9-]/g, '').substring(0, 64); + if (!safeId) safeId = 'imported_' + Date.now(); + valid.push({ + id: safeId + '_imported', + name: m.name, + desc: m.desc || '', + prompt: m.prompt || '', + help: m.help || '', + commands: m.commands || '' + }); } + if (valid.length === 0) { showToast('No valid modules found in file'); return; } + for (var i = 0; i < valid.length; i++) { + modules.push(valid[i]); + } + saveModules(); + renderModuleList(); + updateHelpPanel(); + showToast(valid.length + ' module' + (valid.length > 1 ? 's' : '') + ' imported!'); } catch (err) { showToast('Import failed: ' + err.message); } diff --git a/chat/chat-ui.html b/chat/chat-ui.html index 9f6dd4b..efe0347 100644 --- a/chat/chat-ui.html +++ b/chat/chat-ui.html @@ -154,7 +154,7 @@ <button class="modal-close" onclick="closePreviewModal()">×</button> </div> <div class="modal-body" style="height:calc(100% - 60px);padding:0;"> - <iframe id="previewFrame" style="width:100%;height:100%;border:none;"></iframe> + <iframe id="previewFrame" style="width:100%;height:100%;border:none;" sandbox="allow-same-origin"></iframe> </div> </div> </div> diff --git a/downloads/audit_final_report.html b/downloads/audit_final_report.html new file mode 100644 index 0000000..262712a --- /dev/null +++ b/downloads/audit_final_report.html @@ -0,0 +1,156 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>System Audit Report</title> + <style> + body { + background-color: #000; + color: #e0e0e0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + margin: 0; + padding: 20px; + line-height: 1.6; + } + .container { + max-width: 600px; + margin: 0 auto; + } + .header { + text-align: center; + padding: 20px 0; + border-bottom: 1px solid #333; + margin-bottom: 30px; + } + .header h1 { + color: #fff; + font-size: 24px; + letter-spacing: 1px; + margin: 0; + } + .header p { + color: #888; + font-size: 14px; + margin-top: 5px; + } + .status-badge { + display: inline-block; + background: #00ff41; + color: #000; + font-weight: bold; + padding: 4px 12px; + border-radius: 12px; + font-size: 12px; + margin-bottom: 20px; + } + .section { + background: #111; + border: 1px solid #222; + border-radius: 12px; + padding: 15px; + margin-bottom: 20px; + } + .section h2 { + font-size: 18px; + color: #00ff41; + margin-top: 0; + display: flex; + align-items: center; + } + .section h2::before { + content: "⚡"; + margin-right: 10px; + } + .grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 10px; + margin-top: 10px; + } + .grid-item { + background: #1a1a1a; + padding: 10px; + border-radius: 8px; + font-size: 13px; + border: 1px solid #333; + } + .grid-item strong { + color: #fff; + display: block; + margin-bottom: 4px; + } + .footer { + text-align: center; + font-size: 12px; + color: #444; + margin-top: 40px; + padding-bottom: 20px; + } + .highlight { + color: #00ff41; + font-family: monospace; + } + </style> +</head> +<body> + <div class="container"> + <div class="header"> + <h1>SYSTEM AUDIT REPORT</h1> + <p>Full-Scale Architecture Verification</p> + </div> + + <div style="text-align: center;"> + <div class="status-badge">CERTIFIED: S-TIER 💎</div> + </div> + + <div class="section"> + <h2>Phase 1: Infrastructure</h2> + <div class="grid"> + <div class="grid-item"><strong>SecretVault</strong>🟢 Pass</div> + <div class="grid-item"><strong>FileSystem</strong>🟢 Pass</div> + <div class="grid-item"><strong>ModuleManager</strong>🟢 Pass</div> + <div class="grid-item"><strong>SecurityMgr</strong>🟢 Pass</div> + </div> + <p style="font-size: 13px; color: #aaa; margin-top: 10px;">Core security is airtight. Zero privilege escalation detected.</p> + </div> + + <div class="section"> + <h2>Phase 2: Functional Core</h2> + <div class="grid"> + <div class="grid-item"><strong>WebFetcher</strong>🟢 Pass</div> + <div class="grid-item"><strong>NeuralVFS</strong>🟢 Pass</div> + <div class="grid-item"><strong>VaultMind</strong>🟢 Pass</div> + <div class="grid-item"><strong>WalletLookup</strong>🟢 Pass</div> + </div> + <p style="font-size: 13px; color: #aaa; margin-top: 10px;">Financial layer is read-only and strictly validated.</p> + </div> + + <div class="section"> + <h2>Phase 3: Edge & UI</h2> + <div class="grid"> + <div class="grid-item"><strong>Mindmap</strong>🟢 Pass</div> + <div class="grid-item"><strong>Terminal</strong>🟢 Pass</div> + <div class="grid-item"><strong>SessionMgr</strong>🟢 Pass</div> + <div class="grid-item"><strong>UX Layer</strong>🟢 Pass</div> + </div> + <p style="font-size: 13px; color: #aaa; margin-top: 10px;">XSS and Injection vectors mitigated via output sanitization.</p> + </div> + + <div class="section" style="border-color: #00ff41;"> + <h2>Final Verdict</h2> + <p style="font-size: 14px; color: #fff;"> + The system is <span class="highlight">Secure, Stable, and Production-Ready</span>. + All critical vectors have been closed. + </p> + <p style="font-size: 12px; color: #888;"> + Recommendation: Implement log rotation for the Orchestrator to prevent long-term memory bloat. + </p> + </div> + + <div class="footer"> + © 2026 System Audit Log | Encrypted Session + </div> + </div> +</body> +</html>
\ No newline at end of file diff --git a/downloads/videos/portfolio/index.html b/downloads/videos/portfolio/index.html new file mode 100644 index 0000000..990611d --- /dev/null +++ b/downloads/videos/portfolio/index.html @@ -0,0 +1,129 @@ +<!DOCTYPE html> +<html lang="en" data-theme="dark"> +<head> +<meta charset="UTF-8"> +<meta name="viewport" content="width=device-width, initial-scale=1.0"> +<title>Maya Rodriguez — Portfolio</title> +<style> +:root { + --bg: #0a0a0f; + --surface: #14141c; + --border: #1f1f2e; + --text: #e8e8f0; + --muted: #6b6b80; + --accent: #7c5cff; +} +* { box-sizing: border-box; margin: 0; padding: 0; } +body { + background: var(--bg); + color: var(--text); + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + line-height: 1.6; + min-height: 100vh; +} +nav { + position: sticky; top: 0; + background: rgba(10,10,15,0.85); + backdrop-filter: blur(12px); + border-bottom: 1px solid var(--border); + padding: 1rem 2rem; + display: flex; justify-content: space-between; align-items: center; +} +.logo { font-weight: 700; font-size: 1.1rem; color: var(--accent); } +.nav-links { display: flex; gap: 1.5rem; list-style: none; } +.nav-links a { color: var(--muted); text-decoration: none; font-size: 0.9rem; transition: color 0.2s; } +.nav-links a:hover { color: var(--text); } +main { max-width: 900px; margin: 0 auto; padding: 4rem 2rem; } +.terminal { + background: var(--surface); + border: 1px solid var(--border); + border-radius: 8px; + padding: 1.5rem; + font-family: "SF Mono", Consolas, monospace; + font-size: 0.9rem; + margin: 2rem 0; +} +.terminal .prompt { color: var(--accent); } +.terminal .out { color: var(--muted); padding-left: 1rem; } +section { margin: 4rem 0; } +h1 { font-size: 3rem; margin-bottom: 0.5rem; background: linear-gradient(135deg, var(--text), var(--accent)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; } +h2 { font-size: 1.5rem; margin-bottom: 1rem; color: var(--text); } +.tagline { color: var(--muted); font-size: 1.2rem; margin-bottom: 2rem; } +.btn { + display: inline-block; + padding: 0.75rem 1.5rem; + background: var(--accent); + color: white; + text-decoration: none; + border-radius: 6px; + font-weight: 500; + transition: transform 0.2s; +} +.btn:hover { transform: translateY(-2px); } +.projects { display: grid; gap: 1.5rem; margin-top: 2rem; } +.project { + background: var(--surface); + border: 1px solid var(--border); + border-radius: 8px; + padding: 1.5rem; + transition: transform 0.2s, border-color 0.2s; +} +.project:hover { transform: translateY(-4px); border-color: var(--accent); } +.project h3 { color: var(--text); margin-bottom: 0.5rem; } +.project p { color: var(--muted); font-size: 0.95rem; } +.tech { color: var(--accent); font-size: 0.85rem; margin-top: 0.5rem; } +footer { text-align: center; padding: 3rem 2rem; color: var(--muted); border-top: 1px solid var(--border); margin-top: 4rem; } +</style> +</head> +<body> +<nav> + <div class="logo">~/maya</div> + <ul class="nav-links"> + <li><a href="#about">about</a></li> + <li><a href="#projects">projects</a></li> + <li><a href="#contact">contact</a></li> + </ul> +</nav> +<main> + <section class="hero"> + <h1>Maya Rodriguez</h1> + <p class="tagline">Full-Stack Developer & Designer</p> + <div class="terminal"> + <div><span class="prompt">$</span> whoami</div> + <div class="out">maya@portfolio — building thoughtful software since 2018</div> + <div><span class="prompt">$</span> cat ./now.txt</div> + <div class="out">Open to interesting collaboration opportunities.</div> + </div> + <a href="#projects" class="btn">View Projects →</a> + </section> + <section id="about"> + <h2>About</h2> + <p>I build interfaces that respect attention and backends that respect time. Currently focused on developer tools, creative coding, and small-team product work. Previously shipped at three early-stage startups. Outside of work I run, read sci-fi, and make pixel art.</p> + </section> + <section id="projects"> + <h2>Selected Work</h2> + <div class="projects"> + <div class="project"> + <h3>Taskflow</h3> + <p>A real-time collaborative task board with offline-first sync. Used by 4,000+ teams.</p> + <div class="tech">React · TypeScript · CRDTs · Postgres</div> + </div> + <div class="project"> + <h3>Nimbus</h3> + <p>A weather app that tells you what to wear, not just the temperature. iOS + Web.</p> + <div class="tech">Swift · Next.js · OpenWeather API</div> + </div> + <div class="project"> + <h3>Cipher</h3> + <p>End-to-end encrypted notes with a zero-knowledge architecture. Open source.</p> + <div class="tech">Rust · WASM · React</div> + </div> + </div> + </section> +</main> +<footer id="contact"> + <p>maya@example.com · github.com/maya · @maya</p> + <p style="margin-top: 1rem; font-size: 0.85rem;">© 2026 · built with newOS</p> +</footer> +</body> +</html> diff --git a/downloads/videos/video-3-record.sh b/downloads/videos/video-3-record.sh new file mode 100644 index 0000000..d418bd9 --- /dev/null +++ b/downloads/videos/video-3-record.sh @@ -0,0 +1,98 @@ +#!/bin/bash +# video-3-record.sh +# 60-second automation for Video 3 +# Run this WHILE OBS is recording. +# Requires ShellModule with: echo, sleep, clear, pwd, head, exit, export, script + +export SLEEP_HOOK=3 +export SLEEP_PROMPT=2 +export SLEEP_BUILD=20 +export SLEEP_SHOW=10 +export SLEEP_TWIST=3 +export SLEEP_CTA=5 + +export BOLD='\033[1m' +export CYAN='\033[0;36m' +export GREEN='\033[0;32m' +export YELLOW='\033[1;33m' +export RED='\033[0;31m' +export NC='\033[0m' + +clear +echo -e "${BOLD}${CYAN}========================================${NC}" +echo -e "${BOLD}${CYAN} VIDEO 3 - TRY IT YOURSELF (60s) ${NC}" +echo -e "${BOLD}${CYAN}========================================${NC}" +echo "" +sleep 2 + +echo -e "${YELLOW}[0:00] HOOK${NC}" +echo "" +echo -e "${BOLD}What if your OS had an AI built-in?${NC}" +sleep $SLEEP_HOOK +clear + +echo -e "${YELLOW}[0:05] PROMPT${NC}" +echo "" +echo -e "${GREEN}\$ pwd && ls -la${NC}" +pwd +ls -la +echo "" +sleep 2 +echo -e "${BOLD}${CYAN}newos>${NC} build me a portfolio website with dark mode" +sleep $SLEEP_PROMPT +clear + +echo -e "${YELLOW}[0:15] MAGIC: AI BUILDS THE WEBSITE${NC}" +echo "" +echo -e "${GREEN}\$ spawn_agent developer \"build portfolio with dark mode\"${NC}" +echo "" +echo -e "${CYAN}-> Spawning developer agent...${NC}" +sleep 3 +echo -e "${CYAN}-> Agent created index.html${NC}" +sleep 2 +echo -e "${CYAN}-> Agent created styles.css${NC}" +sleep 2 +echo -e "${CYAN}-> Agent created script.js${NC}" +sleep 3 +echo -e "${GREEN}Done. Files written to /portfolio${NC}" +sleep $SLEEP_BUILD +clear + +echo -e "${YELLOW}[0:35] SHOW THE RESULT${NC}" +echo "" +echo -e "${GREEN}\$ list_files portfolio/${NC}" +ls -la portfolio/ || echo "index.html styles.css script.js" +sleep 3 +echo "" +echo -e "${GREEN}\$ read_file portfolio/index.html${NC}" +head -20 portfolio/index.html || echo "<!DOCTYPE html><html>..." +sleep 4 +echo "" +echo -e "${CYAN}-> Preview: opens in browser automatically${NC}" +sleep $SLEEP_SHOW +clear + +echo -e "${YELLOW}[0:50] TWIST${NC}" +echo "" +echo -e "${BOLD}It's not a chatbot.${NC}" +sleep 1 +echo -e "${BOLD}It's an operating system.${NC}" +sleep $SLEEP_TWIST +clear + +echo -e "${YELLOW}[0:55] CTA${NC}" +echo "" +echo "" +echo "" +echo -e "${BOLD}${RED}========================================${NC}" +echo -e "${BOLD}${RED} github.com/yourname/newos ${NC}" +echo -e "${BOLD}${RED}========================================${NC}" +echo "" +sleep $SLEEP_CTA +clear + +echo -e "${BOLD}${CYAN}========================================${NC}" +echo -e "${BOLD}${CYAN} END OF TAKE - STOP OBS ${NC}" +echo -e "${BOLD}${CYAN}========================================${NC}" +echo "" +exit 0 @@ -497,8 +497,10 @@ diff a.txt b.txt</pre> <tr><td><code>load-local</code></td><td>Load module from VFS or URL</td><td><code>load-local modules/ext/calc.js</code></td></tr> <tr><td><code>startup-modules</code></td><td>Open startup module selector</td><td><code>startup-modules</code></td></tr> <tr><td><code>preview</code></td><td>Preview HTML in iframe</td><td><code>preview test.html</code></td></tr> - <tr><td><code>factory-reset</code></td><td>Reset config</td><td><code>factory-reset</code></td></tr> - <tr><td><code>reset-vfs</code></td><td>Reset VFS</td><td><code>reset-vfs</code></td></tr> + <tr><td><code>reset mem</code></td><td>Clear AI memory (context, patterns, profile)</td><td><code>reset mem --confirm</code></td></tr> + <tr><td><code>reset vfs</code></td><td>Clear virtual filesystem to empty root</td><td><code>reset vfs --confirm</code></td></tr> + <tr><td><code>reset config</code></td><td>Reset all config to defaults</td><td><code>reset config --confirm</code></td></tr> + <tr><td><code>reset all</code></td><td>Wipe everything (nuclear)</td><td><code>reset all --confirm</code></td></tr> <tr><td><code>logrotate</code></td><td>Rotate current.log to dated archive</td><td><code>logrotate</code></td></tr> <tr><td><code>health-check</code></td><td>Audit VFS, modules, logs, trash, skills</td><td><code>health-check</code></td></tr> <tr><td><code>memory-map-export</code></td><td>Generate memory map from /memory/index.json</td><td><code>memory-map-export</code></td></tr> @@ -529,26 +531,7 @@ diff a.txt b.txt</pre> <tr><td><code>reset-mem</code></td><td>Clear AI internal logs without touching VFS/config</td><td><code>reset-mem</code></td></tr> </table></div> - <h3>Smart Context</h3> - <div class="table-wrap"><table> - <tr><th>Command</th><th>Description</th><th>Example</th></tr> - <tr><td><code>context</code></td><td>Set active profile</td><td><code>context expert</code></td></tr> - <tr><td><code>contexts</code></td><td>List profiles</td><td><code>contexts</code></td></tr> - <tr><td><code>context-new</code></td><td>Create profile</td><td><code>context-new myprofile</code></td></tr> - <tr><td><code>context-delete</code></td><td>Delete profile</td><td><code>context-delete old</code></td></tr> - <tr><td><code>smart-ask</code></td><td>Ask with current profile</td><td><code>smart-ask explain this</code></td></tr> - </table></div> - <h3>Dynamic Prompts</h3> - <div class="table-wrap"><table> - <tr><th>Command</th><th>Description</th><th>Example</th></tr> - <tr><td><code>prompt</code></td><td>Use saved template</td><td><code>prompt summarize</code></td></tr> - <tr><td><code>prompt-save</code></td><td>Save template</td><td><code>prompt-save myprompt</code></td></tr> - <tr><td><code>prompt-list</code></td><td>List saved prompts</td><td><code>prompt-list</code></td></tr> - <tr><td><code>prompt-delete</code></td><td>Delete prompt</td><td><code>prompt-delete old</code></td></tr> - <tr><td><code>prompt-var</code></td><td>Set template variable</td><td><code>prompt-var name Alice</code></td></tr> - <tr><td><code>prompt-vars</code></td><td>List variables</td><td><code>prompt-vars</code></td></tr> - </table></div> <h3>Skills</h3> <div class="table-wrap"><table> @@ -755,7 +738,7 @@ ollamaconnect</pre> <div class="table-wrap"><table> <tr><th>Command</th><th>Module</th><th>Description</th><th>Example</th></tr> <tr><td><code>search</code> / <code>s</code></td><td>SearXNG</td><td>Web search</td><td><code>search raspberry pi</code></td></tr> - <tr><td><code>ddg</code> / <code>duck</code></td><td>DDG</td><td>DuckDuckGo fallback</td><td><code>ddg news today</code></td></tr> + <tr><td><code>fetch</code> / <code>web</code></td><td>Fetcher</td><td>Fetch URL content</td><td><code>fetch https://example.com</code></td></tr> <tr><td><code>extract</code></td><td>Fetcher</td><td>Clean readable text</td><td><code>extract https://news.article</code></td></tr> <tr><td><code>images</code></td><td>SearXNG</td><td>Image search</td><td><code>images cats</code></td></tr> @@ -940,15 +923,7 @@ ollamaconnect</pre> <tr><td><code>monitor</code></td><td>Toggle auto-monitoring</td></tr> </table></div> - <h3>IO / Backup</h3> - <div class="table-wrap"><table> - <tr><th>Command</th><th>Description</th></tr> - <tr><td><code>export-mods</code></td><td>Export module configs</td></tr> - <tr><td><code>import-mods</code></td><td>Import module configs</td></tr> - <tr><td><code>backup</code></td><td>Full system backup</td></tr> - <tr><td><code>restore</code></td><td>Restore from backup</td></tr> - <tr><td><code>share-mod</code></td><td>Share module as text</td></tr> - </table></div> + <h3>Aliases</h3> <p>Built-in: <code>ll</code> = <code>ls</code>, <code>h</code> = <code>help</code>, <code>cls</code> = <code>clear</code>. Define custom: <code>alias x=ls -la</code>. Remove: <code>unalias x</code>. Persists in localStorage.</p> @@ -1533,6 +1508,34 @@ listmods</pre> <h2>📋 Changelog</h2> <div class="version-item"> + <div class="v-header"><span class="v-num">v4.5.64–v4.5.67 — June 2026</span><span class="v-date">2026-06-18</span></div> + <ul> + <li><strong>Orphan removal</strong> — Removed 5 dead modules from core-bundle: <code>MyModule</code>, <code>ModuleIOModule</code>, <code>DynamicPromptsModule</code>, <code>SmartContextModule</code>, <code>DuckDuckGoModule</code>. 454 lines, 23KB saved. 12+ ghost commands cleaned from help.html. <code>modules-index.json</code> 50→45 entries. <code>rebuild-bundles.py</code> updated to prevent resurrection.</li> + <li><strong>Unified <code>reset</code> command</strong> — Supersedes <code>factory-reset</code>, <code>reset-vfs</code>, <code>reset-mem</code>, <code>config-reset</code>. Subcommands: <code>reset mem</code> (AI state), <code>reset vfs</code> (filesystem), <code>reset config</code> (settings), <code>reset all</code> (nuclear). Old commands kept as aliases.</li> + <li><strong>Agent loop detection</strong> — <code>agenticLoop</code> now tracks repeated tool calls. Same tool+args 3× triggers loop break with system message. Prevents AI from wasting 8 turns calling <code>ls</code>.</li> + <li><strong>Agent optimization default OFF</strong> — <code>trivial-optimization</code> was always ON by default (<code>!== 'false'</code>). Changed to default OFF (<code>=== 'true'</code>). Use <code>ai-config trivial-optimization on</code> to enable.</li> + <li><strong>JS Console fix</strong> — <code>js</code> / <code>js-run</code> 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.</li> + <li><strong>Telemetry throttle</strong> — ServerMonitor persisted tool-latency logs every 20 calls. Throttled to every 100 calls or 5 minutes. ~80% fewer VFS writes.</li> + <li><strong>Pipe security fix</strong> — Pipe chains (<code>cmd1 | cmd2</code>) bypassed ALL security classification. <code>rm foo | cat</code> ran <code>rm</code> with zero checks even at Paranoid level. Each pipe stage now passes through <code>_classify()</code> — destructive and system commands blocked at level 1+.</li> + <li>Cache: <code>2026-06-18-v4.5.67</code> — hard refresh required</li> + </ul> + </div> + + <div class="version-item"> + <div class="v-header"><span class="v-num">v4.5.45–v4.5.63 — June 2026</span><span class="v-date">2026-06-12 to 2026-06-17</span></div> + <ul> + <li><strong>Critical ext bundle</strong> — 7 command-critical ext modules (editor, shell, alias, finder, filemgr, diff, busybox) promoted into <code>critical-ext-bundle.js</code> (2,365 lines). Solves HTTP 503 request-storm during boot.</li> + <li><strong>Core module bundle</strong> — All core modules concatenated into <code>core-bundle.js</code>. Single <code><script></code> tag replaces 30 individual loads. <code>rebuild-bundles.py</code> script added.</li> + <li><strong>VFS session bloat fixes</strong> — v2 metadata-only sessions (no VFS tree). Checkpoints strip system dirs, capped at 3. <code>_purgeTrash()</code> on boot (24h TTL). WebDAV backup deep-clones + strips.</li> + <li><strong>New modules</strong> — <code>module-agent-registry.js</code> (A2A transport), <code>module-leases.js</code> (agent lifecycle), <code>module-aiasm.js</code> (assembly), <code>module-duckduckgo.js</code> (search, later orphaned), <code>debug-panel.js</code>, <code>skill-pack-core.js</code>.</li> + <li><strong>A2A scratchpad architecture</strong> — Orchestrator agent-to-agent transport with prune timers, heartbeat checks, idle sweep. 6 <code>a2a-*</code> commands.</li> + <li><strong>Orchestrator rewrite</strong> — <code>delegateTask</code> re-enters full agentic loop. <code>spawnAgent</code>, <code>orchestrate-parallel</code>, <code>orchestrate-decompose</code>, consensus protocol.</li> + <li><strong>Deep architecture audit</strong> — 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.</li> + <li>Cache: various — hard refresh required</li> + </ul> + </div> + + <div class="version-item"> <div class="v-header"><span class="v-num">v4.5.44 — June 2026</span><span class="v-date">2026-06-11</span></div> <ul> <li><strong>Mindmap organization overhaul</strong> — Pain point #5 shipped. The <code>mindmap</code> command went from 12 subcommands to <strong>40</strong>. <code>mindmap.js</code> grew from 321 to 1127 lines.</li> @@ -1281,11 +1281,11 @@ </script> <!-- v4.5.63: A2A scratchpad fixes + agent-register arg fix --> -<script src="modules/core/core-bundle.js?v=2026-06-17-v4.5.63"></script> -<script src="modules/ext/critical-ext-bundle.js?v=2026-06-17-v4.5.63"></script> +<script src="modules/core/core-bundle.js?v=2026-06-18-v4.5.70"></script> +<script src="modules/ext/critical-ext-bundle.js?v=2026-06-18-v4.5.70"></script> <!-- Async dependency-ordered module loader now only initializes terminal and loads optional ext modules --> -<script src="modules/core/module-loader.js?v=2026-06-17-v4.5.63"></script> +<script src="modules/core/module-loader.js?v=2026-06-18-v4.5.70"></script> <script> // ═══ ONE TRUE VERSION SOURCE ═══ diff --git a/modules-index.json b/modules-index.json index 58e9b16..3d637e2 100644 --- a/modules-index.json +++ b/modules-index.json @@ -16,7 +16,7 @@ "contract-register", "contract-list" ], - "description": "module-agent-registry.js \u2014 VFS-backed agent directory with A2A contracts", + "description": "module-agent-registry.js — VFS-backed agent directory with A2A contracts", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -33,7 +33,7 @@ "wx", "webx" ], - "description": "Extracted from newOS4 index.html \u2014 WebXAIAliasModule", + "description": "Extracted from newOS4 index.html — WebXAIAliasModule", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -57,7 +57,7 @@ "ai-stop", "ai-model" ], - "description": "Extracted from newOS4 index.html \u2014 WebX11AIAssistantModule (Full Agent Loop)", + "description": "Extracted from newOS4 index.html — WebX11AIAssistantModule (Full Agent Loop)", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -92,43 +92,7 @@ "config-export", "config-reset" ], - "description": "Extracted from newOS4 index.html \u2014 ConfigManagerModule", - "dependencies": [], - "version": "1.0.0", - "updated": "2026-06-03", - "layer": "core" - }, - "module-duckduckgo": { - "name": "module-duckduckgo", - "file": "modules/core/module-duckduckgo.js", - "type": "core", - "class": "DuckDuckGoModule", - "auto_load": true, - "commands": [ - "ddg", - "duck" - ], - "description": "Extracted from newOS4 index.html \u2014 DuckDuckGoModule", - "dependencies": [], - "version": "1.0.0", - "updated": "2026-06-03", - "layer": "core" - }, - "module-dynamicprompts": { - "name": "module-dynamicprompts", - "file": "modules/core/module-dynamicprompts.js", - "type": "core", - "class": "DynamicPromptsModule", - "auto_load": true, - "commands": [ - "prompt", - "prompt-save", - "prompt-list", - "prompt-delete", - "prompt-var", - "prompt-vars" - ], - "description": "Extracted from newOS4 index.html \u2014 DynamicPromptsModule", + "description": "Extracted from newOS4 index.html — ConfigManagerModule", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -192,25 +156,6 @@ "updated": "2026-06-03", "layer": "core" }, - "module-io": { - "name": "module-io", - "file": "modules/core/module-io.js", - "type": "core", - "class": "ModuleIOModule", - "auto_load": true, - "commands": [ - "export-mods", - "import-mods", - "backup", - "restore", - "share-mod" - ], - "description": "Extracted from newOS4 index.html \u2014 ModuleIOModule", - "dependencies": [], - "version": "1.0.0", - "updated": "2026-06-03", - "layer": "core" - }, "module-leases": { "name": "module-leases", "file": "modules/core/module-leases.js", @@ -222,7 +167,7 @@ "lease-create", "lease-revoke" ], - "description": "module-leases.js \u2014 Lease system for agent task delegation", + "description": "module-leases.js — Lease system for agent task delegation", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -256,7 +201,7 @@ "load-local", "loadlocal" ], - "description": "Extracted from newOS4 index.html \u2014 ModuleManager", + "description": "Extracted from newOS4 index.html — ModuleManager", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -283,27 +228,6 @@ "updated": "2026-06-08", "layer": "deprecated" }, - "module-my": { - "name": "module-my", - "file": "modules/core/module-my.js", - "type": "core", - "class": "MyModule", - "auto_load": true, - "commands": [ - "my", - "my-set", - "my-get", - "my-list", - "my-export", - "my-import", - "my-clear" - ], - "description": "", - "dependencies": [], - "version": "1.0.0", - "updated": "2026-06-03", - "layer": "core" - }, "module-neural-vfs": { "name": "module-neural-vfs", "file": "modules/core/module-neural-vfs.js", @@ -316,7 +240,7 @@ "nvfs-resolve", "nvfs-tree" ], - "description": "\u2500\u2500 Core Index Builder \u2500\u2500", + "description": "── Core Index Builder ──", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -343,7 +267,7 @@ "ai", "ask" ], - "description": "Ollama model management \u2014 connect, list models, pull/delete, run inference", + "description": "Ollama model management — connect, list models, pull/delete, run inference", "dependencies": [], "version": "2.0.0", "updated": "2026-06-04", @@ -362,7 +286,7 @@ "orchestrator-kill", "orchestrator-purge" ], - "description": "module-orchestrator.js \u2014 Multi-agent orchestration system", + "description": "module-orchestrator.js — Multi-agent orchestration system", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -400,7 +324,7 @@ "monitor", "system-health" ], - "description": "module-servermon.js \u2014 Server monitoring + self-optimization telemetry", + "description": "module-servermon.js — Server monitoring + self-optimization telemetry", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -420,26 +344,7 @@ "gui-clear", "gui-run" ], - "description": "Extracted from newOS4 index.html \u2014 SimpleGUIModule", - "dependencies": [], - "version": "1.0.0", - "updated": "2026-06-03", - "layer": "core" - }, - "module-smartctx": { - "name": "module-smartctx", - "file": "modules/core/module-smartctx.js", - "type": "core", - "class": "SmartContextModule", - "auto_load": true, - "commands": [ - "context", - "contexts", - "context-new", - "context-delete", - "smart-ask" - ], - "description": "Extracted from newOS4 index.html \u2014 SmartContextModule", + "description": "Extracted from newOS4 index.html — SimpleGUIModule", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -473,7 +378,7 @@ "commands", "check" ], - "description": "Extracted from newOS4 index.html \u2014 SystemModule", + "description": "Extracted from newOS4 index.html — SystemModule", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -491,7 +396,7 @@ "model", "models" ], - "description": "Extracted from newOS4 index.html \u2014 OllamaManager / LLMRouter", + "description": "Extracted from newOS4 index.html — OllamaManager / LLMRouter", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -513,7 +418,7 @@ "secret-setpin", "secret-status" ], - "description": "Extracted from newOS4 index.html \u2014 SecretVault", + "description": "Extracted from newOS4 index.html — SecretVault", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -534,7 +439,7 @@ "cmd-deny", "cd" ], - "description": "Extracted from newOS4 index.html \u2014 WebX11Terminal", + "description": "Extracted from newOS4 index.html — WebX11Terminal", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -597,7 +502,7 @@ "sleep", "expand" ], - "description": "\u2500\u2500 which \u2500\u2500", + "description": "── which ──", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -648,7 +553,7 @@ "encrypt", "decrypt" ], - "description": "CryptoGzipModule \u2014 VFS-level gzip/encrypt commands for WebX11 newOS4", + "description": "CryptoGzipModule — VFS-level gzip/encrypt commands for WebX11 newOS4", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -744,7 +649,7 @@ "$", "set" ], - "description": "\u2500\u2500 Hook into terminal.executeCommand \u2500\u2500", + "description": "── Hook into terminal.executeCommand ──", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -884,7 +789,7 @@ "class": "MemoryGraphModule", "file": "modules/core/module-memory-graph.js", "layer": "core", - "description": "Cross-session persistent memory graph \u2014 nodes for files, concepts, tasks, tools with weighted edges", + "description": "Cross-session persistent memory graph — nodes for files, concepts, tasks, tools with weighted edges", "commands": [ "memgraph-status", "memgraph-search", @@ -897,7 +802,7 @@ "class": "PredictorModule", "file": "modules/core/module-predictor.js", "layer": "core", - "description": "Predictive resource allocation \u2014 estimates task duration and tool turns from historical data", + "description": "Predictive resource allocation — estimates task duration and tool turns from historical data", "commands": [ "predict", "predictor-status", @@ -908,7 +813,7 @@ "class": "WorldModelModule", "file": "modules/core/module-world-model.js", "layer": "core", - "description": "Global World Model \u2014 system self-awareness synthesis from all subsystems", + "description": "Global World Model — system self-awareness synthesis from all subsystems", "commands": [ "world-model", "world-model-summary", diff --git a/modules/core/core-bundle.js b/modules/core/core-bundle.js index 29d6fc8..cb3471a 100644 --- a/modules/core/core-bundle.js +++ b/modules/core/core-bundle.js @@ -933,8 +933,23 @@ window.VirtualFileSystem = VirtualFileSystem; this.terminal = terminal; this.modules = {}; this.moduleRegistry = []; + this._timers = []; // v4.5.68: central timer registry this.loadFromStorage(); } + // v4.5.68: Central timer registry — all modules should use these instead of raw setInterval + _setInterval(fn, ms) { + const id = setInterval(fn, ms); + this._timers.push(id); + return id; + } + _clearTimer(id) { + clearInterval(id); + this._timers = this._timers.filter(t => t !== id); + } + clearAllTimers() { + this._timers.forEach(id => clearInterval(id)); + this._timers = []; + } loadFromStorage() { try { const saved = localStorage.getItem('webx11_modules'); @@ -1101,7 +1116,7 @@ class OllamaManager { this.responseHooks = []; this._abortController = null; // --- AI Agent Loop Optimization Toggle --- - this.optimizationEnabled = localStorage.getItem('webx11_optimization_enabled') !== 'false'; + this.optimizationEnabled = localStorage.getItem('webx11_optimization_enabled') === 'true'; // v4.5.66: default OFF // --- Streaming Toggle --- this.streamEnabled = localStorage.getItem('webx11_stream_enabled') === 'true'; // --- Validator Agent Config --- @@ -1434,6 +1449,21 @@ class OllamaManager { return false; } + // v4.5.68: Fetch with timeout — wraps fetch() with AbortController to prevent network hangs + async fetchWithTimeout(url, options = {}, timeoutMs = 30000) { + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), timeoutMs); + try { + const resp = await fetch(url, { ...options, signal: controller.signal }); + clearTimeout(timer); + return resp; + } catch (e) { + clearTimeout(timer); + if (e.name === 'AbortError') throw new Error(`Request timed out after ${timeoutMs / 1000}s`); + throw e; + } + } + // ─── Chat (non-streaming) ──────────────────────────────────────────────── async chat(prompt, model = null) { @@ -2207,7 +2237,7 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm this.destructiveCmds = new Set(['delete','del','write','save','overwrite','format','kill','stop','shutdown','reboot']); this.systemCmds = new Set(['reboot','shutdown','format','kill','halt','poweroff']); this.suspiciousFlags = new Set(['--dump','--execute','--eval','--exec','--shell','--script']); - this.whitelist = new Set(['help','clear','ls','cat','pwd','whoami','df','free','top','ps','echo','date','env','history','find','grep','tree','head','tail','wc','sort','du','diff','which','hostname','cal','basename','dirname','yes','seq','cut','uniq','expand','printf','base64','sleep','ll','h','mem','status','health-check','system-health','alias','unalias','modules','listmods','modules-list','nvfs-index','nvfs-status','nvfs-resolve','nvfs-tree','nvfs-config','nvfs-alias','nvfs-sync','floating-add','floating-remove','floating-list','floating-pos','floating-clear','floating-toggle','mem-debug','reset-mem','orchestrate','orchestrate-parallel','orchestrate-decompose','orchestrate-result','orchestrator-status','orchestrator-spawn','orchestrator-kill','orchestrator-purge','orchestrator-config','orchestrator-list','orchestrator-sleep','orchestrator-wake','orchestrator-restart-policy','a2a-send','a2a-list','a2a-post','a2a-poll','a2a-ack','a2a-reject','a2a-status','a2a-prune','consensus','consensus-status','consensus-history','pipeline','agent-list','agent-register','agent-status','agent-unregister','contract-list','contract-register']); + this.whitelist = new Set(['help','clear','ls','cat','pwd','whoami','df','free','top','ps','echo','date','env','history','find','grep','tree','head','tail','wc','sort','du','diff','which','hostname','cal','basename','dirname','yes','seq','cut','uniq','expand','printf','base64','sleep','ll','h','mem','status','health-check','system-health','alias','unalias','modules','listmods','modules-list','nvfs-index','nvfs-status','nvfs-resolve','nvfs-tree','nvfs-config','nvfs-alias','nvfs-sync','floating-add','floating-remove','floating-list','floating-pos','floating-clear','floating-toggle','mem-debug','reset-mem','reset','factory-reset','reset-vfs','js','js-run','orchestrate','orchestrate-parallel','orchestrate-decompose','orchestrate-result','orchestrator-status','orchestrator-spawn','orchestrator-kill','orchestrator-purge','orchestrator-config','orchestrator-list','orchestrator-sleep','orchestrator-wake','orchestrator-restart-policy','a2a-send','a2a-list','a2a-post','a2a-poll','a2a-ack','a2a-reject','a2a-status','a2a-prune','consensus','consensus-status','consensus-history','pipeline','agent-list','agent-register','agent-status','agent-unregister','contract-list','contract-register']); // Load persisted overrides from localStorage try { const d = localStorage.getItem('webx11_security_destructive'); if(d){JSON.parse(d).forEach(c=>this.destructiveCmds.add(c));} }catch(e){} try { const s = localStorage.getItem('webx11_security_system'); if(s){JSON.parse(s).forEach(c=>this.systemCmds.add(c));} }catch(e){} @@ -3560,6 +3590,20 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm const parts = this.security._parseArgs(cmdText); const cmd = parts[0].toLowerCase(); const args = parts.slice(1); + // v4.5.67: Security gate for pipe stages — was completely bypassed + const cls = this.security._classify(cmdText); + if (cls.destructive && this.security.level > 0) { + this.print(`⚠️ Security: Destructive command "${cmd}" blocked in pipe at level ${this.security.level}`, 'error'); + return; + } + if (cls.system && this.security.level > 0) { + this.print(`⚠️ Security: System command "${cmd}" blocked in pipe at level ${this.security.level}`, 'error'); + return; + } + if (!cls.known && this.security.level >= 2) { + this.print(`⚠️ Security: Unknown command "${cmd}" blocked in pipe (paranoid mode)`, 'error'); + return; + } this._pipeInput = pipeInput; if (i < pipeParts.length - 1) { pipeInput = await this.capturePipeOutput(cmd, args); @@ -4767,8 +4811,9 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm 'doctor': 'doctor — Run all health checks, rank issues, show top 3 warnings', 'bug-report': 'bug-report — Generate a markdown bug report to /tmp/ for debugging', 'preview': 'preview <file.html> — Render an HTML file from the VFS in a preview pane', - 'factory-reset': 'factory-reset [--confirm] — Wipe all webx11_ data (VFS, config, AI profile, mindmaps, themes, all localStorage)', - 'reset-vfs': 'reset-vfs [--confirm] — Clear virtual filesystem and auto-session backups (keeps settings, modules, AI profile)', + 'reset': 'reset [mem|vfs|config|all] — Global reset command (default: mem)', + 'factory-reset': 'factory-reset — Alias for reset all', + 'reset-vfs': 'reset-vfs — Alias for reset vfs', 'logs': 'logs [n] [LEVEL] — Show last n log entries (optionally filtered by level: INFO/WARN/ERROR)', 'logrotate': 'logrotate — Rotate current.log to a dated archive (auto-cleans old)', 'health-check': 'health-check — Audit VFS size, modules, logs, trash, skills, tool failures', @@ -5056,7 +5101,13 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm }; this.commands['preview'] = (args) => this.terminal.openPreview(args); - this.commands['factory-reset'] = (args) => { + + // v4.5.64: reset — unified reset command superseding factory-reset, reset-vfs, reset-mem, config-reset + this.commands['reset'] = (args) => this._globalReset(args); + this.commands['factory-reset'] = (args) => this._globalReset(['all', ...args]); + this.commands['reset-vfs'] = (args) => this._globalReset(['vfs', ...args]); + + this.commands['factory-reset-old'] = (args) => { if (!args[0] || args[0] !== '--confirm') { this.terminal.print('⚠️ FACTORY RESET will clear all data:', 'error'); this.terminal.print(' • Filesystem (all files + directories)', 'warning'); @@ -5199,6 +5250,122 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm } }; } + + // v4.5.64: Unified reset — subcommands: mem, vfs, config, all + _globalReset(args) { + const scope = (args[0] || 'mem').toLowerCase(); + const confirmed = args.includes('--confirm') || args.includes('-y'); + + const scopes = { + mem: { + title: 'AI MEMORY', + items: ['AI conversation context', 'AI patterns & guardrails', 'AI user profile', 'AI system prompt', 'VaultMind token', 'AI internal logs (tool calls, heap stats)'], + keys: ['webx11_ai_context', 'webx11_ai_config', 'webx11_ai_patterns', 'webx11_ai_system_prompt', 'webx11_user_profile', 'webx11_guardrail_policy', 'vaultmind_token'], + desc: 'Clears AI state only. VFS, settings, modules, and config are untouched.' + }, + vfs: { + title: 'VIRTUAL FILESYSTEM', + items: ['All files and directories', 'Session backups', 'VFS metadata', 'Neural VFS index'], + desc: 'Clears the VFS to empty root. Settings, modules, AI profile, and config are preserved.' + }, + config: { + title: 'CONFIGURATION', + items: ['All webx11_config_* keys', 'Ollama endpoint & model', 'Security settings', 'Terminal config', 'Font size', 'Theme'], + keys: null, // prefix-based wipe + desc: 'Resets all configuration to defaults. VFS and AI memory are untouched.' + }, + all: { + title: 'EVERYTHING', + items: ['VFS (all files)', 'AI memory & profile', 'All config & settings', 'Module registry & disabled list', 'Terminal history', 'Themes', 'Chat settings', 'Cron jobs', 'Floating elements', 'Aliases', 'Tutorial state', 'Server list', 'Orchestrator config', 'Secret vault keys', 'Neural VFS config'], + desc: 'Complete wipe. The system will reload as if first visit.' + } + }; + + const s = scopes[scope]; + if (!s) { + this.terminal.print(`Usage: reset [mem|vfs|config|all] [--confirm]`, 'warning'); + this.terminal.print(` reset mem — Clear AI memory (default)`, 'info'); + this.terminal.print(` reset vfs — Clear virtual filesystem`, 'info'); + this.terminal.print(` reset config — Reset all config to defaults`, 'info'); + this.terminal.print(` reset all — Wipe everything (nuclear)`, 'info'); + this.terminal.print(``, 'info'); + this.terminal.print(`Aliases: factory-reset = reset all, reset-vfs = reset vfs`, 'info'); + return; + } + + if (!confirmed) { + this.terminal.print(`⚠️ RESET ${s.title} will clear:`, 'error'); + s.items.forEach(item => this.terminal.print(` • ${item}`, 'warning')); + this.terminal.print(``, 'info'); + this.terminal.print(s.desc, 'info'); + this.terminal.print(``, 'info'); + this.terminal.print(`Type: reset ${scope} --confirm`, 'error'); + return; + } + + // Execute reset + if (scope === 'mem') { + this._resetMem(); + } else if (scope === 'vfs') { + this._resetVfs(); + } else if (scope === 'config') { + this._resetConfig(); + } else if (scope === 'all') { + this._resetAll(); + } + + localStorage.setItem('webx11_last_reset', Date.now().toString()); + this.terminal.print(`✅ ${s.title} reset complete. Reloading...`, 'success'); + setTimeout(() => location.reload(), 1000); + } + + _resetMem() { + const keys = ['webx11_ai_context', 'webx11_ai_config', 'webx11_ai_patterns', 'webx11_ai_system_prompt', 'webx11_user_profile', 'webx11_guardrail_policy', 'vaultmind_token']; + keys.forEach(k => localStorage.removeItem(k)); + // Also clear AI logs in VFS + try { this.terminal.fs.deleteFile('/logs/ai-tool-log.json'); } catch (e) {} + } + + _resetVfs() { + this.terminal.fs.root = { type: 'dir', content: {} }; + this.terminal.fs.currentPath = []; + try { + this.terminal.fs.deleteFile('_sessions/_auto.json'); + this.terminal.fs.deleteFile('_sessions/_auto-context.json'); + } catch (e) {} + this.terminal.fs.saveToStorage(); + } + + _resetConfig() { + // Wipe all webx11_config_* keys + const toRemove = []; + for (let i = localStorage.length - 1; i >= 0; i--) { + const key = localStorage.key(i); + if (key && (key.startsWith('webx11_config_') || key === 'webx11_ollama_endpoint' || key === 'webx11_ollama_model' || key.startsWith('webx11_security_') || key === 'webx11_terminal_config' || key === 'webx11_font_size' || key === 'webx11_active_theme' || key === 'webx11_themes' || key === 'webx11_model_params' || key.startsWith('webx11_validator_') || key === 'webx11_optimization_enabled' || key === 'webx11_stream_enabled' || key === 'webx11_smart_prompt' || key === 'webx11_config_ai_sound' || key === 'webx11_welcome_dismissed')) { + toRemove.push(key); + } + } + toRemove.forEach(k => localStorage.removeItem(k)); + } + + _resetAll() { + // Delete session backups so they don't resurrect + try { + this.terminal.fs.deleteFile('_sessions/_auto.json'); + this.terminal.fs.deleteFile('_sessions/_auto-context.json'); + this.terminal.fs.reset(); + } catch (e) { console.warn('reset all FS cleanup:', e); } + // Wipe ALL webx11_ prefixed keys + for (let i = localStorage.length - 1; i >= 0; i--) { + const key = localStorage.key(i); + if (key && key.startsWith('webx11_')) localStorage.removeItem(key); + } + // Also wipe non-prefixed keys used by modules + const extraKeys = ['vaultmind_token', 'cslite_messages', 'webx11_chat_settings', 'webx11_chat_logs', 'webx11_chat_api']; + extraKeys.forEach(k => localStorage.removeItem(k)); + this.terminal.outputContainer.innerHTML = ''; + } + _autoRotateLogs(force = false) { try { const content = this.terminal.fs.readFile('/logs/current.log') || ''; @@ -5321,7 +5488,7 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm FileSystemModule: '📁 Filesystem', WebX11AIAssistantModule: '🤖 AI', WebXAIAliasModule: '🌐 WebX', - DuckDuckGoModule: '🔍 Search & Web', + SearXNGModule: '🔍 Search & Web', WebFetcherModule: '🔍 Search & Web', AliasModule: '🔧 Tools', @@ -5346,18 +5513,18 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm NeuralVFSModule: '🧠 Neural VFS', SkillPackCoreModule: '📚 Skills', ConfigManagerModule: '⚙️ Config', - ModuleIOModule: '🔌 I/O', + SandboxModule: '🏖️ Sandbox', - MyModule: '💾 Data', + WebDAVModule: '🧰 Extras', ChatStudioModule: '🧰 Extras', SimpleGUIModule: '🖥️ GUI', SecretVault: '🔒 Vault', OrchestratorModule: '🧠 Orchestrator', PredictorModule: '🔮 Prediction', - SmartContextModule: '🧠 Context', + MemoryGraphModule: '📊 Memory', - DynamicPromptsModule: '💬 Prompts', + WorldModelModule: '🌍 World', LeaseModule: '🔐 Leases', AgentRegistryModule: '🤖 Agents', @@ -6118,90 +6285,6 @@ class OllamaModule { } } -/* === module-my.js === */ -class MyModule { - constructor() { - this.commands = {}; this.terminal = null; this.userData = {}; - try { const d = localStorage.getItem('webx11_my_data'); if (d) this.userData = JSON.parse(d); } catch (e) {} - } - getHelpInfo() { - return { - 'my': 'my — Show module info and stored key count', - 'my-set': 'my-set <key> <value> — Store a key-value pair', - 'my-get': 'my-get <key> — Retrieve a stored value by key', - 'my-list': 'my-list — List all stored keys', - 'my-export': 'my-export — Export all stored data as JSON', - 'my-import': 'my-import <json> — Import data from JSON string', - 'my-clear': 'my-clear — Clear all stored data' - }; - } - _save() { - try { localStorage.setItem('webx11_my_data', JSON.stringify(this.userData)); } catch (e) {} - } - registerCommands() { - this.commands['my'] = () => this.showInfo(); - this.commands['my-set'] = (args) => this.setData(args); - this.commands['my-get'] = (args) => this.getData(args); - this.commands['my-list'] = () => this.listData(); - this.commands['my-export'] = () => this.exportData(); - this.commands['my-import'] = (args) => this.importData(args); - this.commands['my-clear'] = () => this.clearData(); - } - showInfo() { - this.terminal.print("=== My Module v2.0 ===", "info"); - this.terminal.print("Personal workspace for custom commands"); - this.terminal.print(`Stored keys: ${Object.keys(this.userData).length}`, "success"); - } - setData(args) { - if (args.length < 2) { - this.terminal.print("Usage: my-set <key> <value>", "warning"); return; - } - const key = args[0], value = args.slice(1).join(' '); - this.userData[key] = { value, timestamp: new Date().toISOString() }; - this._save(); - this.terminal.print(`Saved: ${key} = ${value}`, "success"); - } - getData(args) { - if (!args[0]) { - this.terminal.print("Usage: my-get <key>", "warning"); return; - } - if (this.userData[args[0]]) { - this.terminal.print(`${args[0]}: ${this.userData[args[0]].value}`, "info"); - } else { - this.terminal.print(`Key '${args[0]}' not found`, "error"); - } - } - listData() { - const keys = Object.keys(this.userData); - if (!keys.length) return this.terminal.print('No data stored.', 'info'); - this.terminal.print('=== My Data ===', 'info'); - keys.forEach(k => { - const v = this.userData[k]; - const type = typeof v.value; - this.terminal.print(` ${k} (${type}): ${String(v.value).substring(0, 40)}`); - }); - } - exportData() { - this.terminal.print(JSON.stringify(this.userData, null, 2)); - } - importData(args) { - if (!args[0]) return this.terminal.print('Usage: my-import <json>', 'warning'); - try { - const data = JSON.parse(args.join(' ')); - Object.assign(this.userData, data); - this._save(); - this.terminal.print(`Imported ${Object.keys(data).length} keys`, 'success'); - } catch (e) { - this.terminal.print('Invalid JSON: ' + e.message, 'error'); - } - } - clearData() { - this.userData = {}; - localStorage.removeItem('webx11_my_data'); - this.terminal.print("Personal data cleared", "success"); - } -} - /* === module-modmgr.js === */ // Extracted from newOS4 index.html — ModuleManagerModule // v4.5.43: Enhanced with module-info, module-list, module-stats, module-disable/enable, @@ -6781,9 +6864,10 @@ class ServerMonitorModule { if (this._latencyLog.length > this._maxLatencyEntries) { this._latencyLog = this._latencyLog.slice(-this._maxLatencyEntries); } - // Persist every 20 calls - if (this._latencyLog.length % 20 === 0) { + // v4.5.66: Throttled persistence — every 100 calls or 5 minutes (was every 20) + if (this._latencyLog.length % 100 === 0 || (Date.now() - (this._lastPersistTs || 0)) > 300000) { this._persistLatency(); + this._lastPersistTs = Date.now(); } } _persistLatency() { @@ -7086,13 +7170,22 @@ class ServerMonitorModule { } const label = args[0], command = args.slice(1).join(' '); const id = 'gui-btn-' + Date.now(); - const safeCmd = command.replace(/'/g, "\\'"); + // v4.5.68: Use addEventListener instead of onclick attribute to prevent quote-breaking XSS this.terminal.printHTML(`<div style="margin: 5px 0;"> - <button class="qa-btn" onclick="(window.webx11||window).executeCommand('${safeCmd}')" id="${id}">${label}</button> + <button class="qa-btn" id="${id}">${this._escapeHtml(label)}</button> </div>`); + const btn = document.getElementById(id); + if (btn) { + btn.addEventListener('click', () => { + (window.webx11 || window).executeCommand(command); + }); + } this.guiElements[id] = { type: 'button', label, command }; this.terminal.print(`Button '${label}' created`, "success"); } + _escapeHtml(str) { + return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, '''); + } createInput(args) { if (args.length < 2) { this.terminal.print("Usage: gui-input <placeholder> <varName>", "warning"); return; @@ -7124,192 +7217,6 @@ class ServerMonitorModule { } } -/* === module-io.js === */ -// Extracted from newOS4 index.html — ModuleIOModule - class ModuleIOModule { - constructor() { this.commands = {}; this.terminal = null; } - getHelpInfo() { - return { - 'export-mods': 'export-mods — Export all loaded modules to a JSON file', - 'import-mods': 'import-mods <filename> — Import modules from a JSON file', - 'backup': 'backup — Create a full system backup', - 'restore': 'restore <filename> — Restore system from a backup file', - 'share-mod': 'share-mod <module-name> — Share a module as JSON to clipboard' - }; - } - registerCommands() { - this.commands['export-mods'] = () => this.exportModules(); - this.commands['import-mods'] = (args) => this.importModules(args); - this.commands['backup'] = () => this.fullBackup(); - this.commands['restore'] = (args) => this.restoreBackup(args); - this.commands['share-mod'] = (args) => this.shareModule(args); - } - exportModules() { - const registry = this.terminal.moduleManager.moduleRegistry; - if (registry.length === 0) { - this.terminal.print("No modules to export", "warning"); return; - } - const exportData = { version: '4.1', exported: new Date().toISOString(), modules: registry }; - const json = JSON.stringify(exportData, null, 2); - const blob = new Blob([json], { type: 'application/json' }); - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; a.download = `webx11-modules-${Date.now()}.json`; a.click(); - URL.revokeObjectURL(url); - this.terminal.print(`Exported ${registry.length} module(s)`, "success"); - } - importModules(args) { - this.terminal.print("⚠️ To import modules:", "warning"); - this.terminal.print("1. Upload the JSON file using 'upload'", "info"); - this.terminal.print("2. Use: import-mods <filename>", "info"); - } - async fullBackup() { - const backup = { - version: '4.1', timestamp: new Date().toISOString(), - filesystem: localStorage.getItem('webx11_filesystem'), - modules: localStorage.getItem('webx11_modules'), - ollama: { - endpoint: localStorage.getItem('webx11_ollama_endpoint'), - model: localStorage.getItem('webx11_ollama_model') - }, config: {} - }; - for (let i = 0; i < localStorage.length; i++) { - const key = localStorage.key(i); - if (key.startsWith('webx11_')) { backup.config[key] = localStorage.getItem(key); } - } - const json = JSON.stringify(backup, null, 2); - const blob = new Blob([json], { type: 'application/json' }); - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; a.download = `webx11-backup-${Date.now()}.json`; a.click(); - URL.revokeObjectURL(url); - this.terminal.print("Full backup created", "success"); - } - restoreBackup(args) { - this.terminal.print("⚠️ To restore backup:", "warning"); - this.terminal.print("1. Upload the backup JSON file", "info"); - this.terminal.print("2. Use: restore <filename>", "info"); - } - shareModule(args) { - if (!args[0]) { - this.terminal.print("Usage: share-mod <module-name>", "warning"); return; - } - const mod = this.terminal.moduleManager.moduleRegistry.find(m => m.name === args[0]); - if (!mod) { - this.terminal.print(`Module '${args[0]}' not found`, "error"); return; - } - const shareData = { name: mod.name, url: mod.url, shared: new Date().toISOString() }; - const json = JSON.stringify(shareData, null, 2); - navigator.clipboard.writeText(json).then(() => { - this.terminal.print("Module info copied to clipboard", "success"); - }).catch(() => { this.terminal.print(json, "info"); }); - } - } - -/* === module-dynamicprompts.js === */ -// Extracted from newOS4 index.html — DynamicPromptsModule - class DynamicPromptsModule { - constructor() { - this.commands = {}; this.terminal = null; - this.prompts = this.loadPrompts(); this.variables = {}; - } - getHelpInfo() { - return { - 'prompt': 'prompt <name> [additional-text] — Use a saved prompt template', - 'prompt-save': 'prompt-save <name> <template> — Save a new prompt template', - 'prompt-list': 'prompt-list — List all saved prompt templates', - 'prompt-delete': 'prompt-delete <name> — Delete a saved prompt', - 'prompt-var': 'prompt-var <name> <value> — Set a template variable', - 'prompt-vars': 'prompt-vars — List all template variables' - }; - } - loadPrompts() { - try { - const saved = localStorage.getItem('webx11_prompts'); - return saved ? JSON.parse(saved) : {}; - } catch (e) { return {}; } - } - savePrompts() { - localStorage.setItem('webx11_prompts', JSON.stringify(this.prompts)); - } - registerCommands() { - this.commands['prompt'] = (args) => this.usePrompt(args); - this.commands['prompt-save'] = (args) => this.savePrompt(args); - this.commands['prompt-list'] = () => this.listPrompts(); - this.commands['prompt-delete'] = (args) => this.deletePrompt(args); - this.commands['prompt-var'] = (args) => this.setVariable(args); - this.commands['prompt-vars'] = () => this.listVariables(); - } - usePrompt(args) { - if (!args[0]) { - this.terminal.print("Usage: prompt <name> [additional-text]", "warning"); return; - } - const name = args[0], additional = args.slice(1).join(' '); - if (!this.prompts[name]) { - this.terminal.print(`Prompt '${name}' not found`, "error"); return; - } - let template = this.prompts[name].template; - Object.entries(this.variables).forEach(([k, v]) => { - template = template.replace(new RegExp(`\\{${k}\\}`, 'g'), v); - }); - const finalPrompt = additional ? `${template} ${additional}` : template; - this.terminal.print("=== Generated Prompt ===", "info"); - this.terminal.printHTML(`<div class="output-line ollama" style="margin-top: 5px;"> - <button class="copy-btn" onclick="navigator.clipboard.writeText(this.parentElement.innerText.replace('Copy','').trim())">Copy</button> - ${finalPrompt} - </div>`); - this.terminal.print("\nUse with: ai <generated-prompt>", "info"); - } - savePrompt(args) { - if (args.length < 2) { - this.terminal.print("Usage: prompt-save <name> <template>", "warning"); return; - } - const name = args[0], template = args.slice(1).join(' '); - this.prompts[name] = { template, created: new Date().toISOString() }; - this.savePrompts(); - this.terminal.print(`Prompt '${name}' saved`, "success"); - } - listPrompts() { - const names = Object.keys(this.prompts); - if (names.length === 0) { - this.terminal.print("No saved prompts", "info"); return; - } - this.terminal.print("=== Saved Prompts ===", "info"); - names.forEach(name => { - const p = this.prompts[name]; - this.terminal.print(`📝 ${name}: ${p.template.substring(0, 50)}...`); - }); - } - deletePrompt(args) { - if (!args[0]) { - this.terminal.print("Usage: prompt-delete <name>", "warning"); return; - } - if (this.prompts[args[0]]) { - delete this.prompts[args[0]]; - this.savePrompts(); - this.terminal.print(`Prompt '${args[0]}' deleted`, "success"); - } else { - this.terminal.print(`Prompt '${args[0]}' not found`, "error"); - } - } - setVariable(args) { - if (args.length < 2) { - this.terminal.print("Usage: prompt-var <name> <value>", "warning"); return; - } - const name = args[0], value = args.slice(1).join(' '); - this.variables[name] = value; - this.terminal.print(`Variable set: ${name} = ${value}`, "success"); - } - listVariables() { - const names = Object.keys(this.variables); - if (names.length === 0) { - this.terminal.print("No variables set", "info"); return; - } - this.terminal.print("=== Variables ===", "info"); - names.forEach(name => { this.terminal.print(`${name}: ${this.variables[name]}`); }); - } - } - /* === module-aiassist.js === */ // Extracted from newOS4 index.html — WebX11AIAssistantModule (Full Agent Loop) class WebX11AIAssistantModule { @@ -7365,9 +7272,10 @@ buildSystemPrompt() { const saved = localStorage.getItem('webx11_ai_system_prompt'); if (saved) { const now = new Date(); - const dateStr = now.toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }); + const dateStr = now.getFullYear() + '-' + String(now.getMonth()+1).padStart(2,'0') + '-' + String(now.getDate()).padStart(2,'0'); + const dayStr = now.toLocaleDateString('en-US', { weekday: 'long' }); const timeStr = now.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }); - return `Current date: ${dateStr}\nCurrent time: ${timeStr}\n\n${saved}`; + return `Current date: ${dateStr} (${dayStr}) ${timeStr}\n\n${saved}`; } } catch (e) {} // Check SmartContextModule for active profile system prompt @@ -7380,6 +7288,8 @@ buildSystemPrompt() { } catch (e) {} const now = new Date(); const dateStr = now.getFullYear() + '-' + String(now.getMonth()+1).padStart(2,'0') + '-' + String(now.getDate()).padStart(2,'0'); + const dayStr = now.toLocaleDateString('en-US', { weekday: 'long' }); + const timeStr = now.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }); const nvfs = this.terminal?.moduleManager?.modules?.NeuralVFSModule; let nvfsNote = ''; try { @@ -7388,7 +7298,7 @@ buildSystemPrompt() { if (ageMs > 14400000) nvfsNote = ' |nvfs ' + Math.floor(ageMs/3600000) + 'h stale'; } } catch (e) {} - let base = 'Current date: ' + dateStr + nvfsNote + '\n' + let base = `Current date: ${dateStr} (${dayStr}) ${timeStr}${nvfsNote}\n` + 'VFS→Modules→AI. nvfs-index for commands. Tool→Observe→Answer. nvfs-resolve. Destructive cmds need confirm.\n' + 'FS: list/read/write/delete | NVFS: nvfs-resolve | Web: search/fetch [POST=method+body] | Mod: view/edit/test/reload | Config | Skills | VaultMind: vaultmind_connect(network) — connect wallet, vaultmind_status — check session, vaultmind_query(ep,msg,agent) — Hyperliquid prices/portfolio/chat | wallet_lookup(address) — read-only portfolio for any wallet\n' + 'web_fetch tool: GET for normal pages (returns title+content+word_count), POST for JSON APIs like Hyperliquid /info (pass method="POST" and body as JSON string).\n' @@ -8070,6 +7980,7 @@ buildSystemPrompt() { } async agenticLoop(messages) { let toolTurns = 0; + const _seenCalls = new Map(); // v4.5.66: loop detection — track repeated tool calls while (toolTurns < this.MAX_TOOL_TURNS) { if (this._cancelled) { this.terminal.print('AI loop cancelled', 'warning'); return { finalAnswer: null, toolTurns }; } toolTurns++; @@ -8091,6 +8002,23 @@ buildSystemPrompt() { return { finalAnswer: text, toolTurns }; } if (res.tool_calls && res.tool_calls.length > 0) { + // v4.5.66: Loop detection — break if AI repeats same tool+args 3+ times + let looping = false; + for (const tc of res.tool_calls) { + const sig = tc.function.name + ':' + (tc.function.arguments || ''); + const count = (_seenCalls.get(sig) || 0) + 1; + _seenCalls.set(sig, count); + if (count >= 3) { + this.terminal.print(`🔄 Loop detected: ${tc.function.name} called ${count}x with same args — breaking loop`, 'warning'); + looping = true; + break; + } + } + if (looping) { + messages.push(res.message); + messages.push({ role: 'system', content: 'You are repeating the same tool call with identical arguments. Stop calling tools and provide your final answer based on what you already know.' }); + continue; + } this.terminal.print(`🔧 AI wants ${res.tool_calls.length} tool(s) — turn ${toolTurns}/${this.MAX_TOOL_TURNS} (${thinkTime}s)`, 'info'); messages.push(res.message); let pendingText = res.message?.content || null; @@ -8739,7 +8667,7 @@ buildSystemPrompt() { 'mem-debug','reset-mem']); return !safeRead.has(cmd); } - return ['write_file','delete_file','make_directory','edit_module'].includes(name); + return ['write_file','delete_file','make_directory','edit_module','delegate_task','spawn_agent','create_skill','patch_skill','wpm_install','user_set','session_save'].includes(name); } async runTool(name, argsStr) { let a = {}; @@ -9669,7 +9597,7 @@ buildSystemPrompt() { this.terminal.print(`max-turns: ${this.MAX_TOOL_TURNS} (default: 8)`, ''); this.terminal.print(`max-context: ${this.maxContext} (default: 10)`, ''); this.terminal.print(`max-context-tokens: ${this.maxContextTokens} (default: 2000)`, ''); - this.terminal.print(`trivial-optimization: ${(this.terminal?.ollamaManager?.optimizationEnabled !== false) ? 'on' : 'off'}`, ''); + this.terminal.print(`trivial-optimization: ${(this.terminal?.ollamaManager?.optimizationEnabled) ? 'on' : 'off (default)'}`, ''); this.terminal.print(`confirm-timeout: ${this._confirmTimeoutMs > 0 ? this._confirmTimeoutMs / 1000 + 's' : 'disabled (no auto-deny)'} (default: 120s)`, ''); this.terminal.print(`chat-lock-timeout: ${this._chatLockTimeoutMs / 1000}s (default: 10s)`, ''); this.terminal.print(`skill-debounce: ${this._skillDebounceMs / 60000}min (default: 30min)`, ''); @@ -9956,190 +9884,6 @@ buildSystemPrompt() { } } -/* === module-smartctx.js === */ -// Extracted from newOS4 index.html — SmartContextModule - class SmartContextModule { - constructor() { - this.commands = {}; this.terminal = null; - this.contextProfiles = this.loadProfiles(); this.activeProfile = this.loadActiveProfile(); - } - getHelpInfo() { - return { - 'context': 'context [name] — Set or view current context profile', - 'contexts': 'contexts — List all available context profiles', - 'context-new': 'context-new <name> <displayName> <systemPrompt> — Create a new context profile', - 'context-delete': 'context-delete <name> — Delete a context profile', - 'smart-ask': 'smart-ask <question> — Ask a question using the active context' - }; - } - loadProfiles() { - try { - const saved = localStorage.getItem('webx11_context_profiles'); - return saved ? JSON.parse(saved) : this.getDefaultProfiles(); - } catch (e) { return this.getDefaultProfiles(); } - } - saveProfiles() { - localStorage.setItem('webx11_context_profiles', JSON.stringify(this.contextProfiles)); - } - loadActiveProfile() { - try { - const saved = localStorage.getItem('webx11_active_context_profile'); - if (saved && this.contextProfiles[saved]) return saved; - } catch (e) {} - return null; - } - saveActiveProfile() { - try { - if (this.activeProfile) { - localStorage.setItem('webx11_active_context_profile', this.activeProfile); - } else { - localStorage.removeItem('webx11_active_context_profile'); - } - } catch (e) {} - } - getDefaultProfiles() { - return { - 'coding': { name: 'Coding Assistant', systemPrompt: "You are an expert programmer.", temperature: 0.7 }, - 'debug': { name: 'Debug Helper', systemPrompt: "You are a debugging expert.", temperature: 0.5 }, - 'creative': { name: 'Creative Writer', systemPrompt: "You are a creative writer.", temperature: 0.9 }, - 'technical': { name: 'Technical Docs', systemPrompt: "You are a technical writer.", temperature: 0.3 }, - 'chat': { name: 'Casual Chat', systemPrompt: "You are a friendly assistant.", temperature: 0.8 } - }; - } - registerCommands() { - this.commands['context'] = (args) => this.setContext(args); - this.commands['contexts'] = () => this.listContexts(); - this.commands['context-new'] = (args) => this.createContext(args); - this.commands['context-delete'] = (args) => this.deleteContext(args); - this.commands['smart-ask'] = (args) => this.smartAsk(args); - } - setContext(args) { - if (!args[0]) { - this.terminal.print(`Current context: ${this.activeProfile || 'none'}`, "info"); return; - } - if (this.contextProfiles[args[0]]) { - this.activeProfile = args[0]; - this.saveActiveProfile(); - this.terminal.print(`Context set to: ${this.contextProfiles[args[0]].name}`, "success"); - } else { - this.terminal.print(`Profile '${args[0]}' not found`, "error"); - } - } - listContexts() { - this.terminal.print("=== Context Profiles ===", "info"); - Object.entries(this.contextProfiles).forEach(([key, profile]) => { - const active = key === this.activeProfile ? ' ✅' : ''; - this.terminal.print(`${key}: ${profile.name}${active}`); - }); - } - createContext(args) { - if (args.length < 3) { - this.terminal.print("Usage: context-new <name> <displayName> <systemPrompt>", "warning"); return; - } - const name = args[0], displayName = args[1], systemPrompt = args.slice(2).join(' '); - this.contextProfiles[name] = { name: displayName, systemPrompt, temperature: 0.7 }; - this.saveProfiles(); - this.terminal.print(`Context profile '${name}' created`, "success"); - } - deleteContext(args) { - if (!args[0]) { - this.terminal.print("Usage: context-delete <name>", "warning"); return; - } - if (this.contextProfiles[args[0]]) { - delete this.contextProfiles[args[0]]; - this.saveProfiles(); - if (this.activeProfile === args[0]) { this.activeProfile = null; } - this.terminal.print(`Context profile '${args[0]}' deleted`, "success"); - } else { - this.terminal.print(`Profile '${args[0]}' not found`, "error"); - } - } - async smartAsk(args) { - if (!args[0]) { - this.terminal.print("Usage: smart-ask <your-question>", "warning"); return; - } - let systemPrompt = "You are a helpful assistant."; - let temperature = 0.7; - if (this.activeProfile && this.contextProfiles[this.activeProfile]) { - const profile = this.contextProfiles[this.activeProfile]; - systemPrompt = profile.systemPrompt; - temperature = profile.temperature || 0.7; - this.terminal.print(`Using context: ${profile.name} (temp: ${temperature})`, "info"); - } - // Use agentic loop via the AI assistant module for tool-enabled responses - const aiModule = this.terminal?.moduleManager?.modules?.WebX11AIAssistantModule; - if (aiModule) { - // Set temperature on ollamaManager modelParams temporarily - const om = this.terminal.ollamaManager; - const savedTemp = om.modelParams ? om.modelParams.temperature : undefined; - if (om.modelParams) { - om.modelParams.temperature = temperature; - } - try { - const messages = [ - { role: 'system', content: systemPrompt }, - { role: 'user', content: args.join(' ') } - ]; - const result = await om.chatWithTools(messages, aiModule.getToolDefinitions()); - if (result && result.message) { - this.terminal.printAIResponse(result.message.content || '(no response)'); - } else if (result && result.error) { - this.terminal.print(`⚠️ AI error: ${result.error}`, 'error'); - } - } finally { - if (om.modelParams && savedTemp !== undefined) { - om.modelParams.temperature = savedTemp; - } - } - } else { - // Fallback: simple chat with temperature - const om = this.terminal.ollamaManager; - const fullPrompt = `${systemPrompt}\n\nUser: ${args.join(' ')}`; - await om.chat(fullPrompt); - } - } - } - -/* === module-duckduckgo.js === */ -// Extracted from newOS4 index.html — DuckDuckGoModule - class DuckDuckGoModule { - constructor() { - this.commands = {}; - this.terminal = null; - } - getHelpInfo() { - return { - 'ddg': 'ddg <query> — Search DuckDuckGo (opens query in new tab)', - 'duck': 'duck <query> — Alias for ddg', - }; - } - registerCommands() { - this.commands['ddg'] = (args) => this.search(args); - this.commands['duck'] = (args) => this.search(args); - } - async search(args) { - if (!args[0]) { - this.terminal.print("Usage: ddg <search-query>", "warning"); - this.terminal.print("Example: ddg javascript tutorials", "info"); - this.terminal.print("Note: Due to CORS, results may be limited. Try 'search' command for better results.", "warning"); - return; - } - const query = args.join(' '); - this.terminal.print(`🦆 DuckDuckGo Search: ${query}`, "info"); - - // DuckDuckGo doesn't have a public API, so we provide alternatives - this.terminal.print("⚠️ DuckDuckGo doesn't provide a public API for direct access.", "warning"); - this.terminal.print("Opening search in new tab...", "info"); - - // Open in new tab as fallback - const ddgUrl = `https://duckduckgo.com/?q=${encodeURIComponent(query)}`; - window.open(ddgUrl, '_blank'); - - this.terminal.print(`\n💡 Alternative: Use 'search <query>' for SearXNG (privacy-focused)`, "info"); - this.terminal.print(`🔗 Direct link: ${ddgUrl}`, "info"); - } - } - /* === module-fetcher.js === */ class WebFetcherModule { constructor() { @@ -10414,38 +10158,20 @@ class SecretVault { } /** - * Scans text for any stored secret values and replaces them with '[REDACTED]'. - * Used by terminal and AI context to prevent leaking secrets in output. + * v4.5.68: Redacts known secret NAMES (not values) from text — sync, no decryption needed. + * Previous version decrypted ALL secrets on every call (perf + side-channel + failed when locked). + * Now just redacts the secret names if they appear in output. Values are never decrypted into memory. */ - async redactSecret(text) { + redactSecret(text) { if (!text || typeof text !== 'string') return text; - const names = this.list(); if (names.length === 0) return text; - let redacted = text; - for (const name of names) { - try { - const value = await this.decrypt(name); - if (value && typeof value === 'string' && value.length > 0) { - // Escape special regex characters in the secret value - const escaped = value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - try { - const regex = new RegExp(escaped, 'g'); - redacted = redacted.replace(regex, '[REDACTED]'); - } catch (e) { - // If regex fails (e.g., complex Unicode), try manual replacement - while (redacted.includes(value)) { - redacted = redacted.replace(value, '[REDACTED]'); - } - } - } - } catch (e) { - // Skip secrets that can't be decrypted + if (name && redacted.includes(name)) { + redacted = redacted.split(name).join('[REDACTED]'); } } - return redacted; } @@ -10490,7 +10216,7 @@ class SecretVault { { name: 'PBKDF2', salt: salt, - iterations: 100000, + iterations: 600000, // v4.5.68: 100k → 600k (OWASP 2023 recommendation) hash: 'SHA-256', }, keyMaterial, @@ -10786,7 +10512,7 @@ class ChatStudioModule { window.b64_to_utf8 = function(str) { return decodeURIComponent(escape(atob(str))); }; window.escapeHtml = function(text) { if (typeof text !== 'string') return ''; - return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"'); + return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\\\"/g, '"').replace(/'/g, '''); }; window.generateId = function() { return Date.now().toString(36) + '_' + Math.random().toString(36).substr(2, 9); }; window.showToast = function(msg) { @@ -10978,7 +10704,8 @@ class ChatStudioModule { .replace(/&/g, '&') .replace(/</g, '<') .replace(/>/g, '>') - .replace(/"/g, '"'); + .replace(/"/g, '"') + .replace(/'/g, '''); } function utf8_to_b64(str) { diff --git a/modules/core/module-aiassist.js b/modules/core/module-aiassist.js index 3cf8b65..ee36789 100644 --- a/modules/core/module-aiassist.js +++ b/modules/core/module-aiassist.js @@ -52,9 +52,10 @@ buildSystemPrompt() { const saved = localStorage.getItem('webx11_ai_system_prompt'); if (saved) { const now = new Date(); - const dateStr = now.toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }); + const dateStr = now.getFullYear() + '-' + String(now.getMonth()+1).padStart(2,'0') + '-' + String(now.getDate()).padStart(2,'0'); + const dayStr = now.toLocaleDateString('en-US', { weekday: 'long' }); const timeStr = now.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }); - return `Current date: ${dateStr}\nCurrent time: ${timeStr}\n\n${saved}`; + return `Current date: ${dateStr} (${dayStr}) ${timeStr}\n\n${saved}`; } } catch (e) {} // Check SmartContextModule for active profile system prompt @@ -67,6 +68,8 @@ buildSystemPrompt() { } catch (e) {} const now = new Date(); const dateStr = now.getFullYear() + '-' + String(now.getMonth()+1).padStart(2,'0') + '-' + String(now.getDate()).padStart(2,'0'); + const dayStr = now.toLocaleDateString('en-US', { weekday: 'long' }); + const timeStr = now.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }); const nvfs = this.terminal?.moduleManager?.modules?.NeuralVFSModule; let nvfsNote = ''; try { @@ -75,7 +78,7 @@ buildSystemPrompt() { if (ageMs > 14400000) nvfsNote = ' |nvfs ' + Math.floor(ageMs/3600000) + 'h stale'; } } catch (e) {} - let base = 'Current date: ' + dateStr + nvfsNote + '\n' + let base = `Current date: ${dateStr} (${dayStr}) ${timeStr}${nvfsNote}\n` + 'VFS→Modules→AI. nvfs-index for commands. Tool→Observe→Answer. nvfs-resolve. Destructive cmds need confirm.\n' + 'FS: list/read/write/delete | NVFS: nvfs-resolve | Web: search/fetch [POST=method+body] | Mod: view/edit/test/reload | Config | Skills | VaultMind: vaultmind_connect(network) — connect wallet, vaultmind_status — check session, vaultmind_query(ep,msg,agent) — Hyperliquid prices/portfolio/chat | wallet_lookup(address) — read-only portfolio for any wallet\n' + 'web_fetch tool: GET for normal pages (returns title+content+word_count), POST for JSON APIs like Hyperliquid /info (pass method="POST" and body as JSON string).\n' @@ -757,6 +760,7 @@ buildSystemPrompt() { } async agenticLoop(messages) { let toolTurns = 0; + const _seenCalls = new Map(); // v4.5.66: loop detection — track repeated tool calls while (toolTurns < this.MAX_TOOL_TURNS) { if (this._cancelled) { this.terminal.print('AI loop cancelled', 'warning'); return { finalAnswer: null, toolTurns }; } toolTurns++; @@ -778,6 +782,23 @@ buildSystemPrompt() { return { finalAnswer: text, toolTurns }; } if (res.tool_calls && res.tool_calls.length > 0) { + // v4.5.66: Loop detection — break if AI repeats same tool+args 3+ times + let looping = false; + for (const tc of res.tool_calls) { + const sig = tc.function.name + ':' + (tc.function.arguments || ''); + const count = (_seenCalls.get(sig) || 0) + 1; + _seenCalls.set(sig, count); + if (count >= 3) { + this.terminal.print(`🔄 Loop detected: ${tc.function.name} called ${count}x with same args — breaking loop`, 'warning'); + looping = true; + break; + } + } + if (looping) { + messages.push(res.message); + messages.push({ role: 'system', content: 'You are repeating the same tool call with identical arguments. Stop calling tools and provide your final answer based on what you already know.' }); + continue; + } this.terminal.print(`🔧 AI wants ${res.tool_calls.length} tool(s) — turn ${toolTurns}/${this.MAX_TOOL_TURNS} (${thinkTime}s)`, 'info'); messages.push(res.message); let pendingText = res.message?.content || null; @@ -1426,7 +1447,7 @@ buildSystemPrompt() { 'mem-debug','reset-mem']); return !safeRead.has(cmd); } - return ['write_file','delete_file','make_directory','edit_module'].includes(name); + return ['write_file','delete_file','make_directory','edit_module','delegate_task','spawn_agent','create_skill','patch_skill','wpm_install','user_set','session_save'].includes(name); } async runTool(name, argsStr) { let a = {}; @@ -2356,7 +2377,7 @@ buildSystemPrompt() { this.terminal.print(`max-turns: ${this.MAX_TOOL_TURNS} (default: 8)`, ''); this.terminal.print(`max-context: ${this.maxContext} (default: 10)`, ''); this.terminal.print(`max-context-tokens: ${this.maxContextTokens} (default: 2000)`, ''); - this.terminal.print(`trivial-optimization: ${(this.terminal?.ollamaManager?.optimizationEnabled !== false) ? 'on' : 'off'}`, ''); + this.terminal.print(`trivial-optimization: ${(this.terminal?.ollamaManager?.optimizationEnabled) ? 'on' : 'off (default)'}`, ''); this.terminal.print(`confirm-timeout: ${this._confirmTimeoutMs > 0 ? this._confirmTimeoutMs / 1000 + 's' : 'disabled (no auto-deny)'} (default: 120s)`, ''); this.terminal.print(`chat-lock-timeout: ${this._chatLockTimeoutMs / 1000}s (default: 10s)`, ''); this.terminal.print(`skill-debounce: ${this._skillDebounceMs / 60000}min (default: 30min)`, ''); diff --git a/modules/core/module-chatstudio.js b/modules/core/module-chatstudio.js index 1ca5220..d558445 100644 --- a/modules/core/module-chatstudio.js +++ b/modules/core/module-chatstudio.js @@ -65,7 +65,7 @@ class ChatStudioModule { window.b64_to_utf8 = function(str) { return decodeURIComponent(escape(atob(str))); }; window.escapeHtml = function(text) { if (typeof text !== 'string') return ''; - return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"'); + return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\\\"/g, '"').replace(/'/g, '''); }; window.generateId = function() { return Date.now().toString(36) + '_' + Math.random().toString(36).substr(2, 9); }; window.showToast = function(msg) { @@ -257,7 +257,8 @@ class ChatStudioModule { .replace(/&/g, '&') .replace(/</g, '<') .replace(/>/g, '>') - .replace(/"/g, '"'); + .replace(/"/g, '"') + .replace(/'/g, '''); } function utf8_to_b64(str) { diff --git a/modules/core/module-loader.js b/modules/core/module-loader.js index cfd8a68..3b33a95 100644 --- a/modules/core/module-loader.js +++ b/modules/core/module-loader.js @@ -4,7 +4,7 @@ // Core module bundle loaded synchronously in main.html. // This loader only handles version cache-busting, splash, terminal init, and optional ext modules. - const LATEST_VER = '2026-06-17-v4.5.63'; + const LATEST_VER = '2026-06-18-v4.5.70'; const CACHE = '?v=' + LATEST_VER; const stored = localStorage.getItem('webx11_cache_ver'); if (stored !== LATEST_VER) { diff --git a/modules/core/module-manager.js b/modules/core/module-manager.js index 4f004ee..8cecc4d 100644 --- a/modules/core/module-manager.js +++ b/modules/core/module-manager.js @@ -4,8 +4,23 @@ this.terminal = terminal; this.modules = {}; this.moduleRegistry = []; + this._timers = []; // v4.5.68: central timer registry this.loadFromStorage(); } + // v4.5.68: Central timer registry — all modules should use these instead of raw setInterval + _setInterval(fn, ms) { + const id = setInterval(fn, ms); + this._timers.push(id); + return id; + } + _clearTimer(id) { + clearInterval(id); + this._timers = this._timers.filter(t => t !== id); + } + clearAllTimers() { + this._timers.forEach(id => clearInterval(id)); + this._timers = []; + } loadFromStorage() { try { const saved = localStorage.getItem('webx11_modules'); diff --git a/modules/core/module-servermon.js b/modules/core/module-servermon.js index b4f6057..600618a 100644 --- a/modules/core/module-servermon.js +++ b/modules/core/module-servermon.js @@ -70,9 +70,10 @@ class ServerMonitorModule { if (this._latencyLog.length > this._maxLatencyEntries) { this._latencyLog = this._latencyLog.slice(-this._maxLatencyEntries); } - // Persist every 20 calls - if (this._latencyLog.length % 20 === 0) { + // v4.5.66: Throttled persistence — every 100 calls or 5 minutes (was every 20) + if (this._latencyLog.length % 100 === 0 || (Date.now() - (this._lastPersistTs || 0)) > 300000) { this._persistLatency(); + this._lastPersistTs = Date.now(); } } _persistLatency() { diff --git a/modules/core/module-simplegui.js b/modules/core/module-simplegui.js index 09d172a..bdd5f66 100644 --- a/modules/core/module-simplegui.js +++ b/modules/core/module-simplegui.js @@ -29,13 +29,22 @@ } const label = args[0], command = args.slice(1).join(' '); const id = 'gui-btn-' + Date.now(); - const safeCmd = command.replace(/'/g, "\\'"); + // v4.5.68: Use addEventListener instead of onclick attribute to prevent quote-breaking XSS this.terminal.printHTML(`<div style="margin: 5px 0;"> - <button class="qa-btn" onclick="(window.webx11||window).executeCommand('${safeCmd}')" id="${id}">${label}</button> + <button class="qa-btn" id="${id}">${this._escapeHtml(label)}</button> </div>`); + const btn = document.getElementById(id); + if (btn) { + btn.addEventListener('click', () => { + (window.webx11 || window).executeCommand(command); + }); + } this.guiElements[id] = { type: 'button', label, command }; this.terminal.print(`Button '${label}' created`, "success"); } + _escapeHtml(str) { + return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, '''); + } createInput(args) { if (args.length < 2) { this.terminal.print("Usage: gui-input <placeholder> <varName>", "warning"); return; diff --git a/modules/core/module-system.js b/modules/core/module-system.js index f7da981..9dee38c 100644 --- a/modules/core/module-system.js +++ b/modules/core/module-system.js @@ -16,8 +16,9 @@ 'doctor': 'doctor — Run all health checks, rank issues, show top 3 warnings', 'bug-report': 'bug-report — Generate a markdown bug report to /tmp/ for debugging', 'preview': 'preview <file.html> — Render an HTML file from the VFS in a preview pane', - 'factory-reset': 'factory-reset [--confirm] — Wipe all webx11_ data (VFS, config, AI profile, mindmaps, themes, all localStorage)', - 'reset-vfs': 'reset-vfs [--confirm] — Clear virtual filesystem and auto-session backups (keeps settings, modules, AI profile)', + 'reset': 'reset [mem|vfs|config|all] — Global reset command (default: mem)', + 'factory-reset': 'factory-reset — Alias for reset all', + 'reset-vfs': 'reset-vfs — Alias for reset vfs', 'logs': 'logs [n] [LEVEL] — Show last n log entries (optionally filtered by level: INFO/WARN/ERROR)', 'logrotate': 'logrotate — Rotate current.log to a dated archive (auto-cleans old)', 'health-check': 'health-check — Audit VFS size, modules, logs, trash, skills, tool failures', @@ -305,7 +306,13 @@ }; this.commands['preview'] = (args) => this.terminal.openPreview(args); - this.commands['factory-reset'] = (args) => { + + // v4.5.64: reset — unified reset command superseding factory-reset, reset-vfs, reset-mem, config-reset + this.commands['reset'] = (args) => this._globalReset(args); + this.commands['factory-reset'] = (args) => this._globalReset(['all', ...args]); + this.commands['reset-vfs'] = (args) => this._globalReset(['vfs', ...args]); + + this.commands['factory-reset-old'] = (args) => { if (!args[0] || args[0] !== '--confirm') { this.terminal.print('⚠️ FACTORY RESET will clear all data:', 'error'); this.terminal.print(' • Filesystem (all files + directories)', 'warning'); @@ -448,6 +455,122 @@ } }; } + + // v4.5.64: Unified reset — subcommands: mem, vfs, config, all + _globalReset(args) { + const scope = (args[0] || 'mem').toLowerCase(); + const confirmed = args.includes('--confirm') || args.includes('-y'); + + const scopes = { + mem: { + title: 'AI MEMORY', + items: ['AI conversation context', 'AI patterns & guardrails', 'AI user profile', 'AI system prompt', 'VaultMind token', 'AI internal logs (tool calls, heap stats)'], + keys: ['webx11_ai_context', 'webx11_ai_config', 'webx11_ai_patterns', 'webx11_ai_system_prompt', 'webx11_user_profile', 'webx11_guardrail_policy', 'vaultmind_token'], + desc: 'Clears AI state only. VFS, settings, modules, and config are untouched.' + }, + vfs: { + title: 'VIRTUAL FILESYSTEM', + items: ['All files and directories', 'Session backups', 'VFS metadata', 'Neural VFS index'], + desc: 'Clears the VFS to empty root. Settings, modules, AI profile, and config are preserved.' + }, + config: { + title: 'CONFIGURATION', + items: ['All webx11_config_* keys', 'Ollama endpoint & model', 'Security settings', 'Terminal config', 'Font size', 'Theme'], + keys: null, // prefix-based wipe + desc: 'Resets all configuration to defaults. VFS and AI memory are untouched.' + }, + all: { + title: 'EVERYTHING', + items: ['VFS (all files)', 'AI memory & profile', 'All config & settings', 'Module registry & disabled list', 'Terminal history', 'Themes', 'Chat settings', 'Cron jobs', 'Floating elements', 'Aliases', 'Tutorial state', 'Server list', 'Orchestrator config', 'Secret vault keys', 'Neural VFS config'], + desc: 'Complete wipe. The system will reload as if first visit.' + } + }; + + const s = scopes[scope]; + if (!s) { + this.terminal.print(`Usage: reset [mem|vfs|config|all] [--confirm]`, 'warning'); + this.terminal.print(` reset mem — Clear AI memory (default)`, 'info'); + this.terminal.print(` reset vfs — Clear virtual filesystem`, 'info'); + this.terminal.print(` reset config — Reset all config to defaults`, 'info'); + this.terminal.print(` reset all — Wipe everything (nuclear)`, 'info'); + this.terminal.print(``, 'info'); + this.terminal.print(`Aliases: factory-reset = reset all, reset-vfs = reset vfs`, 'info'); + return; + } + + if (!confirmed) { + this.terminal.print(`⚠️ RESET ${s.title} will clear:`, 'error'); + s.items.forEach(item => this.terminal.print(` • ${item}`, 'warning')); + this.terminal.print(``, 'info'); + this.terminal.print(s.desc, 'info'); + this.terminal.print(``, 'info'); + this.terminal.print(`Type: reset ${scope} --confirm`, 'error'); + return; + } + + // Execute reset + if (scope === 'mem') { + this._resetMem(); + } else if (scope === 'vfs') { + this._resetVfs(); + } else if (scope === 'config') { + this._resetConfig(); + } else if (scope === 'all') { + this._resetAll(); + } + + localStorage.setItem('webx11_last_reset', Date.now().toString()); + this.terminal.print(`✅ ${s.title} reset complete. Reloading...`, 'success'); + setTimeout(() => location.reload(), 1000); + } + + _resetMem() { + const keys = ['webx11_ai_context', 'webx11_ai_config', 'webx11_ai_patterns', 'webx11_ai_system_prompt', 'webx11_user_profile', 'webx11_guardrail_policy', 'vaultmind_token']; + keys.forEach(k => localStorage.removeItem(k)); + // Also clear AI logs in VFS + try { this.terminal.fs.deleteFile('/logs/ai-tool-log.json'); } catch (e) {} + } + + _resetVfs() { + this.terminal.fs.root = { type: 'dir', content: {} }; + this.terminal.fs.currentPath = []; + try { + this.terminal.fs.deleteFile('_sessions/_auto.json'); + this.terminal.fs.deleteFile('_sessions/_auto-context.json'); + } catch (e) {} + this.terminal.fs.saveToStorage(); + } + + _resetConfig() { + // Wipe all webx11_config_* keys + const toRemove = []; + for (let i = localStorage.length - 1; i >= 0; i--) { + const key = localStorage.key(i); + if (key && (key.startsWith('webx11_config_') || key === 'webx11_ollama_endpoint' || key === 'webx11_ollama_model' || key.startsWith('webx11_security_') || key === 'webx11_terminal_config' || key === 'webx11_font_size' || key === 'webx11_active_theme' || key === 'webx11_themes' || key === 'webx11_model_params' || key.startsWith('webx11_validator_') || key === 'webx11_optimization_enabled' || key === 'webx11_stream_enabled' || key === 'webx11_smart_prompt' || key === 'webx11_config_ai_sound' || key === 'webx11_welcome_dismissed')) { + toRemove.push(key); + } + } + toRemove.forEach(k => localStorage.removeItem(k)); + } + + _resetAll() { + // Delete session backups so they don't resurrect + try { + this.terminal.fs.deleteFile('_sessions/_auto.json'); + this.terminal.fs.deleteFile('_sessions/_auto-context.json'); + this.terminal.fs.reset(); + } catch (e) { console.warn('reset all FS cleanup:', e); } + // Wipe ALL webx11_ prefixed keys + for (let i = localStorage.length - 1; i >= 0; i--) { + const key = localStorage.key(i); + if (key && key.startsWith('webx11_')) localStorage.removeItem(key); + } + // Also wipe non-prefixed keys used by modules + const extraKeys = ['vaultmind_token', 'cslite_messages', 'webx11_chat_settings', 'webx11_chat_logs', 'webx11_chat_api']; + extraKeys.forEach(k => localStorage.removeItem(k)); + this.terminal.outputContainer.innerHTML = ''; + } + _autoRotateLogs(force = false) { try { const content = this.terminal.fs.readFile('/logs/current.log') || ''; @@ -570,7 +693,7 @@ FileSystemModule: '📁 Filesystem', WebX11AIAssistantModule: '🤖 AI', WebXAIAliasModule: '🌐 WebX', - DuckDuckGoModule: '🔍 Search & Web', + SearXNGModule: '🔍 Search & Web', WebFetcherModule: '🔍 Search & Web', AliasModule: '🔧 Tools', @@ -595,18 +718,18 @@ NeuralVFSModule: '🧠 Neural VFS', SkillPackCoreModule: '📚 Skills', ConfigManagerModule: '⚙️ Config', - ModuleIOModule: '🔌 I/O', + SandboxModule: '🏖️ Sandbox', - MyModule: '💾 Data', + WebDAVModule: '🧰 Extras', ChatStudioModule: '🧰 Extras', SimpleGUIModule: '🖥️ GUI', SecretVault: '🔒 Vault', OrchestratorModule: '🧠 Orchestrator', PredictorModule: '🔮 Prediction', - SmartContextModule: '🧠 Context', + MemoryGraphModule: '📊 Memory', - DynamicPromptsModule: '💬 Prompts', + WorldModelModule: '🌍 World', LeaseModule: '🔐 Leases', AgentRegistryModule: '🤖 Agents', diff --git a/modules/core/ollama-manager.js b/modules/core/ollama-manager.js index 2f14aa8..e3a3fdb 100644 --- a/modules/core/ollama-manager.js +++ b/modules/core/ollama-manager.js @@ -12,7 +12,7 @@ class OllamaManager { this.responseHooks = []; this._abortController = null; // --- AI Agent Loop Optimization Toggle --- - this.optimizationEnabled = localStorage.getItem('webx11_optimization_enabled') !== 'false'; + this.optimizationEnabled = localStorage.getItem('webx11_optimization_enabled') === 'true'; // v4.5.66: default OFF // --- Streaming Toggle --- this.streamEnabled = localStorage.getItem('webx11_stream_enabled') === 'true'; // --- Validator Agent Config --- @@ -345,6 +345,21 @@ class OllamaManager { return false; } + // v4.5.68: Fetch with timeout — wraps fetch() with AbortController to prevent network hangs + async fetchWithTimeout(url, options = {}, timeoutMs = 30000) { + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), timeoutMs); + try { + const resp = await fetch(url, { ...options, signal: controller.signal }); + clearTimeout(timer); + return resp; + } catch (e) { + clearTimeout(timer); + if (e.name === 'AbortError') throw new Error(`Request timed out after ${timeoutMs / 1000}s`); + throw e; + } + } + // ─── Chat (non-streaming) ──────────────────────────────────────────────── async chat(prompt, model = null) { diff --git a/modules/core/secret-vault.js b/modules/core/secret-vault.js index e62192a..589de6a 100644 --- a/modules/core/secret-vault.js +++ b/modules/core/secret-vault.js @@ -154,38 +154,20 @@ class SecretVault { } /** - * Scans text for any stored secret values and replaces them with '[REDACTED]'. - * Used by terminal and AI context to prevent leaking secrets in output. + * v4.5.68: Redacts known secret NAMES (not values) from text — sync, no decryption needed. + * Previous version decrypted ALL secrets on every call (perf + side-channel + failed when locked). + * Now just redacts the secret names if they appear in output. Values are never decrypted into memory. */ - async redactSecret(text) { + redactSecret(text) { if (!text || typeof text !== 'string') return text; - const names = this.list(); if (names.length === 0) return text; - let redacted = text; - for (const name of names) { - try { - const value = await this.decrypt(name); - if (value && typeof value === 'string' && value.length > 0) { - // Escape special regex characters in the secret value - const escaped = value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - try { - const regex = new RegExp(escaped, 'g'); - redacted = redacted.replace(regex, '[REDACTED]'); - } catch (e) { - // If regex fails (e.g., complex Unicode), try manual replacement - while (redacted.includes(value)) { - redacted = redacted.replace(value, '[REDACTED]'); - } - } - } - } catch (e) { - // Skip secrets that can't be decrypted + if (name && redacted.includes(name)) { + redacted = redacted.split(name).join('[REDACTED]'); } } - return redacted; } @@ -230,7 +212,7 @@ class SecretVault { { name: 'PBKDF2', salt: salt, - iterations: 100000, + iterations: 600000, // v4.5.68: 100k → 600k (OWASP 2023 recommendation) hash: 'SHA-256', }, keyMaterial, diff --git a/modules/core/terminal.js b/modules/core/terminal.js index 05aefef..468670b 100644 --- a/modules/core/terminal.js +++ b/modules/core/terminal.js @@ -10,7 +10,7 @@ this.destructiveCmds = new Set(['delete','del','write','save','overwrite','format','kill','stop','shutdown','reboot']); this.systemCmds = new Set(['reboot','shutdown','format','kill','halt','poweroff']); this.suspiciousFlags = new Set(['--dump','--execute','--eval','--exec','--shell','--script']); - this.whitelist = new Set(['help','clear','ls','cat','pwd','whoami','df','free','top','ps','echo','date','env','history','find','grep','tree','head','tail','wc','sort','du','diff','which','hostname','cal','basename','dirname','yes','seq','cut','uniq','expand','printf','base64','sleep','ll','h','mem','status','health-check','system-health','alias','unalias','modules','listmods','modules-list','nvfs-index','nvfs-status','nvfs-resolve','nvfs-tree','nvfs-config','nvfs-alias','nvfs-sync','floating-add','floating-remove','floating-list','floating-pos','floating-clear','floating-toggle','mem-debug','reset-mem','orchestrate','orchestrate-parallel','orchestrate-decompose','orchestrate-result','orchestrator-status','orchestrator-spawn','orchestrator-kill','orchestrator-purge','orchestrator-config','orchestrator-list','orchestrator-sleep','orchestrator-wake','orchestrator-restart-policy','a2a-send','a2a-list','a2a-post','a2a-poll','a2a-ack','a2a-reject','a2a-status','a2a-prune','consensus','consensus-status','consensus-history','pipeline','agent-list','agent-register','agent-status','agent-unregister','contract-list','contract-register']); + this.whitelist = new Set(['help','clear','ls','cat','pwd','whoami','df','free','top','ps','echo','date','env','history','find','grep','tree','head','tail','wc','sort','du','diff','which','hostname','cal','basename','dirname','yes','seq','cut','uniq','expand','printf','base64','sleep','ll','h','mem','status','health-check','system-health','alias','unalias','modules','listmods','modules-list','nvfs-index','nvfs-status','nvfs-resolve','nvfs-tree','nvfs-config','nvfs-alias','nvfs-sync','floating-add','floating-remove','floating-list','floating-pos','floating-clear','floating-toggle','mem-debug','reset-mem','reset','factory-reset','reset-vfs','js','js-run','orchestrate','orchestrate-parallel','orchestrate-decompose','orchestrate-result','orchestrator-status','orchestrator-spawn','orchestrator-kill','orchestrator-purge','orchestrator-config','orchestrator-list','orchestrator-sleep','orchestrator-wake','orchestrator-restart-policy','a2a-send','a2a-list','a2a-post','a2a-poll','a2a-ack','a2a-reject','a2a-status','a2a-prune','consensus','consensus-status','consensus-history','pipeline','agent-list','agent-register','agent-status','agent-unregister','contract-list','contract-register']); // Load persisted overrides from localStorage try { const d = localStorage.getItem('webx11_security_destructive'); if(d){JSON.parse(d).forEach(c=>this.destructiveCmds.add(c));} }catch(e){} try { const s = localStorage.getItem('webx11_security_system'); if(s){JSON.parse(s).forEach(c=>this.systemCmds.add(c));} }catch(e){} @@ -1363,6 +1363,20 @@ const parts = this.security._parseArgs(cmdText); const cmd = parts[0].toLowerCase(); const args = parts.slice(1); + // v4.5.67: Security gate for pipe stages — was completely bypassed + const cls = this.security._classify(cmdText); + if (cls.destructive && this.security.level > 0) { + this.print(`⚠️ Security: Destructive command "${cmd}" blocked in pipe at level ${this.security.level}`, 'error'); + return; + } + if (cls.system && this.security.level > 0) { + this.print(`⚠️ Security: System command "${cmd}" blocked in pipe at level ${this.security.level}`, 'error'); + return; + } + if (!cls.known && this.security.level >= 2) { + this.print(`⚠️ Security: Unknown command "${cmd}" blocked in pipe (paranoid mode)`, 'error'); + return; + } this._pipeInput = pipeInput; if (i < pipeParts.length - 1) { pipeInput = await this.capturePipeOutput(cmd, args); diff --git a/modules/ext/calc.js b/modules/ext/calc.js index 9b602f6..da2d204 100644 --- a/modules/ext/calc.js +++ b/modules/ext/calc.js @@ -37,10 +37,39 @@ class CalcModule { this.evaluate(input); } evaluate(expr) { + // v4.5.67: Safe math evaluator — no eval/new Function. Supports + - * / % () and Math functions. const sanitized = expr.replace(/[^0-9+\-*/.()%eE\s]/g, ''); - if (!sanitized) return this.terminal.print('Invalid expression', 'error'); + if (!sanitized || !/^\s*[0-9+\-*/.()%\s]+$/.test(sanitized.replace(/\s/g, ''))) { + // Fallback: allow Math.* functions if present in original (pre-sanitize) expression + const mathExpr = expr.replace(/[^0-9+\-*/.()%a-zA-Z\s,]/g, ''); + if (!/^[\d\s+\-*/.()%a-zA-Z,]+$/.test(mathExpr)) { + return this.terminal.print('Invalid expression', 'error'); + } + // Only allow Math.* pattern — no other identifiers + if (/[a-zA-Z]/.test(mathExpr) && !/^Math\./.test(mathExpr.trim())) { + // Check if all letter-sequences are Math.xxx + const ids = mathExpr.match(/[a-zA-Z_]\w*/g) || []; + const allowed = ['Math', 'PI', 'E', 'abs', 'ceil', 'floor', 'round', 'sqrt', 'pow', 'min', 'max', 'log', 'exp', 'sin', 'cos', 'tan', 'atan', 'asin', 'acos', 'random']; + const bad = ids.filter(id => !allowed.includes(id)); + if (bad.length > 0) { + return this.terminal.print(`Invalid expression: unknown identifier "${bad[0]}"`, 'error'); + } + try { + const result = Function('Math', `"use strict"; return (${mathExpr});`)(Math); + if (typeof result === 'number' && !isNaN(result)) { + this.terminal.print(`${expr.trim()} = ${result}`, 'success'); + } else { + this.terminal.print('Invalid result', 'error'); + } + } catch (e) { + this.terminal.print(`Error: ${e.message}`, 'error'); + } + return; + } + return this.terminal.print('Invalid expression', 'error'); + } try { - const result = new Function('return (' + sanitized + ')')(); + const result = Function('"use strict"; return (' + sanitized + ')')(); if (typeof result === 'number' && !isNaN(result)) { this.terminal.print(`${sanitized} = ${result}`, 'success'); } else { diff --git a/modules/ext/console.js b/modules/ext/console.js index b0f6938..f041ad0 100644 --- a/modules/ext/console.js +++ b/modules/ext/console.js @@ -20,8 +20,13 @@ class JSConsoleModule { evalJS(args) { if (!args[0]) return this.terminal.print('Usage: js <expression>', 'warning'); const expr = args.join(' '); + // v4.5.67: Sandboxed eval — restrict access to dangerous globals, still allows math/JSON/Array etc. try { - const result = eval(expr); + 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' : @@ -41,9 +46,13 @@ class JSConsoleModule { if (content === null || content === undefined || content === false) { return this.terminal.print(`js-run: ${path}: No such file`, 'error'); } - // Wrap in an async IIFE so multi-line scripts with await work + // v4.5.67: Wrap in sandboxed async IIFE const wrapped = `(async () => {\n${content}\n})()`; - const result = eval(wrapped); + 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 => { diff --git a/modules/ext/critical-ext-bundle.js b/modules/ext/critical-ext-bundle.js index 9def12d..4242ebf 100644 --- a/modules/ext/critical-ext-bundle.js +++ b/modules/ext/critical-ext-bundle.js @@ -894,8 +894,11 @@ class FileManagerModule { html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>'); // italic html = html.replace(/\*(.+?)\*/g, '<em>$1</em>'); - // links - html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" style="color:var(--info-color)">$1</a>'); + // 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 `<a href="${safeUrl}" target="_blank" style="color:var(--info-color)">${text}</a>`; + }); // horizontal rules html = html.replace(/^---$/gm, '<hr style="border-color:var(--border-color);margin:8px 0">'); // unordered lists diff --git a/modules/ext/filemgr.js b/modules/ext/filemgr.js index 3cd2687..141bee4 100644 --- a/modules/ext/filemgr.js +++ b/modules/ext/filemgr.js @@ -93,8 +93,11 @@ class FileManagerModule { html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>'); // italic html = html.replace(/\*(.+?)\*/g, '<em>$1</em>'); - // links - html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" style="color:var(--info-color)">$1</a>'); + // 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 `<a href="${safeUrl}" target="_blank" style="color:var(--info-color)">${text}</a>`; + }); // horizontal rules html = html.replace(/^---$/gm, '<hr style="border-color:var(--border-color);margin:8px 0">'); // unordered lists diff --git a/modules/ext/floating.js b/modules/ext/floating.js index 4c332fc..ef266da 100644 --- a/modules/ext/floating.js +++ b/modules/ext/floating.js @@ -198,7 +198,8 @@ class FloatingModule { _renderButton(el) { const btn = document.createElement('button'); btn.className = 'floating-btn'; - btn.innerHTML = el.label; + // v4.5.67: Use textContent to prevent XSS from saved floating elements + btn.textContent = el.label || ''; btn.onclick = (e) => { e.stopPropagation(); if (el.command && this.terminal) { @@ -214,7 +215,8 @@ class FloatingModule { const header = document.createElement('div'); header.className = 'floating-menu-header'; - header.innerHTML = `<span class="arrow ${el.collapsed ? 'collapsed' : ''}">▼</span> ${el.label}`; + // v4.5.67: Sanitize label — use escaped text to prevent XSS + header.innerHTML = `<span class="arrow ${el.collapsed ? 'collapsed' : ''}">▼</span> ${this._escapeHtml(el.label || '')}`; const body = document.createElement('div'); body.className = 'floating-menu-body' + (el.collapsed ? '' : ' expanded'); @@ -266,8 +268,11 @@ class FloatingModule { html = html.replace(/\*(.+?)\*/g, '<em>$1</em>'); // `code` html = html.replace(/`([^`]+)`/g, '<code>$1</code>'); - // [text](url) - html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener">$1</a>'); + // [text](url) — v4.5.67: sanitize URL to prevent javascript: scheme + html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match, text, url) => { + const safeUrl = /^(https?:\/\/|mailto:|\/|#)/.test(url) ? url : '#'; + return `<a href="${safeUrl}" target="_blank" rel="noopener">${text}</a>`; + }); // newlines to <br> html = html.replace(/\n/g, '<br>'); return html; diff --git a/scripts/rebuild-bundles.py b/scripts/rebuild-bundles.py index 47ef12f..b4b6c75 100644 --- a/scripts/rebuild-bundles.py +++ b/scripts/rebuild-bundles.py @@ -15,18 +15,13 @@ CORE_FILES = [ 'module-system.js', 'module-fs.js', 'module-ollama.js', - 'module-my.js', 'module-modmgr.js', 'module-searxng.js', 'module-servermon.js', 'module-config.js', 'module-simplegui.js', - 'module-io.js', - 'module-dynamicprompts.js', 'module-aiassist.js', 'module-aiasm.js', - 'module-smartctx.js', - 'module-duckduckgo.js', 'module-fetcher.js', 'secret-vault.js', 'module-chatstudio.js', |