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

@ -29,7 +29,7 @@ async function fetchIssues(projectRoot: string): Promise<BeadIssue[]> {
export function useBeadsSubscription(
initialIssues: BeadIssue[],
projectRoot: string,
options: { onUpdate?: () => void } = {}
options: { onUpdate?: (kind: 'issues' | 'telemetry' | 'activity') => void } = {}
): UseBeadsSubscriptionResult {
const [issues, setIssues] = useState<BeadIssue[]>(initialIssues);
const refreshInFlightRef = useRef(false);
@ -54,7 +54,7 @@ export function useBeadsSubscription(
try {
const reconciled = await fetchIssues(projectRoot);
setIssues(reconciled);
onUpdate?.();
onUpdate?.('issues');
} catch (error) {
if (!options.silent) {
console.error('[BeadsSubscription] Refresh failed:', error);
@ -77,18 +77,36 @@ export function useBeadsSubscription(
};
const onIssues = (event: MessageEvent) => {
console.log('🚨 SSE RECEIVED:', event.data);
onUpdate?.();
console.log('🚨 SSE ISSUES RECEIVED:', event.data);
onUpdate?.('issues');
void refresh({ silent: true });
};
const onTelemetry = (event: MessageEvent) => {
console.log('📡 SSE TELEMETRY RECEIVED (Silent):', event.data);
// We don't trigger a full refresh or parent update for heartbeats
// This prevents the page from flickering/clearing state while typing.
onUpdate?.('telemetry');
};
const onActivity = (event: MessageEvent) => {
console.log('📝 SSE ACTIVITY RECEIVED:', event.data);
onUpdate?.('activity');
};
source.addEventListener('issues', onIssues as EventListener);
source.addEventListener('telemetry', onTelemetry as EventListener);
source.addEventListener('activity', onActivity as EventListener);
return () => {
console.log('[SSE] Closing connection');
source.removeEventListener('issues', onIssues as EventListener);
source.removeEventListener('telemetry', onTelemetry as EventListener);
source.removeEventListener('activity', onActivity as EventListener);
source.close();
};
// onUpdate is intentionally excluded from deps to avoid re-subscribing on parent re-renders
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [projectRoot, refresh]);
return { issues, refresh, updateLocal };