aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAgando <roduccomm@gmail.com>2026-10-23 18:22:30 +0200
committerAgando <roduccomm@gmail.com>2026-10-23 18:22:30 +0200
commit2bc11203b73fe2eb210ce3dd643773e625609c25 (patch)
tree60222e83add1a2fd05fa705527ff7dc4ce1efea4
parentf3bbb69b25d1b1619f7bd9d5861480af7e9e834f (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.html21
-rw-r--r--main.html6
-rw-r--r--modules/core/core-bundle.js54
-rw-r--r--modules/core/module-fs.js16
-rw-r--r--modules/core/module-loader.js2
-rw-r--r--modules/core/module-orchestrator.js38
6 files changed, 124 insertions, 13 deletions
diff --git a/help.html b/help.html
index 815727b..0937251 100644
--- a/help.html
+++ b/help.html
@@ -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 &lt;name&gt;</code> — render an HTML guide in the preview pane</li>
+ <li><code>cat &lt;name&gt;</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>&amp;&amp;</code> and <code>||</code> are refused with an explicit message instead of being folded into the command's argv (where <code>wc -l f &amp;&amp; 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/&lt;Class&gt;.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 &gt; description/tags &gt; 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>
diff --git a/main.html b/main.html
index 21f6eaa..a76b3d1 100644
--- a/main.html
+++ b/main.html
@@ -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() {