/** * 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. } }