diff options
| author | Agando <roduccomm@gmail.com> | 2026-10-27 15:33:33 +0100 |
|---|---|---|
| committer | Agando <roduccomm@gmail.com> | 2026-10-27 15:33:33 +0100 |
| commit | 240cbf75c7c3cbf23fc95d6b4e0bf807dcd91360 (patch) | |
| tree | 3da9a2684b80b3af99441e602b0195e39f4ab300 /modules/test/test-agent-registry.js | |
| parent | 6b4049d3ba27dd68935c0565cccfe44752175393 (diff) | |
fix(newos4): agent registration is absolute-path and honest about what it stored
Three of the four agent-cluster items, each with a test that is red on HEAD.
- MAINT-008 (module-agent-registry.js + module-orchestrator.js): registerAgent took args[0] as the
agent id and only args[1] as the capability list, while both orchestrator callers passed the
command word first — so the boot log read "Agent 'register' registered with 1 capabilities" and
four capabilities were stored as one. The command word is now stripped, args[1..] are joined and
split on commas (both "a,b" and "a b c" work), and the callers pass [id, ...caps].
- MAINT-007 (registry + aiassist): mkdir/rm used relative paths, so a registration made from /docs
created /docs/agents/<id> and an unregister from any other cwd removed nothing — the leaked
directories the report suspected. Every path in the registry (and the four /_sessions writes in
module-aiassist.js) is absolute now.
- MAINT-009 (listAgents): a directory alone counted as a registered agent, so a hand-made
/agents/foo appeared as "foo — unknown (0 capabilities)". Only a readable status.json with an id
counts now; the rest are reported as unregistered directories.
Verification: test-agent-registry.js (11 asserts). RED on HEAD: 9 of 11 fail, including the exact
`✅ Agent 'register' registered with 1 capabilities` line and `hand-made — unknown (0
capabilities)`. Suite 15/15 files green; maintain --fix "node tests 15/15 pass".
Live on v4.6.109: boot prints "✅ Agent 'orchestrator-0' registered with 4 capabilities";
`agent-list` warns "1 directory(ies) in /agents have no status.json (not registered): hand-made";
register from / with a comma list stores 3; unregister from /docs removes the directory and leaves
no /docs/agents.
MAINT-006 stays open with a narrower note: the hard stop was only reachable when every call in a
batch was blocked, so a mixed batch (one repeated + one novel call) never ended. The same ceiling
now applies on the mixed path, but that branch lives inside agenticLoop and is not covered by a unit
test — it needs a live agent run that repeats a call.
Diffstat (limited to 'modules/test/test-agent-registry.js')
| -rw-r--r-- | modules/test/test-agent-registry.js | 93 |
1 files changed, 93 insertions, 0 deletions
diff --git a/modules/test/test-agent-registry.js b/modules/test/test-agent-registry.js new file mode 100644 index 0000000..0bfc573 --- /dev/null +++ b/modules/test/test-agent-registry.js @@ -0,0 +1,93 @@ +// modules/test/test-agent-registry.js — run: node +// MAINT-007/008/009: registration must not depend on the cwd, must keep every capability it is +// given, and a bare directory in /agents must not count as a registered agent. +const fsMod = require('fs'), path = require('path'); +const ROOT = path.join(__dirname, '..'); +const store = {}; +global.localStorage = { getItem: k => (k in store ? store[k] : null), setItem: (k, v) => { store[k] = String(v); }, removeItem: k => { delete store[k]; } }; +global.indexedDB = { open() { throw new Error('no idb'); }, deleteDatabase() { throw new Error('no idb'); } }; +global.window = global; + +const load = (rel, cls) => new Function(fsMod.readFileSync(path.join(ROOT, rel), 'utf8') + '\nreturn ' + cls + ';')(); +const VFS = load('core/virtual-fs.js', 'VirtualFileSystem'); +const Registry = load('core/module-agent-registry.js', 'AgentRegistryModule'); + +let fails = 0, passes = 0; +function ok(name, cond, detail) { + if (cond) { passes++; return; } + fails++; + console.log('FAIL ' + name + (detail ? ' — ' + detail : '')); +} + +function mk() { + const out = []; + const vfs = new VFS(); + vfs.saveToStorage = () => true; + const reg = Object.create(Registry.prototype); + reg.terminal = { print: (m, t) => out.push({ text: String(m), type: t || '' }), log: () => {}, fs: vfs }; + reg._contracts = {}; + reg._a2aMessages = []; + reg._a2a = { post: () => 'msg-1', unregister: () => {} }; + // the real A2A helpers touch VFS paths; keep them inert for this test + reg.a2aRegister = () => {}; + reg.a2aUnregister = () => {}; + return { reg, vfs, out }; +} +const text = out => out.map(o => o.text).join(' | '); + +// A. the command word is not the agent id, and every capability is kept +{ + const { reg, out } = mk(); + reg.registerAgent(['register', 'orchestrator-0', 'delegate', 'spawn', 'monitor', 'purge']); + const s = JSON.parse(reg.terminal.fs.readFile('/agents/orchestrator-0/status.json') || 'null'); + ok('A1 the agent is named orchestrator-0', !!s && s.id === 'orchestrator-0', text(out)); + ok('A2 all four capabilities are stored', !!s && s.capabilities.length === 4, s ? JSON.stringify(s.capabilities) : 'no status'); + ok('A3 no agent literally named "register"', !reg.terminal.fs.readDir('/agents/register')); + ok('A4 the boot message reports 4, not 1', /registered with 4 capabilities/.test(text(out)), text(out)); +} + +// B. comma-separated capabilities still work +{ + const { reg } = mk(); + reg.registerAgent(['worker', 'read_file,write_file,list_files']); + const s = JSON.parse(reg.terminal.fs.readFile('/agents/worker/status.json')); + ok('B1 comma list parsed', s.capabilities.length === 3 && s.capabilities[1] === 'write_file', JSON.stringify(s.capabilities)); +} + +// C. registering from a subdirectory still writes /agents, never <cwd>/agents +{ + const { reg, vfs } = mk(); + vfs.mkdirRecursive('/docs'); + vfs.currentPath = ['docs']; + reg.registerAgent(['nested', 'a,b']); + ok('C1 the agent dir is at /agents/nested', !!vfs.readDir('/agents/nested'), JSON.stringify(vfs.readDir('/') || [])); + ok('C2 nothing was created under /docs', !vfs.readDir('/docs/agents')); +} + +// D. unregister removes the directory even when the cwd is elsewhere +{ + const { reg, vfs } = mk(); + reg.registerAgent(['temp-agent', 'a']); + vfs.currentPath = ['docs']; + reg.unregisterAgent('temp-agent'); + ok('D1 the directory is gone after unregister', !vfs.readDir('/agents/temp-agent')); +} + +// E. a bare directory is not a registered agent +{ + const { reg, vfs, out } = mk(); + reg.registerAgent(['real-agent', 'a,b']); + vfs.mkdirRecursive('/agents/hand-made'); + reg.listAgents(); + const t = text(out); + ok('E1 the registered agent is listed', /real-agent/.test(t), t); + ok('E2 the bare directory is not listed as an agent', !/hand-made — /.test(t), t); + ok('E3 it is reported as unregistered instead', /no status\.json/.test(t) && /hand-made/.test(t), t); +} + +if (fails === 0) { + console.log('test-agent-registry: ' + passes + ' passed, 0 failed'); +} else { + console.log('test-agent-registry: ' + passes + ' passed, ' + fails + ' FAILED'); + process.exit(1); +} |