summaryrefslogtreecommitdiffhomepage
path: root/.dispatch/transport-contract.reference.md
diff options
context:
space:
mode:
Diffstat (limited to '.dispatch/transport-contract.reference.md')
-rw-r--r--.dispatch/transport-contract.reference.md94
1 files changed, 90 insertions, 4 deletions
diff --git a/.dispatch/transport-contract.reference.md b/.dispatch/transport-contract.reference.md
index 1c3d993..18d1a3d 100644
--- a/.dispatch/transport-contract.reference.md
+++ b/.dispatch/transport-contract.reference.md
@@ -5,10 +5,31 @@
> hangs on a permission prompt). Your CODE still imports `@dispatch/transport-contract` normally —
> this file is for READING only.
>
-> **Orchestrator:** SNAPSHOT of `[email protected]` (reasoning effort shipped).
-> Depends on `@dispatch/[email protected]` (see `wire.reference.md`) + `@dispatch/[email protected]` (see
+> **Orchestrator:** SNAPSHOT of `[email protected]` (message queue + steering).
+> Depends on `@dispatch/[email protected]` (see `wire.reference.md`) + `@dispatch/[email protected]` (see
> `ui-contract.reference.md`).
>
+> **2026-06-21 delta (message-queue + steering handoff — package bumped `0.11.0` → `0.12.0`, ADDITIVE):**
+> adds the enqueue surface for the per-conversation message queue (the wire types `QueuedMessage` /
+> `QueuePayload` + the new `steering` `AgentEvent` live in `[email protected]`, re-exported here). Two
+> additive shapes:
+> 1. **WS `chat.queue` op** — `ChatQueueMessage { type: "chat.queue"; conversationId; text }` (a
+> new `WsClientMessage` union member). Fire-and-forget: on success the server emits NOTHING back
+> — the message-queue SURFACE updates (the new message appears in the snapshot). On failure (empty
+> `text`, unknown conversation) the server replies `chat.error`. **Auto-start when idle
+> (server-owned):** if no turn is active, `chat.queue` does NOT queue — it STARTS A NEW TURN with
+> the message as its opening prompt (equivalent to `chat.send`). So a single op works for both
+> "steer during generation" and "send"; the client doesn't pick. `text` must be non-empty after trim.
+> 2. **HTTP `POST /conversations/:id/queue`** — body `QueueRequest { text }` → `QueueResponse
+> { conversationId; startedTurn: boolean; queue: QueuedMessage[] }`. `startedTurn: true` = was
+> idle, a new turn started (the message is the turn's opening prompt, NOT a queued steering
+> message); `startedTurn: false` = a turn was active, the message was queued (the `queue`
+> snapshot includes it). Empty/whitespace `text` → HTTP 400 `{ error }`. The FE uses the WS op.
+>
+> The queue is read via a per-conversation SURFACE (`message-queue`, scope `conversation`; one
+> `custom` field, `rendererId: "message-queue"`, `payload: QueuePayload`) — NOT via the chat stream.
+> See the handoff for the full flow (steering event, carry-to-new-turn, move-vs-duplicate).
+>
> **2026-06-12 delta (reasoning-effort handoff — package bumped `0.10.0` → `0.11.0`, ADDITIVE):**
> the thinking-depth knob (`ReasoningEffort`, re-exported from `[email protected]`) lands in TWO scopes,
> resolved server-side per turn (per-turn override → persisted conversation value → default
@@ -135,6 +156,11 @@
- `POST /conversations/:id/close` — no body → `200 CloseConversationResponse`. The EXPLICIT tab-close
affordance: aborts any in-flight turn (persists the partial; seals with `finishReason: "aborted"`)
AND stops + disables cache-warming (persisted OFF). Idempotent (`abortedTurn: false` when idle/unknown).
+- `POST /conversations/:id/queue` — body `QueueRequest { text }` → `200 QueueResponse`. Enqueue a user
+ message for mid-turn steering delivery (the WS `chat.queue` op is the FE's path). When a turn is
+ active, the message is queued + delivered at the next tool-result boundary (a `steering` `AgentEvent`
+ fires; the message-queue SURFACE updates). When idle, the enqueue STARTS a new turn with the message
+ as its opening prompt (`startedTurn: true`). Empty/whitespace `text` → `400 { error }`.
- `GET /metrics/throughput?period=day|week|month&date=<...>` — `ThroughputResponse` (token-weighted
tokens/sec per model over the window). Not part of cache-warming; listed for completeness.
- `GET /conversations/:id/cwd` — `CwdResponse` (`cwd` is `null` until set).
@@ -172,10 +198,17 @@
*/
import type { SurfaceClientMessage, SurfaceServerMessage } from "@dispatch/ui-contract";
-import type { AgentEvent, ReasoningEffort, StoredChunk, TurnMetrics } from "@dispatch/wire";
+import type {
+ AgentEvent,
+ QueuedMessage,
+ ReasoningEffort,
+ StoredChunk,
+ TurnMetrics,
+} from "@dispatch/wire";
export type {
AgentEvent,
+ QueuedMessage,
ReasoningEffort,
StepMetrics,
StoredChunk,
@@ -395,6 +428,41 @@ export interface CloseConversationResponse {
readonly abortedTurn: boolean;
}
+// ─── Message queue (steering) ─────────────────────────────────────────────────
+
+/**
+ * Request body for `POST /conversations/:id/queue` — enqueue a user message
+ * onto a conversation's message queue for mid-turn steering delivery.
+ *
+ * When a turn is ACTIVE for the conversation, the message is appended to the
+ * queue (the message-queue extension's per-conversation SURFACE updates) and
+ * delivered at the next tool-result boundary as a steering message the model
+ * sees alongside the tool results (a `steering` `AgentEvent` is emitted). When
+ * NO turn is active, enqueuing instead STARTS a new turn with the message as its
+ * opening prompt (equivalent to `POST /chat`) — so a fire-and-forget enqueue
+ * works regardless of generation state. The resolved queue + whether a turn was
+ * started are returned in `QueueResponse`.
+ *
+ * `text` must be non-empty (after trim) → HTTP 400 `{ error }` otherwise.
+ */
+export interface QueueRequest {
+ readonly text: string;
+}
+
+/**
+ * Response body for `POST /conversations/:id/queue` — the conversation's queue
+ * snapshot AFTER the enqueue, so a client renders the queue from this alone.
+ * `conversationId` echoes the path. `startedTurn` is true when no turn was
+ * active and the enqueue started a new turn (the message is now the turn's
+ * opening prompt, not a queued steering message); the turn's events stream on
+ * the chat channel as usual.
+ */
+export interface QueueResponse {
+ readonly conversationId: string;
+ readonly startedTurn: boolean;
+ readonly queue: readonly QueuedMessage[];
+}
+
// ─── Per-conversation LSP status ──────────────────────────────────────────────
/** The connection state of a single language server for a workspace. */
@@ -550,6 +618,23 @@ export interface ChatUnsubscribeMessage {
}
/**
+ * Client → server: enqueue a message onto a conversation's message queue while
+ * a turn is generating (steering). The WebSocket counterpart of the HTTP
+ * `POST /conversations/:id/queue` (`QueueRequest`). Fire-and-forget: success is
+ * confirmed by the message-queue SURFACE updating (the FE renders the queue
+ * from the surface, not from a reply here); a failure (malformed/empty text,
+ * unknown conversation) arrives as a `chat.error`. When no turn is active, the
+ * enqueue starts a new turn (the turn's events stream as `chat.delta`s), so a
+ * client reuses this op for both "queue while generating" and "send" (the
+ * latter being equivalent to `chat.send`).
+ */
+export interface ChatQueueMessage {
+ readonly type: "chat.queue";
+ readonly conversationId: string;
+ readonly text: string;
+}
+
+/**
* Every client → server WS message: surface ops (`@dispatch/ui-contract`) + chat
* ops. A server discriminates on `type`.
*/
@@ -557,7 +642,8 @@ export type WsClientMessage =
| SurfaceClientMessage
| ChatSendMessage
| ChatSubscribeMessage
- | ChatUnsubscribeMessage;
+ | ChatUnsubscribeMessage
+ | ChatQueueMessage;
/**
* Every server → client WS message: surface ops (`@dispatch/ui-contract`) + chat