/** * The crtrd broker `error` control frame — `{ code, message, id? }`. `id` is a * correlation token echoed only from the request that failed (a read-op / * `dequeue`); absent on uncorrelated errors. There is NO `retryable` field. */ export interface BrokerErrorFrame { type: 'error'; code: string; message: string; /** Correlation token, echoed from the failed request; absent on uncorrelated errors. */ id?: string; } /** * The `welcome` frame's history/state snapshot, narrowed to what a relay consumer * replays: the message history, the streaming flag, and the unrun queue. `M` is * the message shape (pi's `AgentMessage` in a pi consumer; `unknown` by default). * * `queued` carries the same two arrays a live `queue_update` frame carries — the * steer/follow-up texts accepted by the engine but not started yet. A relay that * renders queued rows needs it to survive (re)attach: a message enqueued mid-turn * is in NEITHER `messages` (it never ran) nor any later frame the consumer has * already seen. Optional: a broker on an older runtime generation omits it, which * a consumer reads as an empty queue. */ export interface BrokerWelcomeSnapshot { messages: M[]; /** Stable session-entry ids aligned 1:1 with `messages`. */ messageIds?: string[]; /** Presentation visibility aligned 1:1 with `messages`. */ messageVisibility: Array<'visible' | 'internal'>; /** Presentation visibility of the run currently owned by the engine. */ turnVisibility: 'visible' | 'internal'; state?: { isStreaming?: boolean; }; queued?: { steering?: string[]; followUp?: string[]; }; } /** * The crtrd broker `welcome` control frame — the history/resume catch-up snapshot * delivered on (re)attach. */ export interface BrokerWelcomeFrame { type: 'welcome'; snapshot?: BrokerWelcomeSnapshot; } /** * The crtrd broker `node_named` control frame — the node's generated name at the * instant crtrd committed it. * * A node names itself off its first real message: the naming extension asks a * headless model for a handle, commits it through crtrd's guarded compare-and-set, * and pushes this frame down the attach stream in the same callback. It is sent * ONLY when that guarded commit APPLIED, so a name a human already set is never * announced as generated. * * It exists because the name lands SECONDS AFTER the turn that triggered it — a * consumer that settles its own display state on turn-end reads an unnamed node and * has nothing left to re-read on. This frame is the push: `description` is the * 3-8 word kebab-case handle, `title` the prose sentence a conversation list * shows, `icon` the Nerd Font glyph the namer chose (empty when it chose none), * and `editorLabel` crtrd's own rendered label for the node. */ export interface BrokerNodeNamedFrame { type: 'node_named'; description: string; title: string; icon: string; editorLabel: string; } /** * The broker-control frames a relay consumer reads: `welcome`, `error`, and * `node_named`. The broker interleaves others (display_*, ack, …) * under non-colliding `type` discriminants; a relay mapper drops those through its * `default` arm untyped, so they are not enumerated here. */ export type BrokerControlFrame = BrokerWelcomeFrame | BrokerErrorFrame | BrokerNodeNamedFrame; /** * What the crtrd broker attach delivers to a relay consumer: the live engine * event stream (`E` — pi's `AgentSessionEvent`, relayed verbatim) unioned with the * broker's own control frames. Discriminants never collide: an `AgentSessionEvent` * owns no `welcome` / `error` `type`. */ export type BrokerAttachFrame = E | BrokerControlFrame;