Files
boocode/apps/coder/src/services/agent-status-publish.ts
indifferentketchup 649ce71eff feat: single-source cross-app wire contracts in @boocode/contracts (v2.7.13)
Move all hand-synced cross-app wire contracts into one built workspace
package, @boocode/contracts, consumed by server/web/coder/coder-web via
workspace:* + a per-subpath exports map. The ws-frames and provider-config
Zod schemas are schema-first (z.infer); MessageMetadata, ErrorReason,
AgentSessionConfig, the provider snapshot types, and WorktreeRiskReport are
each single-sourced. Deletes the byte-identical copies and their parity
tests, fixes a live AgentSessionConfig drift (coder dead copy removed,
unified to the web required/nullable shape), removes the dead pending_change
WS arms in the fallback SPA, and inverts the build order (contracts builds
first) across root build, Dockerfile, and the coder deploy docs. Reverses
the shared-package decision declined in v2.5.12.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 21:24:08 +00:00

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