summaryrefslogtreecommitdiffhomepage
path: root/packages/transport-contract/src
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-12 02:25:57 +0900
committerAdam Malczewski <[email protected]>2026-06-12 02:25:57 +0900
commit86b5137c4f7f2bcc08f0580f1edaa05d14015e63 (patch)
tree20215a5dccf1b76cf9cf95fceec1eca450aa8559 /packages/transport-contract/src
parent839f5c02676a0b7def27ace7125fbec3aa08bda5 (diff)
downloaddispatch-86b5137c4f7f2bcc08f0580f1edaa05d14015e63.tar.gz
dispatch-86b5137c4f7f2bcc08f0580f1edaa05d14015e63.zip
feat(turns): detached turns + multi-client live view
A turn no longer dies when its WebSocket connection closes. The turn-broadcast hub moves into the core (session-orchestrator): turns run detached, persist at seal regardless of clients, and fan out AgentEvents to N subscribers per conversation with in-flight buffer replay for late-joiners. transport-ws stops aborting turns on socket close and gains chat.subscribe/chat.unsubscribe so a second device (or a reloaded browser) can watch a running turn. - @dispatch/transport-contract 0.6.0->0.7.0: chat.subscribe/chat.unsubscribe WS ops - session-orchestrator: startTurn/subscribe/isActive; persistent subscribers + per-turn buffer (two-map model); handleMessage = convenience wrapper (no signal) - transport-ws: per-connection chat-subscription fan-out; no turn-abort-on-close - transport-http: test fakes updated for the widened interface (runtime unchanged) - design notes/turn-continuity-design.md; FE courier frontend-turn-continuity-handoff.md Live-verified vs flash (2-client WS): sender disconnect mid-turn -> other client streams to done + turn persists; late-join replays turn from turn-start. 891 vitest + transport bun green; tsc -b EXIT 0; biome clean.
Diffstat (limited to 'packages/transport-contract/src')
-rw-r--r--packages/transport-contract/src/index.ts38
1 files changed, 37 insertions, 1 deletions
diff --git a/packages/transport-contract/src/index.ts b/packages/transport-contract/src/index.ts
index f0e50f8..8283cea 100644
--- a/packages/transport-contract/src/index.ts
+++ b/packages/transport-contract/src/index.ts
@@ -290,10 +290,46 @@ export interface ChatErrorMessage {
}
/**
+ * Client → server: start WATCHING a conversation's live turn events WITHOUT
+ * sending a message. This is what makes a turn viewable independently of who
+ * started it — a second device (multi-client handoff) or a client that reloaded
+ * mid-turn subscribes to receive the in-flight turn.
+ *
+ * On subscribe the server replays the CURRENT in-flight turn's events so far as
+ * `chat.delta` messages (so a late-joiner sees the whole running turn from its
+ * `turn-start`), then streams subsequent live events. If no turn is in-flight,
+ * nothing is replayed (the client relies on `GET /conversations/:id` history).
+ * A client infers "generating" from a replayed `turn-start` with no matching
+ * `done`/`turn-sealed` yet. Idempotent per `(connection, conversationId)`.
+ *
+ * NOTE: `chat.send` auto-subscribes the sending connection, so a client only needs
+ * `chat.subscribe` for conversations it is viewing but did not send to.
+ */
+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 or affect the turn itself (the turn runs to completion regardless
+ * of subscribers). The server also drops all of a connection's subscriptions when
+ * the socket closes — again WITHOUT aborting any in-flight turn.
+ */
+export interface ChatUnsubscribeMessage {
+ readonly type: "chat.unsubscribe";
+ readonly conversationId: string;
+}
+
+/**
* Every client → server WS message: surface ops (`@dispatch/ui-contract`) + chat
* ops. A server discriminates on `type`.
*/
-export type WsClientMessage = SurfaceClientMessage | ChatSendMessage;
+export type WsClientMessage =
+ | SurfaceClientMessage
+ | ChatSendMessage
+ | ChatSubscribeMessage
+ | ChatUnsubscribeMessage;
/**
* Every server → client WS message: surface ops (`@dispatch/ui-contract`) + chat