Scoped half of boocode_code_review_v2 §1 #10 — publish the agent status BooCoder already observes (the config-injection notify-hook is the documented follow-on, clean-room from superset ELv2). - agent_status_updated WS frame (working|blocked|idle|error), server+web parity. - Published from the dispatcher's turn boundaries (warm-acp/opencode/sdk/pty: working at start, idle/error at end) + the permission flow (blocked/working). Best-effort, never breaks a turn. - Clean-room normalizeAgentEvent helper (superset's vendor-event -> Start/blocked /Stop collapse, event names as facts) + 25 tests — reused by the follow-on. - AgentComposerBar status dot (distinct from the WS-liveness dot), tracked per (chat,agent) by a useAgentStatus map in CoderPane. Built by 2 parallel agents vs a pinned frame contract. Server 545 + coder 294 tests passing (25 new); web tsc + builds clean; ws-frames parity green. Clears the actionable review backlog (#1/#3/#4/#6-#12). Builds on v2.7.5. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
56 lines
2.2 KiB
TypeScript
56 lines
2.2 KiB
TypeScript
/**
|
|
* agent-status-publish (#10) — builds + publishes the `agent_status_updated`
|
|
* WS frame on the per-session channel (the same channel CoderPane subscribes to).
|
|
*
|
|
* Kept separate from normalize-agent-status.ts so that module stays a pure,
|
|
* broker-free helper (trivially unit-testable; reused by the config-injection
|
|
* follow-on). The frame contract is pinned in apps/server/src/types/ws-frames.ts
|
|
* (`AgentStatusUpdatedFrame`) and mirrored byte-identical in apps/web.
|
|
*/
|
|
import type { Broker } from '@boocode/server/broker';
|
|
import type { WsFrame } from '@boocode/server/ws-frames';
|
|
import type { AgentStatus } from './normalize-agent-status.js';
|
|
|
|
// The exact slice of Broker we need — accepting just the bound method keeps call
|
|
// sites flexible (pass `broker.publishFrame.bind(broker)` or, since the broker's
|
|
// publishFrame doesn't read `this`, `broker.publishFrame` directly).
|
|
type PublishFrame = Broker['publishFrame'];
|
|
|
|
/**
|
|
* Best-effort publish of a normalized agent status. The broker's publishFrame
|
|
* already fail-closes (validates + logs + drops on bad input, never throws), but
|
|
* we additionally swallow any unexpected error so a publish can NEVER break the
|
|
* turn it's reporting on.
|
|
*
|
|
* @param publishFrame the session channel publisher (broker.publishFrame)
|
|
* @param sessionId WS subscription channel (CoderPane subscribes per-session)
|
|
* @param chatId the (chat) half of the (chat, agent) status key
|
|
* @param agent the (agent) half of the key
|
|
* @param status normalized lifecycle status
|
|
* @param reason free-form discriminator (turn_start / turn_complete / …)
|
|
* @param at ISO timestamp; defaults to now
|
|
*/
|
|
export function publishAgentStatus(
|
|
publishFrame: PublishFrame,
|
|
sessionId: string,
|
|
chatId: string,
|
|
agent: string,
|
|
status: AgentStatus,
|
|
reason?: string,
|
|
at: string = new Date().toISOString(),
|
|
): void {
|
|
try {
|
|
const frame: WsFrame = {
|
|
type: 'agent_status_updated',
|
|
chat_id: chatId,
|
|
agent,
|
|
status,
|
|
...(reason ? { reason } : {}),
|
|
at,
|
|
};
|
|
publishFrame(sessionId, frame);
|
|
} catch {
|
|
// never let a status publish break the turn — best-effort only.
|
|
}
|
|
}
|