STORY: Agent identities were stored in a local JSON registry, but they should be first-class beads visible in the BeadBoard system. This consolidates agent identity to bd CLI as the source of truth. COLLABORATION: Replaced local JSON registry with bd CLI wrapper in agent-registry.ts: - All agent operations now delegate to bd CLI - Agents appear as team-visible beads with gt:agent label - Identity isolation prevents agent beads from polluting mission lists The consolidation makes agents visible to the entire team and ensures consistent identity management across all tools. DELIVERABLES: - src/lib/agent-registry.ts refactored to bd CLI wrapper - tests/lib/agent-registry-bd.test.ts for bd integration - tools/bb.ts updated for consolidated identity ops VERIFICATION: - All registry tests PASS - Agents appear on agent page but NOT in task lists - Quality gates (typecheck, lint) GREEN CLOSES: bb-1y7 EPIC: bb-u6f
128 lines
4.6 KiB
TypeScript
128 lines
4.6 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 { execSync } from 'node:child_process';
|
|
|
|
import {
|
|
extendActivityLease,
|
|
listAgents,
|
|
registerAgent,
|
|
showAgent,
|
|
setAgentState,
|
|
} from '../../src/lib/agent-registry';
|
|
|
|
async function withTempProject(run: (projectRoot: string) => Promise<void>): Promise<void> {
|
|
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'beadboard-agent-bd-test-'));
|
|
|
|
// Initialize bd rig
|
|
execSync('bd init --prefix bb --force', { cwd: tempDir, stdio: 'ignore' });
|
|
|
|
try {
|
|
await run(tempDir);
|
|
} finally {
|
|
await fs.rm(tempDir, { recursive: true, force: true });
|
|
}
|
|
}
|
|
|
|
test('BD REGISTRY: registerAgent creates a bd agent bead', async () => {
|
|
await withTempProject(async (projectRoot) => {
|
|
const result = await registerAgent(
|
|
{
|
|
name: 'test-agent',
|
|
display: 'Test Agent Display',
|
|
role: 'infra',
|
|
},
|
|
{ projectRoot }
|
|
);
|
|
|
|
assert.equal(result.ok, true, `Register failed: ${result.error?.message}`);
|
|
assert.equal(result.data?.agent_id, 'test-agent');
|
|
assert.equal(result.data?.display_name, 'Test Agent Display');
|
|
assert.equal(result.data?.role, 'infra');
|
|
assert.equal(result.data?.status, 'idle');
|
|
|
|
// Verify via direct bd call
|
|
const showRaw = execSync('bd agent show bb-test-agent --json', { cwd: projectRoot, encoding: 'utf8' });
|
|
const show = JSON.parse(showRaw);
|
|
assert.equal(show.id, 'bb-test-agent');
|
|
assert.equal(show.title, 'Agent: Test Agent Display');
|
|
});
|
|
});
|
|
|
|
test('BD REGISTRY: showAgent returns agent data', async () => {
|
|
await withTempProject(async (projectRoot) => {
|
|
await registerAgent({ name: 'show-agent', role: 'ui' }, { projectRoot });
|
|
|
|
// Note: showAgent doesn't take projectRoot currently, it uses process.cwd()
|
|
const originalCwd = process.cwd();
|
|
process.chdir(projectRoot);
|
|
try {
|
|
const result = await showAgent({ agent: 'show-agent' });
|
|
assert.equal(result.ok, true);
|
|
assert.equal(result.data?.agent_id, 'show-agent');
|
|
} finally {
|
|
process.chdir(originalCwd);
|
|
}
|
|
});
|
|
});
|
|
|
|
test('BD REGISTRY: listAgents returns all agents', async () => {
|
|
await withTempProject(async (projectRoot) => {
|
|
await registerAgent({ name: 'agent-a', role: 'ui' }, { projectRoot });
|
|
await registerAgent({ name: 'agent-b', role: 'backend' }, { projectRoot });
|
|
|
|
const originalCwd = process.cwd();
|
|
process.chdir(projectRoot);
|
|
try {
|
|
const result = await listAgents({});
|
|
assert.equal(result.ok, true);
|
|
assert.equal(result.data?.length, 2);
|
|
assert.equal(result.data?.[0].agent_id, 'agent-a');
|
|
assert.equal(result.data?.[1].agent_id, 'agent-b');
|
|
} finally {
|
|
process.chdir(originalCwd);
|
|
}
|
|
});
|
|
});
|
|
|
|
test('BD REGISTRY: extendActivityLease emits wisp and preserves issue state', async () => {
|
|
await withTempProject(async (projectRoot) => {
|
|
const agentId = 'telemetry-agent';
|
|
await registerAgent({ name: agentId, role: 'infra' }, { projectRoot });
|
|
|
|
const issuesPath = path.join(projectRoot, '.beads', 'issues.jsonl');
|
|
const beforeState = await fs.readFile(issuesPath, 'utf8');
|
|
|
|
const result = await extendActivityLease({ agent: agentId }, { projectRoot });
|
|
if (!result.ok) console.error('Extend Lease Failed:', result.error);
|
|
assert.equal(result.ok, true);
|
|
|
|
const afterState = await fs.readFile(issuesPath, 'utf8');
|
|
assert.equal(beforeState, afterState, 'Durable issues.jsonl should NOT change during lease extension');
|
|
|
|
// Verify wisp exists via direct bd list
|
|
const listRaw = execSync('bd list --wisp-type heartbeat --json', { cwd: projectRoot, encoding: 'utf8' });
|
|
const wisps = JSON.parse(listRaw);
|
|
assert.ok(wisps.length > 0, 'Heartbeat wisp should be present in the stream');
|
|
assert.ok(wisps[0].title.startsWith('pulse:'), 'Wisp title should match pulse prefix');
|
|
});
|
|
});
|
|
|
|
test('BD REGISTRY: setAgentState updates ZFC state', async () => {
|
|
await withTempProject(async (projectRoot) => {
|
|
const agentId = 'state-agent';
|
|
await registerAgent({ name: agentId, role: 'infra' }, { projectRoot });
|
|
|
|
// Transition to working
|
|
const result = await setAgentState({ agent: agentId, state: 'working' }, { projectRoot });
|
|
assert.equal(result.ok, true);
|
|
assert.equal(result.data?.status, 'working');
|
|
|
|
// Transition to stuck
|
|
const failResult = await setAgentState({ agent: agentId, state: 'stuck' }, { projectRoot });
|
|
assert.equal(failResult.ok, true);
|
|
assert.equal(failResult.data?.status, 'stuck');
|
|
});
|
|
});
|