diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index e96367f..54ca5e2 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -129,7 +129,7 @@ {"id":"bb-u6f.5","title":"Legacy Track: Session Metrics Overlays","description":"Add completion rate, throughput, and active span metrics to the Session UI. Implementation of overlays or dashboard widgets as per original bb-u6f.3 goal.","notes":"Naming normalization: marked as Legacy Track to distinguish from Protocol Track 6 branch beads.","status":"open","priority":2,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-13T21:50:36.5056349-08:00","created_by":"zenchantlive","updated_at":"2026-02-14T10:07:39.440563-08:00","dependencies":[{"issue_id":"bb-u6f.5","depends_on_id":"bb-u6f","type":"parent-child","created_at":"2026-02-13T21:50:36.5078348-08:00","created_by":"zenchantlive"},{"issue_id":"bb-u6f.5","depends_on_id":"bb-u6f.3","type":"blocks","created_at":"2026-02-13T21:51:40.941554-08:00","created_by":"zenchantlive"}]} {"id":"bb-u6f.6","title":"Protocol Track 6: Operative Protocol End-to-End Implementation","description":"Implement the Operative Protocol roadmap for multi-agent coordination in Sessions, with spec-first execution and stable contracts before UI behavior changes.\n\nScope\n- Build protocol as a layered delivery sequence: specification -\u003e backend semantics -\u003e CLI surface -\u003e Sessions UI rendering -\u003e skill/docs closeout -\u003e final acceptance sweep.\n- Keep existing bd source-of-truth constraints and avoid direct writes to .beads/issues.jsonl.\n- Keep language plain for user-facing labels (Passed to, Needs input, Seen, Accepted).\n\nPrimary existing code touchpoints\n- src/lib/agent-registry.ts\n- src/lib/agent-reservations.ts\n- src/lib/agent-mail.ts\n- src/lib/agent-sessions.ts\n- src/lib/realtime.ts\n- src/app/api/sessions/route.ts\n- src/app/api/sessions/[beadId]/conversation/route.ts\n- src/components/sessions/sessions-page.tsx\n- src/components/sessions/session-feed-card.tsx\n- src/components/sessions/session-task-feed.tsx\n- src/components/sessions/conversation-drawer.tsx\n- src/hooks/use-beads-subscription.ts\n- src/hooks/use-session-feed.ts\n- tools/bb.ts\n- scripts/bb-init.mjs (new)\n- skills/beadboard-driver/SKILL.md\n\nExecution constraints\n- Protocol contracts must land before behavior work.\n- No architecture pivots after protocol spec bead is approved.\n- Every implementation bead includes tests tied to touched behavior.\r\n","acceptance_criteria":"Protocol umbrella is decomposed into ordered child beads; each child bead has concrete file-level scope and acceptance; dependency graph enforces spec-first and docs-last execution.","notes":"Naming normalization: added 'Protocol Track 6' prefix to reduce feed ambiguity with bb-u6f.5 and descendant leaf collisions.","status":"open","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-14T09:53:06.2666654-08:00","created_by":"zenchantlive","updated_at":"2026-02-14T10:07:39.0190306-08:00","labels":["agents","protocol","sessions"],"dependencies":[{"issue_id":"bb-u6f.6","depends_on_id":"bb-u6f","type":"parent-child","created_at":"2026-02-14T09:53:06.2682608-08:00","created_by":"zenchantlive"}]} {"id":"bb-u6f.6.1","title":"Protocol Spec Gate: identity, heartbeat, overlap, event schema","description":"Define the Session Constitution that all later implementation beads must follow.\n\nRequired deliverable\n- Create protocol spec doc at docs/protocols/operative-protocol-v1.md (or docs/adr equivalent with explicit protocol contract sections).\n\nSpec sections (must be explicit and testable)\n1) Identity Trust Model\n- Define adoption policy for prior identity, with strict conditions:\n - allowed when uncommitted changes exist in claimed scope, OR\n - allowed when target identity owns an in_progress bead.\n- Define mandatory audit event for adoption/session resume (SESSION_RESUME).\n- Define failure responses and non-interactive defaults.\n\n2) Heartbeat Contract\n- Define heartbeat cadence, stale threshold, and eviction threshold.\n- Default stale threshold uses BB_AGENT_STALE_MINUTES with default 15.\n- Eviction transition at T+30m (stale grace window documented).\n- Define how registry status and reservation takeover consume these states.\n\n3) Path Overlap Canonicalization\n- Define normalization rules used for overlap detection:\n - absolute resolution,\n - lowercase comparison on Windows,\n - normalized slash separators.\n- Define overlap classes:\n - exact,\n - parent-child (partial overlap),\n - disjoint.\n- Include examples for src/* and src/lib/parser.ts patterns.\n\n4) Protocol Event Schema (stable JSON contract)\n- Finalize payload schemas for HANDOFF, BLOCKED, INCURSION, RESUME.\n- Include required fields (event id, bead id, from/to agent, scope, timestamp, version).\n- Document rendering intent in Sessions UI and SSE transport mapping.\n\nFiles likely touched\n- docs/protocols/operative-protocol-v1.md (new) OR docs/adr/*.md\n- Optional schema types if extracted: src/lib/agent-protocol.ts (new)\n\nOut of scope\n- No UI behavior change in this bead.\n- No command behavior change in this bead.\r\n","acceptance_criteria":"Protocol doc exists with identity, heartbeat, path overlap, and event schema sections; constants/defaults are unambiguous; downstream beads can implement without reinterpreting semantics.","notes":"Delivered protocol spec doc: docs/protocols/operative-protocol-v1.md. Includes normative contracts for identity adoption, heartbeat stale/evicted thresholds, path overlap normalization/classification, stable protocol event schema (HANDOFF/BLOCKED/INCURSION/RESUME), UI label mapping, CLI non-interactive requirements, and explicit file/module mapping for downstream beads.","status":"closed","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-14T09:53:06.7850841-08:00","created_by":"zenchantlive","updated_at":"2026-02-14T09:59:48.7642462-08:00","closed_at":"2026-02-14T09:59:48.7642462-08:00","close_reason":"Spec gate completed. Session Constitution v1 published for downstream implementation without semantic ambiguity.","labels":["agents","protocol","sessions","spec"],"dependencies":[{"issue_id":"bb-u6f.6.1","depends_on_id":"bb-u6f.6","type":"parent-child","created_at":"2026-02-14T09:53:06.8110216-08:00","created_by":"zenchantlive"}]} -{"id":"bb-u6f.6.2","title":"Backend Engine: heartbeat, stale takeover, overlap, protocol events","description":"Implement protocol semantics in backend libraries and API composition paths.\n\nImplementation targets\n1) Agent liveness registry\n- Add heartbeat update function in src/lib/agent-registry.ts (e.g., heartbeatAgent(agentId)).\n- Ensure last_seen_at updates use UTC ISO timestamps.\n- Add utility to derive liveness state from last_seen_at using protocol thresholds.\n\n2) Reservation stale takeover + overlap logic\n- Update src/lib/agent-reservations.ts:\n - enforce stale/eviction behavior from spec,\n - gate takeover behavior on stale owner when takeover flag provided,\n - normalize scopes before conflict checks,\n - classify overlap (exact/partial/disjoint) for incursion detection.\n- Keep current conflict behavior deterministic and backwards-compatible where possible.\n\n3) Protocol event dispatch surfaces\n- Add/extend typed protocol event emission in src/lib/realtime.ts (or dedicated protocol event module) so UI/SSE can consume stable contract events.\n- Ensure API feed builders can read protocol events without duplicating parsing logic.\n\n4) Session aggregation integration\n- Update src/lib/agent-sessions.ts and src/app/api/sessions/route.ts so session-state derivation can consume liveness/overlap/pending-ack semantics consistently.\n\nTesting requirements\n- Extend/add tests:\n - tests/lib/agent-registry.test.ts (heartbeat and liveness transitions)\n - tests/lib/agent-reservations.test.ts (stale takeover allowed/blocked + overlap classification)\n - tests/lib/agent-sessions.test.ts (session state reflects protocol semantics)\n - add tests/lib/agent-heartbeat.test.ts if separation improves clarity.\n\nNon-goals\n- No CLI UX additions in this bead.\n- No major visual changes in this bead.\r\n","acceptance_criteria":"Heartbeat updates and liveness state are implemented; stale takeover and overlap detection work per spec; protocol events are emitted in a stable format; backend/unit tests cover active vs stale owner behavior and overlap edge cases.","notes":"Implementing protocol backend contracts from docs/protocols/operative-protocol-v1.md. Starting with Liveness Registry and Heartbeat logic.","status":"in_progress","priority":1,"issue_type":"task","assignee":"zenchantlive","owner":"jordanlive121@gmail.com","created_at":"2026-02-14T09:53:07.4658312-08:00","created_by":"zenchantlive","updated_at":"2026-02-14T10:33:39.151914-08:00","labels":["agents","backend","protocol","sessions"],"dependencies":[{"issue_id":"bb-u6f.6.2","depends_on_id":"bb-u6f.6","type":"parent-child","created_at":"2026-02-14T09:53:07.4696351-08:00","created_by":"zenchantlive"},{"issue_id":"bb-u6f.6.2","depends_on_id":"bb-u6f.6.1","type":"blocks","created_at":"2026-02-14T09:53:07.477934-08:00","created_by":"zenchantlive"}]} +{"id":"bb-u6f.6.2","title":"Backend Engine: heartbeat, stale takeover, overlap, protocol events","description":"Implement protocol semantics in backend libraries and API composition paths.\n\nImplementation targets\n1) Agent liveness registry\n- Add heartbeat update function in src/lib/agent-registry.ts (e.g., heartbeatAgent(agentId)).\n- Ensure last_seen_at updates use UTC ISO timestamps.\n- Add utility to derive liveness state from last_seen_at using protocol thresholds.\n\n2) Reservation stale takeover + overlap logic\n- Update src/lib/agent-reservations.ts:\n - enforce stale/eviction behavior from spec,\n - gate takeover behavior on stale owner when takeover flag provided,\n - normalize scopes before conflict checks,\n - classify overlap (exact/partial/disjoint) for incursion detection.\n- Keep current conflict behavior deterministic and backwards-compatible where possible.\n\n3) Protocol event dispatch surfaces\n- Add/extend typed protocol event emission in src/lib/realtime.ts (or dedicated protocol event module) so UI/SSE can consume stable contract events.\n- Ensure API feed builders can read protocol events without duplicating parsing logic.\n\n4) Session aggregation integration\n- Update src/lib/agent-sessions.ts and src/app/api/sessions/route.ts so session-state derivation can consume liveness/overlap/pending-ack semantics consistently.\n\nTesting requirements\n- Extend/add tests:\n - tests/lib/agent-registry.test.ts (heartbeat and liveness transitions)\n - tests/lib/agent-reservations.test.ts (stale takeover allowed/blocked + overlap classification)\n - tests/lib/agent-sessions.test.ts (session state reflects protocol semantics)\n - add tests/lib/agent-heartbeat.test.ts if separation improves clarity.\n\nNon-goals\n- No CLI UX additions in this bead.\n- No major visual changes in this bead.\r\n","acceptance_criteria":"Heartbeat updates and liveness state are implemented; stale takeover and overlap detection work per spec; protocol events are emitted in a stable format; backend/unit tests cover active vs stale owner behavior and overlap edge cases.","notes":"OPERATIVE REPORT: silver-castle (backend) reporting. \nI have completed the core backend logic for the Operative Protocol v1. \n\nKEY ACHIEVEMENTS:\n1. LIVENESS: Implemented heartbeatAgent and deriveLiveness in agent-registry.ts. Established the 15m/30m active/stale/evicted contract.\n2. OVERLAP: Implemented normalizePath (Windows-aware canonicalization) and classifyOverlap (exact/partial/disjoint) in agent-reservations.ts.\n3. TAKEOVER: Refactored reserveAgentScope to respect owner liveness. Active agents are protected; stale/evicted ones can be overtaken via --takeover-stale.\n4. PROTOCOL: Defined the v1 event envelope in agent-protocol.ts.\n5. SESSIONS: Integrated liveness maps into the session aggregation logic (agent-sessions.ts) and the GET /api/sessions endpoint.\n\nCHALLENGES:\n- Encountered a syntax error (duplicate return) during the agent-sessions.ts refactor; identified via TDD and fixed immediately.\n- Normalized path logic required careful replacement of backslashes for cross-platform (Windows) consistency.\n\nVERIFICATION:\n- tests/lib/agent-liveness.test.ts: 100% PASS\n- tests/lib/path-overlap.test.ts: 100% PASS\n- tests/lib/agent-takeover.test.ts: 100% PASS\n- tests/lib/agent-protocol.test.ts: 100% PASS\n- tests/lib/agent-sessions.test.ts: 100% PASS (including new liveness cases)\n\nNext steps: Final sweep of typecheck/lint before closing this bead.","status":"in_progress","priority":1,"issue_type":"task","assignee":"zenchantlive","owner":"jordanlive121@gmail.com","created_at":"2026-02-14T09:53:07.4658312-08:00","created_by":"zenchantlive","updated_at":"2026-02-14T10:42:15.3132516-08:00","labels":["agents","backend","protocol","sessions"],"dependencies":[{"issue_id":"bb-u6f.6.2","depends_on_id":"bb-u6f.6","type":"parent-child","created_at":"2026-02-14T09:53:07.4696351-08:00","created_by":"zenchantlive"},{"issue_id":"bb-u6f.6.2","depends_on_id":"bb-u6f.6.1","type":"blocks","created_at":"2026-02-14T09:53:07.477934-08:00","created_by":"zenchantlive"}]} {"id":"bb-u6f.6.3","title":"CLI Surface: bb-init and agent heartbeat command","description":"Expose non-interactive protocol operations for agent runtimes, with zero manual bb commands required from end users.\n\nImplementation targets\n1) bb agent heartbeat\n- Extend tools/bb.ts command surface to support `bb agent heartbeat --agent \u003cid\u003e`.\n- Command must call backend heartbeat mutation and support --json output contract.\n- Keep help text clear about per-session unique naming policy.\n\n2) bb-init bootstrap tool (runtime-owned orchestration)\n- Create scripts/bb-init.mjs.\n- Required flags:\n - --non-interactive\n - --adopt \u003cagentId\u003e\n - --register \u003cname\u003e\n - --json\n - --start-heartbeat\n - --stop-heartbeat\n- Runtime responsibilities:\n - resolve bb.ps1 path robustly,\n - inspect git working tree for uncommitted changes,\n - query reservation/status context and recommend/execute adopt vs register,\n - automatically start heartbeat worker in non-interactive runtime flow,\n - provide explicit stop path for cleanup on session end.\n\n3) Background heartbeat worker contract\n- Heartbeat loop cadence default: 60s.\n- Worker ownership tracked via runtime pid/lock file under `.beadboard/agent/runtime/`.\n- One active heartbeat worker per session identity (duplicate-start protection).\n- Worker emits structured errors and exits non-zero when heartbeat fails repeatedly.\n\n4) Zero-manual-user-command principle\n- End users should not need to run `bb` directly for normal operation.\n- Agent runtime/wrapper must invoke bb-init + heartbeat management automatically.\n\n5) Robust non-interactive behavior\n- Non-interactive mode must never block on prompt.\n- All failures return structured machine-readable payloads.\n\nTesting requirements\n- Add tests/scripts/bb-init.test.ts for:\n - non-interactive adopt/register resolution,\n - start/stop heartbeat lifecycle,\n - duplicate heartbeat protection,\n - pid/lock cleanup behavior.\n- Extend CLI tests for `bb agent heartbeat` behavior, json envelope, and help output.\n\nFiles\n- tools/bb.ts\n- scripts/bb-init.mjs (new)\n- tests/scripts/bb-init.test.ts (new)\n- optional runtime helper module if needed (e.g., scripts/lib/heartbeat-runtime.mjs)\r\n","acceptance_criteria":"CLI/runtime layer provides fully automatic session bootstrap and heartbeat lifecycle (start/stop) with no manual user bb commands; heartbeat cadence/locking/cleanup semantics are implemented and tested; non-interactive flows are deterministic and machine-readable.","status":"open","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-14T09:53:08.0983784-08:00","created_by":"zenchantlive","updated_at":"2026-02-14T10:33:35.5162347-08:00","labels":["agents","cli","protocol","sessions"],"dependencies":[{"issue_id":"bb-u6f.6.3","depends_on_id":"bb-u6f.6","type":"parent-child","created_at":"2026-02-14T09:53:08.100007-08:00","created_by":"zenchantlive"},{"issue_id":"bb-u6f.6.3","depends_on_id":"bb-u6f.6.2","type":"blocks","created_at":"2026-02-14T09:53:08.1088582-08:00","created_by":"zenchantlive"}]} {"id":"bb-u6f.6.4","title":"Sessions Hub UI: protocol event and incursion visibility","description":"Integrate protocol events and liveness/incursion semantics into Sessions Hub UI without reintroducing page duplication.\n\nImplementation targets\n1) Session feed visual states\n- Update src/components/sessions/session-feed-card.tsx and related status utils:\n - surface stale/incursion/conflict signals from protocol-derived state,\n - keep plain-language user labels and clear status hierarchy.\n\n2) Task feed + context drawer protocol rendering\n- Update src/components/sessions/session-task-feed.tsx and src/components/sessions/conversation-drawer.tsx:\n - render protocol events (HANDOFF/BLOCKED/INCURSION/RESUME) as first-class rows,\n - keep actions intuitive (Seen/Accepted),\n - preserve current mobile/desktop behavior.\n\n3) Data subscription and freshness flow\n- Update src/components/sessions/sessions-page.tsx, src/hooks/use-session-feed.ts, src/hooks/use-beads-subscription.ts as needed:\n - consume new protocol event payloads,\n - avoid stale UI state requiring manual refresh,\n - keep SSE-based refresh behavior aligned with existing working Kanban patterns.\n\n4) API shaping\n- Update src/app/api/sessions/route.ts and src/app/api/sessions/[beadId]/conversation/route.ts to expose protocol metadata required by UI.\n\nTesting requirements\n- Extend/add component/store tests under tests/components/sessions/* and API tests under tests/api/* for protocol event rendering and state mapping.\n- If visual behavior changes, capture updated artifacts for desktop/mobile sessions views.\n\nFiles\n- src/components/sessions/sessions-page.tsx\n- src/components/sessions/session-feed-card.tsx\n- src/components/sessions/session-task-feed.tsx\n- src/components/sessions/conversation-drawer.tsx\n- src/hooks/use-session-feed.ts\n- src/hooks/use-beads-subscription.ts\n- src/app/api/sessions/route.ts\n- src/app/api/sessions/[beadId]/conversation/route.ts\r\n","acceptance_criteria":"Sessions UI displays protocol states/events clearly (including stale/incursion); feed and drawer update via SSE without manual refresh; component/API tests assert mapping and rendering of protocol messages and actions.","status":"open","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-14T09:53:08.8513396-08:00","created_by":"zenchantlive","updated_at":"2026-02-14T09:53:08.8513396-08:00","labels":["agents","protocol","sessions","ui"],"dependencies":[{"issue_id":"bb-u6f.6.4","depends_on_id":"bb-u6f.6","type":"parent-child","created_at":"2026-02-14T09:53:08.8534775-08:00","created_by":"zenchantlive"},{"issue_id":"bb-u6f.6.4","depends_on_id":"bb-u6f.6.1","type":"blocks","created_at":"2026-02-14T09:53:08.8599565-08:00","created_by":"zenchantlive"},{"issue_id":"bb-u6f.6.4","depends_on_id":"bb-u6f.6.2","type":"blocks","created_at":"2026-02-14T09:53:08.8647791-08:00","created_by":"zenchantlive"}]} {"id":"bb-u6f.6.5","title":"Protocol Track 6: Skill Closeout (beadboard-driver v2 runbook)","description":"Update operator guidance so all agents follow the finalized protocol and command surface.\n\nImplementation targets\n1) Rewrite beadboard-driver skill workflow\n- Update skills/beadboard-driver/SKILL.md to codify loop:\n - bb-init (boot/adopt)\n - reserve scope\n - perform work\n - send protocol messages (INFO/HANDOFF/BLOCKED)\n - heartbeat cadence\n - release scope\n- Include non-interactive examples for automation contexts.\n\n2) Operational guardrails\n- Add explicit anti-pattern section for silent incursions, skipped acknowledgements, and identity reuse.\n- Add troubleshooting section for missing BB_REPO, bb.ps1 path resolution, and stale heartbeat.\n\n3) Command reference sync\n- Ensure docs reflect actual commands and flags implemented in Phase CLI bead.\n- Include expected JSON response snippets for machine usage.\n\nFiles\n- skills/beadboard-driver/SKILL.md\n- Optional supplemental docs in skills/beadboard-driver/references/* if needed.\r\n","acceptance_criteria":"Skill documentation matches implemented CLI/protocol behavior exactly, includes non-interactive examples, and documents red-flag anti-patterns with corrective actions.","status":"open","priority":2,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-14T09:53:09.4862557-08:00","created_by":"zenchantlive","updated_at":"2026-02-14T10:07:38.1681695-08:00","labels":["agents","docs","protocol","sessions","skills"],"dependencies":[{"issue_id":"bb-u6f.6.5","depends_on_id":"bb-u6f.6.3","type":"blocks","created_at":"2026-02-14T09:57:08.0044861-08:00","created_by":"zenchantlive"},{"issue_id":"bb-u6f.6.5","depends_on_id":"bb-u6f.6.4","type":"blocks","created_at":"2026-02-14T09:57:08.4186193-08:00","created_by":"zenchantlive"}]} diff --git a/src/app/api/sessions/route.ts b/src/app/api/sessions/route.ts index 0478b46..a237fc9 100644 --- a/src/app/api/sessions/route.ts +++ b/src/app/api/sessions/route.ts @@ -1,7 +1,7 @@ import { NextResponse } from 'next/server'; import { readIssuesFromDisk } from '../../../lib/read-issues'; import { activityEventBus } from '../../../lib/realtime'; -import { buildSessionTaskFeed, getCommunicationSummary } from '../../../lib/agent-sessions'; +import { buildSessionTaskFeed, getCommunicationSummary, getAgentLivenessMap } from '../../../lib/agent-sessions'; export const dynamic = 'force-dynamic'; @@ -13,8 +13,9 @@ export async function GET(request: Request): Promise { const issues = await readIssuesFromDisk({ projectRoot, preferBd: true }); const activity = activityEventBus.getHistory(projectRoot); const communication = await getCommunicationSummary(); + const livenessMap = await getAgentLivenessMap(); - const feed = buildSessionTaskFeed(issues, activity, communication); + const feed = buildSessionTaskFeed(issues, activity, communication, livenessMap); return NextResponse.json({ ok: true, feed }); } catch (error) { diff --git a/src/lib/agent-reservations.ts b/src/lib/agent-reservations.ts index e338dbe..5244feb 100644 --- a/src/lib/agent-reservations.ts +++ b/src/lib/agent-reservations.ts @@ -3,7 +3,6 @@ import os from 'node:os'; import path from 'node:path'; import { showAgent, deriveLiveness } from './agent-registry'; -import type { AgentRecord } from './agent-registry'; import type { AgentMessage } from './agent-mail'; const MIN_TTL_MINUTES = 5; diff --git a/src/lib/agent-sessions.ts b/src/lib/agent-sessions.ts index 5ffc8a1..883b63a 100644 --- a/src/lib/agent-sessions.ts +++ b/src/lib/agent-sessions.ts @@ -1,9 +1,9 @@ import type { ActivityEvent } from './activity'; import type { BeadIssue } from './types'; -import { listAgents } from './agent-registry'; +import { listAgents, deriveLiveness } from './agent-registry'; import { inboxAgentMessages, type AgentMessage } from './agent-mail'; -export type AgentSessionState = 'active' | 'reviewing' | 'deciding' | 'needs_input' | 'completed' | 'stale'; +export type AgentSessionState = 'active' | 'reviewing' | 'deciding' | 'needs_input' | 'completed' | 'stale' | 'evicted'; export interface SessionTaskCard { id: string; @@ -34,8 +34,21 @@ export interface CommunicationSummary { messages: AgentMessage[]; } -// 24 hours in ms -const STALE_THRESHOLD_MS = 24 * 60 * 60 * 1000; +// 15 minutes default stale threshold +const STALE_THRESHOLD_MS = 15 * 60 * 1000; + +export async function getAgentLivenessMap(): Promise> { + const agentsResult = await listAgents({}); + const agents = agentsResult.data ?? []; + const map: Record = {}; + const now = new Date(); + + for (const agent of agents) { + map[agent.agent_id] = deriveLiveness(agent.last_seen_at, now); + } + + return map; +} /** * Gathers all relevant communication for all agents to build a summary for aggregation. @@ -96,7 +109,8 @@ export async function getAgentMetrics( export function buildSessionTaskFeed( issues: BeadIssue[], activity: ActivityEvent[], - communicationSummary: CommunicationSummary + communicationSummary: CommunicationSummary, + agentLivenessMap: Record = {} ): EpicBucket[] { const epics = issues.filter(i => i.issue_type === 'epic'); const tasks = issues.filter(i => i.issue_type !== 'epic'); @@ -136,11 +150,20 @@ export function buildSessionTaskFeed( .sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime())[0] ?? null; }; - const deriveState = (task: BeadIssue, lastEvent: ActivityEvent | null, pendingRequired: boolean): AgentSessionState => { + const deriveState = ( + task: BeadIssue, + lastEvent: ActivityEvent | null, + pendingRequired: boolean, + ownerLiveness?: string + ): AgentSessionState => { if (task.status === 'closed') return 'completed'; if (task.status === 'blocked' || pendingRequired) return 'needs_input'; - // Check staleness + // If agent is evicted, the task session state is definitely evicted + if (ownerLiveness === 'evicted') return 'evicted'; + if (ownerLiveness === 'stale') return 'stale'; + + // Check staleness of the TASK activity itself const lastActiveTime = lastEvent ? new Date(lastEvent.timestamp).getTime() : new Date(task.updated_at).getTime(); if (Date.now() - lastActiveTime > STALE_THRESHOLD_MS) { return 'stale'; @@ -172,7 +195,8 @@ export function buildSessionTaskFeed( const pendingRequired = taskMessages.some(m => m.requires_ack && m.state !== 'acked'); const latestMessage = taskMessages.sort((a, b) => b.created_at.localeCompare(a.created_at))[0]; - const sessionState = deriveState(task, lastEvent, pendingRequired); + const ownerLiveness = task.assignee ? agentLivenessMap[task.assignee] : undefined; + const sessionState = deriveState(task, lastEvent, pendingRequired, ownerLiveness); const card: SessionTaskCard = { id: task.id, diff --git a/tests/lib/agent-protocol.test.ts b/tests/lib/agent-protocol.test.ts index b6265fa..881656b 100644 --- a/tests/lib/agent-protocol.test.ts +++ b/tests/lib/agent-protocol.test.ts @@ -1,8 +1,7 @@ import test from 'node:test'; import assert from 'node:assert/strict'; import { - createProtocolEvent, - type ProtocolEvent + createProtocolEvent } from '../../src/lib/agent-protocol'; test('createProtocolEvent generates a valid v1 envelope', () => { diff --git a/tests/lib/agent-sessions.test.ts b/tests/lib/agent-sessions.test.ts index c68e5a0..2b438d4 100644 --- a/tests/lib/agent-sessions.test.ts +++ b/tests/lib/agent-sessions.test.ts @@ -104,4 +104,14 @@ describe('Agent Sessions Aggregation', () => { const card = feed[0].tasks[0]; assert.strictEqual(card.sessionState, 'stale'); }); + + it('should reflect agent liveness (evicted) in session state', () => { + const issues = [MOCK_ISSUE]; + const livenessMap = { 'agent-smith': 'evicted' }; + + const feed = buildSessionTaskFeed(issues, [], { messages: [] }, livenessMap); + + const card = feed[0].tasks[0]; + assert.strictEqual(card.sessionState, 'evicted'); + }); }); diff --git a/tests/lib/agent-takeover.test.ts b/tests/lib/agent-takeover.test.ts index 3ff3afa..a4db87d 100644 --- a/tests/lib/agent-takeover.test.ts +++ b/tests/lib/agent-takeover.test.ts @@ -4,7 +4,7 @@ import fs from 'node:fs/promises'; import os from 'node:os'; import path from 'node:path'; -import { registerAgent, heartbeatAgent } from '../../src/lib/agent-registry'; +import { registerAgent } from '../../src/lib/agent-registry'; import { reserveAgentScope } from '../../src/lib/agent-reservations'; async function withTempUserProfile(run: () => Promise): Promise {