feat(telemetry): complete bb-buff.1.3 - Backend Liveness Refactor

STORY:
The session backend needed to aggregate agent health from a live
telemetry stream rather than static bead metadata. This refactor
makes liveness signals real-time and accurate.

COLLABORATION:
We extended the ActivityEvent model with a native 'heartbeat' kind,
updated extendActivityLease() to emit through the activity bus, and
refactored getAgentLivenessMap() to prioritize heartbeat activity
history over stale bead metadata.

DELIVERABLES:
- ActivityEvent extended with 'heartbeat' kind
- extendActivityLease() emits heartbeats through activity bus
- getAgentLivenessMap() prefers telemetry over static metadata
- Registry APIs support projectRoot injection for testing
- Tests verify preference logic via TDD

VERIFICATION:
- 93/93 tests PASSING
- Heartbeat override verified in isolated temp projects

CLOSES: bb-buff.1.3
BLOCKS: bb-buff.3.2, bb-buff.3.3, bb-buff.2.1
This commit is contained in:
zenchantlive 2026-02-15 21:14:05 -08:00
parent 0016b57e37
commit 4ee550c333
36 changed files with 1380 additions and 541 deletions

View file

@ -188,6 +188,21 @@ async function resolveRegisteredAgent(agentId: string): Promise<AgentRecord | nu
return result.ok ? result.data : null;
}
async function resolveRecipients(to: string, from: string): Promise<string[]> {
if (to === 'broadcast') {
const agents = (await listAgents({})).data ?? [];
return agents.map((a) => a.agent_id).filter((id) => id !== from);
}
if (to.startsWith('role:')) {
const role = to.slice(5);
const agents = (await listAgents({ role })).data ?? [];
return agents.map((a) => a.agent_id).filter((id) => id !== from);
}
return [to];
}
export async function sendAgentMessage(
input: SendAgentMessageInput,
deps: Partial<SendAgentMessageDeps> = {},
@ -210,7 +225,9 @@ export async function sendAgentMessage(
return invalid(command, 'UNKNOWN_RECIPIENT', 'Recipient agent is required.');
}
if (to !== 'broadcast' && !(await resolveRegisteredAgent(to))) {
const isRoleOrBroadcast = to === 'broadcast' || to.startsWith('role:');
if (!isRoleOrBroadcast && !(await resolveRegisteredAgent(to))) {
return invalid(command, 'UNKNOWN_RECIPIENT', 'Recipient agent is not registered.');
}
@ -229,12 +246,17 @@ export async function sendAgentMessage(
try {
const now = deps.now ? deps.now() : new Date().toISOString();
const generateId = deps.idGenerator ?? (() => defaultMessageId(now));
const recipientIds =
to === 'broadcast'
? ((await listAgents({})).data ?? []).map((agent) => agent.agent_id).filter((agentId) => agentId !== from)
: [to];
const recipientIds = await resolveRecipients(to, from);
if (recipientIds.length === 0) {
if (to.startsWith('role:')) {
const role = to.slice(5);
const allWithRole = (await listAgents({ role })).data ?? [];
if (allWithRole.length === 0) {
return invalid(command, 'UNKNOWN_RECIPIENT', `no agents found with role '${role}'.`);
}
return invalid(command, 'UNKNOWN_RECIPIENT', 'all recipients were excluded (sender).');
}
return invalid(command, 'UNKNOWN_RECIPIENT', 'No recipients available for broadcast.');
}