diff options
| author | Agando <roduccomm@gmail.com> | 2026-10-23 18:22:30 +0200 |
|---|---|---|
| committer | Agando <roduccomm@gmail.com> | 2026-10-23 18:22:30 +0200 |
| commit | 2bc11203b73fe2eb210ce3dd643773e625609c25 (patch) | |
| tree | 60222e83add1a2fd05fa705527ff7dc4ce1efea4 | |
| parent | f3bbb69b25d1b1619f7bd9d5861480af7e9e834f (diff) | |
feat(newos4): agent status says never-scheduled vs waiting; `guides` command (v4.6.63)
Packet 6 and 7.
6 — four agents showed `sleeping` and nothing distinguished "waiting for work" from
"never handed a task". Added tasksAssigned/lastTaskAt to the agent record (counted
where a task is actually assigned), and the status line now reads
idle — NEVER SCHEDULED (0 tasks since 2026-10-23 16:22)
idle — waiting for work (3 task(s), last 2026-10-23 16:22)
parked — NEVER SCHEDULED ...
plus "Dispatch: 0/3 agent(s) have ever been given a task" and a warning that
spawning is not dispatching.
7 — /guides/ was reachable by nobody. New `guides` command lists it newest-first
with sizes and the preview/cat hints; help.html gained a Guides nav entry and
section with links to the published guides.
Verified live on v4.6.63: `guides` → 2 entries after seeding two files;
orchestrator-status shows 3x NEVER SCHEDULED on a fresh session (the packet's own
observation), and "waiting for work (3 task(s) ...)" once a counter is set, with
"Dispatch: 1/3".
| -rw-r--r-- | help.html | 21 | ||||
| -rw-r--r-- | main.html | 6 | ||||
| -rw-r--r-- | modules/core/core-bundle.js | 54 | ||||
| -rw-r--r-- | modules/core/module-fs.js | 16 | ||||
| -rw-r--r-- | modules/core/module-loader.js | 2 | ||||
| -rw-r--r-- | modules/core/module-orchestrator.js | 38 |
6 files changed, 124 insertions, 13 deletions
@@ -244,6 +244,7 @@ footer .tagline{color:var(--accent2);margin-top:10px;font-weight:500} <a href="#overview" class="nav-link">Overview</a> <a href="#start" class="nav-link">Quick Start</a> <a href="#commands" class="nav-link">Commands</a> + <a href="#guides" class="nav-link">Guides</a> <a href="#filesystem" class="nav-link">Filesystem</a> <a href="#ai" class="nav-link">AI Agent</a> <a href="skills.html" class="nav-link">Skills → dynamic</a> @@ -587,6 +588,22 @@ diff a.txt b.txt</pre> </section> <!-- ── Filesystem ── --> + <section id="guides"> + <h2>📚 Guides</h2> + <p>Long-form guides live in the VFS under <code>/guides/</code>. The AI writes them there; you can + reach them three ways:</p> + <ul> + <li><code>guides</code> — list what exists, newest first (this is the index that did not exist before v4.6.63)</li> + <li><code>preview <name></code> — render an HTML guide in the preview pane</li> + <li><code>cat <name></code> — read a markdown guide as text</li> + </ul> + <p>Published guides on this server:</p> + <ul> + <li><a href="../agando/guides/newos-future.html">newOS4 — future directions</a></li> + <li><a href="../agando/guides/request-to-newos4-team.md">Request to the newOS4 team (2026-10-23)</a></li> + </ul> + </section> + <section id="filesystem"> <h2>📁 Filesystem</h2> <p>All VFS files persist in <code>localStorage</code>. <code>ls</code>, <code>cd</code>, <code>pwd</code> work like a real terminal. Clickable output — tap a file to <code>cat</code>, tap a dir to <code>cd</code>.</p> @@ -1672,13 +1689,15 @@ listmods</pre> <h2>📋 Changelog</h2> <div class="version-item"> - <div class="v-header"><span class="v-num">v4.6.57–62 — October 2026</span><span class="v-date">2026-10-23</span></div> + <div class="v-header"><span class="v-num">v4.6.57–63 — October 2026</span><span class="v-date">2026-10-23</span></div> <ul> <li><strong>No shell — and it now says so</strong> — <code>;</code>, <code>&&</code> and <code>||</code> are refused with an explicit message instead of being folded into the command's argv (where <code>wc -l f && grep -c x f</code> returned a plausible wrong number). A <code>|</code> arriving from the AI <code>run_command</code> tool is refused too, because that path bypasses the pipe splitter and degraded the pattern to empty. Documented at the top of the command reference.</li> <li><strong>Multiple paths in <code>wc</code></strong> — <code>wc -c a b</code> printed only <code>a</code> and exited clean. It now prints one line per file plus a <code>total</code>, and reports each missing file separately.</li> <li><strong><code>ls</code> prints text</strong> — it rendered only clickable chips (DOM-only), so any capture — the AI <code>run_command</code> tool, the diagnostics sweep — saw an empty result and reported "directory empty".</li> <li><strong><code>view_module</code> shows what is deployed</strong> — it returned the <code>_mods/<Class>.js</code> edit cache first, unversioned, so after a rebuild it showed pre-fix source while the bundle was new. It now fetches the deployed file and prefixes the version; the cache is a labelled fallback.</li> <li><strong><code>load_skills</code> searches tokens, not one phrase</strong> — the query used to be tested whole against name+description+tags, so <code>"telemetry dashboard"</code> returned 0 while <code>"telemetry"</code> returned 1, and a sentence could never match. Skill <em>bodies</em> were never searched. It now tokenises (stopwords dropped), scores name > description/tags > body, weights by how many tokens matched, keeps a phrase bonus so word order cannot zero a real hit, and reports <code>matchedTokens: 2/3</code> with each result.</li> + <li><strong>Agent status says what it means</strong> — four agents showed <code>sleeping</code> with no way to tell "waiting for work" from "never handed a task". They now read <code>parked — NEVER SCHEDULED (0 tasks since …)</code> or <code>waiting for work (3 tasks, last …)</code>, plus a dispatch line and a warning when spawning happened but nothing was ever dispatched.</li> + <li><strong>A guide you can actually find</strong> — <code>guides</code> lists <code>/guides/</code> newest-first; the help page has a Guides section with the command, the preview/cat hints and links to published guides.</li> <li><strong>One error counter</strong> — <code>errors</code>, <code>get_dashboard_data</code> and the world model tallied three different sinks (0 / 18 / 11 in the same second). All three now read <code>terminal.countErrors()</code> (DebugSupervisor DB + failed tool calls); the VFS log is reported separately as a mirror, and <code>terminal.log('ERROR', …)</code> now records into the same DB instead of a second store nobody read.</li> <li><strong>Predictor arithmetic</strong> — the stored field named <code>mape</code> held a sum, so any JSON consumer read a mean that was <em>n</em> times too large (now <code>errorSum</code>); <code>errorPct</code> is stored raw and clamped only at render (a 475× miss used to be flattened to 500%); predictions are floored at 1 s and floor-hits are not scored, because one 0.079 s aborted run was rounding the next prediction to 0 s.</li> </ul> @@ -1349,13 +1349,13 @@ </script> <!-- v4.5.63: A2A scratchpad fixes + agent-register arg fix --> -<script src="modules/core/core-bundle.js?v=v4.6.62"></script> +<script src="modules/core/core-bundle.js?v=v4.6.63"></script> <!-- Async dependency-ordered module loader now only initializes terminal and loads optional ext modules --> -<script src="modules/core/module-loader.js?v=v4.6.62"></script> +<script src="modules/core/module-loader.js?v=v4.6.63"></script> <!-- Critical ext bundle loads AFTER webx11 exists so registerModule calls work --> -<script src="modules/ext/critical-ext-bundle.js?v=v4.6.62"></script> +<script src="modules/ext/critical-ext-bundle.js?v=v4.6.63"></script> <script> // ═══ ONE TRUE VERSION SOURCE ═══ diff --git a/modules/core/core-bundle.js b/modules/core/core-bundle.js index 48d9dbe..df4563d 100644 --- a/modules/core/core-bundle.js +++ b/modules/core/core-bundle.js @@ -6470,6 +6470,22 @@ class FileSystemModule { if (!capturing && typeof this.terminal.printClickable === 'function') this.terminal.printClickable(filtered); } }; + this.commands['guides'] = (args) => { + // v4.6.63: /guides/ was reachable by nobody — the AI writes guides there, and the + // only way to find one was to guess its filename (packet §7). One command, newest first. + let items = []; + try { items = this.terminal.fs.readDir('/guides') || []; } catch (e) { items = []; } + const docs = items + .filter(f => f && f.type !== 'dir' && /\.(html?|md|txt)$/i.test(f.name)) + .sort((a, b) => (b.mtime || 0) - (a.mtime || 0)); + if (!docs.length) { + this.terminal.print('No guides in /guides/ yet.', 'info'); + this.terminal.print(' Guides written by the AI land here — `guides` lists them, `preview <name>` renders HTML, `cat <name>` shows markdown.', 'text-muted'); + return; + } + this.terminal.print(`📚 Guides (${docs.length}) — preview <name> for HTML, cat <name> for markdown`, 'header'); + docs.forEach(f => this.terminal.print(` ${f.name} ${f.size || 0} B`, 'info')); + }; this.commands['cat'] = (args) => { if(!args[0]) return this.terminal.print('Usage: cat <filename>', 'warning'); const content = this.terminal.fs.readFile(args[0]); @@ -15807,7 +15823,11 @@ class OrchestratorModule { restartPolicy: this._RESTART_POLICY, failureCount: 0, restartCount: 0, - nextRestartAt: null + nextRestartAt: null, + // v4.6.63: "sleeping" alone could not tell an agent WAITING for work from one + // that was never handed a task (packet §6: 4 registered, 0 executing). + tasksAssigned: 0, + lastTaskAt: null }; this._agents[id] = agent; // Create VFS agent directory with status @@ -16634,6 +16654,8 @@ class OrchestratorModule { agent.currentTask = task; agent.leaseId = lease?.id; agent.lastHeartbeat = Date.now(); + agent.tasksAssigned = (agent.tasksAssigned || 0) + 1; + agent.lastTaskAt = new Date().toISOString(); // Send A2A request via inbox this.sendA2AMessage([agent.id, JSON.stringify({ @@ -16852,12 +16874,36 @@ class OrchestratorModule { this.terminal.print(` Default restart: ${this._RESTART_POLICY}`, 'info'); this.terminal.print(` Default agent model: ${this._AGENT_DEFAULT_MODEL || 'none (uses main AI model)'}`, '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)})` : ''; + // v4.6.63: say what the state MEANS. A parked agent with 0 assignments and a parked + // agent between tasks looked identical from outside (packet §6). + const agentsArr = Object.values(this._agents); + const everScheduled = agentsArr.filter(a => (a.tasksAssigned || 0) > 0).length; + const describe = (a) => { + const n = a.tasksAssigned || 0; + const since = a.createdAt ? String(a.createdAt).slice(0, 16).replace('T', ' ') : '?'; + const last = a.lastTaskAt ? String(a.lastTaskAt).slice(0, 16).replace('T', ' ') : null; + if (a.status === 'busy') { + return 'busy' + (a.currentTask ? ' — ' + String(a.currentTask).substring(0, 40) : ''); + } + if (a.status === 'failed' || a.status === 'restarting') { + return a.status + ` — ${a.failureCount || 0} failure(s), ${n} task(s) ever`; + } + if (n === 0) { + return (a.status === 'sleeping' ? 'parked' : a.status) + + ` — NEVER SCHEDULED (0 tasks since ${since})`; + } + return (a.status === 'sleeping' ? 'parked' : a.status) + + ` — waiting for work (${n} task(s), last ${last || '?'})`; + }; + agentsArr.forEach(a => { const modelInfo = a.model ? ` [model: ${a.model}]` : ''; 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}${modelInfo}${task}${backoff}`, 'info'); + this.terminal.print(` ${a.id} [${a.role}] — ${describe(a)}${modelInfo}${backoff}`, 'info'); }); + this.terminal.print(` Dispatch: ${everScheduled}/${total} agent(s) have ever been given a task`, everScheduled === 0 && total > 0 ? 'warning' : 'info'); + if (everScheduled === 0 && total > 0) { + this.terminal.print(' ⚠️ No agent has ever been scheduled — spawning is not dispatching. Use orchestrate <task> or orchestrator-spawn <role> <task>.', 'warning'); + } } listAgents() { diff --git a/modules/core/module-fs.js b/modules/core/module-fs.js index f93553a..fd1dfc1 100644 --- a/modules/core/module-fs.js +++ b/modules/core/module-fs.js @@ -72,6 +72,22 @@ class FileSystemModule { if (!capturing && typeof this.terminal.printClickable === 'function') this.terminal.printClickable(filtered); } }; + this.commands['guides'] = (args) => { + // v4.6.63: /guides/ was reachable by nobody — the AI writes guides there, and the + // only way to find one was to guess its filename (packet §7). One command, newest first. + let items = []; + try { items = this.terminal.fs.readDir('/guides') || []; } catch (e) { items = []; } + const docs = items + .filter(f => f && f.type !== 'dir' && /\.(html?|md|txt)$/i.test(f.name)) + .sort((a, b) => (b.mtime || 0) - (a.mtime || 0)); + if (!docs.length) { + this.terminal.print('No guides in /guides/ yet.', 'info'); + this.terminal.print(' Guides written by the AI land here — `guides` lists them, `preview <name>` renders HTML, `cat <name>` shows markdown.', 'text-muted'); + return; + } + this.terminal.print(`📚 Guides (${docs.length}) — preview <name> for HTML, cat <name> for markdown`, 'header'); + docs.forEach(f => this.terminal.print(` ${f.name} ${f.size || 0} B`, 'info')); + }; this.commands['cat'] = (args) => { if(!args[0]) return this.terminal.print('Usage: cat <filename>', 'warning'); const content = this.terminal.fs.readFile(args[0]); diff --git a/modules/core/module-loader.js b/modules/core/module-loader.js index 2871ef3..5bf2a4c 100644 --- a/modules/core/module-loader.js +++ b/modules/core/module-loader.js @@ -4,7 +4,7 @@ // Core module bundle loaded synchronously in main.html. // This loader only handles version cache-busting, splash, terminal init, and optional ext modules. - const LATEST_VER = 'v4.6.62'; + const LATEST_VER = 'v4.6.63'; const CACHE = '?v=' + LATEST_VER; const stored = localStorage.getItem('webx11_cache_ver'); if (stored !== LATEST_VER) { diff --git a/modules/core/module-orchestrator.js b/modules/core/module-orchestrator.js index ad9f020..d03f7eb 100644 --- a/modules/core/module-orchestrator.js +++ b/modules/core/module-orchestrator.js @@ -369,7 +369,11 @@ class OrchestratorModule { restartPolicy: this._RESTART_POLICY, failureCount: 0, restartCount: 0, - nextRestartAt: null + nextRestartAt: null, + // v4.6.63: "sleeping" alone could not tell an agent WAITING for work from one + // that was never handed a task (packet §6: 4 registered, 0 executing). + tasksAssigned: 0, + lastTaskAt: null }; this._agents[id] = agent; // Create VFS agent directory with status @@ -1196,6 +1200,8 @@ class OrchestratorModule { agent.currentTask = task; agent.leaseId = lease?.id; agent.lastHeartbeat = Date.now(); + agent.tasksAssigned = (agent.tasksAssigned || 0) + 1; + agent.lastTaskAt = new Date().toISOString(); // Send A2A request via inbox this.sendA2AMessage([agent.id, JSON.stringify({ @@ -1414,12 +1420,36 @@ class OrchestratorModule { this.terminal.print(` Default restart: ${this._RESTART_POLICY}`, 'info'); this.terminal.print(` Default agent model: ${this._AGENT_DEFAULT_MODEL || 'none (uses main AI model)'}`, '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)})` : ''; + // v4.6.63: say what the state MEANS. A parked agent with 0 assignments and a parked + // agent between tasks looked identical from outside (packet §6). + const agentsArr = Object.values(this._agents); + const everScheduled = agentsArr.filter(a => (a.tasksAssigned || 0) > 0).length; + const describe = (a) => { + const n = a.tasksAssigned || 0; + const since = a.createdAt ? String(a.createdAt).slice(0, 16).replace('T', ' ') : '?'; + const last = a.lastTaskAt ? String(a.lastTaskAt).slice(0, 16).replace('T', ' ') : null; + if (a.status === 'busy') { + return 'busy' + (a.currentTask ? ' — ' + String(a.currentTask).substring(0, 40) : ''); + } + if (a.status === 'failed' || a.status === 'restarting') { + return a.status + ` — ${a.failureCount || 0} failure(s), ${n} task(s) ever`; + } + if (n === 0) { + return (a.status === 'sleeping' ? 'parked' : a.status) + + ` — NEVER SCHEDULED (0 tasks since ${since})`; + } + return (a.status === 'sleeping' ? 'parked' : a.status) + + ` — waiting for work (${n} task(s), last ${last || '?'})`; + }; + agentsArr.forEach(a => { const modelInfo = a.model ? ` [model: ${a.model}]` : ''; 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}${modelInfo}${task}${backoff}`, 'info'); + this.terminal.print(` ${a.id} [${a.role}] — ${describe(a)}${modelInfo}${backoff}`, 'info'); }); + this.terminal.print(` Dispatch: ${everScheduled}/${total} agent(s) have ever been given a task`, everScheduled === 0 && total > 0 ? 'warning' : 'info'); + if (everScheduled === 0 && total > 0) { + this.terminal.print(' ⚠️ No agent has ever been scheduled — spawning is not dispatching. Use orchestrate <task> or orchestrator-spawn <role> <task>.', 'warning'); + } } listAgents() { |