From 2bc11203b73fe2eb210ce3dd643773e625609c25 Mon Sep 17 00:00:00 2001 From: Agando Date: Fri, 23 Oct 2026 18:22:30 +0200 Subject: feat(newos4): agent status says never-scheduled vs waiting; `guides` command (v4.6.63) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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". --- modules/core/core-bundle.js | 54 ++++++++++++++++++++++++++++++++++--- modules/core/module-fs.js | 16 +++++++++++ modules/core/module-loader.js | 2 +- modules/core/module-orchestrator.js | 38 +++++++++++++++++++++++--- 4 files changed, 101 insertions(+), 9 deletions(-) (limited to 'modules/core') 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 ` renders HTML, `cat ` shows markdown.', 'text-muted'); + return; + } + this.terminal.print(`📚 Guides (${docs.length}) — preview for HTML, cat 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 ', '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 or orchestrator-spawn .', '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 ` renders HTML, `cat ` shows markdown.', 'text-muted'); + return; + } + this.terminal.print(`📚 Guides (${docs.length}) — preview for HTML, cat 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 ', '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 or orchestrator-spawn .', 'warning'); + } } listAgents() { -- cgit v1.2.3