aboutsummaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-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
4 files changed, 101 insertions, 9 deletions
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() {