aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--chat/chat-modules.js43
-rw-r--r--chat/chat-ui.html2
-rw-r--r--downloads/audit_final_report.html156
-rw-r--r--downloads/videos/portfolio/index.html129
-rw-r--r--downloads/videos/video-3-record.sh98
-rw-r--r--help.html65
-rw-r--r--main.html6
-rw-r--r--modules-index.json137
-rw-r--r--modules/core/core-bundle.js727
-rw-r--r--modules/core/module-aiassist.js31
-rw-r--r--modules/core/module-chatstudio.js5
-rw-r--r--modules/core/module-loader.js2
-rw-r--r--modules/core/module-manager.js15
-rw-r--r--modules/core/module-servermon.js5
-rw-r--r--modules/core/module-simplegui.js13
-rw-r--r--modules/core/module-system.js139
-rw-r--r--modules/core/ollama-manager.js17
-rw-r--r--modules/core/secret-vault.js32
-rw-r--r--modules/core/terminal.js16
-rw-r--r--modules/ext/calc.js33
-rw-r--r--modules/ext/console.js15
-rw-r--r--modules/ext/critical-ext-bundle.js7
-rw-r--r--modules/ext/filemgr.js7
-rw-r--r--modules/ext/floating.js13
-rw-r--r--scripts/rebuild-bundles.py5
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
diff --git a/help.html b/help.html
index 2429121..79bc121 100644
--- a/help.html
+++ b/help.html
@@ -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>&lt;script&gt;</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>
diff --git a/main.html b/main.html
index 2e963bc..e548b81 100644
--- a/main.html
+++ b/main.html
@@ -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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#039;');
+ }
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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;');
+ return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\\\"/g, '&quot;').replace(/'/g, '&#39;');
};
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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
- .replace(/"/g, '&quot;');
+ .replace(/"/g, '&quot;')
+ .replace(/'/g, '&#39;');
}
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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;');
+ return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\\\"/g, '&quot;').replace(/'/g, '&#39;');
};
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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
- .replace(/"/g, '&quot;');
+ .replace(/"/g, '&quot;')
+ .replace(/'/g, '&#39;');
}
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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#039;');
+ }
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',