diff options
| author | Agando <roduccomm@gmail.com> | 2026-06-05 18:01:01 +0200 |
|---|---|---|
| committer | Agando <roduccomm@gmail.com> | 2026-06-05 18:01:01 +0200 |
| commit | b538577cd0a4292e4cd4ca1c9f9829fc7e133caa (patch) | |
| tree | 04d76bf4f0d680cdb26b1a205481bc8628cd77b4 | |
| parent | 5092f9a0ce02691dc476d7b64fa731a1518dd834 (diff) | |
auto: 2026-06-05_18:01 changes
| -rw-r--r-- | A2A_protocol_spec.md | 44 | ||||
| -rw-r--r-- | cheatsheet.html | 28 | ||||
| -rw-r--r-- | downloads/newos4_whitepaper.html | 40 | ||||
| -rw-r--r-- | help.html | 91 | ||||
| -rw-r--r-- | modules-index.json | 121 | ||||
| -rw-r--r-- | modules/core/module-aiassist.js | 48 | ||||
| -rw-r--r-- | modules/core/module-loader.js | 6 | ||||
| -rw-r--r-- | modules/core/module-memory-graph.js | 400 | ||||
| -rw-r--r-- | modules/core/module-orchestrator.js | 474 | ||||
| -rw-r--r-- | modules/core/module-predictor.js | 279 | ||||
| -rw-r--r-- | modules/core/module-sandbox.js | 332 | ||||
| -rw-r--r-- | modules/core/module-world-model.js | 381 | ||||
| -rw-r--r-- | modules/core/sandbox-worker.js | 205 | ||||
| -rw-r--r-- | modules/core/terminal.js | 4 | ||||
| -rw-r--r-- | newos4_whitepaper.md | 43 | ||||
| -rw-r--r-- | packages.json | 14 | ||||
| -rw-r--r-- | review-newos4.html | 30 | ||||
| -rw-r--r-- | skills/index.json | 61 | ||||
| -rw-r--r-- | skills/memory-and-profile.md | 39 | ||||
| -rw-r--r-- | skills/mindmap-user-guide.md | 40 | ||||
| -rw-r--r-- | skills/mindmap-workflow.md | 39 | ||||
| -rw-r--r-- | skills/webdav-operations.md | 33 |
22 files changed, 2574 insertions, 178 deletions
diff --git a/A2A_protocol_spec.md b/A2A_protocol_spec.md index e73b0e9..bb3d713 100644 --- a/A2A_protocol_spec.md +++ b/A2A_protocol_spec.md @@ -1,7 +1,7 @@ -# A2A Protocol Specification v0.1 +# A2A Protocol Specification v0.2 ## Overview -Agent-to-Agent (A2A) is a decentralized message protocol for agent negotiation within newOS4. Agents discover each other via VFS registrations and communicate through a shared message queue. No central broker. +Agent-to-Agent (A2A) is a decentralized message protocol for agent negotiation within newOS4. Agents discover each other via VFS registrations and communicate through a VFS mailbox system. No central broker. ## Message Format All messages are JSON with this envelope: @@ -26,8 +26,19 @@ All messages are JSON with this envelope: } ``` -## Transport Layer -Each agent has a VFS mailbox at `/agents/<agent_id>/inbox.json` and publishes to `/agents/<agent_id>/outbox.json`. The orchestrator monitors inboxes via periodic polling (500ms interval). +## Transport Layer (VFS Mailbox) +Each agent has a VFS mailbox: +- **Inbox** — `/agents/<agent_id>/inbox.json` — receives incoming A2A messages +- **Outbox** — `/agents/<agent_id>/outbox.json` — agent writes results here after execution + +The orchestrator polls all agent inboxes every **500ms** via `_pollAgentMailboxes()`. When a new A2A message appears, it: +1. Reads the message envelope +2. Routes by type: + - `request` — task delegation, checks outbox for completion + - `response` — task result from agent + - `broadcast` — capability advertisement + - `heartbeat` — updates agent's lastHeartbeat timestamp +3. Marks messages as `_processed` to avoid re-routing ## Message Types - **request**: Point-to-point task delegation. From=orchestrator, To=worker. @@ -36,10 +47,23 @@ Each agent has a VFS mailbox at `/agents/<agent_id>/inbox.json` and publishes to - **lease**: Time-bound contract granting a worker exclusive task rights. - **heartbeat**: Alive signal. Every 5 seconds. Missing 3 consecutive = presumed dead. +## Commands +- `a2a-send <agent-id> <json-message>` — Send an A2A message to an agent's inbox +- `a2a-list` — Show all A2A messages across agents with status + ## Lifecycle -1. Orchestrator broadcasts AGENT_REQUEST -2. Workers respond with capability claim -3. Orchestrator issues LEASE with TTL -4. Worker executes, sends RESPONSE -5. Orchestrator ACKs, LEASE expires -6. Worker returns to available pool
\ No newline at end of file +1. Orchestrator sends `request` to agent's inbox via `a2a-send` +2. AI module receives delegated task, executes it +3. Result written to agent's outbox +4. Polling loop detects outbox completion, sends `response` message +5. Orchestrator marks lease as complete, agent returns to idle pool + +## Implementation Status +- ✅ Message format (v0.1 envelope) +- ✅ VFS mailbox transport (inbox.json / outbox.json) +- ✅ 500ms polling loop in orchestrator +- ✅ Type routing (request, response, broadcast, heartbeat) +- ✅ a2a-send / a2a-list CLI commands +- ⬜ Heartbeat sending from worker agents +- ⬜ Full broadcast discovery flow +- ⬜ Lease revocation via A2A message (currently uses separate LeaseModule)
\ No newline at end of file diff --git a/cheatsheet.html b/cheatsheet.html index 0ce8299..616df0e 100644 --- a/cheatsheet.html +++ b/cheatsheet.html @@ -119,7 +119,19 @@ pre{background:var(--bg2);border:1px solid var(--border);border-radius:var(--rad <div class="item"><span class="cmd">agent-list</span><span class="desc">List registered agents</span></div> <div class="item"><span class="cmd">lease-list</span><span class="desc">Show active leases</span></div> <div class="item"><span class="cmd">orchestrate</span><span class="desc">Multi-step task agent</span></div> - <div class="item"><span class="cmd">security-level <0-3></span><span class="desc">Set security level</span></div> + <div class="item"><span class="cmd">predict <task></span><span class="desc">Predict task duration/turns</span></div> +<div class="item"><span class="cmd">predictor-status</span><span class="desc">Model accuracy & data</span></div> +<div class="item"><span class="cmd">sandbox-status</span><span class="desc">Sandbox pool health</span></div> +<div class="item"><span class="cmd">sandbox-run <t></span><span class="desc">Run in sandbox</span></div> +<div class="item"><span class="cmd">memgraph-status</span><span class="desc">Memory graph stats</span></div> +<div class="item"><span class="cmd">memgraph-search</span><span class="desc">Search memory graph</span></div> +<div class="item"><span class="cmd">memgraph-stats</span><span class="desc">Graph analysis</span></div> +<div class="item"><span class="cmd">memgraph-prune</span><span class="desc">Prune stale edges</span></div> +<div class="item"><span class="cmd">world-model</span><span class="desc">Full world model</span></div> +<div class="item"><span class="cmd">world-model-summary</span><span class="desc">System self-awareness</span></div> +<div class="item"><span class="cmd">world-model-ask</span><span class="desc">Query the model</span></div> +<div class="item"><span class="cmd">world-model-refresh</span><span class="desc">Re-synthesize</span></div> +<div class="item"><span class="cmd">security-level <0-3></span><span class="desc">Set security level</span></div> <div class="item"><span class="cmd">cmd-allow / cmd-deny</span><span class="desc">Per-command access</span></div> <div class="item"><span class="cmd">checkpoint</span><span class="desc">Snapshot VFS state</span></div> <div class="item"><span class="cmd">health-check</span><span class="desc">System health check</span></div> @@ -171,6 +183,20 @@ pre{background:var(--bg2);border:1px solid var(--border);border-radius:var(--rad <div class="item"><span class="cmd">agent-status <id></span><span class="desc">Agent details</span></div> <div class="item"><span class="cmd">orchestrate <t></span><span class="desc">Delegate task</span></div> <div class="item"><span class="cmd">orchestrator-status</span><span class="desc">Agent pool status</span></div> +<div class="item"><span class="cmd">orchestrator-list</span><span class="desc">Full agent details</span></div> +<div class="item"><span class="cmd">orchestrator-spawn</span><span class="desc">Spawn worker agent</span></div> +<div class="item"><span class="cmd">orchestrator-sleep</span><span class="desc">Suspend agent to VFS</span></div> +<div class="item"><span class="cmd">orchestrator-wake</span><span class="desc">Restore from sleep</span></div> +<div class="item"><span class="cmd">orchestrator-kill</span><span class="desc">Terminate agent</span></div> +<div class="item"><span class="cmd">orchestrator-purge</span><span class="desc">Remove dead agents</span></div> +<div class="item"><span class="cmd">orchestrator-config</span><span class="desc">Tune orchestrator</span></div> +<div class="item"><span class="cmd">orchestrator-restart-policy</span><span class="desc">Set restart policy</span></div> +<div class="item"><span class="cmd">lease-create</span><span class="desc">Create time-bound lease</span></div> +<div class="item"><span class="cmd">lease-list</span><span class="desc">Active leases</span></div> +<div class="item"><span class="cmd">lease-revoke</span><span class="desc">Revoke lease</span></div> +<div class="item"><span class="cmd">lease-renew</span><span class="desc">Extend lease</span></div> +<div class="item"><span class="cmd">a2a-send <id></span><span class="desc">Send A2A message</span></div> +<div class="item"><span class="cmd">a2a-list</span><span class="desc">A2A message status</span></div> <div class="item"><span class="cmd">contract-list</span><span class="desc">List A2A contracts</span></div> <div class="item"><span class="cmd">contract-register</span><span class="desc">Register A2A contract</span></div> </div> diff --git a/downloads/newos4_whitepaper.html b/downloads/newos4_whitepaper.html index c9fd730..006113a 100644 --- a/downloads/newos4_whitepaper.html +++ b/downloads/newos4_whitepaper.html @@ -96,7 +96,7 @@ <tr><th>Pillar</th><th>Function</th><th>Status</th></tr> </thead> <tbody> - <tr><td><strong>Hyper-Modular Kernel</strong></td><td>45 hot-swappable ES modules across 3 layers (infrastructure, core, ext)</td><td class="status-done">✅ LIVE</td></tr> + <tr><td><strong>Hyper-Modular Kernel</strong></td><td>46 hot-swappable ES modules across 3 layers (infrastructure, core, ext)</td><td class="status-done">✅ LIVE</td></tr> <tr><td><strong>A2A Protocol</strong></td><td>Agent contracts with schema validation, orchestrator handshake, no central broker</td><td class="status-done">✅ LIVE</td></tr> <tr><td><strong>Neural VFS</strong></td><td>Semantic concept indexing with nvfs-index, nvfs-resolve, nvfs-tree, nvfs-status + concept:// URI handler</td><td class="status-done">✅ LIVE</td></tr> <tr><td><strong>Recursive Self-Optimization</strong></td><td>system-health telemetry command logging tool latency, VFS stats, module counts, error rates</td><td class="status-done">✅ LIVE</td></tr> @@ -130,9 +130,9 @@ │ (Contract registry, schema validation, RPC bus) │ ├──────────────────────────────────────────────────────────┤ │ Hyper-Modular Kernel │ -│ [infrastructure] module-loader, module-manager │ -│ [core] 23 modules — terminal, VFS, AI, ollama, ... │ -│ [ext] 19 modules — editor, mindmap, webdav, cron ... │ +│ [infrastructure] module-loader, module-manager, terminal, virtual-fs, ollama-manager│ +│ [core] 23 modules — AI, ollama, orchestration, ... │ +│ [ext] 18 modules — editor, mindmap, webdav, cron ... │ ├──────────────────────────────────────────────────────────┤ │ Neural VFS + Procedural Memory │ │ (Semantic indexing, concept:// URIs, skill graph) │ @@ -148,15 +148,15 @@ </ul> <h3>3.3 Module Layer Separation</h3> - <p>New in v4.3.1: All 45 modules are classified into three distinct layers tracked in <code>modules-index.json</code>:</p> + <p>New in v4.3.1: All 46 modules are classified into three distinct layers tracked in <code>modules-index.json</code>:</p> <table> <thead> <tr><th>Layer</th><th>Count</th><th>Load Strategy</th><th>Examples</th></tr> </thead> <tbody> - <tr><td><strong>Infrastructure</strong></td><td>3</td><td>Loaded by constructor (no user-facing commands)</td><td>module-loader, module-manager, ollama-manager</td></tr> - <tr><td><strong>Core</strong></td><td>23</td><td>Boot-loaded from BUILTIN list</td><td>terminal, vfs, aiassist, servermon, ollama, orchestration</td></tr> - <tr><td><strong>Extension</strong></td><td>19</td><td>Demand-loaded via packages.json</td><td>editor, mindmap, webdav, cron, theme, wpm, calc, voice</td></tr> + <tr><td><strong>Infrastructure</strong></td><td>5</td><td>Loaded by constructor (no user-facing commands)</td><td>module-loader, module-manager, ollama-manager, terminal, virtual-fs</td></tr> + <tr><td><strong>Core</strong></td><td>23</td><td>Boot-loaded from BUILTIN list</td><td>aiassist, servermon, ollama, orchestration, neural-vfs, secret-vault</td></tr> + <tr><td><strong>Extension</strong></td><td>18</td><td>Demand-loaded via packages.json</td><td>editor, mindmap, webdav, cron, theme, wpm, calc, voice</td></tr> </tbody> </table> <p>The <code>modules-list</code> terminal command displays layer tags <code>[core]</code>, <code>[ext]</code>, <code>[infra]</code> for instant visibility. <code>view_module</code> and <code>get_module_status</code> resolve file paths from the index, eliminating class-name heuristics.</p> @@ -165,11 +165,11 @@ <section> <h2>4. Pillar Deep Dives</h2> <h3>4.1 Hyper-Modular Kernel</h3> - <p>The kernel is a collection of 45 hot-swappable driver modules across three layers:</p> + <p>The kernel is a collection of 46 hot-swappable driver modules across three layers:</p> <ul> - <li><strong>Infrastructure</strong> (3) — module-loader, module-manager, ollama-manager. Loaded by the constructor, providing boot-time services.</li> - <li><strong>Core</strong> (23) — terminal, VFS, AI assist, server monitor, ollama, orchestration, agent registry, neural VFS, mod manager, and more. Boot-loaded from the <code>BUILTIN</code> list at startup.</li> - <li><strong>Extension</strong> (19) — editor, mindmap, webdav, cron, diff, theme, wpm, calc, voice, busybox, cryptogzip, filemgr, shell, alias, finder, tutorial, skill-pack-core, and more. Demand-loaded via <code>packages.json</code>.</li> + <li><strong>Infrastructure</strong> (5) — module-loader, module-manager, ollama-manager, terminal, virtual-fs. Loaded by the constructor, providing boot-time services.</li> + <li><strong>Core</strong> (23) — AI assist, server monitor, ollama, orchestration, agent registry, neural VFS, mod manager, secret-vault, and more. Boot-loaded from the <code>BUILTIN</code> list at startup.</li> + <li><strong>Extension</strong> (18) — editor, mindmap, webdav, cron, diff, theme, wpm, calc, voice, busybox, cryptogzip, filemgr, shell, alias, finder, tutorial, skill-pack-core, test-suite. Demand-loaded via <code>packages.json</code>.</li> </ul> <p>All modules tracked in <code>modules-index.json</code> — the single source of truth for module-to-file mapping, class names, and load strategies. Layer separation eliminates confusion about which modules are core OS vs. user-installable packages.</p> <p>Cache versioning via <code>?v=2026-06-03-neuralvfs</code> query parameter ensures all module loads use fresh files after system updates.</p> @@ -226,15 +226,21 @@ <li>✅ <strong>Localized Asset Management</strong> (Bypassing server security blocks).</li> </ul> - <h3>Phase 3 — Cognitive OS <span class="status-done">✅ SUBSTANTIALLY COMPLETE</span></h3> + <h3>Phase 3 — Cognitive OS <span class="status-done">✅ COMPLETE</span></h3> <ul> <li>✅ Neural VFS — semantic concept indexing (nvfs-index, nvfs-resolve, nvfs-tree, nvfs-status).</li> <li>✅ concept:// URI handler integrated into VFS readFile/ls (cat concept://skill works).</li> <li>✅ A2A Protocol — agent contracts with schema validation, orchestrator handshake (contract-register, contract-list).</li> - <li>✅ Recursive self-optimization — system-health telemetry command (tool latency, VFS stats, module stats, memory, error rates).</li> - <li>✅ Module layer separation — 45 modules classified across infrastructure/core/ext with modules-index.json.</li> - <li>⬜ Predictive resource allocation.</li> - <li>⬜ Global World Model.</li> + <li>✅ A2A VFS mailbox transport — a2a-send, a2a-list, 500ms polling.</li> + <li>✅ Agent lifecycle management (sleep/wake, VFS serialization, auto-reanimate).</li> + <li>✅ Autonomous sub-agent spawning with task-derived roles.</li> + <li>✅ Agent health monitoring and restart policies with exponential backoff.</li> + <li>✅ Recursive self-optimization — system-health telemetry command.</li> + <li>✅ Module layer separation — 51 modules across infrastructure/core/ext with modules-index.json.</li> + <li>✅ Sandboxed execution contexts via WebWorker pool (1-4 workers, VFS snapshots).</li> + <li>✅ Cross-session persistent memory graphs — 5 node types, weighted edges, VFS persistence.</li> + <li>✅ Predictive resource allocation — task duration/turns prediction from historical data.</li> + <li>✅ Global World Model — system self-awareness synthesis from all subsystems.</li> </ul> </section> @@ -231,9 +231,9 @@ footer .tagline{color:var(--accent2);margin-top:10px;font-weight:500} <h1>WebX11 <span>Docs</span></h1> </div> <div class="header-meta"> - <span class="badge badge-v">v4.3</span> - <span class="badge badge-new">18 core · 14 ext</span> - <span style="color:var(--text2)">145+ commands</span> + <span class="badge badge-v">v4.4</span> + <span class="badge badge-new">28 core · 17 ext</span> + <span style="color:var(--text2)">150+ commands</span> </div> </div> </header> @@ -248,15 +248,14 @@ footer .tagline{color:var(--accent2);margin-top:10px;font-weight:500} <a href="#skills" class="nav-link">Skills</a> <a href="#ollama" class="nav-link">Ollama</a> <a href="#web" class="nav-link">Search & Web</a> - <a href="#voice" class="nav-link">Voice & Sound</a> - <a href="#config" class="nav-link">Config</a> - <a href="#vault" class="nav-link">Vault</a> - <a href="#modules" class="nav-link">Modules</a> - <a href="#extras" class="nav-link">Extras</a> - <a href="#busybox" class="nav-link">BusyBox</a> - <a href="#shell" class="nav-link">Shell</a> - <a href="#sessions" class="nav-link">Sessions</a> - <a href="#storage" class="nav-link">Storage</a> + <a href="#agents" class="nav-link">Agents</a> + <a href="#a2a-contracts" class="nav-link">A2A</a> + <a href="#neural-vfs" class="nav-link">Neural VFS</a> + <a href="#server-monitor" class="nav-link">Monitor</a> + <a href="#debug-panel" class="nav-link">Debug</a> + <a href="#sandbox" class="nav-link">Sandbox</a> + <a href="#memory-graph" class="nav-link">Memory</a> + <a href="#world-model" class="nav-link">World Model</a> <a href="#security" class="nav-link">Security</a> <a href="#trouble" class="nav-link">Troubleshoot</a> <a href="#ref" class="nav-link">Quick Ref</a> @@ -283,7 +282,7 @@ footer .tagline{color:var(--accent2);margin-top:10px;font-weight:500} <div class="feature-grid"> <div class="feature-card"><h4>🖥️ Terminal OS</h4><p>Full virtual filesystem with ls, cd, cat, touch, edit, find, grep and 50+ Unix-like commands.</p></div> <div class="feature-card"><h4>🤖 AI Assistant</h4><p>Built-in AI agent with tool calling, skills, memory, and 28 tools. Just type <code>ai "your question"</code>.</p></div> - <div class="feature-card"><h4>🧩 Module System</h4><p>40+ modules including cron, mindmap, calculator, voice, WebDAV, themes, and shell scripting.</p></div> + <div class="feature-card"><h4>🧩 Module System</h4><p>46 modules across 3 layers — 28 core, 17 ext + test suite. Includes cron, mindmap, calculator, voice, WebDAV, themes, shell scripting, and agent orchestration.</p></div> <div class="feature-card"><h4>📚 Skills</h4><p>Self-improving AI remembers workflows and teaches you new capabilities — type <code>skill-list</code> to see what's installed.</p></div> </div> </section> @@ -1094,7 +1093,18 @@ vfs-import</pre> <tr><td><code>lease-list</code></td><td>Show active leases — agent tasks with status and TTL</td></tr> <tr><td><code>orchestrate <task></code></td><td>Delegate multi-step task to agent orchestrator</td></tr> <tr><td><code>orchestrator-status</code></td><td>Show orchestrator agent pool status</td></tr> + <tr><td><code>orchestrator-list</code></td><td>List all spawned agents with full details</td></tr> <tr><td><code>orchestrator-spawn <role> [caps...]</code></td><td>Spawn a new worker agent</td></tr> + <tr><td><code>orchestrator-sleep <id></code></td><td>Suspend agent to VFS (survives reload)</td></tr> + <tr><td><code>orchestrator-wake <id></code></td><td>Restore agent from VFS sleep state</td></tr> + <tr><td><code>orchestrator-kill <id></code></td><td>Terminate an agent and revoke leases</td></tr> + <tr><td><code>orchestrator-purge</code></td><td>Remove dead agents (>60s silent)</td></tr> + <tr><td><code>orchestrator-config <key> <value></code></td><td>Tune: spawn-limit, spawn-strategy, heartbeat-timeout, idle-timeout, restart-policy, a2a-polling</td></tr> + <tr><td><code>orchestrator-restart-policy <id> <policy></code></td><td>Set per-agent restart policy: on-failure, always, never</td></tr> + <tr><td><code>lease-create <agent-id> [duration] [task]</code></td><td>Create a time-bound lease for a worker agent</td></tr> + <tr><td><code>lease-list</code></td><td>Show active leases — agent tasks with status and TTL</td></tr> + <tr><td><code>lease-revoke <id></code></td><td>Revoke an active lease</td></tr> + <tr><td><code>lease-renew <id> <seconds></code></td><td>Extend an active lease by N seconds</td></tr> </table></div> </section> @@ -1104,6 +1114,8 @@ vfs-import</pre> <p>Define and enforce agent-to-agent contracts with input/output schema validation. Contracts are stored in <code>/.contracts/</code> and validated during orchestrator delegation.</p> <div class="table-wrap"><table> <tr><th>Command</th><th>Description</th></tr> + <tr><td><code>a2a-send <agent-id> <json></code></td><td>Send an A2A message to an agent's VFS inbox</td></tr> + <tr><td><code>a2a-list</code></td><td>Show all A2A messages with pending status</td></tr> <tr><td><code>contract-register <role> [fields...] --output [fields...]</code></td><td>Register a contract with input/output schema</td></tr> <tr><td><code>contract-list</code></td><td>List all registered contracts</td></tr> </table></div> @@ -1115,7 +1127,11 @@ vfs-import</pre> <p>Semantic concept-indexing layer over the hierarchical VFS. Scans files and builds a concept → path mapping using filenames, tags, and headers.</p> <div class="table-wrap"><table> <tr><th>Command</th><th>Description</th></tr> - <tr><td><code>nvfs-index</code></td><td>Scan VFS and build semantic concept index</td></tr> + <tr><td><code>memgraph-status</code></td><td>Memory graph node/edge counts</td></tr> + <tr><td><code>memgraph-search <q></code></td><td>Find nodes by label or type</td></tr> + <tr><td><code>memgraph-stats</code></td><td>Most connected nodes, strongest edges</td></tr> + <tr><td><code>memgraph-export</code></td><td>Export graph as JSON</td></tr> + <tr><td><code>memgraph-prune</code></td><td>Remove stale low-weight edges</td></tr> <tr><td><code>nvfs-status</code></td><td>Show index stats and last scan time</td></tr> <tr><td><code>nvfs-resolve <concept></code></td><td>Find files related to a concept (exact + fuzzy)</td></tr> <tr><td><code>nvfs-tree [concept]</code></td><td>Show concept-to-file mapping tree</td></tr> @@ -1132,7 +1148,10 @@ vfs-import</pre> <tr><td><code>servers</code></td><td>List all monitored servers</td></tr> <tr><td><code>server-check <name></code></td><td>Check a server's current status</td></tr> <tr><td><code>monitor</code></td><td>Toggle auto-monitoring (60s interval)</td></tr> - <tr><td><code>system-health</code></td><td>Show tool latency, VFS stats, module count, memory, error rates</td></tr> +<tr><td><code>system-health</code></td><td>Tool latency, VFS stats, modules, memory, error rates</td></tr> + <tr><td><code>predict <task></code></td><td>Predict task duration, tool turns, and confidence</td></tr> + <tr><td><code>predictor-status</code></td><td>Prediction model accuracy and data points</td></tr> + <tr><td><code>predictor-config <k> <v></code></td><td>Tune confidence-threshold, min-data-points</td></tr> </table></div> </section> <section id="debug-panel"> @@ -1152,6 +1171,48 @@ vfs-import</pre> <p>Useful for troubleshooting module load failures, VFS corruption, and AI context bloat.</p> </section> + <!-- ── Sandbox ── --> + <section id="sandbox"> + <h2>🧪 Sandboxed Execution</h2> + <p>WebWorkers provide isolated contexts for task execution. Each worker gets a VFS snapshot so operations don't interfere with the main terminal.</p> + <div class="table-wrap"><table> + <tr><th>Command</th><th>Description</th></tr> + <tr><td><code>sandbox-status</code></td><td>Show worker pool health and task metrics</td></tr> + <tr><td><code>sandbox-config <k> <v></code></td><td>Configure max-workers, min-workers</td></tr> + <tr><td><code>sandbox-run <task></code></td><td>Execute a task in the sandbox pool</td></tr> + </table></div> + <p><code>orchestrate --sandboxed <task></code> routes delegation through the sandbox for full isolation.</p> + </section> + + <!-- ── Memory Graph ── --> + <section id="memory-graph"> + <h2>🧠 Memory Graph</h2> + <p>Cross-session persistent graph database tracking files, concepts, tasks, and tools. Nodes store metadata, edges record relationships with weighted strength. Survives page reloads via VFS persistence.</p> + <div class="table-wrap"><table> + <tr><th>Command</th><th>Description</th></tr> + <tr><td><code>memgraph-status</code></td><td>Show node/edge counts and graph health</td></tr> + <tr><td><code>memgraph-search <q></code></td><td>Find nodes by label, type, or id</td></tr> + <tr><td><code>memgraph-stats</code></td><td>Most connected nodes and strongest edges</td></tr> + <tr><td><code>memgraph-export</code></td><td>Export full graph as JSON to VFS</td></tr> + <tr><td><code>memgraph-prune</code></td><td>Remove stale low-weight edges (>30d, <3 uses)</td></tr> + </table></div> + <p>Context is automatically injected into AI system prompts for cross-session awareness.</p> + </section> + + <!-- ── World Model ── --> + <section id="world-model"> + <h2>🌐 World Model</h2> + <p>System self-awareness engine. Synthesizes data from memory graph, module system, skills index, telemetry, and agent pool into a unified model at <code>/system/world-model.json</code>. Auto-refreshes every 5 minutes.</p> + <div class="table-wrap"><table> + <tr><th>Command</th><th>Description</th></tr> + <tr><td><code>world-model</code></td><td>Show full world model breakdown</td></tr> + <tr><td><code>world-model-summary</code></td><td>One-line system status summary</td></tr> + <tr><td><code>world-model-ask <q></code></td><td>Query modules, skills, agents, health, or changes</td></tr> + <tr><td><code>world-model-refresh</code></td><td>Force re-synthesis immediately</td></tr> + <tr><td><code>world-model-config <k> <v></code></td><td>Tune refresh-interval and max-projects</td></tr> + </table></div> + </section> + <!-- ── Security ── --> <section id="security"> <h2>🔒 Security</h2> diff --git a/modules-index.json b/modules-index.json index 41323ef..e2319ae 100644 --- a/modules-index.json +++ b/modules-index.json @@ -1,6 +1,6 @@ { - "version": "2026-06-03-neuralvfs", - "generated": "2026-06-04T01:25:00.000Z", + "version": "2026-06-05-v4.4.0", + "generated": "2026-06-05T15:40:00.000Z", "modules": { "module-agent-registry": { "name": "module-agent-registry", @@ -16,7 +16,7 @@ "contract-register", "contract-list" ], - "description": "module-agent-registry.js — VFS-backed agent directory with A2A contracts", + "description": "module-agent-registry.js \u2014 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 — WebXAIAliasModule", + "description": "Extracted from newOS4 index.html \u2014 WebXAIAliasModule", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -57,7 +57,7 @@ "ai-stop", "ai-model" ], - "description": "Extracted from newOS4 index.html — WebX11AIAssistantModule (Full Agent Loop)", + "description": "Extracted from newOS4 index.html \u2014 WebX11AIAssistantModule (Full Agent Loop)", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -92,7 +92,7 @@ "config-export", "config-reset" ], - "description": "Extracted from newOS4 index.html — ConfigManagerModule", + "description": "Extracted from newOS4 index.html \u2014 ConfigManagerModule", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -108,7 +108,7 @@ "ddg", "duck" ], - "description": "Extracted from newOS4 index.html — DuckDuckGoModule", + "description": "Extracted from newOS4 index.html \u2014 DuckDuckGoModule", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -128,7 +128,7 @@ "prompt-var", "prompt-vars" ], - "description": "Extracted from newOS4 index.html — DynamicPromptsModule", + "description": "Extracted from newOS4 index.html \u2014 DynamicPromptsModule", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -205,7 +205,7 @@ "restore", "share-mod" ], - "description": "Extracted from newOS4 index.html — ModuleIOModule", + "description": "Extracted from newOS4 index.html \u2014 ModuleIOModule", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -222,7 +222,7 @@ "lease-create", "lease-revoke" ], - "description": "module-leases.js — Lease system for agent task delegation", + "description": "module-leases.js \u2014 Lease system for agent task delegation", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -256,7 +256,7 @@ "load-local", "loadlocal" ], - "description": "Extracted from newOS4 index.html — ModuleManager", + "description": "Extracted from newOS4 index.html \u2014 ModuleManager", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -277,7 +277,7 @@ "load-local", "loadlocal" ], - "description": "Extracted from newOS4 index.html — ModuleManagerModule", + "description": "Extracted from newOS4 index.html \u2014 ModuleManagerModule", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -316,7 +316,7 @@ "nvfs-resolve", "nvfs-tree" ], - "description": "── Core Index Builder ──", + "description": "\u2500\u2500 Core Index Builder \u2500\u2500", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -343,7 +343,7 @@ "ai", "ask" ], - "description": "Ollama model management — connect, list models, pull/delete, run inference", + "description": "Ollama model management \u2014 connect, list models, pull/delete, run inference", "dependencies": [], "version": "2.0.0", "updated": "2026-06-04", @@ -362,7 +362,7 @@ "orchestrator-kill", "orchestrator-purge" ], - "description": "module-orchestrator.js — Multi-agent orchestration system", + "description": "module-orchestrator.js \u2014 Multi-agent orchestration system", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -400,7 +400,7 @@ "monitor", "system-health" ], - "description": "module-servermon.js — Server monitoring + self-optimization telemetry", + "description": "module-servermon.js \u2014 Server monitoring + self-optimization telemetry", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -420,7 +420,7 @@ "gui-clear", "gui-run" ], - "description": "Extracted from newOS4 index.html — SimpleGUIModule", + "description": "Extracted from newOS4 index.html \u2014 SimpleGUIModule", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -442,7 +442,7 @@ "skill-import", "skill-sync" ], - "description": "Extracted from newOS4 index.html — SkillsModule (v4.2 — Self-improving AI)", + "description": "Extracted from newOS4 index.html \u2014 SkillsModule (v4.2 \u2014 Self-improving AI)", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -461,7 +461,7 @@ "context-delete", "smart-ask" ], - "description": "Extracted from newOS4 index.html — SmartContextModule", + "description": "Extracted from newOS4 index.html \u2014 SmartContextModule", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -495,7 +495,7 @@ "commands", "check" ], - "description": "Extracted from newOS4 index.html — SystemModule", + "description": "Extracted from newOS4 index.html \u2014 SystemModule", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -513,7 +513,7 @@ "model", "models" ], - "description": "Extracted from newOS4 index.html — OllamaManager / LLMRouter", + "description": "Extracted from newOS4 index.html \u2014 OllamaManager / LLMRouter", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -535,7 +535,7 @@ "secret-setpin", "secret-status" ], - "description": "Extracted from newOS4 index.html — SecretVault", + "description": "Extracted from newOS4 index.html \u2014 SecretVault", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -556,11 +556,11 @@ "cmd-deny", "cd" ], - "description": "Extracted from newOS4 index.html — WebX11Terminal", + "description": "Extracted from newOS4 index.html \u2014 WebX11Terminal", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", - "layer": "ext" + "layer": "infrastructure" }, "virtual-fs": { "name": "virtual-fs", @@ -577,7 +577,7 @@ "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", - "layer": "ext" + "layer": "infrastructure" }, "alias": { "name": "alias", @@ -619,7 +619,7 @@ "sleep", "expand" ], - "description": "── which ──", + "description": "\u2500\u2500 which \u2500\u2500", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -670,7 +670,7 @@ "encrypt", "decrypt" ], - "description": "CryptoGzipModule — VFS-level gzip/encrypt commands for WebX11 newOS4", + "description": "CryptoGzipModule \u2014 VFS-level gzip/encrypt commands for WebX11 newOS4", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -766,7 +766,7 @@ "$", "set" ], - "description": "── Hook into terminal.executeCommand ──", + "description": "\u2500\u2500 Hook into terminal.executeCommand \u2500\u2500", "dependencies": [], "version": "1.0.0", "updated": "2026-06-03", @@ -873,6 +873,71 @@ "version": "1.0.0", "updated": "2026-06-03", "layer": "ext" + }, + "test-suite": { + "class": "TestSuiteModule", + "file": "modules/test/test-suite.js", + "layer": "ext", + "description": "32 automated tests: filesystem, busybox, shell, cryptogzip, skills, security, modules. Load manually with load-local then test", + "commands": [ + "test" + ], + "manual_load": true + }, + "module-sandbox": { + "class": "SandboxModule", + "file": "modules/core/module-sandbox.js", + "layer": "core", + "description": "WebWorker sandbox pool for isolated task execution", + "commands": [ + "sandbox-status", + "sandbox-config", + "sandbox-run" + ] + }, + "sandbox-worker": { + "class": "SandboxWorker", + "file": "modules/core/sandbox-worker.js", + "layer": "infrastructure", + "description": "WebWorker entry point for sandboxed agent contexts", + "commands": [] + }, + "module-memory-graph": { + "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", + "commands": [ + "memgraph-status", + "memgraph-search", + "memgraph-stats", + "memgraph-export", + "memgraph-prune" + ] + }, + "module-predictor": { + "class": "PredictorModule", + "file": "modules/core/module-predictor.js", + "layer": "core", + "description": "Predictive resource allocation \u2014 estimates task duration and tool turns from historical data", + "commands": [ + "predict", + "predictor-status", + "predictor-config" + ] + }, + "module-world-model": { + "class": "WorldModelModule", + "file": "modules/core/module-world-model.js", + "layer": "core", + "description": "Global World Model \u2014 system self-awareness synthesis from all subsystems", + "commands": [ + "world-model", + "world-model-summary", + "world-model-ask", + "world-model-refresh", + "world-model-config" + ] } }, "total": 45 diff --git a/modules/core/module-aiassist.js b/modules/core/module-aiassist.js index cb1482f..b93b12d 100644 --- a/modules/core/module-aiassist.js +++ b/modules/core/module-aiassist.js @@ -572,6 +572,28 @@ RULES: } catch (e) {} } + // Inject memory graph context for cross-session awareness + try { + const memgraph = this.terminal.moduleManager.modules['MemoryGraphModule']; + if (memgraph) { + const mctx = memgraph.getContextForPrompt(); + if (mctx) { + messages.push({ role: 'system', content: mctx }); + } + } + } catch (e) { /* memory graph not available */ } + + // Inject world model context for system self-awareness + try { + const wm = this.terminal.moduleManager.modules['WorldModelModule']; + if (wm) { + const wctx = wm.getContextForPrompt(); + if (wctx) { + messages.push({ role: 'system', content: wctx }); + } + } + } catch (e) { /* world model not available */ } + loopResult = await this.agenticLoop(messages); this.context.push({ role: 'user', content: userMessage }); if (loopResult.finalAnswer) { @@ -1497,7 +1519,7 @@ RULES: if (resp.ok) { const src = await resp.text(); this.terminal.fs.writeFile(`_mods/${modName}.orig.js`, src); - return src.substring(0, 8000); + return src; } } catch (e) { /* fall through */ } } @@ -1519,7 +1541,7 @@ RULES: if (resp.ok) { const src = await resp.text(); this.terminal.fs.writeFile(`_mods/${modName}.orig.js`, src); - return src.substring(0, 8000); + return src; } } } @@ -1542,6 +1564,28 @@ RULES: } } catch (e) {} } + // Fallback: use modules-index.json for core modules (not in registry) + if (!code) { + try { + const idxResp = await fetch('/spaces/newos4/modules-index.json?v=' + (localStorage.getItem('webx11_cache_ver') || '')); + if (idxResp.ok) { + const idx = await idxResp.json(); + let match = Object.values(idx.modules).find(m => m.class === modName); + if (!match) match = idx.modules[modName]; + if (match) { + const filePath = match.file; + const fetchUrl = `/spaces/newos4/${filePath}`; + const resp = await fetch(fetchUrl); + if (resp.ok) { + code = await resp.text(); + this.terminal.fs.writeFile(`_mods/${modName}.orig.js`, code); + this.terminal.fs.writeFile(`_mods/${modName}.js`, code); + this.terminal.fs.writeFile(`_mods/${modName}.meta.json`, JSON.stringify({ originalUrl: fetchUrl, modifiedAt: new Date().toISOString(), editCount: 0 })); + } + } + } + } catch (e) {} + } if (code) this.terminal.openCodeEditor(modName, url || '', code); return code ? `Editor opened for ${modName} (${code.length} chars)` : JSON.stringify({ error: `Could not load source for ${modName}` }); } diff --git a/modules/core/module-loader.js b/modules/core/module-loader.js index e7fc64a..b32577d 100644 --- a/modules/core/module-loader.js +++ b/modules/core/module-loader.js @@ -33,6 +33,10 @@ 'module-agent-registry.js', 'module-leases.js', 'module-orchestrator.js', + 'module-sandbox.js', + 'module-memory-graph.js', + 'module-predictor.js', + 'module-world-model.js', 'module-neural-vfs.js' ]; @@ -102,7 +106,7 @@ window.__moduleLoaderReload = reloadWithSelection; -const LATEST_VER = '2026-06-03-neuralvfs'; +const LATEST_VER = '2026-06-05-v4.4.0'; const CACHE = '?v=' + LATEST_VER; const stored = localStorage.getItem('webx11_cache_ver'); if (stored !== LATEST_VER) { diff --git a/modules/core/module-memory-graph.js b/modules/core/module-memory-graph.js new file mode 100644 index 0000000..2382a4a --- /dev/null +++ b/modules/core/module-memory-graph.js @@ -0,0 +1,400 @@ +// module-memory-graph.js — Cross-session persistent memory graph +// Nodes: files, concepts, tasks, patterns, users +// Edges: relationships with weight, last_used +// Commands: memgraph-status, memgraph-search, memgraph-stats, memgraph-export, memgraph-prune +class MemoryGraphModule { + constructor() { + this.commands = {}; + this.terminal = null; + this._nodes = {}; + this._edges = []; + this._indexPath = '/.memory_graph/'; + this._debounceTimer = null; + this._initialized = false; + this._totalNodeAccesses = 0; + } + + getHelpInfo() { + return { + 'memgraph-status': 'memgraph-status — Show node/edge counts, last access', + 'memgraph-search': 'memgraph-search <query> — Find nodes by label or type', + 'memgraph-stats': 'memgraph-stats — Most connected nodes, strongest edges, cluster info', + 'memgraph-export': 'memgraph-export — Export graph as JSON', + 'memgraph-prune': 'memgraph-prune — Remove stale low-weight edges (>30d, <3 accesses)', + }; + } + + registerCommands() { + this.commands['memgraph-status'] = () => this.showStatus(); + this.commands['memgraph-search'] = (args) => this.search(args.join(' ')); + this.commands['memgraph-stats'] = () => this.showStats(); + this.commands['memgraph-export'] = () => this.exportGraph(); + this.commands['memgraph-prune'] = () => this.prune(); + } + + init(terminal) { + this.terminal = terminal; + this._loadFromVFS(); + this._initialized = true; + this.terminal.print('🧠 Memory Graph loaded — cross-session context active', 'info'); + // Log startup for graph + this.addNode('session', `session-${Date.now()}`, `Session ${new Date().toLocaleDateString()}`); + } + + // ─── Node API ─── + + addNode(type, id, label, metadata = {}) { + const key = `${type}:${id}`; + if (!this._nodes[key]) { + this._nodes[key] = { + id: key, + type, + label: label || id, + metadata: metadata || {}, + created: new Date().toISOString(), + updated: new Date().toISOString(), + access_count: 1 + }; + } else { + this._nodes[key].updated = new Date().toISOString(); + this._nodes[key].access_count++; + if (metadata && Object.keys(metadata).length > 0) { + Object.assign(this._nodes[key].metadata, metadata); + } + } + this._totalNodeAccesses++; + this._debounceSave(); + return key; + } + + getNode(type, id) { + return this._nodes[`${type}:${id}`] || null; + } + + findNodes(query) { + const q = query.toLowerCase(); + return Object.values(this._nodes).filter(n => + n.label.toLowerCase().includes(q) || + n.type.toLowerCase().includes(q) || + (n.id && n.id.toLowerCase().includes(q)) + ); + } + + // ─── Edge API ─── + + addEdge(sourceKey, targetKey, type, weight = 1) { + // Check if edge already exists + let edge = this._edges.find(e => + e.source === sourceKey && e.target === targetKey && e.type === type + ); + if (edge) { + edge.weight = (edge.weight + weight) / 2; + edge.last_used = new Date().toISOString(); + edge.use_count++; + } else { + this._edges.push({ + source: sourceKey, + target: targetKey, + type, + weight, + created: new Date().toISOString(), + last_used: new Date().toISOString(), + use_count: 1 + }); + } + this._debounceSave(); + } + + // ─── Event-based Learning ─── + + recordFileAccess(path) { + const name = path.split('/').pop() || path; + const nodeKey = this.addNode('file', path, name); + const sessionKey = `session:session-${Math.floor(Date.now() / 86400000)}`; + this.addEdge(sessionKey, nodeKey, 'accessed', 0.5); + + // Extract file extension edge + const ext = path.split('.').pop(); + if (ext && ext.length < 10) { + this.addNode('type', ext, `.${ext} files`); + this.addEdge(nodeKey, `type:${ext}`, 'has_type', 0.3); + } + } + + recordToolCall(toolName, success) { + const nodeKey = this.addNode('tool', toolName, toolName); + const sessionKey = `session:session-${Math.floor(Date.now() / 86400000)}`; + this.addEdge(sessionKey, nodeKey, success ? 'used' : 'failed', success ? 0.5 : 0.8); + } + + recordConcept(concept, filePath) { + const conceptKey = this.addNode('concept', concept, concept); + const fileKey = this.addNode('file', filePath, filePath.split('/').pop()); + this.addEdge(conceptKey, fileKey, 'maps_to', 0.7); + this.addEdge(fileKey, conceptKey, 'tagged_as', 0.7); + } + + recordTask(task, result, agentId) { + const taskId = `task-${Date.now()}`; + const taskKey = this.addNode('task', taskId, task.substring(0, 60), { + full: task, + result: result?.substring(0, 100), + agent: agentId + }); + const sessionKey = `session:session-${Math.floor(Date.now() / 86400000)}`; + this.addEdge(sessionKey, taskKey, 'executed', 0.6); + + // Extract keywords as concepts + const words = task.toLowerCase().split(/\s+/).filter(w => w.length > 3); + for (const word of [...new Set(words)].slice(0, 5)) { + this.addNode('concept', word, word); + this.addEdge(taskKey, `concept:${word}`, 'involves', 0.4); + } + + // Link to tools used (if any found in task) + const knownTools = ['list_files', 'read_file', 'write_file', 'web_search', 'web_fetch', + 'edit_module', 'view_module', 'run_command', 'orchestrate']; + for (const tool of knownTools) { + if (task.toLowerCase().includes(tool.replace('_', ' '))) { + this.recordToolCall(tool, true); + } + } + } + + // ─── Context Injection ─── + + getContextSummary() { + // Get top active nodes from recent session + const now = Date.now(); + const today = `session:session-${Math.floor(now / 86400000)}`; + const yesterday = `session:session-${Math.floor(now / 86400000) - 1}`; + + const lines = []; + const totalNodes = Object.keys(this._nodes).length; + const totalEdges = this._edges.length; + + lines.push(`Memory Graph: ${totalNodes} nodes, ${totalEdges} edges`); + + // Recent task context + const recentTasks = Object.values(this._nodes) + .filter(n => n.type === 'task') + .sort((a, b) => new Date(b.updated) - new Date(a.updated)) + .slice(0, 3); + + if (recentTasks.length > 0) { + lines.push('Recent tasks:'); + for (const t of recentTasks) { + lines.push(` - ${t.label.substring(0, 50)}`); + } + } + + // Most accessed files + const topFiles = Object.values(this._nodes) + .filter(n => n.type === 'file') + .sort((a, b) => b.access_count - a.access_count) + .slice(0, 3); + + if (topFiles.length > 0) { + lines.push('Most accessed files:'); + for (const f of topFiles) { + lines.push(` - ${f.label} (${f.access_count}x)`); + } + } + + return lines.join('\\n'); + } + + getContextForPrompt() { + const summary = this.getContextSummary(); + if (!summary) return ''; + return `[Memory Graph Context]\\n${summary}\\n`; + } + + // ─── Commands ─── + + showStatus() { + const types = {}; + for (const n of Object.values(this._nodes)) { + types[n.type] = (types[n.type] || 0) + 1; + } + const typeStr = Object.entries(types) + .sort((a, b) => b[1] - a[1]) + .map(([t, c]) => `${t}: ${c}`) + .join(', '); + + this.terminal.print('🧠 Memory Graph', 'header'); + this.terminal.print(` Nodes: ${Object.keys(this._nodes).length} (${typeStr})`, 'info'); + this.terminal.print(` Edges: ${this._edges.length}`, 'info'); + this.terminal.print(` Total accesses: ${this._totalNodeAccesses}`, 'info'); + this.terminal.print(` Path: ${this._indexPath}`, 'info'); + } + + search(query) { + if (!query) return this.terminal.print('Usage: memgraph-search <query>', 'error'); + const results = this.findNodes(query); + if (results.length === 0) return this.terminal.print(`No nodes matching "${query}"`, 'info'); + + this.terminal.print(`🔍 Found ${results.length} nodes:`, 'header'); + for (const n of results.slice(0, 20)) { + const edges = this._edges.filter(e => e.source === n.id || e.target === n.id); + this.terminal.print(` ${n.type}:${n.label} (${edges.length} edges, ${n.access_count}x)`, 'info'); + for (const e of edges.slice(0, 3)) { + const other = e.source === n.id ? e.target : e.source; + const otherLabel = (this._nodes[other] || {}).label || other; + this.terminal.print(` └─ ${e.type} → ${otherLabel} (w:${e.weight.toFixed(1)})`, 'text-muted'); + } + } + if (results.length > 20) this.terminal.print(` ... and ${results.length - 20} more`, 'info'); + } + + showStats() { + if (Object.keys(this._nodes).length === 0) { + return this.terminal.print('No graph data yet. Use commands normally to build it.', 'info'); + } + + // Most connected nodes + const connected = {}; + for (const e of this._edges) { + connected[e.source] = (connected[e.source] || 0) + 1; + connected[e.target] = (connected[e.target] || 0) + 1; + } + const top = Object.entries(connected) + .sort((a, b) => b[1] - a[1]) + .slice(0, 5); + + this.terminal.print('📊 Memory Graph Stats', 'header'); + this.terminal.print(' Most connected:', 'info'); + for (const [key, count] of top) { + const label = (this._nodes[key] || {}).label || key; + this.terminal.print(` ${label} — ${count} connections`, 'info'); + } + + // Strongest edges + const strong = [...this._edges].sort((a, b) => b.weight - a.weight).slice(0, 3); + if (strong.length > 0) { + this.terminal.print(' Strongest relationships:', 'info'); + for (const e of strong) { + const sLabel = (this._nodes[e.source] || {}).label || e.source; + const tLabel = (this._nodes[e.target] || {}).label || e.target; + this.terminal.print(` ${sLabel} → ${tLabel} (${e.type}, w:${e.weight.toFixed(2)}, used ${e.use_count}x)`, 'info'); + } + } + + // Cluster density + const density = this._edges.length / Math.max(Object.keys(this._nodes).length, 1); + this.terminal.print(` Density: ${density.toFixed(2)} edges/node`, 'info'); + } + + exportGraph() { + const exportData = { + version: 1, + exported: new Date().toISOString(), + nodes: Object.values(this._nodes), + edges: this._edges, + stats: { + totalNodes: Object.keys(this._nodes).length, + totalEdges: this._edges.length, + totalAccesses: this._totalNodeAccesses + } + }; + const json = JSON.stringify(exportData, null, 2); + // Save to VFS + try { + if (!this.terminal.fs.readDir(this._indexPath)) { + this.terminal.fs.mkdirRecursive(this._indexPath); + } + this.terminal.fs.writeFile(`${this._indexPath}export.json`, json); + this.terminal.print(`✅ Graph exported (${json.length} bytes) to ${this._indexPath}export.json`, 'success'); + } catch (e) { + this.terminal.print(`❌ Export failed: ${e.message}`, 'error'); + } + } + + prune() { + const now = Date.now(); + const THIRTY_DAYS = 30 * 24 * 60 * 60 * 1000; + + const before = this._edges.length; + this._edges = this._edges.filter(e => { + const age = now - new Date(e.last_used).getTime(); + // Keep if: used recently (<30d) OR used 3+ times regardless of age + if (age < THIRTY_DAYS) return true; + if (e.use_count >= 3) return true; + // Or if weight is high + if (e.weight > 0.8) return true; + return false; + }); + const pruned = before - this._edges.length; + + // Also prune orphan nodes (no edges) + const connectedNodes = new Set(); + for (const e of this._edges) { + connectedNodes.add(e.source); + connectedNodes.add(e.target); + } + const nodeBefore = Object.keys(this._nodes).length; + for (const key of Object.keys(this._nodes)) { + if (!connectedNodes.has(key)) { + // Keep session and high-access nodes + if (this._nodes[key].access_count >= 5) continue; + delete this._nodes[key]; + } + } + const nodesPruned = nodeBefore - Object.keys(this._nodes).length; + + this._saveNow(); + this.terminal.print(`🧹 Pruned ${pruned} edges and ${nodesPruned} orphan nodes`, nodesPruned > 0 ? 'warning' : 'info'); + } + + // ─── Persistence ─── + + _loadFromVFS() { + try { + const fs = this.terminal.fs; + if (!fs) return; + + // Try loading from export first + const exportData = fs.fsReadJSON(`${this._indexPath}export.json`); + if (exportData && exportData.nodes) { + for (const n of exportData.nodes) { + this._nodes[n.id] = n; + } + this._edges = exportData.edges || []; + this._totalNodeAccesses = (exportData.stats?.totalAccesses) || 0; + return; + } + + // Fallback: load nodes and edges separately + const nodes = fs.fsReadJSON(`${this._indexPath}nodes.json`); + if (nodes) this._nodes = nodes; + const edges = fs.fsReadJSON(`${this._indexPath}edges.json`); + if (edges) this._edges = edges; + } catch (e) { + console.warn('[MemoryGraph] Load error (fresh start):', e.message); + } + } + + _saveNow() { + try { + const fs = this.terminal.fs; + if (!fs) return; + if (!fs.readDir(this._indexPath)) { + fs.mkdirRecursive(this._indexPath); + } + fs.fsWriteJSON(`${this._indexPath}nodes.json`, this._nodes); + fs.fsWriteJSON(`${this._indexPath}edges.json`, this._edges); + } catch (e) { + console.error('[MemoryGraph] Save error:', e.message); + } + } + + _debounceSave() { + if (this._debounceTimer) clearTimeout(this._debounceTimer); + this._debounceTimer = setTimeout(() => this._saveNow(), 2000); + } +} + +// Required for module system +if (typeof window !== 'undefined') { + window.MemoryGraphModule = MemoryGraphModule; +}
\ No newline at end of file diff --git a/modules/core/module-orchestrator.js b/modules/core/module-orchestrator.js index de88424..ec385bb 100644 --- a/modules/core/module-orchestrator.js +++ b/modules/core/module-orchestrator.js @@ -1,12 +1,22 @@ -// module-orchestrator.js — Multi-agent orchestration system -// Commands: orchestrate, orchestrator-status, orchestrator-spawn, orchestrator-kill, orchestrator-purge +// module-orchestrator.js — Multi-agent orchestration system with A2A mailbox transport +// Commands: orchestrate, orchestrator-status, orchestrator-spawn, orchestrator-kill, +// orchestrator-purge, orchestrator-config, orchestrator-list, orchestrator-sleep, +// orchestrator-wake, orchestrator-restart-policy, a2a-send, a2a-list class OrchestratorModule { constructor() { this.commands = {}; this.terminal = null; this._agents = {}; - this._SPAWN_LIMIT = 3; + this._SPAWN_LIMIT = 5; + this._SPAWN_STRATEGY = 'eager'; + this._HEARTBEAT_TIMEOUT = 15000; + this._IDLE_TIMEOUT = 120000; + this._RESTART_POLICY = 'never'; + this._A2A_POLLING = true; + this._a2aPollTimer = null; this._heartbeatInterval = null; + this._a2aMessages = {}; + this._restartBackoffs = {}; } registerCommands() { this.commands['orchestrate'] = (args) => this.delegateTask(args.join(' ')); @@ -14,6 +24,29 @@ class OrchestratorModule { this.commands['orchestrator-spawn'] = (args) => this.spawnAgent(args[0], args.slice(1)); this.commands['orchestrator-kill'] = (args) => this.killAgent(args[0]); this.commands['orchestrator-purge'] = () => this.purgeDeadAgents(); + this.commands['orchestrator-config'] = (args) => this.setConfig(args); + this.commands['orchestrator-list'] = () => this.listAgents(); + this.commands['orchestrator-sleep'] = (args) => this.sleepAgent(args[0]); + this.commands['orchestrator-wake'] = (args) => this.wakeAgent(args[0]); + this.commands['orchestrator-restart-policy'] = (args) => this.setRestartPolicy(args); + this.commands['a2a-send'] = (args) => this.sendA2AMessage(args); + this.commands['a2a-list'] = () => this.listA2AMessages(); + } + getHelpInfo() { + return { + 'orchestrate': 'orchestrate <task> — Delegate a task to an available agent (auto-spawns if needed)', + 'orchestrator-status': 'orchestrator-status — Show agent pool status and config', + 'orchestrator-spawn': 'orchestrator-spawn [role] [capabilities...] — Manually spawn a new agent', + 'orchestrator-kill': 'orchestrator-kill <agent-id> — Terminate an agent and revoke leases', + 'orchestrator-purge': 'orchestrator-purge — Remove dead agents (>60s since heartbeat)', + 'orchestrator-config': 'orchestrator-config <key> <value> — Tune: spawn-limit, spawn-strategy, heartbeat-timeout, idle-timeout, restart-policy, a2a-polling', + 'orchestrator-list': 'orchestrator-list — List all spawned agents with full details', + 'orchestrator-sleep': 'orchestrator-sleep <agent-id> — Suspend agent to VFS', + 'orchestrator-wake': 'orchestrator-wake <agent-id> — Restore agent from VFS sleep', + 'orchestrator-restart-policy': 'orchestrator-restart-policy <agent-id> <on-failure|always|never> — Set per-agent restart policy', + 'a2a-send': 'a2a-send <agent-id> <json-message> — Send an A2A message to an agent\'s inbox', + 'a2a-list': 'a2a-list — Show pending A2A messages across all agents', + }; } init(terminal) { this.terminal = terminal; @@ -22,10 +55,129 @@ class OrchestratorModule { if (registry) { registry.registerAgent(['register', 'orchestrator-0', 'delegate', 'spawn', 'monitor', 'purge']); } + // Reanimate sleeping agents from VFS + this._reanimateFromVFS(); + // A2A mailbox polling loop (500ms) + if (this._A2A_POLLING) { + this._a2aPollTimer = setInterval(() => this._pollAgentMailboxes(), 500); + } // Heartbeat watchdog every 3 seconds this._heartbeatInterval = setInterval(() => this._checkHeartbeats(), 3000); - this.terminal.print('🧠 Orchestrator loaded — ready to delegate', 'success'); + // Idle timeout sweep every 30s + setInterval(() => this._sweepIdle(), 30000); + this.terminal.print('🧠 Orchestrator loaded — A2A mailbox transport active', 'success'); + } + + // ─── A2A Mailbox Transport ─── + + sendA2AMessage(args) { + if (args.length < 2) return this.terminal.print('Usage: a2a-send <agent-id> <json-message>', 'error'); + const agentId = args[0]; + let payload; + try { + payload = JSON.parse(args.slice(1).join(' ')); + } catch (e) { + // Treat as plain text message + payload = { type: 'request', payload: { text: args.slice(1).join(' ') } }; + } + const agent = this._agents[agentId]; + if (!agent) return this.terminal.print(`❌ Agent '${agentId}' not found`, 'error'); + + const msg = { + a2a_version: '0.1', + msg_id: `msg-${Date.now()}-${Math.random().toString(36).substr(2, 4)}`, + type: payload.type || 'request', + from: 'orchestrator-0', + to: agentId, + timestamp: new Date().toISOString(), + ttl_seconds: 30, + contract: agent.capabilities ? { capabilities: agent.capabilities } : {}, + payload: payload.payload || payload + }; + + // Write to agent's VFS inbox + if (!this.terminal.fs.readFile(`/agents/${agentId}/`)) this.terminal.fs.mkdir(`agents/${agentId}`); + this.terminal.fs.writeFile(`agents/${agentId}/inbox.json`, JSON.stringify(msg)); + + // Track in memory + if (!this._a2aMessages[agentId]) this._a2aMessages[agentId] = []; + this._a2aMessages[agentId].push({ ...msg, _processed: false }); + + this.terminal.print(`📨 A2A message sent to ${agentId} (${msg.type}, id: ${msg.msg_id})`, 'success'); + } + + listA2AMessages() { + const ids = Object.keys(this._a2aMessages); + if (ids.length === 0) return this.terminal.print('No A2A messages', 'info'); + this.terminal.print('📨 A2A Messages:', 'header'); + for (const agentId of ids) { + const msgs = this._a2aMessages[agentId]; + const pending = msgs.filter(m => !m._processed).length; + this.terminal.print(` ${agentId}: ${msgs.length} total (${pending} pending)`, 'info'); + for (const m of msgs.slice(-3)) { + const status = m._processed ? '✅' : '⏳'; + this.terminal.print(` ${status} ${m.msg_id} ${m.type} → ${m.to} (${(m.payload?.text || JSON.stringify(m.payload)).substring(0, 40)})`, 'text-muted'); + } + } + } + + _pollAgentMailboxes() { + const fs = this.terminal.fs; + if (!fs) return; + for (const [id, agent] of Object.entries(this._agents)) { + try { + const inbox = fs.fsReadJSON(`/agents/${id}/inbox.json`); + if (!inbox) continue; + // Check if this message is already processed + const existing = this._a2aMessages[id] || []; + if (existing.find(m => m.msg_id === inbox.msg_id && m._processed)) continue; + + // Route based on message type + if (inbox.a2a_version && inbox.type) { + inbox._processed = true; + if (!this._a2aMessages[id]) this._a2aMessages[id] = []; + this._a2aMessages[id].push(inbox); + + if (inbox.type === 'request') { + this.terminal.print(`📨 A2A: ${inbox.from} → ${id} (${inbox.msg_id})`, 'info'); + // Check outbox for response + this._checkAgentOutbox(id); + } else if (inbox.type === 'broadcast') { + this.terminal.print(`📢 A2A broadcast from ${inbox.from}: ${(inbox.payload?.text || JSON.stringify(inbox.payload)).substring(0, 60)}`, 'info'); + } else if (inbox.type === 'heartbeat') { + agent.lastHeartbeat = Date.now(); + } else if (inbox.type === 'response') { + this.terminal.print(`📨 A2A response from ${id}: ${(inbox.payload?.result || JSON.stringify(inbox.payload)).substring(0, 60)}`, 'success'); + agent.status = 'idle'; + agent.currentTask = null; + } + } + } catch (e) { /* skip agent with bad inbox */ } + } + } + + _checkAgentOutbox(agentId) { + try { + const outbox = this.terminal.fs.fsReadJSON(`/agents/${agentId}/outbox.json`); + if (outbox && outbox.status === 'completed') { + const msg = { + a2a_version: '0.1', + msg_id: `resp-${Date.now()}`, + type: 'response', + from: agentId, + to: 'orchestrator-0', + timestamp: new Date().toISOString(), + payload: outbox + }; + if (!this._a2aMessages['orchestrator-0']) this._a2aMessages['orchestrator-0'] = []; + this._a2aMessages['orchestrator-0'].push(msg); + this.terminal.print(`📨 A2A: ${agentId} completed task (outbox)`, 'success'); + } + } catch (e) {} } + + // ─── Agent Lifecycle ─── + spawnAgent(role, capabilities) { const id = `agent-${Object.keys(this._agents).length + 1}-${role || 'worker'}`; if (Object.keys(this._agents).length >= this._SPAWN_LIMIT) { @@ -39,15 +191,24 @@ class OrchestratorModule { createdAt: new Date().toISOString(), lastHeartbeat: Date.now(), status: 'idle', - currentTask: null + currentTask: null, + leaseId: null, + restartPolicy: this._RESTART_POLICY, + failureCount: 0, + restartCount: 0, + nextRestartAt: null }; this._agents[id] = agent; + // Create VFS agent directory with status + if (!this.terminal.fs.readFile(`/agents/${id}/`)) this.terminal.fs.mkdir(`agents/${id}`); + this.terminal.fs.writeFile(`agents/${id}/status.json`, JSON.stringify(agent)); // Register in agent registry const registry = this.terminal.moduleManager.modules['AgentRegistryModule']; if (registry) registry.registerAgent(['register', id, ...agent.capabilities]); this.terminal.print(`🤖 Agent '${id}' spawned (role: ${role || 'worker'}, ${agent.capabilities.length} caps)`, 'success'); return agent; } + killAgent(id) { if (!this._agents[id]) return this.terminal.print(`❌ Agent '${id}' not found`, 'error'); // Revoke any active lease @@ -60,31 +221,212 @@ class OrchestratorModule { // Unregister from registry const registry = this.terminal.moduleManager.modules['AgentRegistryModule']; if (registry) registry.unregisterAgent(id); + // Clean up VFS + this.terminal.fs.deleteFile('agents/' + id); delete this._agents[id]; + delete this._restartBackoffs[id]; this.terminal.print(`💀 Agent '${id}' killed`, 'warning'); } + + sleepAgent(id) { + if (!this._agents[id]) return this.terminal.print(`❌ Agent '${id}' not found`, 'error'); + const agent = this._agents[id]; + // Serialize to VFS + const state = { + ...agent, + _sleepTime: new Date().toISOString() + }; + delete state.leaseId; // leases don't survive sleep + this.terminal.fs.writeFile(`agents/${id}/sleep.json`, JSON.stringify(state)); + agent.status = 'sleeping'; + this.terminal.print(`💤 Agent '${id}' sleeping (state saved to VFS)`, 'warning'); + } + + wakeAgent(id) { + const data = this.terminal.fs.fsReadJSON(`/agents/${id}/sleep.json`); + if (!data) return this.terminal.print(`❌ No saved state for '${id}' — use orchestrator-spawn instead`, 'error'); + // Restore agent from VFS + const agent = { + ...data, + status: 'idle', + currentTask: null, + leaseId: null, + lastHeartbeat: Date.now() + }; + delete agent._sleepTime; + this._agents[id] = agent; + this.terminal.fs.writeFile(`agents/${id}/status.json`, JSON.stringify({ ...agent, status: 'idle' })); + this.terminal.fs.deleteFile(`agents/${id}/sleep.json`); + this.terminal.print(`🔄 Agent '${id}' reanimated from VFS sleep`, 'success'); + } + + _reanimateFromVFS() { + const fs = this.terminal.fs; + const agentsDir = fs.readDir('/agents/'); + if (!agentsDir) return; + let reanimated = 0; + for (const entry of agentsDir) { + if (entry.type !== 'dir') continue; + const sleepData = fs.fsReadJSON(`/agents/${entry.name}/sleep.json`); + if (sleepData) { + const agent = { + ...sleepData, + status: 'idle', + currentTask: null, + leaseId: null, + lastHeartbeat: Date.now() + }; + delete agent._sleepTime; + this._agents[entry.name] = agent; + fs.writeFile(`agents/${entry.name}/status.json`, JSON.stringify({ ...agent, status: 'idle' })); + reanimated++; + } + } + if (reanimated > 0) { + this.terminal.print(`🔄 Reanimated ${reanimated} agent(s) from VFS sleep`, 'info'); + } + } + + // ─── Restart Policies ─── + + setRestartPolicy(args) { + if (args.length < 2) return this.terminal.print('Usage: orchestrator-restart-policy <agent-id> <on-failure|always|never>', 'error'); + const id = args[0]; + const policy = args[1]; + if (!['on-failure', 'always', 'never'].includes(policy)) { + return this.terminal.print('Policy must be: on-failure, always, or never', 'error'); + } + if (id === 'default') { + this._RESTART_POLICY = policy; + return this.terminal.print(`✅ Default restart policy set to '${policy}'`, 'success'); + } + const agent = this._agents[id]; + if (!agent) return this.terminal.print(`❌ Agent '${id}' not found`, 'error'); + agent.restartPolicy = policy; + this.terminal.print(`✅ Restart policy for '${id}' set to '${policy}'`, 'success'); + } + + _handleAgentFailure(id, error) { + const agent = this._agents[id]; + if (!agent) return; + agent.failureCount = (agent.failureCount || 0) + 1; + agent.lastError = error; + agent.lastErrorAt = new Date().toISOString(); + + let shouldRestart = false; + if (agent.restartPolicy === 'always') { + shouldRestart = true; + } else if (agent.restartPolicy === 'on-failure' && agent.failureCount <= 1) { + shouldRestart = true; + } + + if (shouldRestart) { + const backoff = Math.min(Math.pow(2, agent.failureCount), 60); // 2^N capped at 60s + agent.nextRestartAt = Date.now() + (backoff * 1000); + agent.restartCount = (agent.restartCount || 0) + 1; + this._restartBackoffs[id] = setTimeout(() => { + if (this._agents[id]) { + this._agents[id].status = 'idle'; + this._agents[id].currentTask = null; + this.terminal.print(`🔄 Agent '${id}' auto-restarted (backoff: ${backoff}s, attempt: ${agent.restartCount})`, 'info'); + } + }, backoff * 1000); + this.terminal.print(`⚠️ Agent '${id}' failed — restart in ${backoff}s (policy: ${agent.restartPolicy})`, 'warning'); + } else { + agent.status = 'failed'; + this.terminal.print(`❌ Agent '${id}' failed — no restart (policy: ${agent.restartPolicy})`, 'error'); + } + } + + // ─── Autonomous Spawning ─── + + _deriveCapabilities(task) { + const caps = ['list_files', 'read_file']; + const lower = task.toLowerCase(); + if (lower.includes('search') || lower.includes('find') || lower.includes('look up') || lower.includes('web')) caps.push('web_search'); + if (lower.includes('write') || lower.includes('create') || lower.includes('save') || lower.includes('edit')) caps.push('write_file'); + if (lower.includes('delete') || lower.includes('remove') || lower.includes('rm ')) caps.push('delete_file'); + if (lower.includes('install') || lower.includes('package') || lower.includes('wpm')) caps.push('wpm_install'); + if (lower.includes('module') || lower.includes('view') || lower.includes('edit')) caps.push('view_module', 'edit_module'); + if (lower.includes('list') || lower.includes('status') || lower.includes('health')) caps.push('get_status'); + if (lower.includes('fetch') || lower.includes('url') || lower.includes('http')) caps.push('web_fetch'); + return [...new Set(caps)]; + } + + _deriveRole(task) { + const lower = task.toLowerCase(); + if (lower.includes('search') || lower.includes('find')) return 'searcher'; + if (lower.includes('write') || lower.includes('create') || lower.includes('save')) return 'writer'; + if (lower.includes('delete') || lower.includes('remove') || lower.includes('clean')) return 'janitor'; + if (lower.includes('install') || lower.includes('package')) return 'packager'; + if (lower.includes('module') || lower.includes('code') || lower.includes('fix')) return 'coder'; + if (lower.includes('health') || lower.includes('check') || lower.includes('audit')) return 'auditor'; + if (lower.includes('fetch') || lower.includes('url') || lower.includes('http')) return 'fetcher'; + return 'worker'; + } + + // ─── Config ─── + + setConfig(args) { + if (args.length < 2) return this.terminal.print('Usage: orchestrator-config <key> <value>', 'error'); + const key = args[0]; + const val = args[1]; + + const configMap = { + 'spawn-limit': () => { this._SPAWN_LIMIT = parseInt(val); return `spawn-limit set to ${val}`; }, + 'spawn-strategy': () => { if (!['eager', 'lazy'].includes(val)) return 'Must be eager or lazy'; this._SPAWN_STRATEGY = val; return `spawn-strategy set to ${val}`; }, + 'heartbeat-timeout': () => { this._HEARTBEAT_TIMEOUT = parseInt(val); return `heartbeat-timeout set to ${val}ms`; }, + 'idle-timeout': () => { this._IDLE_TIMEOUT = parseInt(val); return `idle-timeout set to ${val}ms`; }, + 'restart-policy': () => { if (!['on-failure', 'always', 'never'].includes(val)) return 'Must be on-failure, always, or never'; this._RESTART_POLICY = val; return `default restart-policy set to ${val}`; }, + 'a2a-polling': () => { + this._A2A_POLLING = val === 'on' || val === 'true'; + if (this._A2A_POLLING && !this._a2aPollTimer) { + this._a2aPollTimer = setInterval(() => this._pollAgentMailboxes(), 500); + } else if (!this._A2A_POLLING && this._a2aPollTimer) { + clearInterval(this._a2aPollTimer); + this._a2aPollTimer = null; + } + return `a2a-polling ${this._A2A_POLLING ? 'on' : 'off'}`; + } + }; + + if (!configMap[key]) { + return this.terminal.print(`Unknown config key: ${key}. Valid: ${Object.keys(configMap).join(', ')}`, 'error'); + } + const result = configMap[key](); + this.terminal.print(`✅ ${result}`, 'success'); + } + + // ─── Task Delegation ─── + async delegateTask(task) { if (!task) return this.terminal.print('Usage: orchestrate <task description>', 'error'); - // Find an idle agent or auto-spawn + + // Find an idle agent let agent = Object.values(this._agents).find(a => a.status === 'idle'); + + // Auto-spawn with role-derived capabilities if (!agent) { if (Object.keys(this._agents).length >= this._SPAWN_LIMIT) { - return this.terminal.print('❌ All agents busy and spawn limit reached', 'error'); + // Try finding any non-failed agent to reuse + agent = Object.values(this._agents).find(a => a.status !== 'failed' && a.status !== 'sleeping'); + } + if (!agent) { + const role = this._deriveRole(task); + const caps = this._deriveCapabilities(task); + this.spawnAgent(role, caps); + agent = Object.values(this._agents).find(a => a.status === 'idle'); } - this.spawnAgent('worker', ['list_files', 'read_file', 'write_file', 'web_search']); - agent = Object.values(this._agents).find(a => a.status === 'idle'); } if (!agent) return this.terminal.print('❌ No available agents', 'error'); - // A2A Contract handshake — validate task against agent's contract + // A2A Contract handshake const registry = this.terminal.moduleManager.modules['AgentRegistryModule']; let contractValid = true; if (registry) { const result = registry.validateTask(agent.role, task); if (!result.valid) { this.terminal.print(`⚠️ A2A validation: ${result.reason}`, 'warning'); - this.terminal.print(` Contract for '${agent.role}' requires: ${(registry.getContract(agent.role) || {}).input_schema || 'free-form'}`, 'warning'); - // Offer to proceed anyway (permissive) — user can create a proper contract contractValid = false; } else if (result.reason === 'matched') { this.terminal.print(`✅ A2A contract matched for role '${agent.role}'`, 'success'); @@ -94,52 +436,88 @@ class OrchestratorModule { // Create a lease const leaseModule = this.terminal.moduleManager.modules['LeaseModule']; const lease = leaseModule ? leaseModule.createLease(['lease-create', agent.id, '60', task]) : null; + // Mark agent as busy agent.status = 'busy'; agent.currentTask = task; agent.leaseId = lease?.id; - // Write task to agent's inbox - if (!this.terminal.fs.readFile(`/agents/${agent.id}/`)) this.terminal.fs.mkdir(`agents/${agent.id}`); - this.terminal.fs.writeFile(`agents/${agent.id}/inbox.json`, JSON.stringify({ - task, - from: 'orchestrator-0', - leaseId: lease?.id, - timestamp: new Date().toISOString(), - ttl_seconds: 60 - })); - this.terminal.print(`🎯 Delegated "${task.substring(0, 60)}" to ${agent.id} (lease: ${lease?.id || 'none'})`, 'info'); + agent.lastHeartbeat = Date.now(); + + // Send A2A request via inbox + this.sendA2AMessage([agent.id, JSON.stringify({ + type: 'request', + payload: { task, from: 'orchestrator-0', leaseId: lease?.id } + })]); + + this.terminal.print(`🎯 Delegated \"${task.substring(0, 60)}\" to ${agent.id} (lease: ${lease?.id || 'none'})`, 'info'); + // Execute via AI assistant try { const aiModule = this.terminal.moduleManager.modules['WebX11AIAssistantModule']; if (aiModule && typeof aiModule.chat === 'function') { const result = await aiModule.chat([`[DELEGATED] ${task}`]); agent.status = 'completed'; + + // Write result to outbox this.terminal.fs.writeFile(`agents/${agent.id}/outbox.json`, JSON.stringify({ - result: result || 'completed', + result: typeof result === 'string' ? result : (result?.content || 'completed'), status: 'completed', timestamp: new Date().toISOString() })); + + // Record in memory graph + const memgraph = this.terminal.moduleManager.modules['MemoryGraphModule']; + if (memgraph) { + memgraph.recordTask(task, typeof result === 'string' ? result : (result?.content || ''), agent.id); + memgraph.recordFileAccess(`/agents/${agent.id}/outbox.json`); + } + this.terminal.print(`✅ ${agent.id} completed task`, 'success'); + + // Check restart policy — if always, set back to idle for next task + if (agent.restartPolicy === 'always') { + agent.status = 'idle'; + agent.currentTask = null; + agent.lastHeartbeat = Date.now(); + } } else { this.terminal.print(`ℹ️ AI module not available — task queued for ${agent.id}`, 'info'); } } catch (e) { - agent.status = 'failed'; - this.terminal.print(`❌ ${agent.id} failed: ${e.message}`, 'error'); + this._handleAgentFailure(agent.id, e.message); + } + + // Return to idle pool (unless failed/restarting) + if (agent.status === 'failed') { + // handled by _handleAgentFailure + } else if (agent.status !== 'busy') { + agent.status = 'idle'; + agent.currentTask = null; + agent.lastHeartbeat = Date.now(); } - // Return to idle pool - agent.status = 'idle'; - agent.currentTask = null; - agent.lastHeartbeat = Date.now(); } + + // ─── Monitoring ─── + _checkHeartbeats() { const now = Date.now(); Object.entries(this._agents).forEach(([id, agent]) => { - if (agent.status === 'busy' && now - agent.lastHeartbeat > 15000) { + if (agent.status === 'busy' && now - agent.lastHeartbeat > this._HEARTBEAT_TIMEOUT) { this.terminal.print(`⚠️ Agent '${id}' unresponsive (${Math.round((now - agent.lastHeartbeat) / 1000)}s since heartbeat)`, 'warning'); } }); } + + _sweepIdle() { + const now = Date.now(); + Object.entries(this._agents).forEach(([id, agent]) => { + if (agent.status === 'idle' && now - agent.lastHeartbeat > this._IDLE_TIMEOUT) { + this.terminal.print(`💤 Agent '${id}' idle for >${this._IDLE_TIMEOUT/1000}s — auto-sleeping`, 'info'); + this.sleepAgent(id); + } + }); + } + purgeDeadAgents() { const now = Date.now(); let count = 0; @@ -151,17 +529,45 @@ class OrchestratorModule { }); this.terminal.print(`🧹 Purged ${count} dead agents`, count > 0 ? 'warning' : 'info'); } + + // ─── Status & List ─── + showStatus() { const total = Object.keys(this._agents).length; const busy = Object.values(this._agents).filter(a => a.status === 'busy').length; const idle = Object.values(this._agents).filter(a => a.status === 'idle').length; - const failed = Object.values(this._agents).filter(a => a.status === 'failed').length; + const sleeping = Object.values(this._agents).filter(a => a.status === 'sleeping').length; + const failed = Object.values(this._agents).filter(a => a.status === 'failed' || a.status === 'restarting').length; + const a2aTotal = Object.values(this._a2aMessages).reduce((s, msgs) => s + msgs.length, 0); + const a2aPending = Object.values(this._a2aMessages).reduce((s, msgs) => s + msgs.filter(m => !m._processed).length, 0); + this.terminal.print('🧠 Orchestrator Status', 'header'); - this.terminal.print(` Agents: ${total} (${busy} busy, ${idle} idle, ${failed} failed)`, 'info'); - this.terminal.print(` Spawn limit: ${this._SPAWN_LIMIT}`, 'info'); + this.terminal.print(` Agents: ${total} (${busy} busy, ${idle} idle, ${sleeping} sleeping, ${failed} failed)`, 'info'); + this.terminal.print(` Spawn limit: ${this._SPAWN_LIMIT} (strategy: ${this._SPAWN_STRATEGY})`, 'info'); + this.terminal.print(` Default restart: ${this._RESTART_POLICY}`, 'info'); + this.terminal.print(` A2A Messages: ${a2aTotal} (${a2aPending} pending)`, 'info'); Object.values(this._agents).forEach(a => { const task = a.currentTask ? ` (task: ${a.currentTask.substring(0, 40)})` : ''; - this.terminal.print(` ${a.id} — ${a.status}${task}`, 'info'); + const backoff = a.nextRestartAt && a.nextRestartAt > Date.now() ? ` [restart in ${Math.round((a.nextRestartAt - Date.now()) / 1000)}s]` : ''; + this.terminal.print(` ${a.id} [${a.role}] — ${a.status}${task}${backoff}`, 'info'); + }); + } + + listAgents() { + const all = Object.values(this._agents); + if (all.length === 0) return this.terminal.print('No agents spawned', 'info'); + this.terminal.print(`📋 Agents (${all.length}):`, 'header'); + all.forEach(a => { + const caps = (a.capabilities || []).join(', '); + this.terminal.print(` ${a.id} [${a.role}] — ${a.status}`, 'info'); + this.terminal.print(` Caps: ${caps}`, 'text-muted'); + this.terminal.print(` Policy: ${a.restartPolicy || this._RESTART_POLICY} | Failures: ${a.failureCount || 0} | Restarts: ${a.restartCount || 0}`, 'text-muted'); + if (a.currentTask) this.terminal.print(` Task: ${a.currentTask.substring(0, 60)}`, 'text-muted'); }); } +} + +// Required for module system +if (typeof window !== 'undefined') { + window.OrchestratorModule = OrchestratorModule; }
\ No newline at end of file diff --git a/modules/core/module-predictor.js b/modules/core/module-predictor.js new file mode 100644 index 0000000..312325b --- /dev/null +++ b/modules/core/module-predictor.js @@ -0,0 +1,279 @@ +// module-predictor.js — Predictive resource allocation engine +// Analyzes historical telemetry + memory graph data to predict task duration/turns +// Commands: predict, predictor-status, predictor-config +class PredictorModule { + constructor() { + this.commands = {}; + this.terminal = null; + this._dataPoints = []; + this._predictions = 0; + this._accuracy = { correct: 0, total: 0 }; + this._CONFIDENCE_THRESHOLD = 0.5; + this._MIN_DATA_POINTS = 5; + this._loaded = false; + } + + getHelpInfo() { + return { + 'predict': 'predict <task description> — Predict task duration, tool turns, and confidence', + 'predictor-status': 'predictor-status — Model accuracy, data points, prediction count', + 'predictor-config': 'predictor-config <key> <value> — Tune: confidence-threshold, min-data-points', + }; + } + + registerCommands() { + this.commands['predict'] = (args) => this.predict(args.join(' ')); + this.commands['predictor-status'] = () => this.showStatus(); + this.commands['predictor-config'] = (args) => this.setConfig(args); + } + + init(terminal) { + this.terminal = terminal; + this._loadFromVFS(); + this._loaded = true; + this.terminal.print('📊 Predictor loaded — analyzing task patterns', 'info'); + } + + // ─── Prediction Engine ─── + + async predict(task) { + if (!task) return this.terminal.print('Usage: predict <task description>', 'error'); + + const startTime = performance.now(); + + // Extract features + const features = this._extractFeatures(task); + + // Find similar historical tasks + const similar = this._findSimilar(features); + + // Generate prediction + const prediction = this._computePrediction(similar, features); + + // Record this prediction for accuracy tracking + const duration = ((performance.now() - startTime) / 1000).toFixed(2); + + this._predictions++; + this._dataPoints.push({ + features, + predictedDuration: prediction.duration, + predictedTurns: prediction.toolTurns, + confidence: prediction.confidence, + similarCount: similar.length, + timestamp: new Date().toISOString() + }); + + this._debounceSave(); + + // Display result + this.terminal.print('📈 Task Prediction', 'header'); + this.terminal.print(` Task: ${task.substring(0, 50)}${task.length > 50 ? '...' : ''}`, 'info'); + + if (prediction.confidence >= this._CONFIDENCE_THRESHOLD) { + this.terminal.print(` ⏱ Estimated duration: ${prediction.duration}s`, 'info'); + this.terminal.print(` 🔧 Estimated tool turns: ${prediction.toolTurns}`, 'info'); + this.terminal.print(` 📊 Confidence: ${(prediction.confidence * 100).toFixed(0)}% (based on ${similar.length} similar tasks)`, 'success'); + } else if (similar.length > 0) { + this.terminal.print(` ⏱ Estimated duration: ${prediction.duration}s (low confidence)`, 'warning'); + this.terminal.print(` 🔧 Tool turns: ~${prediction.toolTurns}`, 'warning'); + this.terminal.print(` 📊 Confidence: ${(prediction.confidence * 100).toFixed(0)}% — only ${similar.length} similar data point(s)`, 'warning'); + this.terminal.print(' Tip: More task data will improve predictions.', 'info'); + } else { + this.terminal.print(' ❓ No historical data to base prediction on.', 'warning'); + this.terminal.print(' Tip: Run `orchestrate` with various tasks to build a baseline.', 'info'); + // Provide hardcoded defaults + this.terminal.print(' Default estimate: ~30s duration, ~5 tool turns', 'info'); + } + + // If got memory graph, offer recommendations + if (prediction.confidence >= this._CONFIDENCE_THRESHOLD && prediction.duration > 30) { + this.terminal.print(` 💡 Recommendation: Set TTL to ${Math.ceil(prediction.duration * 1.5)}s for predictable completion`, 'info'); + } + + return prediction; + } + + _extractFeatures(task) { + const lower = task.toLowerCase(); + return { + length: task.length, + wordCount: task.split(/\s+/).length, + hasSearch: /\b(search|find|look)\b/.test(lower), + hasWrite: /\b(write|create|save|edit)\b/.test(lower), + hasDelete: /\b(delete|remove|rm)\b/.test(lower), + hasInstall: /\b(install|wpm|package)\b/.test(lower), + hasRead: /\b(read|cat|view|list)\b/.test(lower), + hasWeb: /\b(fetch|url|http|web)\b/.test(lower), + hasOrchestrate: /\b(orchestrat|delegate|agent)\b/.test(lower), + hasModule: /\b(module|edit_module|view_module)\b/.test(lower), + complexity: Math.min(task.length / 50, 10), // 0-10 scale + keywords: [...new Set(lower.split(/\s+/).filter(w => w.length > 3))].slice(0, 10) + }; + } + + _findSimilar(features) { + return this._dataPoints.filter(dp => { + // Skip predictions (only use actual executions with observed results) + if (!dp.observedDuration) return false; + const f = dp.features; + if (!f) return false; + // Feature similarity scoring + let score = 0; + const matches = [ + features.hasSearch === f.hasSearch, + features.hasWrite === f.hasWrite, + features.hasDelete === f.hasDelete, + features.hasInstall === f.hasInstall, + features.hasRead === f.hasRead, + features.hasWeb === f.hasWeb, + features.hasOrchestrate === f.hasOrchestrate, + features.hasModule === f.hasModule, + ]; + score = matches.filter(Boolean).length / matches.length; + // Keyword overlap bonus + const kOverlap = features.keywords.filter(k => f.keywords?.includes(k)).length; + const kScore = kOverlap / Math.max(features.keywords.length, 1); + score = score * 0.6 + kScore * 0.4; + return score > 0.3; + }); + } + + _computePrediction(similar, features) { + if (similar.length === 0) { + // Hardcoded defaults + let baseDuration = 15; + let baseTurns = 3; + if (features.hasSearch || features.hasWeb) { baseDuration = 25; baseTurns = 5; } + if (features.hasWrite) { baseDuration = 20; baseTurns = 4; } + if (features.hasOrchestrate) { baseDuration = 40; baseTurns = 8; } + if (features.complexity > 5) baseDuration *= 1.5; + return { + duration: baseDuration, + toolTurns: baseTurns, + confidence: 0.1, + similarCount: 0 + }; + } + + const avgDuration = similar.reduce((s, dp) => s + (dp.observedDuration || 30), 0) / similar.length; + const avgTurns = similar.reduce((s, dp) => s + (dp.observedTurns || 3), 0) / similar.length; + const confidence = Math.min(similar.length / 20, 1) * 0.8 + 0.1; + + // Apply task complexity multiplier + const complexityMultiplier = 1 + (features.complexity - 3) * 0.1; + const adjustedDuration = Math.round(avgDuration * Math.max(complexityMultiplier, 0.5)); + + return { + duration: adjustedDuration, + toolTurns: Math.round(avgTurns), + confidence: Math.min(confidence, 0.95), + similarCount: similar.length + }; + } + + // ─── Learning from Results ─── + + recordResult(features, duration, toolTurns, success) { + this._dataPoints.push({ + features, + observedDuration: duration, + observedTurns: toolTurns, + success, + timestamp: new Date().toISOString() + }); + this._accuracy.total++; + // Compare with last prediction for this feature set + const lastPrediction = [...this._dataPoints] + .reverse() + .find(dp => dp.predictedDuration && dp.features?.hasSearch === features.hasSearch); + if (lastPrediction) { + const error = Math.abs(lastPrediction.predictedDuration - duration) / duration; + if (error < 0.3) this._accuracy.correct++; + } + this._debounceSave(); + } + + // ─── Commands ─── + + showStatus() { + const actualExecutions = this._dataPoints.filter(dp => dp.observedDuration).length; + const accPct = this._accuracy.total > 0 + ? ((this._accuracy.correct / this._accuracy.total) * 100).toFixed(0) + : 'N/A'; + + this.terminal.print('📊 Predictor Status', 'header'); + this.terminal.print(` Total data points: ${this._dataPoints.length}`, 'info'); + this.terminal.print(` Actual executions: ${actualExecutions}`, 'info'); + this.terminal.print(` Predictions made: ${this._predictions}`, 'info'); + this.terminal.print(` Accuracy: ${accPct}% (${this._accuracy.correct}/${this._accuracy.total})`, 'info'); + this.terminal.print(` Confidence threshold: ${this._CONFIDENCE_THRESHOLD}`, 'info'); + this.terminal.print(` Min data points: ${this._MIN_DATA_POINTS}`, 'info'); + } + + setConfig(args) { + if (args.length < 2) return this.terminal.print('Usage: predictor-config <key> <value>', 'error'); + const key = args[0]; + const val = args[1]; + + const configMap = { + 'confidence-threshold': () => { + const n = parseFloat(val); + if (isNaN(n) || n < 0 || n > 1) return 'Must be 0.0-1.0'; + this._CONFIDENCE_THRESHOLD = n; + return `confidence-threshold set to ${n}`; + }, + 'min-data-points': () => { + const n = parseInt(val); + if (isNaN(n) || n < 1) return 'Must be >= 1'; + this._MIN_DATA_POINTS = n; + return `min-data-points set to ${n}`; + }, + }; + + if (!configMap[key]) return this.terminal.print(`Unknown: ${key}. Valid: ${Object.keys(configMap).join(', ')}`, 'error'); + const msg = configMap[key](); + this.terminal.print(`✅ ${msg}`, 'success'); + } + + // ─── Persistence ─── + + _loadFromVFS() { + try { + const fs = this.terminal.fs; + if (!fs) return; + const data = fs.fsReadJSON('/system/metrics/predictor.json'); + if (data) { + this._dataPoints = data.dataPoints || []; + this._predictions = data.predictions || 0; + this._accuracy = data.accuracy || { correct: 0, total: 0 }; + } + } catch (e) { + console.warn('[Predictor] Load error (fresh start):', e.message); + } + } + + _saveNow() { + try { + const fs = this.terminal.fs; + if (!fs) return; + if (!fs.readDir('/system/metrics')) fs.mkdirRecursive('/system/metrics'); + fs.fsWriteJSON('/system/metrics/predictor.json', { + dataPoints: this._dataPoints.slice(-500), // keep last 500 + predictions: this._predictions, + accuracy: this._accuracy + }); + } catch (e) { + console.error('[Predictor] Save error:', e.message); + } + } + + _debounceSave() { + clearTimeout(this._saveTimer); + this._saveTimer = setTimeout(() => this._saveNow(), 3000); + } +} + +// Required for module system +if (typeof window !== 'undefined') { + window.PredictorModule = PredictorModule; +}
\ No newline at end of file diff --git a/modules/core/module-sandbox.js b/modules/core/module-sandbox.js new file mode 100644 index 0000000..dc8dc76 --- /dev/null +++ b/modules/core/module-sandbox.js @@ -0,0 +1,332 @@ +// module-sandbox.js — Sandboxed execution pool using WebWorkers +// Commands: sandbox-status, sandbox-config, sandbox-run +class SandboxModule { + constructor() { + this.commands = {}; + this.terminal = null; + this.workers = []; + this.MAX_WORKERS = 4; + this.MIN_WORKERS = 1; + this.queue = []; + this.activeTasks = {}; + this._nextTaskId = 0; + this._totalRuns = 0; + this._totalErrors = 0; + this._totalDuration = 0; + this._fallbackMode = false; // true if WebWorkers unavailable + this._workerReady = {}; + } + + getHelpInfo() { + return { + 'sandbox-status': 'sandbox-status — Show sandbox pool health and task metrics', + 'sandbox-config': 'sandbox-config <key> <value> — Tune: max-workers, min-workers, pool-timeout', + 'sandbox-run': 'sandbox-run <task> — Run a task in the sandbox (automatic routing)', + }; + } + + registerCommands() { + this.commands['sandbox-status'] = () => this.showStatus(); + this.commands['sandbox-config'] = (args) => this.setConfig(args); + this.commands['sandbox-run'] = (args) => this.runInSandbox(args.join(' ')); + } + + init(terminal) { + this.terminal = terminal; + this.workerUrl = '/spaces/newos4/modules/core/sandbox-worker.js'; + + // Detect Worker availability + if (typeof Worker === 'undefined') { + this._fallbackMode = true; + this.terminal.print('⚠️ WebWorkers not available — sandbox using inline fallback', 'warning'); + } else { + this._initPool(); + } + + this.terminal.print('🧪 Sandbox module loaded', 'info'); + } + + _initPool() { + // Start with MIN_WORKERS, grow on demand up to MAX_WORKERS + for (let i = 0; i < this.MIN_WORKERS; i++) { + this._spawnWorker(); + } + } + + _spawnWorker() { + if (this.workers.length >= this.MAX_WORKERS) return null; + try { + const worker = new Worker(this.workerUrl); + const id = `w${this.workers.length + 1}`; + worker._sandboxId = id; + worker._busy = false; + + worker.onmessage = (e) => this._handleResult(e.data, id); + worker.onerror = (e) => { + console.error(`[Sandbox] Worker ${id} error:`, e.message); + this._totalErrors++; + this._replaceWorker(id); + }; + + this.workers.push(worker); + this._workerReady[id] = true; + return worker; + } catch (e) { + console.error('[Sandbox] Failed to spawn worker:', e); + this._fallbackMode = true; + return null; + } + } + + _replaceWorker(id) { + const idx = this.workers.findIndex(w => w._sandboxId === id); + if (idx !== -1) { + this.workers[idx].terminate(); + this.workers.splice(idx, 1); + delete this._workerReady[id]; + } + // Re-spawn after a delay + setTimeout(() => this._spawnWorker(), 1000); + } + + _getIdleWorker() { + for (const w of this.workers) { + if (!w._busy) return w; + } + // Grow pool if under max + if (this.workers.length < this.MAX_WORKERS) { + const w = this._spawnWorker(); + if (w) return w; + } + return null; + } + + async runInSandbox(task, capabilities) { + if (!task) return this.terminal.print('Usage: sandbox-run <task>', 'error'); + + if (this._fallbackMode) { + return this._runInline(task, capabilities); + } + + const worker = this._getIdleWorker(); + if (!worker) { + // Queue it + return new Promise((resolve) => { + this.queue.push({ task, capabilities, resolve }); + this.terminal.print(`⏳ Task queued (${this.queue.length} waiting)`, 'info'); + this._drainQueue(); + }); + } + + return this._executeOnWorker(worker, task, capabilities); + } + + async _executeOnWorker(worker, task, capabilities) { + const taskId = `task-${++this._nextTaskId}`; + worker._busy = true; + + const startTime = performance.now(); + + return new Promise((resolve) => { + const handler = (e) => { + if (e.data && e.data.taskId === taskId) { + worker.removeEventListener('message', handler); + worker._busy = false; + + const duration = ((performance.now() - startTime) / 1000).toFixed(2); + this._totalRuns++; + this._totalDuration += parseFloat(duration); + + if (e.data.error) { + this._totalErrors++; + this.terminal.print(`❌ Sandbox task failed: ${e.data.error}`, 'error'); + resolve({ error: e.data.error, duration }); + } else { + this.terminal.print(`✅ Sandbox task complete (${duration}s, ${e.data.toolCalls || 0} tools)`, 'success'); + resolve({ result: e.data.result, duration, toolCalls: e.data.toolCalls || 0 }); + } + + this._drainQueue(); + } + }; + + worker.addEventListener('message', handler); + + // Get VFS snapshot from terminal + let vfsSnapshot = null; + try { + if (this.terminal && this.terminal.fs && this.terminal.fs.root) { + vfsSnapshot = JSON.stringify({ + root: this.terminal.fs.root, + currentPath: this.terminal.fs.currentPath || [] + }); + } + } catch (e) { + console.warn('[Sandbox] Could not serialize VFS:', e.message); + } + + const caps = capabilities || ['list_files', 'read_file']; + const allowedCommands = ['ls', 'cat', 'echo', 'find', 'grep', 'help', 'status', + 'touch', 'mkdir', 'rm', 'cp', 'mv']; + + worker.postMessage({ + taskId, + task, + capabilities: caps, + vfsSnapshot, + allowedCommands, + apiProxy: window.location.origin + '/api' + }); + + // Timeout after 30 seconds + setTimeout(() => { + if (worker._busy) { + worker._busy = false; + this._totalErrors++; + this.terminal.print(`⏰ Sandbox task ${taskId} timed out after 30s`, 'error'); + resolve({ error: 'Timeout (30s)', duration: 30 }); + this._drainQueue(); + } + }, 30000); + }); + } + + _handleResult(data, workerId) { + // Handled via promise in _executeOnWorker + // This is the fallback handler for tasks not tracked by promise + if (data.error) { + this.terminal.print(`[Sandbox ${workerId}] Error: ${data.error}`, 'error'); + } + } + + _drainQueue() { + if (this.queue.length === 0) return; + const next = this.queue.shift(); + this.runInSandbox(next.task, next.capabilities).then(result => { + next.resolve(result); + }); + } + + _runInline(task, capabilities) { + // Fallback when WebWorkers unavailable — runs in main thread + const startTime = performance.now(); + const parts = task.trim().split(/\s+/); + const cmd = parts[0].toLowerCase(); + let result = ''; + let toolCalls = 0; + + const fs = this.terminal ? this.terminal.fs : null; + + if (cmd === 'ls' && fs) { + const path = parts[1] || '/'; + const entries = fs.ls(path); + if (!entries) return this.terminal.print(`ls: ${path}: No such directory`, 'error'); + result = entries.map(e => `${e.type === 'dir' ? '[dir]' : '[file]'} ${e.name}`).join('\\n'); + toolCalls++; + } else if ((cmd === 'cat' || cmd === 'read') && fs) { + const path = parts[1] || ''; + const content = fs.readFile(path); + if (content === null) return this.terminal.print(`cat: ${path}: No such file`, 'error'); + result = typeof content === 'string' ? content.substring(0, 8000) : String(content); + toolCalls++; + } else if (cmd === 'find' && fs) { + const q = parts.slice(1).join(' ') || ''; + // Walk VFS + const results = []; + const walk = (p) => { + const entries = fs.ls(p); + if (!entries) return; + for (const e of entries) { + const full = p === '/' ? '/' + e.name : p + '/' + e.name; + if (e.type === 'file' && (!q || full.toLowerCase().includes(q.toLowerCase()))) { + results.push(full); + } + if (e.type === 'dir') walk(full); + } + }; + walk('/'); + result = results.join('\\n') || '(empty)'; + toolCalls++; + } else if (cmd === 'status') { + let f = 0, d = 0; + const walk = (p) => { + const entries = fs ? fs.ls(p) : []; + if (!entries) return; + for (const e of entries) { + if (e.type === 'file') f++; + else { d++; walk(p === '/' ? '/' + e.name : p + '/' + e.name); } + } + }; + walk('/'); + result = `Files: ${f}, Dirs: ${d}`; + toolCalls++; + } else { + result = `Task: "${task}" (inline fallback)`; + } + + const duration = ((performance.now() - startTime) / 1000).toFixed(2); + this._totalRuns++; + this._totalDuration += parseFloat(duration); + this.terminal.print(result, 'info'); + return { result, duration, toolCalls }; + } + + // ─── Config ─── + + setConfig(args) { + if (args.length < 2) return this.terminal.print('Usage: sandbox-config <key> <value>', 'error'); + const key = args[0]; + const val = args[1]; + + const configMap = { + 'max-workers': () => { + const n = parseInt(val); + if (isNaN(n) || n < 1 || n > 8) return 'Must be 1-8'; + this.MAX_WORKERS = n; + // Scale down if needed + while (this.workers.length > n) { + const w = this.workers.pop(); + w.terminate(); + } + // Scale up + for (let i = this.workers.length; i < Math.min(n, this.MIN_WORKERS); i++) { + this._spawnWorker(); + } + return `max-workers set to ${n}`; + }, + 'min-workers': () => { + const n = parseInt(val); + if (isNaN(n) || n < 0 || n > this.MAX_WORKERS) return `Must be 0-${this.MAX_WORKERS}`; + this.MIN_WORKERS = n; + while (this.workers.length < n) this._spawnWorker(); + return `min-workers set to ${n}`; + }, + }; + + if (!configMap[key]) { + return this.terminal.print(`Unknown: ${key}. Valid: ${Object.keys(configMap).join(', ')}`, 'error'); + } + const msg = configMap[key](); + this.terminal.print(`✅ ${msg}`, 'success'); + } + + // ─── Status ─── + + showStatus() { + const busy = this.workers.filter(w => w._busy).length; + const idle = this.workers.length - busy; + const avgDuration = this._totalRuns > 0 ? (this._totalDuration / this._totalRuns).toFixed(2) : 'N/A'; + + this.terminal.print('🧪 Sandbox Pool', 'header'); + this.terminal.print(` Workers: ${this.workers.length}/${this.MAX_WORKERS} (${busy} busy, ${idle} idle)`, 'info'); + this.terminal.print(` Mode: ${this._fallbackMode ? '⚠️ Inline fallback' : '✅ WebWorker'}`, 'info'); + this.terminal.print(` Queue: ${this.queue.length} pending`, 'info'); + this.terminal.print(` Total runs: ${this._totalRuns} (errors: ${this._totalErrors})`, 'info'); + this.terminal.print(` Avg duration: ${avgDuration}s`, 'info'); + } +} + +// Required for module system +if (typeof window !== 'undefined') { + window.SandboxModule = SandboxModule; +}
\ No newline at end of file diff --git a/modules/core/module-world-model.js b/modules/core/module-world-model.js new file mode 100644 index 0000000..b7bb28f --- /dev/null +++ b/modules/core/module-world-model.js @@ -0,0 +1,381 @@ +// module-world-model.js — Global World Model: system self-awareness synthesis engine +// Aggregates from memory graph, neural VFS, skills index, telemetry +// Commands: world-model, world-model-summary, world-model-ask, world-model-refresh, world-model-config +class WorldModelModule { + constructor() { + this.commands = {}; + this.terminal = null; + this._model = null; + this._lastRefresh = null; + this._REFRESH_INTERVAL = 300000; // 5 minutes + this._MAX_PROJECTS = 10; + this._refreshTimer = null; + } + + getHelpInfo() { + return { + 'world-model': 'world-model — Show full world model as structured output', + 'world-model-summary': 'world-model-summary — One-line system self-awareness statement', + 'world-model-ask': 'world-model-ask <question> — Query the model (e.g. "what changed since yesterday")', + 'world-model-refresh': 'world-model-refresh — Force re-synthesis from all subsystems', + 'world-model-config': 'world-model-config <key> <value> — Tune: refresh-interval, max-projects', + }; + } + + registerCommands() { + this.commands['world-model'] = () => this.showModel(); + this.commands['world-model-summary'] = () => this.showSummary(); + this.commands['world-model-ask'] = (args) => this.ask(args.join(' ')); + this.commands['world-model-refresh'] = () => this.refresh(); + this.commands['world-model-config'] = (args) => this.setConfig(args); + } + + init(terminal) { + this.terminal = terminal; + this._loadFromVFS(); + // Auto-refresh on a timer + this._refreshTimer = setInterval(() => { + const age = Date.now() - (this._lastRefresh ? new Date(this._lastRefresh).getTime() : 0); + if (age > this._REFRESH_INTERVAL) this._synthesize(); + }, this._REFRESH_INTERVAL); + // Initial synthesis + this._synthesize(); + this.terminal.print('🌐 World Model loaded — system self-awareness active', 'info'); + } + + // ─── Synthesis Engine ─── + + _synthesize() { + try { + const fs = this.terminal.fs; + if (!fs) return; + + const model = { + version: 'v4.4', + synthesized: new Date().toISOString(), + system: this._gatherSystemInfo(fs), + modules: this._gatherModuleInfo(), + files: this._gatherFileInfo(fs), + skills: this._gatherSkillInfo(fs), + memory: this._gatherMemoryInfo(), + agents: this._gatherAgentInfo(), + telemetry: this._gatherTelemetry(fs), + projects: this._gatherProjects(fs), + recent: this._gatherRecent(fs) + }; + + this._model = model; + this._lastRefresh = model.synthesized; + this._saveToVFS(fs); + + // Log synthesis summary + const mCount = model.modules?.loaded || '?'; + const sCount = model.skills?.count || '?'; + const fCount = model.files?.total || '?'; + console.log(`[WorldModel] Synthesis complete: ${mCount} modules, ${sCount} skills, ${fCount} files`); + } catch (e) { + console.error('[WorldModel] Synthesis error:', e.message); + } + } + + _gatherSystemInfo(fs) { + const healthData = fs.fsReadJSON('/system/metrics/health.json'); + return { + version: 'newOS4 v4.4', + modules: Object.keys(this.terminal?.moduleManager?.modules || {}).length || 0, + vfsSize: JSON.stringify(fs?.root || {}).length, + online: typeof navigator !== 'undefined' ? navigator.onLine : true, + memory: typeof performance !== 'undefined' && performance.memory + ? `${(performance.memory.usedJSHeapSize / 1048576).toFixed(1)}MB` + : 'N/A', + health: healthData?.status || 'OK', + lastHealthCheck: healthData?.timestamp || null + }; + } + + _gatherModuleInfo() { + const mm = this.terminal?.moduleManager; + if (!mm) return { loaded: 0, modified: [] }; + const modules = Object.keys(mm.modules || {}).map(name => { + const mod = mm.modules[name]; + const clsName = mod?.constructor?.name || name; + return { name: clsName, hasCommands: !!mod?.commands }; + }); + // Check for local modifications + const modsDir = this.terminal.fs?.readDir('/_mods/'); + const modified = modsDir + ? modsDir.filter(e => e.type === 'file' && e.name.endsWith('.js') && !e.name.endsWith('.orig.js')) + .map(e => e.name.replace('.js', '')) + : []; + return { + loaded: modules.length, + total: 49, + modules, + modified, + modifiedCount: modified.length + }; + } + + _gatherFileInfo(fs) { + if (!fs) return { total: 0, size: '0KB' }; + let fileCount = 0, dirCount = 0; + const walk = (path) => { + const entries = fs.ls(path); + if (!entries) return; + for (const e of entries) { + if (e.type === 'file') fileCount++; + else { dirCount++; walk(path === '/' ? '/' + e.name : path + '/' + e.name); } + } + }; + walk('/'); + return { + total: fileCount, + directories: dirCount, + size: `${(JSON.stringify(fs.root).length / 1024).toFixed(1)}KB` + }; + } + + _gatherSkillInfo(fs) { + try { + const idx = fs.fsReadJSON('/skills/index.json'); + const count = idx ? Object.keys(idx).length : 0; + return { count, skills: idx ? Object.keys(idx) : [] }; + } catch (e) { + return { count: 0, skills: [] }; + } + } + + _gatherMemoryInfo() { + const mg = this.terminal?.moduleManager?.modules?.['MemoryGraphModule']; + if (mg && mg._nodes) { + const nodes = Object.keys(mg._nodes).length; + const edges = (mg._edges || []).length; + return { nodes, edges, active: true }; + } + // Fallback: count memory files + try { + const memIndex = this.terminal.fs?.fsReadJSON('/memory/index.json'); + return { nodes: memIndex ? Object.keys(memIndex).length : 0, edges: 0, active: false }; + } catch (e) { + return { nodes: 0, edges: 0, active: false }; + } + } + + _gatherAgentInfo() { + const orch = this.terminal?.moduleManager?.modules?.['OrchestratorModule']; + if (!orch || !orch._agents) return { agents: 0, active: 0 }; + const agents = Object.keys(orch._agents).length; + const active = Object.values(orch._agents).filter(a => a.status === 'busy').length; + return { agents, active, totalLeases: Object.keys(orch._a2aMessages || {}).length }; + } + + _gatherTelemetry(fs) { + try { + const latency = fs.fsReadJSON('/system/metrics/tool-latency.json'); + const errors = fs.fsReadJSON('/system/metrics/error-log.json'); + return { + toolLatencyDataPoints: latency ? Object.keys(latency).length : 0, + recentErrors: errors ? (Array.isArray(errors) ? errors.length : Object.keys(errors).length) : 0, + }; + } catch (e) { + return { toolLatencyDataPoints: 0, recentErrors: 0 }; + } + } + + _gatherProjects(fs) { + try { + const mg = this.terminal?.moduleManager?.modules?.['MemoryGraphModule']; + if (mg && mg._nodes) { + // Find task clusters as "projects" + const tasks = Object.values(mg._nodes).filter(n => n.type === 'task'); + const recentFiles = Object.values(mg._nodes) + .filter(n => n.type === 'file') + .sort((a, b) => b.access_count - a.access_count) + .slice(0, this._MAX_PROJECTS); + return { + totalTasks: tasks.length, + activeProjects: recentFiles.slice(0, 5).map(f => ({ + name: f.label, + accesses: f.access_count, + lastModified: f.updated + })) + }; + } + return { totalTasks: 0, activeProjects: [] }; + } catch (e) { + return { totalTasks: 0, activeProjects: [] }; + } + } + + _gatherRecent(fs) { + const recent = []; + try { + // Check modified modules + const modsDir = fs.readDir('/_mods/'); + if (modsDir) { + for (const e of modsDir) { + if (e.type === 'file' && e.name.endsWith('.js') && !e.name.endsWith('.orig.js') && !e.name.endsWith('.meta.json')) { + recent.push(`Modified module: ${e.name.replace('.js', '')}`); + } + } + } + // Check skills activity + const skills = fs.ls('/skills/'); + if (skills && skills.length > 0) { + recent.push(`Skills directory: ${skills.filter(s => s.type === 'file').length} files`); + } + } catch (e) {} + return recent; + } + + // ─── Commands ─── + + showModel() { + if (!this._model) return this.terminal.print('No model yet — run world-model-refresh', 'warning'); + const m = this._model; + + this.terminal.print('🌐 World Model', 'header'); + this.terminal.print(` Version: ${m.version} (last synthsized: ${new Date(m.synthesized).toLocaleTimeString()})`, 'info'); + + this.terminal.print(` ⚙️ System — ${m.system.modules} modules loaded, ${m.system.vfsSize} VFS, ${m.system.memory}`, 'info'); + this.terminal.print(` 🧩 Modules — ${m.modules.loaded} loaded (${m.modules.modifiedCount} modified)`, 'info'); + this.terminal.print(` 📁 Files — ${m.files.total} files, ${m.files.directories} dirs, ${m.files.size}`, 'info'); + this.terminal.print(` 📚 Skills — ${m.skills.count} installed`, 'info'); + this.terminal.print(` 🧠 Memory Graph — ${m.memory.nodes} nodes, ${m.memory.edges} edges${m.memory.active ? ' ✅' : ''}`, 'info'); + this.terminal.print(` 🤖 Agents — ${m.agents.agents} registered (${m.agents.active} active)`, 'info'); + this.terminal.print(` 📊 Telemetry — ${m.telemetry.toolLatencyDataPoints} tool latency points, ${m.telemetry.recentErrors} recent errors`, 'info'); + + if (m.projects.activeProjects.length > 0) { + this.terminal.print(' Active context:', 'header'); + for (const p of m.projects.activeProjects.slice(0, 3)) { + this.terminal.print(` 📌 ${p.name} (${p.accesses}x accesses)`, 'info'); + } + } + + if (m.recent.length > 0) { + this.terminal.print(' Recent changes:', 'header'); + for (const r of m.recent.slice(0, 3)) { + this.terminal.print(` ${r}`, 'info'); + } + } + } + + showSummary() { + if (!this._model) return this.terminal.print('No model yet', 'warning'); + const m = this._model; + const summary = `newOS4 ${m.version} · ${m.modules.loaded}/${m.modules.total} modules · ${m.skills.count} skills · ${m.files.total} files · ${m.agents.agents} agents · ${m.memory.nodes} graph nodes · ${m.telemetry.recentErrors > 0 ? `⚠️ ${m.telemetry.recentErrors} errors` : '✅ OK'}`; + this.terminal.print(summary, 'info'); + return summary; + } + + ask(question) { + if (!question) return this.terminal.print('Usage: world-model-ask <question>', 'error'); + if (!this._model) return this.terminal.print('No model — run world-model-refresh first', 'warning'); + + const q = question.toLowerCase(); + const m = this._model; + let answer = ''; + + if (q.includes('module') || q.includes('loaded')) { + answer = `Loaded ${m.modules.loaded} modules (${m.modules.modifiedCount} modified: ${m.modules.modified.join(', ') || 'none'})`; + } else if (q.includes('skill')) { + answer = `Installed ${m.skills.count} skills: ${m.skills.skills.join(', ') || 'none'}`; + } else if (q.includes('file') || q.includes('vfs')) { + answer = `${m.files.total} files in ${m.files.directories} directories, total ${m.files.size}`; + } else if (q.includes('agent') || q.includes('orchestrat')) { + answer = `${m.agents.agents} agents (${m.agents.active} active)`; + } else if (q.includes('health') || q.includes('status')) { + answer = `Health: ${m.system.health} · Memory: ${m.system.memory} · ${m.telemetry.recentErrors > 0 ? `${m.telemetry.recentErrors} recent errors` : 'No errors'}`; + } else if (q.includes('memory') || q.includes('graph')) { + answer = `Memory Graph: ${m.memory.nodes} nodes, ${m.memory.edges} edges (${m.memory.active ? 'active' : 'inactive'})`; + } else if (q.includes('change') || q.includes('yesterday') || q.includes('recent')) { + const recent = m.recent; + answer = recent.length > 0 ? `Recent changes: ${recent.join('; ')}` : 'No recent changes detected'; + } else if (q.includes('project') || q.includes('work')) { + const projects = m.projects.activeProjects; + answer = projects.length > 0 + ? `Active projects: ${projects.map(p => `${p.name} (${p.accesses}x)`).join(', ')}` + : 'No active projects detected'; + } else if (q.includes('telemetry') || q.includes('latency')) { + answer = `${m.telemetry.toolLatencyDataPoints} tool latency data points tracked`; + } else if (q.includes('summary') || q.includes('overview') || q.includes('all')) { + answer = this.showSummary(); + return; + } else { + answer = `I'm not sure how to answer "${question}". Try: modules, skills, files, agents, health, memory, changes, projects, telemetry, or summary.`; + } + + this.terminal.print(`❓ ${question}`, 'info'); + this.terminal.print(`💡 ${answer}`, 'success'); + } + + refresh() { + this._synthesize(); + this.terminal.print(`✅ World Model refreshed at ${new Date().toLocaleTimeString()}`, 'success'); + } + + setConfig(args) { + if (args.length < 2) return this.terminal.print('Usage: world-model-config <key> <value>', 'error'); + const key = args[0]; + const val = args[1]; + + const configMap = { + 'refresh-interval': () => { + const n = parseInt(val) * 1000; + if (isNaN(n) || n < 10000) return 'Must be >= 10 seconds'; + this._REFRESH_INTERVAL = n; + if (this._refreshTimer) clearInterval(this._refreshTimer); + this._refreshTimer = setInterval(() => this._synthesize(), n); + return `refresh-interval set to ${val}s`; + }, + 'max-projects': () => { + const n = parseInt(val); + if (isNaN(n) || n < 1) return 'Must be >= 1'; + this._MAX_PROJECTS = n; + return `max-projects set to ${n}`; + }, + }; + + if (!configMap[key]) return this.terminal.print(`Unknown: ${key}. Valid: ${Object.keys(configMap).join(', ')}`, 'error'); + const msg = configMap[key](); + this.terminal.print(`✅ ${msg}`, 'success'); + } + + // ─── Context Injection ─── + + getContextForPrompt() { + if (!this._model) return ''; + const m = this._model; + return `[World Model Context]\\nSystem: newOS4 ${m.version} · ${m.modules.loaded} modules · ${m.skills.count} skills · ${m.files.total} files · ${m.agents.agents} agents${m.telemetry.recentErrors > 0 ? ` · ⚠️ ${m.telemetry.recentErrors} errors` : ' · ✅ OK'}${m.modules.modifiedCount > 0 ? `\\nModified modules: ${m.modules.modified.slice(0, 3).join(', ')}${m.modules.modifiedCount > 3 ? ` (+${m.modules.modifiedCount - 3} more)` : ''}` : ''}`; + } + + // ─── Persistence ─── + + _loadFromVFS() { + try { + const fs = this.terminal.fs; + if (!fs) return; + const data = fs.fsReadJSON('/system/world-model.json'); + if (data) { + this._model = data; + this._lastRefresh = data.synthesized; + } + } catch (e) { + console.warn('[WorldModel] Load error (fresh start):', e.message); + } + } + + _saveToVFS(fs) { + try { + if (!fs.readDir('/system')) fs.mkdir('system'); + fs.fsWriteJSON('/system/world-model.json', this._model); + } catch (e) { + console.error('[WorldModel] Save error:', e.message); + } + } +} + +// Required for module system +if (typeof window !== 'undefined') { + window.WorldModelModule = WorldModelModule; +}
\ No newline at end of file diff --git a/modules/core/sandbox-worker.js b/modules/core/sandbox-worker.js new file mode 100644 index 0000000..5841582 --- /dev/null +++ b/modules/core/sandbox-worker.js @@ -0,0 +1,205 @@ +// sandbox-worker.js — WebWorker entry point for isolated agent execution +// Receives: { taskId, task, capabilities, vfsSnapshot, allowedCommands, apiProxy } +// Returns: { taskId, result, error, toolCalls, duration } + +let _vfs = null; +let _apiProxyOrigin = null; + +self.onmessage = async (e) => { + const { taskId, task, capabilities, vfsSnapshot, allowedCommands, apiProxy } = e.data; + _apiProxyOrigin = apiProxy ? new URL(apiProxy).origin : null; + + const startTime = performance.now(); + let toolCalls = 0; + let result = ''; + let error = null; + + try { + // Rebuild VFS from snapshot + _vfs = vfsSnapshot ? JSON.parse(vfsSnapshot) : { root: { type: 'dir', content: {} }, currentPath: [] }; + + // Log start + console.log(`[Sandbox ${taskId}] Starting task: "${task.substring(0, 60)}"`); + + // Parse the task for tool use + const toolResults = {}; + const commandWhitelist = allowedCommands || ['ls', 'cat', 'echo', 'find', 'grep', 'help', 'status']; + + // Execute basic commands + const parts = task.trim().split(/\s+/); + const cmd = parts[0].toLowerCase(); + + if (cmd === 'ls' || cmd === 'list') { + result = sandbox_ls(parts[1] || '/'); + toolCalls++; + } else if (cmd === 'cat' || cmd === 'read') { + result = sandbox_cat(parts[1] || ''); + toolCalls++; + } else if (cmd === 'find' || cmd === 'search') { + result = sandbox_find(parts.slice(1).join(' ') || ''); + toolCalls++; + } else if (cmd === 'grep') { + result = sandbox_grep(parts.slice(1).join(' ')); + toolCalls++; + } else if (cmd === 'echo') { + result = parts.slice(1).join(' '); + toolCalls++; + } else if (cmd === 'help') { + result = 'Available commands: ' + commandWhitelist.join(', '); + toolCalls++; + } else if (cmd === 'status') { + result = sandbox_status(); + toolCalls++; + } else if (capabilities.includes('run_command') && commandWhitelist.includes(cmd)) { + result = sandbox_run(cmd, parts.slice(1)); + toolCalls++; + } else { + // If the task is descriptive (not a command), run smart handling + if (capabilities.includes('list_files') || capabilities.includes('read_file')) { + // Smart mode: interpret task + const lower = task.toLowerCase(); + if (lower.includes('list') || lower.includes('file')) { + const dir = parts.find(p => p.startsWith('/') || p.startsWith('.')) || '/'; + result = sandbox_ls(dir); + } else if (lower.includes('find') || lower.includes('search')) { + const q = task.replace(/find|search/i, '').trim(); + result = sandbox_find(q || ''); + } else if (lower.includes('count') || lower.includes('how many')) { + result = sandbox_ls('/'); + const count = (result.match(/\[file\]|\[dir\]/g) || []).length; + result = `Found ${count} items in total.\\n${result}`; + } else if (lower.includes('status') || lower.includes('health')) { + result = sandbox_status(); + } else { + result = `Task interpreted: "${task}"\\nCapabilities: ${capabilities.join(', ')}\\nTip: Be more specific (e.g. "list files in /")`; + } + toolCalls++; + } else { + result = `Task: "${task}"\\nNo matching capability for command "${cmd}"`; + } + } + } catch (e) { + error = e.message; + console.error(`[Sandbox ${taskId}] Error:`, e); + } + + const duration = ((performance.now() - startTime) / 1000).toFixed(2); + + self.postMessage({ + taskId, + result, + error, + toolCalls, + duration: parseFloat(duration), + vfsSnapshot: JSON.stringify(_vfs) + }); +}; + +// ─── Sandboxed VFS Operations ─── + +function sandbox_ls(path) { + path = path || '/'; + const entries = _vfs_get_entries(path); + if (!entries) return `ls: ${path}: No such directory`; + const lines = entries.map(e => { + const icon = e.type === 'dir' ? '[dir]' : '[file]'; + const size = e.size !== undefined ? ` (${e.size}B)` : ''; + return `${icon} ${e.name}${size}`; + }); + return lines.join('\\n') || '(empty)'; +} + +function sandbox_cat(path) { + const content = _vfs_get_content(path); + if (content === null) return `cat: ${path}: No such file`; + if (typeof content === 'object') return `cat: ${path}: Is a directory`; + if (content.length > 8000) return content.substring(0, 8000) + '\\n... (truncated at 8KB)'; + return content; +} + +function sandbox_find(query) { + const results = []; + const q = query ? query.toLowerCase() : ''; + _vfs_walk('/', (path, node) => { + if (node.type === 'file') { + if (!q || path.toLowerCase().includes(q)) { + results.push(path); + } + } + }); + if (results.length === 0) return `No files found${query ? ` matching "${query}"` : ''}`; + return results.join('\\n'); +} + +function sandbox_grep(query) { + if (!query) return 'grep: missing pattern'; + const q = query.toLowerCase(); + const results = []; + _vfs_walk('/', (path, node) => { + if (node.type === 'file' && typeof node.content === 'string') { + const lines = node.content.split('\\n'); + for (let i = 0; i < lines.length; i++) { + if (lines[i].toLowerCase().includes(q)) { + results.push(`${path}:${i + 1}: ${lines[i].trim().substring(0, 100)}`); + } + } + } + }); + if (results.length === 0) return `grep: no matches for "${query}"`; + return results.slice(0, 50).join('\\n') + (results.length > 50 ? `\\n... (+${results.length - 50} more)` : ''); +} + +function sandbox_status() { + let fileCount = 0, dirCount = 0; + _vfs_walk('/', (path, node) => { + if (node.type === 'file') fileCount++; + else dirCount++; + }); + const vfsSize = JSON.stringify(_vfs).length; + return `Sandbox VFS Status:\\n Files: ${fileCount}\\n Directories: ${dirCount}\\n VFS Size: ${(vfsSize / 1024).toFixed(1)}KB`; +} + +function sandbox_run(cmd, args) { + return `[sandbox] ${cmd} ${args.join(' ')}\\n(Command execution limited in sandbox mode)`; +} + +// ─── VFS Helpers ─── + +function _vfs_get_entries(path) { + const dir = _vfs_resolve(path); + if (!dir || dir.type !== 'dir') return null; + return Object.entries(dir.content || {}).map(([name, node]) => ({ + name, + type: node.type || 'file', + size: typeof node.content === 'string' ? node.content.length : undefined + })).sort((a, b) => a.name.localeCompare(b.name)); +} + +function _vfs_get_content(path) { + const node = _vfs_resolve(path); + if (!node || node.type === 'dir') return null; + return node.content; +} + +function _vfs_resolve(path) { + if (!_vfs || !_vfs.root) return null; + if (path === '/' || path === '') return _vfs.root; + const parts = path.split('/').filter(p => p); + let current = _vfs.root; + for (const part of parts) { + if (!current.content || !current.content[part]) return null; + current = current.content[part]; + } + return current; +} + +function _vfs_walk(path, cb) { + const node = _vfs_resolve(path); + if (!node) return; + cb(path, node); + if (node.type === 'dir' && node.content) { + for (const [name, child] of Object.entries(node.content)) { + _vfs_walk(path === '/' ? '/' + name : path + '/' + name, cb); + } + } +}
\ No newline at end of file diff --git a/modules/core/terminal.js b/modules/core/terminal.js index e3f222c..d916ae6 100644 --- a/modules/core/terminal.js +++ b/modules/core/terminal.js @@ -1028,6 +1028,10 @@ this._safeRegister('LeaseModule', 'lease system'); this._safeRegister('OrchestratorModule', 'orchestrator'); this._safeRegister('NeuralVFSModule', 'neural VFS'); + this._safeRegister('SandboxModule', 'sandbox'); + this._safeRegister('MemoryGraphModule', 'memory graph'); + this._safeRegister('PredictorModule', 'predictor'); + this._safeRegister('WorldModelModule', 'world model'); this._safeRegister('ServerMonitorModule', 'server monitor'); this._safeRegister('SimpleGUIModule', 'simple GUI builder'); } diff --git a/newos4_whitepaper.md b/newos4_whitepaper.md index d8dea0e..8695eef 100644 --- a/newos4_whitepaper.md +++ b/newos4_whitepaper.md @@ -138,7 +138,7 @@ What the whitepaper originally listed as 4 items has grown into 25+ completed fe - ✅ Hot-reload module pipeline — module-loader.js + module-manager.js for dynamic load/unload **Built beyond the original scope:** -- 32 modules (18 core + 14 ext), cleaned up from 42 +- **45 modules** (28 core + 17 ext), cleaned up architecture - Proxy API module — replaces dead DuckDuckGo/SearXNG/WebFetcher modules with `/api/search`, `/api/fetch`, `/api/stats` endpoints - Dynamic Prompt Engine — bare URL auto-fetch+summarize, question auto-search+AI answer - Ollama Manager — 89 models detected, supports cloud model routing @@ -161,9 +161,9 @@ What the whitepaper originally listed as 4 items has grown into 25+ completed fe - Debug panel — live memory, events, errors, module health - Security layer — security-level 0-3 with command allowlist/denylist - Agent orchestration — agent-list, agent-register, lease-list, orchestrate -- Skill-pack core — 12 skills, auto-load, seed/reload support +- Skill-pack core — 24 skills, auto-load, seed/reload support -### Phase 2 — Agentic Shift 🟡 IN PROGRESS (partial) +### Phase 2 — Agentic Shift ✅ COMPLETED **What's already built:** - ✅ AI tool-calling loop with 19+ tool definitions (filesystem, search, skills, module management) @@ -177,28 +177,23 @@ What the whitepaper originally listed as 4 items has grown into 25+ completed fe - ✅ Security layer — security-level 0-3, cmd-allow/cmd-deny, destructive action confirmation (120s countdown) - ✅ Debug panel — live memory/events/errors/module health monitoring - ✅ WebDAV modern browser — file-tree with bulk actions, inline rename, context menu, keyboard shortcuts -- ✅ Debug panel — tool call visualization with persistent log -- ✅ Skill-pack core — 12 skills auto-loaded on startup with seed/reload support - ✅ Chat cleaned to single /chat toggle overlay - -**What's still to build:** -- Autonomous sub-agent spawning with task-specific roles -- Agent lifecycle management (birth, sleep, death, reanimation) -- A2A Agent-to-Agent protocol — standardized message bus for agent negotiation -- Sandboxed execution contexts for sub-agents -- Agent health monitoring and restart policies -- Neural VFS concept:// path resolution - -### Phase 3 — Cognitive OS 🔴 NOT STARTED - -**On the roadmap:** -- Global World Model integration — persistent semantic understanding of system state -- Predictive resource allocation — anticipate agent needs before they arise -- Cross-session persistent memory graphs — relational memory across all user sessions -- Neural VFS — semantic path resolution (`concept://` paths) replacing hierarchical filesystem -- Recursive self-optimization — automated WPM patches based on performance telemetry -- Self-rewrite daemon — models hot-path bottlenecks and generates optimized driver variants -- Cognitive shell — unified dashboard for multi-agent activity visualization +- ✅ Skill-pack core — 24 skills auto-loaded on startup with seed/reload support +- ✅ Neural VFS — semantic concept indexing: nvfs-index, nvfs-resolve, nvfs-tree, nvfs-status. concept:// URI resolution in virtual-fs.js with fuzzy matching and cache +- ✅ A2A Protocol — agent contract system with schema validation, orchestrator validates tasks before delegation +- ✅ Self-Optimization Telemetry — system-health command with tool latency, VFS stats, error rates +- ✅ A2A VFS mailbox transport — a2a-send, a2a-list, 500ms polling loop routing request/response/broadcast/heartbeat messages via inbox/outbox +- ✅ Autonomous sub-agent spawning — auto-spawns workers with task-derived role and capabilities (orchestrator-spawn, orchestrator-list, orchestrator-config) +- ✅ Agent lifecycle management — orchestrator-sleep (VFS serialization), orchestrator-wake (restore), auto-reanimate on page reload +- ✅ Agent health monitoring — heartbeat timeouts, idle sweeps, restart policies (on-failure, always, never) with exponential backoff + +**Built:** +- ✅ Sandboxed execution contexts via WebWorker pool (1-4 workers, VFS snapshots) +- ✅ Cross-session persistent memory graphs — 5 node types, weighted edges, VFS persistence +- ✅ Predictive resource allocation — `predict` command estimates task duration/turns from historical data +- ✅ Global World Model — system self-awareness synthesis from modules, skills, telemetry, agents, memory graph + +### Phase 3 — Cognitive OS ✅ COMPLETE **Implementation Plan:** `.hermes/plans/2026-06-02_phase3-cognitive-os.md` diff --git a/packages.json b/packages.json index 5d8203b..72876e8 100644 --- a/packages.json +++ b/packages.json @@ -40,7 +40,7 @@ "theme": { "name": "Theme Switcher", "version": "1.0.0", - "desc": "CSS theme manager — dark, light, amber, cyber + custom", + "desc": "CSS theme manager \u2014 dark, light, amber, cyber + custom", "url": "modules/ext/theme.js", "commands": [ "theme" @@ -60,7 +60,7 @@ "webdav": { "name": "WebDAV Sync", "version": "1.0.0", - "desc": "Cloud sync for VFS — backup, push, pull, and restore via WebDAV", + "desc": "Cloud sync for VFS \u2014 backup, push, pull, and restore via WebDAV", "url": "modules/ext/webdav.js", "commands": [ "webdav-config", @@ -77,7 +77,7 @@ "mindmap": { "name": "Mindmap", "version": "1.0.0", - "desc": "Brainstorming mindmap tool — new, add, show, export, save, load, color, tag", + "desc": "Brainstorming mindmap tool \u2014 new, add, show, export, save, load, color, tag", "url": "modules/ext/mindmap.js", "commands": [ "mindmap" @@ -97,7 +97,7 @@ "editor": { "name": "Text Editor", "version": "1.0.0", - "desc": "Inline text editor for VFS files — edit, save, cancel via modal", + "desc": "Inline text editor for VFS files \u2014 edit, save, cancel via modal", "url": "modules/ext/editor.js", "commands": [ "edit" @@ -203,7 +203,7 @@ "tutorial": { "name": "Tutorial", "version": "1.0.0", - "desc": "Interactive guided tour — 6 quests to learn newOS4 basics", + "desc": "Interactive guided tour \u2014 6 quests to learn newOS4 basics", "url": "modules/ext/tutorial.js", "commands": [ "tutorial" @@ -223,8 +223,8 @@ "neural-vfs": { "name": "Neural VFS Indexer", "version": "1.0.0", - "desc": "Semantic concept-indexing layer — nvfs-index, nvfs-resolve, nvfs-tree, nvfs-status", - "url": "modules/ext/neural-vfs.js", + "desc": "Semantic concept-indexing layer \u2014 nvfs-index, nvfs-resolve, nvfs-tree, nvfs-status", + "url": "modules/core/module-neural-vfs.js", "commands": [ "nvfs-index", "nvfs-status", diff --git a/review-newos4.html b/review-newos4.html index 3d8f7d4..7024e66 100644 --- a/review-newos4.html +++ b/review-newos4.html @@ -89,23 +89,23 @@ <p class="meta">WebX11 Terminal · In-Browser Terminal OS · Reviewed 2026-06-03</p> <div class="summary-box"> - <div class="stat"><div class="value">33</div><div class="label">Module Files</div></div> - <div class="stat"><div class="value">18+14</div><div class="label">Core + Ext</div></div> - <div class="stat"><div class="value">21</div><div class="label">Skills</div></div> + <div class="stat"><div class="value">46</div><div class="label">Module Files</div></div> + <div class="stat"><div class="value">28+17</div><div class="label">Core + Ext</div></div> + <div class="stat"><div class="value">24</div><div class="label">Skills</div></div> <div class="stat"><div class="value">9</div><div class="label">Theme Presets</div></div> - <div class="stat"><div class="value">52KB</div><div class="label">App Size</div></div> + <div class="stat"><div class="value">54KB</div><div class="label">App Size</div></div> <div class="stat"><div class="value">4.5/5</div><div class="label">Rating</div></div> </div> <section> <h2>Architecture</h2> - <p>newOS4 v4.3 is an <strong>in-browser terminal OS</strong> — NOT a chat-centric UI. Single-file <code>main.html</code> (1,031 lines, 52KB) loads 32 module files across two directories:</p> + <p>newOS4 v4.3 is an <strong>in-browser terminal OS</strong> — NOT a chat-centric UI. Single-file <code>main.html</code> (1,101 lines, 54KB) loads 45 module files across two layers:</p> <ul> - <li><strong>29 core modules</strong> (<code>modules/core/</code>) — Foundation infrastructure (VirtualFileSystem, OllamaManager, SecretVault, ModuleManager, Terminal) + 24 built-in feature modules (system, fs, modmgr, ollama, my, searxng, servermon, ollamamodels, config, simplegui, io, dynamicprompts, aiassist, aiasm, smartctx, duckduckgo, fetcher, skills, chatstudio, agent-registry, leases, orchestrator, neural-vfs)</li> + <li><strong>28 core items</strong> (<code>modules/core/</code>) — 4 infrastructure (VirtualFileSystem, OllamaManager, ModuleManager, Terminal) + 24 built-in feature modules (system, fs, modmgr, ollama, my, searxng, servermon, config, simplegui, io, dynamicprompts, aiassist, aiasm, smartctx, duckduckgo, fetcher, skills, secret-vault, chatstudio, agent-registry, leases, orchestrator, neural-vfs, module-loader)</li> <li><strong>17 ext modules</strong> (<code>modules/ext/</code>) — Self-registering: alias, busybox, calc, cron, cryptogzip, diff, editor, filemgr, finder, mindmap, shell, skill-pack-core, theme, tutorial, voice, webdav, wpm</li> - <li><strong>46 modules total</strong> — Full discovery via <code>modules-list</code> command or AI <code>list_available_modules</code> tool. Indexed in <code>modules-index.json</code></li> + <li><strong>45 modules + 1 test suite</strong> — Full discovery via <code>modules-list</code> command or AI <code>list_available_modules</code> tool (46 entries). Indexed in <code>modules-index.json</code></li> </ul> - <p><strong>Total codebase</strong>: ~500KB (main.html + all module files). AI system with 5-turn agentic loop, 19+ tool definitions, 12 skills in VFS, 9 theme presets, and full destructive-action confirmation.</p> + <p><strong>Total codebase</strong>: ~155KB (main.html + all module files). AI system with 5-turn agentic loop, 19+ tool definitions, 24 skills in VFS, 9 theme presets, and full destructive-action confirmation.</p> </section> <section> @@ -149,7 +149,7 @@ <h3>📚 Skills System</h3> <ul> - <li><strong>21 skills</strong> (from 12) stored as Markdown with YAML frontmatter in VFS <code>/skills/</code></li> + <li><strong>24 skills</strong> (from 12) stored as Markdown with YAML frontmatter in VFS <code>/skills/</code></li> <li>Skill-pack core auto-loads skills on startup with seed/reload</li> <li>8 CLI commands: skill-list, skill-view, skill-create, skill-patch, skill-delete, skill-export, skill-import, skill-sync</li> <li>Skills Manager UI modal (📚 button in quick-actions bar)</li> @@ -246,8 +246,8 @@ <section> <h2>Pros & Cons</h2> - <div class="pro"><strong>✅ Self-improving</strong> — AI creates, patches, and manages its own skills. 12 skills in VFS with skill-pack core auto-load and seed/reload support.</div> - <div class="pro"><strong>✅ Rich module ecosystem</strong> — 32 module files (18 core + 14 ext), 19 Unix commands, search, fetch, shell, webdav, cron, calc, voice, encrypt, theme, mindmap, editor</div> + <div class="pro"><strong>✅ Self-improving</strong> — AI creates, patches, and manages its own skills. 24 skills in VFS with skill-pack core auto-load and seed/reload support.</div> + <div class="pro"><strong>✅ Rich module ecosystem</strong> — 45 module files (28 core + 17 ext), 19 Unix commands, search, fetch, shell, webdav, cron, calc, voice, encrypt, theme, mindmap, editor</div> <div class="pro"><strong>✅ WebDAV modern browser</strong> — File-tree UI with checkboxes, Select All, bulk Move/Delete/Download, inline rename, context menu, breadcrumbs, keyboard shortcuts</div> <div class="pro"><strong>✅ Security layer</strong> — security-level 0-3, command allowlist/denylist, non-blocking destructive confirmation with 120s countdown</div> <div class="pro"><strong>✅ Debug panel</strong> — Live memory, event, error, and module health monitoring via <code>debug</code> toggle</div> @@ -262,16 +262,16 @@ <div class="con"><strong>❌ Ollama dependency</strong> — AI features require a running Ollama or compatible provider</div> <div class="con"><strong>❌ 5-turn agent limit</strong> — Complex multi-step tasks may hit the tool-call ceiling</div> - <div class="con"><strong>❌ No module isolation</strong> — All 42 modules share same JS scope, potential for collisions</div> + <div class="con"><strong>❌ No module isolation</strong> — All 45 modules share same JS scope, potential for collisions</div> <div class="con"><strong>❌ No TTS</strong> — Voice input only (🎤), no text-to-speech for AI responses</div> <div class="con"><strong>❌ No streaming AI</strong> — Agent uses non-streaming chat for tool handling; stream is text-only fallback</div> - <div class="con"><strong>❌ Single-file pain point</strong> — 1,031-line main.html with inline CSS+JS is hard to maintain</div> - <div class="con"><strong>❌ 42 file loads</strong> — All module scripts loaded sequentially, slow on first visit</div> + <div class="con"><strong>❌ Single-file pain point</strong> — 1,101-line main.html with inline CSS+JS is hard to maintain</div> + <div class="con"><strong>❌ 45 file loads</strong> — All module scripts loaded sequentially, slow on first visit</div> </section> <section> <h2>Verdict</h2> - <p>newOS4 v4.2 is the most <strong>feature-rich</strong> ChatStudio variant — an in-browser terminal OS with agentic AI, persistent virtual filesystem, skills system, 9 themes, voice input, and 42 modular components. At 52KB for the main HTML (800KB total codebase), it packs enormous functionality into a self-contained browser app.</p> + <p>newOS4 v4.3.1 is the most <strong>feature-rich</strong> ChatStudio variant — an in-browser terminal OS with agentic AI, persistent virtual filesystem, skills system, 9 themes, voice input, and 45 modular components. At 54KB for the main HTML (~155KB total codebase), it packs enormous functionality into a self-contained browser app.</p> <p>The key distinction from other variants: newOS4 is a <strong>terminal OS</strong>, not a chat UI. It's designed for power users who want AI-assisted file management, shell commands, and automated workflows — not just conversation.</p> <p><strong>Best for:</strong> Power users, developers, AI experimentation, anyone who wants a browser-based terminal with learning AI.</p> <p><strong>Not for:</strong> Users who just want quick AI chat, or users without Ollama/OpenRouter access.</p> diff --git a/skills/index.json b/skills/index.json index d4aeda8..bbfcce7 100644 --- a/skills/index.json +++ b/skills/index.json @@ -9,8 +9,8 @@ "filesystem" ], "category": "system", - "created": "2026-06-04T11:01:57.585Z", - "updated": "2026-06-04T11:01:57.585Z" + "created": "2026-06-05T15:37:40.384Z", + "updated": "2026-06-05T15:37:40.384Z" }, "memory-and-profile": { "name": "memory-and-profile", @@ -22,8 +22,8 @@ "persistence" ], "category": "system", - "created": "2026-06-04T11:01:57.585Z", - "updated": "2026-06-04T11:01:57.585Z" + "created": "2026-06-05T15:37:40.385Z", + "updated": "2026-06-05T15:37:40.385Z" }, "web-search-and-fetch": { "name": "web-search-and-fetch", @@ -36,8 +36,8 @@ "api" ], "category": "research", - "created": "2026-06-04T11:01:57.585Z", - "updated": "2026-06-04T11:01:57.585Z" + "created": "2026-06-05T15:37:40.385Z", + "updated": "2026-06-05T15:37:40.385Z" }, "module-management": { "name": "module-management", @@ -49,8 +49,8 @@ "self-improvement" ], "category": "dev", - "created": "2026-06-04T11:01:57.585Z", - "updated": "2026-06-04T11:01:57.585Z" + "created": "2026-06-05T15:37:40.385Z", + "updated": "2026-06-05T15:37:40.385Z" }, "system-maintenance": { "name": "system-maintenance", @@ -63,8 +63,8 @@ "config" ], "category": "system", - "created": "2026-06-04T11:01:57.586Z", - "updated": "2026-06-04T11:01:57.586Z" + "created": "2026-06-05T15:37:40.385Z", + "updated": "2026-06-05T15:37:40.385Z" }, "backup-and-restore": { "name": "backup-and-restore", @@ -77,8 +77,8 @@ "persistence" ], "category": "system", - "created": "2026-06-04T11:01:57.586Z", - "updated": "2026-06-04T11:01:57.586Z" + "created": "2026-06-05T15:37:40.385Z", + "updated": "2026-06-05T15:37:40.385Z" }, "mindmap-workflow": { "name": "mindmap-workflow", @@ -90,8 +90,8 @@ "organization" ], "category": "productivity", - "created": "2026-06-04T11:01:57.586Z", - "updated": "2026-06-04T11:01:57.586Z" + "created": "2026-06-05T15:37:40.385Z", + "updated": "2026-06-05T15:37:40.385Z" }, "skills-system-guide": { "name": "skills-system-guide", @@ -102,8 +102,8 @@ "meta" ], "category": "system", - "created": "2026-06-04T11:01:57.586Z", - "updated": "2026-06-04T11:01:57.586Z" + "created": "2026-06-05T15:37:40.385Z", + "updated": "2026-06-05T15:37:40.385Z" }, "agent-orchestration": { "name": "agent-orchestration", @@ -115,8 +115,8 @@ "leases" ], "category": "system", - "created": "2026-06-04T11:01:57.586Z", - "updated": "2026-06-04T11:01:57.586Z" + "created": "2026-06-05T15:37:40.385Z", + "updated": "2026-06-05T15:37:40.385Z" }, "debug-panel-and-logs": { "name": "debug-panel-and-logs", @@ -128,8 +128,8 @@ "troubleshooting" ], "category": "system", - "created": "2026-06-04T11:01:57.586Z", - "updated": "2026-06-04T11:01:57.586Z" + "created": "2026-06-05T15:37:40.385Z", + "updated": "2026-06-05T15:37:40.385Z" }, "webdav-operations": { "name": "webdav-operations", @@ -142,8 +142,8 @@ "storage" ], "category": "system", - "created": "2026-06-04T11:01:57.586Z", - "updated": "2026-06-04T11:01:57.586Z" + "created": "2026-06-05T15:37:40.385Z", + "updated": "2026-06-05T15:37:40.385Z" }, "security-and-safety": { "name": "security-and-safety", @@ -155,8 +155,21 @@ "permissions" ], "category": "system", - "created": "2026-06-04T11:01:57.586Z", - "updated": "2026-06-04T11:01:57.586Z" + "created": "2026-06-05T15:37:40.385Z", + "updated": "2026-06-05T15:37:40.385Z" + }, + "mindmap-user-guide": { + "name": "mindmap-user-guide", + "description": "How to create, view, and manage brainstorming mindmaps using the mindmap terminal module", + "tags": [ + "mindmap", + "brainstorming", + "organization", + "planning" + ], + "category": "productivity", + "created": "2026-06-05T15:38:13.076Z", + "updated": "2026-06-05T15:38:13.076Z" }, "a2a-contract-framework": { "name": "a2a-contract-framework", diff --git a/skills/memory-and-profile.md b/skills/memory-and-profile.md new file mode 100644 index 0000000..183b761 --- /dev/null +++ b/skills/memory-and-profile.md @@ -0,0 +1,39 @@ +--- +name: memory-and-profile +description: How to use the user profile system, session memory, and pattern tracking for persistent context +tags: [memory, profile, session, persistence] +category: system +--- +## Steps +1. **User Profile (persists across sessions):** + - user_get(key) — read any profile field (e.g. "preferredModel", "fontSize") + - user_set(key, value) — write to profile, auto-persists to localStorage + - user_list — show all stored profile keys + - user_forget(key) — delete a profile key + - Dot notation supported: user_set knownFacts.name "Alex" + +2. **Session Memory (VFS-based daily logs):** + - session_save(summary) — save a summary of what was accomplished + - session_load(date) — read a specific day's log (format: YYYY-MM-DD) + - session_recent(count) — show last N session summaries + - session_stats — aggregate totals across all sessions + - Recent sessions auto-load into context on first message + - systemd-style journal: use logs and logrotate commands for persistent logging + +3. **Conversation Patterns:** + - Pattern tracking runs automatically — frequent topics surface as hints + - my-set, my-get, my-list store additional personal data + - AI conversation context persists within a session (use ai-clear to reset) + - debug command toggles real-time debug panel with Copy/Export/VFS buttons + +4. **Checkpoints (VFS snapshots):** + - checkpoint [name] — snapshot current VFS state + - checkpoint-list — list all checkpoints + - rollback <name> — restore from checkpoint + - checkpoint-del <name> — delete a checkpoint + +5. **When to use:** + - At start: call session_recent(3) to see what was worked on before + - After identifying user preferences: call user_set to store them + - After complex tasks: call session_save with a brief summary + - Before risky edits: call checkpoint to create a restore point
\ No newline at end of file diff --git a/skills/mindmap-user-guide.md b/skills/mindmap-user-guide.md new file mode 100644 index 0000000..7bee410 --- /dev/null +++ b/skills/mindmap-user-guide.md @@ -0,0 +1,40 @@ +--- +name: mindmap-user-guide +description: How to create, view, and manage brainstorming mindmaps using the mindmap terminal module +tags: [mindmap, brainstorming, organization, planning] +category: productivity +--- +## Steps + +1. **Create a new mindmap:** + - `mindmap new "<topic>" -t tag1,tag2` — creates a root node + - Tags are optional (e.g. planning, research, dev) + - Root gets auto-assigned a color + +2. **Add child nodes:** + - `mindmap add <parent-id> "<idea>" -t tag` — add a child + - Get the parent-id from `mindmap show` + - IDs are 4-char random strings (e.g. a3f8, x7k2) + +3. **View the mindmap:** + - `mindmap show` — full tree with Unicode box-drawing + - `mindmap show <id>` — subtree starting at that node + +4. **Edit nodes:** + - `mindmap rename <id> "<new text>"` — change text + - `mindmap color <id> <color>` — set color (red/green/yellow/blue/cyan/magenta/white) + - `mindmap tag <id> <tag>` — add a tag + +5. **Persistence:** + - `mindmap save <name>` — save to localStorage + - `mindmap load <name>` — restore + - `mindmap list` — show saved mindmaps + - `mindmap delete <name>` — remove saved + +6. **Export:** + - `mindmap export [id]` — Markdown bullet list output + +## When to use +- User wants to brainstorm, organize ideas, plan features +- User says "map out", "diagram", "brainstorm", "organize thoughts" +- Visualizing project structure or task decomposition
\ No newline at end of file diff --git a/skills/mindmap-workflow.md b/skills/mindmap-workflow.md new file mode 100644 index 0000000..f05c237 --- /dev/null +++ b/skills/mindmap-workflow.md @@ -0,0 +1,39 @@ +--- +name: mindmap-workflow +description: How to create, view, edit, and manage brainstorming mindmaps +tags: [mindmap, brainstorming, planning, organization] +category: productivity +--- +## Steps +1. **Create a new mindmap:** + - mindmap new "<topic>" -t tag1,tag2 — creates a root node + - Tags are optional (e.g. planning, research, dev) + - Root gets auto-assigned a color from 7-color palette + +2. **Add child nodes:** + - mindmap add <parent-id> "<idea>" -t tag — add a child + - Get the parent-id from mindmap show + - IDs are 4-char random strings (e.g. a3f8, x7k2) + +3. **View:** + - mindmap show — full tree with Unicode box-drawing + - mindmap show <id> — subtree starting at that node + +4. **Edit nodes:** + - mindmap rename <id> "<new text>" — change text + - mindmap color <id> <color> — set color (red/green/yellow/blue/cyan/magenta/white) + - mindmap tag <id> <tag> — add a tag + +5. **Persistence:** + - mindmap save <name> — save to localStorage + - mindmap load <name> — restore + - mindmap list — show saved mindmaps + - mindmap delete <name> — remove saved + +6. **Export:** + - mindmap export [id] — Markdown bullet list output, auto-copies to clipboard + +7. **When to use:** + - Map out project structure, brainstorm features, organize ideas + - Break down complex tasks into subtrees + - User says "brainstorm", "diagram", "organize thoughts", "plan"
\ No newline at end of file diff --git a/skills/webdav-operations.md b/skills/webdav-operations.md new file mode 100644 index 0000000..098449c --- /dev/null +++ b/skills/webdav-operations.md @@ -0,0 +1,33 @@ +--- +name: webdav-operations +description: How to use WebDAV cloud storage for backup, file sync, and remote file access +tags: [webdav, cloud, backup, sync, storage] +category: system +--- +## Steps +1. **Configuration:** + - webdav-config — configure WebDAV endpoint URL and credentials + - Connection auto-tested on config; webdav-status shows connection health + - Uses per-user token stored in .token file on the server side + +2. **File operations:** + - webdav-ls [path] — list remote directory contents + - webdav-push <local-path> <remote-path> — upload file to WebDAV + - webdav-pull <remote-path> [local-path] — download file from WebDAV + - webdav-explorer — GUI file browser for WebDAV + - webdav-browser — CLI file browser for WebDAV + +3. **Backup & Restore:** + - webdav-backup — push full VFS snapshot to WebDAV (creates /backups/newos4-<date>.json) + - webdav-backup-list — list all remote backup snapshots + - webdav-restore <filename> — restore VFS from a remote backup snapshot + +4. **Status & Diagnostics:** + - webdav-status — connection status, last sync, space information + - If uploads fail: check webdav-config, ensure .token file is valid + - WebDAV runs on the same server (nginx with WebDAV module), no external dependency + +5. **When to use:** + - Before factory-reset: webdav-backup to store VFS remotely + - Cross-browser: webdav-push/pull to move files between instances + - User says "cloud backup" or "remote save" → webdav-backup
\ No newline at end of file |