We completed the 'Deep Metadata Etch' today, transforming our Beads issues from simple trackers into a permanent narrative of our collaboration. Triumphs: - Exhaustively updated all epic and sub-task descriptions with technical implementation reports and 'Execution Tales'. - Finalized the 'bb' agent CLI skill (bb.ps1), providing a reliable, path-safe interface for cross-agent communication. - Published ADR-001 and RFC-001 to document our coordination protocols. - Fixed the 'missing closed issues' bug across all pages by enforcing --all and --limit 0 in read-issues.ts. Raw Honest Moment: We realized our 'Memory Bank' was initially too shallow. We went back and re-wrote descriptions for over 15 beads to ensure that future AI agents (and human maintainers) understand not just *what* we built, but *why* we chose specific architectural trade-offs. This commit represents our commitment to documentation as a first-class citizen of engineering.
139 lines
4.5 KiB
TypeScript
139 lines
4.5 KiB
TypeScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs/promises';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
|
|
import {
|
|
agentFilePath,
|
|
listAgents,
|
|
registerAgent,
|
|
showAgent,
|
|
type AgentRecord,
|
|
} from '../../src/lib/agent-registry';
|
|
|
|
async function withTempUserProfile(run: () => Promise<void>): Promise<void> {
|
|
const previous = process.env.USERPROFILE;
|
|
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'beadboard-agent-reg-'));
|
|
process.env.USERPROFILE = tempDir;
|
|
|
|
try {
|
|
await run();
|
|
} finally {
|
|
if (previous === undefined) {
|
|
delete process.env.USERPROFILE;
|
|
} else {
|
|
process.env.USERPROFILE = previous;
|
|
}
|
|
|
|
await fs.rm(tempDir, { recursive: true, force: true });
|
|
}
|
|
}
|
|
|
|
test('registerAgent creates stable metadata file with idle status', async () => {
|
|
await withTempUserProfile(async () => {
|
|
const now = '2026-02-13T23:55:00.000Z';
|
|
const result = await registerAgent(
|
|
{
|
|
name: 'agent-ui-1',
|
|
display: 'UI Agent 1',
|
|
role: 'ui',
|
|
},
|
|
{ now: () => now },
|
|
);
|
|
|
|
assert.equal(result.ok, true);
|
|
assert.equal(result.command, 'agent register');
|
|
assert.equal(result.data?.agent_id, 'agent-ui-1');
|
|
assert.equal(result.data?.status, 'idle');
|
|
assert.equal(result.data?.created_at, now);
|
|
assert.equal(result.data?.last_seen_at, now);
|
|
assert.equal(result.data?.version, 1);
|
|
|
|
const file = await fs.readFile(agentFilePath('agent-ui-1'), 'utf8');
|
|
const parsed = JSON.parse(file) as AgentRecord;
|
|
assert.equal(parsed.agent_id, 'agent-ui-1');
|
|
assert.equal(parsed.display_name, 'UI Agent 1');
|
|
});
|
|
});
|
|
|
|
test('registerAgent rejects duplicate id without --force-update', async () => {
|
|
await withTempUserProfile(async () => {
|
|
await registerAgent({ name: 'agent-ui-1', role: 'ui' }, { now: () => '2026-02-13T23:55:00.000Z' });
|
|
|
|
const duplicate = await registerAgent({ name: 'agent-ui-1', role: 'ui' }, { now: () => '2026-02-13T23:56:00.000Z' });
|
|
|
|
assert.equal(duplicate.ok, false);
|
|
assert.equal(duplicate.error?.code, 'DUPLICATE_AGENT_ID');
|
|
});
|
|
});
|
|
|
|
test('registerAgent force update mutates display/role but keeps created_at', async () => {
|
|
await withTempUserProfile(async () => {
|
|
const first = await registerAgent(
|
|
{ name: 'agent-ui-1', display: 'UI Agent', role: 'ui' },
|
|
{ now: () => '2026-02-13T23:55:00.000Z' },
|
|
);
|
|
assert.equal(first.ok, true);
|
|
|
|
const updated = await registerAgent(
|
|
{ name: 'agent-ui-1', display: 'Frontend Agent', role: 'frontend', forceUpdate: true },
|
|
{ now: () => '2026-02-13T23:56:00.000Z' },
|
|
);
|
|
|
|
assert.equal(updated.ok, true);
|
|
assert.equal(updated.data?.display_name, 'Frontend Agent');
|
|
assert.equal(updated.data?.role, 'frontend');
|
|
assert.equal(updated.data?.created_at, '2026-02-13T23:55:00.000Z');
|
|
assert.equal(updated.data?.last_seen_at, '2026-02-13T23:56:00.000Z');
|
|
});
|
|
});
|
|
|
|
test('listAgents sorts and filters by role/status', async () => {
|
|
await withTempUserProfile(async () => {
|
|
await registerAgent({ name: 'agent-b', role: 'backend' }, { now: () => '2026-02-13T23:55:00.000Z' });
|
|
await registerAgent({ name: 'agent-a', role: 'ui' }, { now: () => '2026-02-13T23:55:00.000Z' });
|
|
|
|
await registerAgent(
|
|
{ name: 'agent-b', role: 'backend', forceUpdate: true },
|
|
{ now: () => '2026-02-13T23:56:00.000Z' },
|
|
);
|
|
|
|
const all = await listAgents({});
|
|
assert.equal(all.ok, true);
|
|
assert.deepEqual(
|
|
all.data?.map((agent) => agent.agent_id),
|
|
['agent-a', 'agent-b'],
|
|
);
|
|
|
|
const byRole = await listAgents({ role: 'ui' });
|
|
assert.deepEqual(
|
|
byRole.data?.map((agent) => agent.agent_id),
|
|
['agent-a'],
|
|
);
|
|
|
|
const byStatus = await listAgents({ status: 'idle' });
|
|
assert.equal(byStatus.ok, true);
|
|
assert.equal(byStatus.data?.length, 2);
|
|
});
|
|
});
|
|
|
|
test('showAgent returns AGENT_NOT_FOUND for unknown id', async () => {
|
|
await withTempUserProfile(async () => {
|
|
const result = await showAgent({ agent: 'agent-missing' });
|
|
assert.equal(result.ok, false);
|
|
assert.equal(result.error?.code, 'AGENT_NOT_FOUND');
|
|
});
|
|
});
|
|
|
|
test('registerAgent validates id pattern and role', async () => {
|
|
await withTempUserProfile(async () => {
|
|
const badName = await registerAgent({ name: 'Agent_Upper', role: 'ui' });
|
|
assert.equal(badName.ok, false);
|
|
assert.equal(badName.error?.code, 'INVALID_AGENT_ID');
|
|
|
|
const badRole = await registerAgent({ name: 'agent-ok-1', role: ' ' });
|
|
assert.equal(badRole.ok, false);
|
|
assert.equal(badRole.error?.code, 'INVALID_ROLE');
|
|
});
|
|
});
|