diff options
Diffstat (limited to '.dispatch')
| -rw-r--r-- | .dispatch/transport-contract.reference.md | 59 | ||||
| -rw-r--r-- | .dispatch/wire.reference.md | 30 |
2 files changed, 83 insertions, 6 deletions
diff --git a/.dispatch/transport-contract.reference.md b/.dispatch/transport-contract.reference.md index 40ced1e..86eac50 100644 --- a/.dispatch/transport-contract.reference.md +++ b/.dispatch/transport-contract.reference.md @@ -5,9 +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]` (the metrics endpoint shipped + -> version-bumped + LIVE-VERIFIED). Depends on `@dispatch/[email protected]` (see `wire.reference.md`) + -> `@dispatch/[email protected]` (see `ui-contract.reference.md`). +> **Orchestrator:** SNAPSHOT of `[email protected]` (CR-3 user-message shipped). Depends on +> `@dispatch/[email protected]` (see `wire.reference.md`) + `@dispatch/[email protected]` (see +> `ui-contract.reference.md`). +> +> **2026-06-12 delta (CR-3 user-message handoff — package bumped `0.7.0` → `0.8.0`):** NO transport +> shape change — it re-exports `AgentEvent` (which `chat.delta` / `/chat` NDJSON carry), and that union +> gained the additive `TurnInputEvent` (`{ type: "user-message"; conversationId; turnId; text }`), the +> turn's user prompt, emitted as the FIRST event of every turn (before `turn-start`) and replayed to +> watchers/late-joiners. See the `wire.reference.md` CR-3 delta + `TurnInputEvent` for the definition. +> +> **2026-06-12 delta (turn-continuity handoff — package bumped `0.6.0` → `0.7.0`, ADDITIVE):** a turn +> is no longer bound to the WS connection — it runs to completion server-side regardless of any +> client, and any number of connections can watch the same conversation (incl. a late-joiner that +> connects mid-turn). Two new client→server WS messages: `ChatSubscribeMessage` +> (`{ type: "chat.subscribe"; conversationId }`) and `ChatUnsubscribeMessage` +> (`{ type: "chat.unsubscribe"; conversationId }`); `WsClientMessage` now unions both. Server→client +> is UNCHANGED (turn events still arrive as `chat.delta`, replayed AND live). Semantics: `chat.subscribe` +> registers the connection + immediately REPLAYS the in-flight turn's events so far (from its +> `turn-start`) then streams live (nothing replayed if idle); `chat.send` AUTO-subscribes the sending +> connection (a 2nd send while generating ⇒ `chat.error` + you stay subscribed to watch the running +> turn); `chat.unsubscribe`/socket-close drops the subscription but NEVER stops the turn; subscriptions +> persist across turns. FE consumes via the `chat` feature + app store (re-subscribe every open +> conversation on (re)connect + page load; derive a "running" state structurally from +> `turn-start`…no-`done`/`turn-sealed`-yet). OUT of scope: per-step crash-resume, concurrent-send +> arbitration, explicit stop. > > **2026-06-12 delta (context-size handoff — package bumped `0.5.0` → `0.6.0`, depends on > `[email protected]`):** no NEW transport shape — the optional `contextSize?: number` rides the @@ -293,8 +315,37 @@ export interface ChatErrorMessage { readonly message: string; } +/** + * Client → server: start WATCHING a conversation's live turn events WITHOUT sending. + * On subscribe the server REPLAYS the current in-flight turn so far (from its + * `turn-start`) as `chat.delta`, then streams live; nothing replayed if idle (rely + * on `GET /conversations/:id` history). Infer "generating" from a replayed + * `turn-start` with no matching `done`/`turn-sealed` yet. `chat.send` already + * auto-subscribes the sender, so this is for conversations you VIEW but didn't send to + * (a 2nd device, or a reloaded/reconnected client). Idempotent per (connection, id). + */ +export interface ChatSubscribeMessage { + readonly type: "chat.subscribe"; + readonly conversationId: string; +} + +/** + * Client → server: stop watching a conversation's turn events on this connection. + * Does NOT stop/affect the turn (it runs to completion regardless of subscribers). + * Socket close drops all of a connection's subscriptions the same way — again + * WITHOUT aborting any in-flight turn. + */ +export interface ChatUnsubscribeMessage { + readonly type: "chat.unsubscribe"; + readonly conversationId: string; +} + /** Every client → server WS message: surface ops + chat ops. Discriminate on `type`. */ -export type WsClientMessage = SurfaceClientMessage | ChatSendMessage; +export type WsClientMessage = + | SurfaceClientMessage + | ChatSendMessage + | ChatSubscribeMessage + | ChatUnsubscribeMessage; /** Every server → client WS message: surface ops + chat ops. Discriminate on `type`. */ export type WsServerMessage = SurfaceServerMessage | ChatDeltaMessage | ChatErrorMessage; diff --git a/.dispatch/wire.reference.md b/.dispatch/wire.reference.md index cf1410a..40f94cf 100644 --- a/.dispatch/wire.reference.md +++ b/.dispatch/wire.reference.md @@ -4,8 +4,16 @@ > 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]` (the metrics types below shipped + version-bumped). -> Regenerate whenever `@dispatch/wire` changes. +> **Orchestrator:** SNAPSHOT of `[email protected]` (the metrics types below + the `user-message` turn event +> shipped + version-bumped). Regenerate whenever `@dispatch/wire` changes. +> +> **2026-06-12 delta (CR-3 user-message handoff — package bumped `0.5.0` → `0.6.0`, ADDITIVE):** adds a +> new `AgentEvent` union member `TurnInputEvent` (`{ type: "user-message"; conversationId; turnId; text }`) +> that surfaces the turn's USER prompt INTO the outward event stream. Emitted ONCE as the FIRST event of +> every turn (before `turn-start`), so it is buffered + replayed to every subscriber — live AND late-join +> — and rides `chat.delta`/NDJSON like any other event. Fixes CR-3 (a pure watcher couldn't see the prompt +> until seal). The sender still echoes its own prompt optimistically, so consumers DE-DUP against that +> (by text); a pure watcher renders it directly. Persistence/metrics unchanged. See `TurnInputEvent` below. > > **2026-06-12 delta (context-size handoff — package bumped `0.4.0` → `0.5.0`):** adds an OPTIONAL > `contextSize?: number` to BOTH `TurnDoneEvent` (live `done`) and `TurnMetrics` (persisted) — the @@ -249,6 +257,7 @@ export interface TurnMetrics { export type AgentEvent = | StatusEvent | TurnStartEvent + | TurnInputEvent | TurnTextDeltaEvent | TurnReasoningDeltaEvent | TurnToolCallEvent @@ -274,6 +283,23 @@ export interface TurnStartEvent { readonly turnId: string; } +/** + * The user prompt that opened this turn, surfaced INTO the turn's outward event + * stream so a WATCHER (subscribed but not the sender) can render the prompt + * mid-turn — the user message is otherwise persisted only at seal. Emitted ONCE + * as the FIRST event of the turn (before `turn-start`); buffered + replayed to + * every subscriber (live + late-join). The sender echoes its own prompt + * optimistically, so DE-DUP against that (by text); a pure watcher renders it + * directly. Carries the raw `text` passed to the provider. (Turn-scoped: it + * carries `turnId`, so a multi-turn transcript attributes each prompt to its turn.) + */ +export interface TurnInputEvent { + readonly type: "user-message"; + readonly conversationId: string; + readonly turnId: string; + readonly text: string; +} + /** Incremental text content from the model during a turn. */ export interface TurnTextDeltaEvent { readonly type: "text-delta"; |
