diff options
Diffstat (limited to '.dispatch/wire.reference.md')
| -rw-r--r-- | .dispatch/wire.reference.md | 85 |
1 files changed, 82 insertions, 3 deletions
diff --git a/.dispatch/wire.reference.md b/.dispatch/wire.reference.md index 34984d2..c2c4d43 100644 --- a/.dispatch/wire.reference.md +++ b/.dispatch/wire.reference.md @@ -4,8 +4,31 @@ > types WITHOUT following the `file:` dep symlink out of this repo (which hangs on a permission > prompt). Your CODE still imports `@dispatch/wire` normally — this file is for READING only. > -> **Orchestrator:** SNAPSHOT of `[email protected]` (reasoning effort — the thinking-depth knob). -> Regenerate whenever `@dispatch/wire` changes. +> **Orchestrator:** SNAPSHOT of `[email protected]` (message queue + steering). Regenerate +> whenever `@dispatch/wire` changes. +> +> **2026-06-21 delta (message-queue + steering handoff — package bumped `0.7.0` → `0.8.0`, ADDITIVE):** +> adds the per-conversation **message queue** + **steering** feature. While a turn is GENERATING, +> a client enqueues a user message (via the `chat.queue` WS op or `POST /conversations/:id/queue`, +> see `[email protected]`); it is delivered mid-turn as **steering** — injected at the next +> tool-result boundary so the model sees it alongside the tool results and can adjust course. If the +> turn ends with a non-empty queue (no tool call fired), the queue is carried into a NEW turn as its +> opening prompt (no `steering` event — the new turn's `user-message` covers it). +> +> Adds: +> - **`QueuedMessage`** (`{ id, text, queuedAt }`) — a message held in the queue (stable id for UI +> keying + dedup). +> - **`QueuePayload`** (`{ messages: QueuedMessage[] }`) — the payload of the message-queue +> extension's per-conversation `custom` surface field (`rendererId: "message-queue"`). Carried on +> the SURFACE channel (NOT the chat stream) — the queue is control/state. Empty `messages` = empty +> queue. See `transport-contract.reference.md` for the surface + the enqueue op. +> - **`TurnSteeringEvent`** (`{ type: "steering"; conversationId; turnId; text }`) — a NEW +> `AgentEvent` union member, emitted on the chat stream when the kernel drains a non-empty queue +> at a tool-result boundary. Render `text` as a USER bubble in the transcript (positioned after +> the tool-result it followed); the queue surface separately clears on drain. One event per drain; +> `text` is the combined text of all drained messages. Late-join safe (buffered into the in-flight +> turn's event buffer, mirroring `user-message`). Carry-to-new-turn does NOT emit `steering`. +> ADDITIVE to the union — if you have an exhaustive `AgentEvent` switch, add a `steering` case. > > **2026-06-12 delta (reasoning-effort handoff — package bumped `0.6.1` → `0.7.0`, ADDITIVE):** > adds the **`ReasoningEffort`** type — the per-request thinking-depth ladder @@ -284,6 +307,37 @@ export interface TurnMetrics { readonly contextSize?: number; } +// ─── Message queue + steering ─────────────────────────────────────────────── + +/** + * A user message held in a conversation's message queue, awaiting mid-turn + * steering delivery. The message-queue extension owns the queue and exposes it + * as a per-conversation `custom` surface field; this type is the shared shape + * the surface payload, the enqueue response, and the extension's service all + * use (so a separate frontend repo can depend on the wire alone to render it). + */ +export interface QueuedMessage { + /** Stable id (client-visible) for UI keying + dedup. */ + readonly id: string; + /** The message text the client enqueued. */ + readonly text: string; + /** When the message was enqueued (epoch-ms). */ + readonly queuedAt: number; +} + +/** + * The payload of the message-queue extension's per-conversation `custom` + * surface field (`rendererId: "message-queue"`): the current queue snapshot a + * frontend renders. Carried on the SURFACE channel (NOT the chat stream) — the + * queue is control/state, distinct from turn content. An empty `messages` + * array means the queue is empty (no pending steering). The frontend moves a + * message from this queue surface into the transcript when it is drained (the + * surface clears) and/or when the matching `TurnSteeringEvent` arrives. + */ +export interface QueuePayload { + readonly messages: readonly QueuedMessage[]; +} + // ─── Outward events ───────────────────────────────────────────────────────── /** @@ -303,7 +357,8 @@ export type AgentEvent = | TurnStepCompleteEvent | TurnErrorEvent | TurnDoneEvent - | TurnSealedEvent; + | TurnSealedEvent + | TurnSteeringEvent; /** Status change for a conversation (e.g. idle → running). */ export interface StatusEvent { @@ -498,4 +553,28 @@ export interface TurnSealedEvent { readonly conversationId: string; readonly turnId: string; } + +/** + * A steering message was injected into an in-flight turn at the tool-result + * boundary (the model sees it alongside the tool results and may adjust + * course). Drawn from the conversation's message queue (which the drain + * clears); the cleared queue arrives as a message-queue SURFACE update, while + * THIS event carries the injected `text` so a frontend can place a user bubble + * in the transcript live — and so a late-joining watcher sees it before seal + * (mirroring `TurnInputEvent` for the opening prompt; emitted into the + * in-flight buffer by the session-orchestrator). + * + * Emitted by the session-orchestrator (in its `drainSteering` wrapper) only + * when the kernel drained a non-empty queue at a tool-result boundary. If the + * turn instead ENDS with a non-empty queue (no tool call fired), the queue is + * carried into a NEW turn whose opening `user-message` event covers the + * transcript — so no `steering` event is emitted in that case. One `steering` + * event per drain; the combined text of all drained messages. + */ +export interface TurnSteeringEvent { + readonly type: "steering"; + readonly conversationId: string; + readonly turnId: string; + readonly text: string; +} ``` |
