@trigger.dev/sdk 0.0.0-chat-prerelease-20260415164455 → 0.0.0-chat-prerelease-20260417152143

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -46,6 +46,26 @@ export type { InferChatUIMessage };
46
46
  * ```
47
47
  */
48
48
  export declare function useTriggerChatTransport<TTask extends AnyTask = AnyTask>(options: UseTriggerChatTransportOptions<TTask>): TriggerChatTransport;
49
+ /**
50
+ * Sync chat messages across browser tabs.
51
+ *
52
+ * Requires `multiTab: true` on the transport. Handles:
53
+ * - Tracking read-only state (`isReadOnly`) when another tab is active
54
+ * - Broadcasting messages from the active tab to other tabs
55
+ * - Receiving messages from other tabs and updating local state via `setMessages`
56
+ *
57
+ * @example
58
+ * ```tsx
59
+ * const transport = useTriggerChatTransport({ task: "my-chat", multiTab: true, accessToken });
60
+ * const { messages, setMessages } = useChat({ id: chatId, transport });
61
+ * const { isReadOnly } = useMultiTabChat(transport, chatId, messages, setMessages);
62
+ *
63
+ * <input disabled={isReadOnly} placeholder={isReadOnly ? "Active in another tab" : "Type a message..."} />
64
+ * ```
65
+ */
66
+ export declare function useMultiTabChat<T = unknown>(transport: TriggerChatTransport, chatId: string, messages: T[], setMessages: (messages: T[]) => void): {
67
+ isReadOnly: boolean;
68
+ };
49
69
  /** A pending message tracked by `usePendingMessages`. */
50
70
  export type PendingMessage = {
51
71
  id: string;
@@ -2,6 +2,7 @@
2
2
  "use strict";
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.useTriggerChatTransport = useTriggerChatTransport;
5
+ exports.useMultiTabChat = useMultiTabChat;
5
6
  exports.usePendingMessages = usePendingMessages;
6
7
  /**
7
8
  * @module @trigger.dev/sdk/chat/react
@@ -74,8 +75,61 @@ function useTriggerChatTransport(options) {
74
75
  (0, react_1.useEffect)(() => {
75
76
  ref.current?.setTriggerTask(triggerTask);
76
77
  }, [triggerTask]);
78
+ // Note: dispose() is NOT called in effect cleanup because React strict mode
79
+ // runs cleanup+re-setup, but the transport lives in a ref and isn't recreated.
80
+ // Calling dispose() would permanently close the BroadcastChannel.
81
+ // The coordinator's beforeunload handler handles tab close cleanup instead.
77
82
  return ref.current;
78
83
  }
84
+ /**
85
+ * Sync chat messages across browser tabs.
86
+ *
87
+ * Requires `multiTab: true` on the transport. Handles:
88
+ * - Tracking read-only state (`isReadOnly`) when another tab is active
89
+ * - Broadcasting messages from the active tab to other tabs
90
+ * - Receiving messages from other tabs and updating local state via `setMessages`
91
+ *
92
+ * @example
93
+ * ```tsx
94
+ * const transport = useTriggerChatTransport({ task: "my-chat", multiTab: true, accessToken });
95
+ * const { messages, setMessages } = useChat({ id: chatId, transport });
96
+ * const { isReadOnly } = useMultiTabChat(transport, chatId, messages, setMessages);
97
+ *
98
+ * <input disabled={isReadOnly} placeholder={isReadOnly ? "Active in another tab" : "Type a message..."} />
99
+ * ```
100
+ */
101
+ function useMultiTabChat(transport, chatId, messages, setMessages) {
102
+ const [isReadOnly, setIsReadOnly] = (0, react_1.useState)(() => transport.isReadOnly(chatId));
103
+ // Track read-only state
104
+ (0, react_1.useEffect)(() => {
105
+ const listener = (id, readOnly) => {
106
+ if (id === chatId)
107
+ setIsReadOnly(readOnly);
108
+ };
109
+ transport.addReadOnlyListener(listener);
110
+ setIsReadOnly(transport.isReadOnly(chatId));
111
+ return () => transport.removeReadOnlyListener(listener);
112
+ }, [transport, chatId]);
113
+ // Active tab: broadcast messages to other tabs on change.
114
+ // Only broadcast when THIS tab holds the claim (is the current sender).
115
+ // Using !isReadOnly alone causes a feedback loop when both tabs are idle.
116
+ (0, react_1.useEffect)(() => {
117
+ if (transport.hasClaim(chatId) && messages.length > 0) {
118
+ transport.broadcastMessages(chatId, messages);
119
+ }
120
+ }, [transport, chatId, messages]);
121
+ // Read-only tab: receive messages from the active tab
122
+ (0, react_1.useEffect)(() => {
123
+ const listener = (id, msgs) => {
124
+ if (id === chatId) {
125
+ setMessages(msgs);
126
+ }
127
+ };
128
+ transport.addMessagesListener(listener);
129
+ return () => transport.removeMessagesListener(listener);
130
+ }, [transport, chatId, setMessages]);
131
+ return { isReadOnly };
132
+ }
79
133
  /**
80
134
  * React hook for managing pending messages (steering) during streaming.
81
135
  *
@@ -1 +1 @@
1
- {"version":3,"file":"chat-react.js","sourceRoot":"","sources":["../../../src/v3/chat-react.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"chat-react.js","sourceRoot":"","sources":["../../../src/v3/chat-react.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Coordinates multi-tab access to chat sessions via BroadcastChannel.
3
+ *
4
+ * When multiple browser tabs open the same chat, only one can be the active
5
+ * sender. Others enter read-only mode. The coordinator uses a simple
6
+ * claim/release/heartbeat protocol to track ownership per chatId.
7
+ *
8
+ * Gracefully degrades to a no-op when BroadcastChannel is unavailable
9
+ * (SSR, Node.js, old browsers).
10
+ *
11
+ * @internal
12
+ */
13
+ type ReadOnlyListener = (chatId: string, isReadOnly: boolean) => void;
14
+ type MessagesListener = (chatId: string, messages: unknown[]) => void;
15
+ type SessionListener = (chatId: string, session: {
16
+ lastEventId?: string;
17
+ }) => void;
18
+ export declare class ChatTabCoordinator {
19
+ private tabId;
20
+ private channel;
21
+ /** Claims held by OTHER tabs: chatId -> { tabId, lastSeen } */
22
+ private claims;
23
+ /** chatIds that THIS tab has claimed */
24
+ private myClaims;
25
+ private listeners;
26
+ private messagesListeners;
27
+ private sessionListeners;
28
+ private heartbeatTimer;
29
+ private beforeUnloadHandler;
30
+ constructor();
31
+ /**
32
+ * Attempt to claim a chatId for sending.
33
+ * Returns false if another tab already holds it.
34
+ */
35
+ claim(chatId: string): boolean;
36
+ /** Release a chatId so other tabs can claim it. */
37
+ release(chatId: string): void;
38
+ /** Check if THIS tab currently holds a claim for the chatId. */
39
+ hasClaim(chatId: string): boolean;
40
+ /** Check if another tab holds this chatId. */
41
+ isReadOnly(chatId: string): boolean;
42
+ addListener(fn: ReadOnlyListener): void;
43
+ removeListener(fn: ReadOnlyListener): void;
44
+ /** Broadcast the current messages to other tabs (for real-time sync). */
45
+ broadcastMessages(chatId: string, messages: unknown[]): void;
46
+ addMessagesListener(fn: MessagesListener): void;
47
+ removeMessagesListener(fn: MessagesListener): void;
48
+ /** Broadcast session state (lastEventId) to other tabs. */
49
+ broadcastSession(chatId: string, session: {
50
+ lastEventId?: string;
51
+ }): void;
52
+ addSessionListener(fn: SessionListener): void;
53
+ removeSessionListener(fn: SessionListener): void;
54
+ /** Clean up channel, timers, and event listeners. */
55
+ dispose(): void;
56
+ private handleMessage;
57
+ private sendHeartbeats;
58
+ private expireStaleClaimsFromOtherTabs;
59
+ private releaseAll;
60
+ private broadcast;
61
+ private notify;
62
+ private notifyMessages;
63
+ private notifySession;
64
+ }
65
+ export {};
@@ -0,0 +1,235 @@
1
+ "use strict";
2
+ /**
3
+ * Coordinates multi-tab access to chat sessions via BroadcastChannel.
4
+ *
5
+ * When multiple browser tabs open the same chat, only one can be the active
6
+ * sender. Others enter read-only mode. The coordinator uses a simple
7
+ * claim/release/heartbeat protocol to track ownership per chatId.
8
+ *
9
+ * Gracefully degrades to a no-op when BroadcastChannel is unavailable
10
+ * (SSR, Node.js, old browsers).
11
+ *
12
+ * @internal
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.ChatTabCoordinator = void 0;
16
+ const CHANNEL_NAME = "trigger-chat-tab-coord";
17
+ const HEARTBEAT_INTERVAL_MS = 5_000;
18
+ const HEARTBEAT_TIMEOUT_MS = 10_000;
19
+ class ChatTabCoordinator {
20
+ tabId;
21
+ channel = null;
22
+ /** Claims held by OTHER tabs: chatId -> { tabId, lastSeen } */
23
+ claims = new Map();
24
+ /** chatIds that THIS tab has claimed */
25
+ myClaims = new Set();
26
+ listeners = new Set();
27
+ messagesListeners = new Set();
28
+ sessionListeners = new Set();
29
+ heartbeatTimer = null;
30
+ beforeUnloadHandler = null;
31
+ constructor() {
32
+ this.tabId =
33
+ typeof crypto !== "undefined" && crypto.randomUUID
34
+ ? crypto.randomUUID()
35
+ : `tab-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
36
+ if (typeof BroadcastChannel === "undefined") {
37
+ return; // No-op mode
38
+ }
39
+ this.channel = new BroadcastChannel(CHANNEL_NAME);
40
+ this.channel.onmessage = (event) => {
41
+ this.handleMessage(event.data);
42
+ };
43
+ // Heartbeat: send for our claims + check for stale claims from other tabs
44
+ this.heartbeatTimer = setInterval(() => {
45
+ this.sendHeartbeats();
46
+ this.expireStaleClaimsFromOtherTabs();
47
+ }, HEARTBEAT_INTERVAL_MS);
48
+ // Best-effort release on tab close
49
+ this.beforeUnloadHandler = () => this.releaseAll();
50
+ if (typeof window !== "undefined") {
51
+ window.addEventListener("beforeunload", this.beforeUnloadHandler);
52
+ }
53
+ }
54
+ /**
55
+ * Attempt to claim a chatId for sending.
56
+ * Returns false if another tab already holds it.
57
+ */
58
+ claim(chatId) {
59
+ if (!this.channel)
60
+ return true; // No-op mode
61
+ const existing = this.claims.get(chatId);
62
+ if (existing && existing.tabId !== this.tabId) {
63
+ return false; // Another tab holds this chat
64
+ }
65
+ this.myClaims.add(chatId);
66
+ this.broadcast({ type: "claim", chatId, tabId: this.tabId });
67
+ return true;
68
+ }
69
+ /** Release a chatId so other tabs can claim it. */
70
+ release(chatId) {
71
+ if (!this.channel)
72
+ return;
73
+ if (!this.myClaims.has(chatId))
74
+ return;
75
+ this.myClaims.delete(chatId);
76
+ this.broadcast({ type: "release", chatId, tabId: this.tabId });
77
+ }
78
+ /** Check if THIS tab currently holds a claim for the chatId. */
79
+ hasClaim(chatId) {
80
+ return this.myClaims.has(chatId);
81
+ }
82
+ /** Check if another tab holds this chatId. */
83
+ isReadOnly(chatId) {
84
+ if (!this.channel)
85
+ return false;
86
+ const claim = this.claims.get(chatId);
87
+ return claim != null && claim.tabId !== this.tabId;
88
+ }
89
+ addListener(fn) {
90
+ this.listeners.add(fn);
91
+ }
92
+ removeListener(fn) {
93
+ this.listeners.delete(fn);
94
+ }
95
+ /** Broadcast the current messages to other tabs (for real-time sync). */
96
+ broadcastMessages(chatId, messages) {
97
+ if (!this.channel)
98
+ return;
99
+ this.broadcast({ type: "messages", chatId, tabId: this.tabId, messages });
100
+ }
101
+ addMessagesListener(fn) {
102
+ this.messagesListeners.add(fn);
103
+ }
104
+ removeMessagesListener(fn) {
105
+ this.messagesListeners.delete(fn);
106
+ }
107
+ /** Broadcast session state (lastEventId) to other tabs. */
108
+ broadcastSession(chatId, session) {
109
+ if (!this.channel)
110
+ return;
111
+ this.broadcast({ type: "session", chatId, tabId: this.tabId, session });
112
+ }
113
+ addSessionListener(fn) {
114
+ this.sessionListeners.add(fn);
115
+ }
116
+ removeSessionListener(fn) {
117
+ this.sessionListeners.delete(fn);
118
+ }
119
+ /** Clean up channel, timers, and event listeners. */
120
+ dispose() {
121
+ this.releaseAll();
122
+ if (this.heartbeatTimer) {
123
+ clearInterval(this.heartbeatTimer);
124
+ this.heartbeatTimer = null;
125
+ }
126
+ if (this.beforeUnloadHandler && typeof window !== "undefined") {
127
+ window.removeEventListener("beforeunload", this.beforeUnloadHandler);
128
+ this.beforeUnloadHandler = null;
129
+ }
130
+ if (this.channel) {
131
+ this.channel.close();
132
+ this.channel = null;
133
+ }
134
+ this.listeners.clear();
135
+ this.messagesListeners.clear();
136
+ this.sessionListeners.clear();
137
+ }
138
+ // --- Private ---
139
+ handleMessage(msg) {
140
+ if (msg.tabId === this.tabId)
141
+ return; // Ignore own messages
142
+ switch (msg.type) {
143
+ case "claim": {
144
+ const wasReadOnly = this.isReadOnly(msg.chatId);
145
+ this.claims.set(msg.chatId, { tabId: msg.tabId, lastSeen: Date.now() });
146
+ if (!wasReadOnly) {
147
+ this.notify(msg.chatId, true);
148
+ }
149
+ break;
150
+ }
151
+ case "release": {
152
+ const claim = this.claims.get(msg.chatId);
153
+ if (claim && claim.tabId === msg.tabId) {
154
+ this.claims.delete(msg.chatId);
155
+ this.notify(msg.chatId, false);
156
+ }
157
+ break;
158
+ }
159
+ case "heartbeat": {
160
+ const claim = this.claims.get(msg.chatId);
161
+ if (claim && claim.tabId === msg.tabId) {
162
+ claim.lastSeen = Date.now();
163
+ }
164
+ break;
165
+ }
166
+ case "messages": {
167
+ this.notifyMessages(msg.chatId, msg.messages);
168
+ break;
169
+ }
170
+ case "session": {
171
+ this.notifySession(msg.chatId, msg.session);
172
+ break;
173
+ }
174
+ }
175
+ }
176
+ sendHeartbeats() {
177
+ for (const chatId of this.myClaims) {
178
+ this.broadcast({ type: "heartbeat", chatId, tabId: this.tabId });
179
+ }
180
+ }
181
+ expireStaleClaimsFromOtherTabs() {
182
+ const now = Date.now();
183
+ for (const [chatId, claim] of this.claims) {
184
+ if (claim.tabId !== this.tabId && now - claim.lastSeen > HEARTBEAT_TIMEOUT_MS) {
185
+ this.claims.delete(chatId);
186
+ this.notify(chatId, false);
187
+ }
188
+ }
189
+ }
190
+ releaseAll() {
191
+ for (const chatId of [...this.myClaims]) {
192
+ this.release(chatId);
193
+ }
194
+ }
195
+ broadcast(msg) {
196
+ try {
197
+ this.channel?.postMessage(msg);
198
+ }
199
+ catch {
200
+ // Channel may be closed
201
+ }
202
+ }
203
+ notify(chatId, isReadOnly) {
204
+ for (const fn of this.listeners) {
205
+ try {
206
+ fn(chatId, isReadOnly);
207
+ }
208
+ catch {
209
+ // Non-fatal
210
+ }
211
+ }
212
+ }
213
+ notifyMessages(chatId, messages) {
214
+ for (const fn of this.messagesListeners) {
215
+ try {
216
+ fn(chatId, messages);
217
+ }
218
+ catch {
219
+ // Non-fatal
220
+ }
221
+ }
222
+ }
223
+ notifySession(chatId, session) {
224
+ for (const fn of this.sessionListeners) {
225
+ try {
226
+ fn(chatId, session);
227
+ }
228
+ catch {
229
+ // Non-fatal
230
+ }
231
+ }
232
+ }
233
+ }
234
+ exports.ChatTabCoordinator = ChatTabCoordinator;
235
+ //# sourceMappingURL=chat-tab-coordinator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-tab-coordinator.js","sourceRoot":"","sources":["../../../src/v3/chat-tab-coordinator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,140 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const vitest_1 = require("vitest");
4
+ const chat_tab_coordinator_js_1 = require("./chat-tab-coordinator.js");
5
+ // Mock BroadcastChannel for testing
6
+ class MockBroadcastChannel {
7
+ name;
8
+ static instances = [];
9
+ onmessage = null;
10
+ closed = false;
11
+ constructor(name) {
12
+ this.name = name;
13
+ MockBroadcastChannel.instances.push(this);
14
+ }
15
+ postMessage(data) {
16
+ if (this.closed)
17
+ return;
18
+ // Deliver to all OTHER instances on the same channel
19
+ for (const instance of MockBroadcastChannel.instances) {
20
+ if (instance !== this && instance.name === this.name && !instance.closed) {
21
+ instance.onmessage?.({ data });
22
+ }
23
+ }
24
+ }
25
+ close() {
26
+ this.closed = true;
27
+ MockBroadcastChannel.instances = MockBroadcastChannel.instances.filter((i) => i !== this);
28
+ }
29
+ }
30
+ (0, vitest_1.describe)("ChatTabCoordinator", () => {
31
+ (0, vitest_1.beforeEach)(() => {
32
+ MockBroadcastChannel.instances = [];
33
+ vitest_1.vi.stubGlobal("BroadcastChannel", MockBroadcastChannel);
34
+ vitest_1.vi.useFakeTimers();
35
+ });
36
+ (0, vitest_1.afterEach)(() => {
37
+ vitest_1.vi.restoreAllMocks();
38
+ vitest_1.vi.useRealTimers();
39
+ });
40
+ (0, vitest_1.it)("tab A claims, tab B sees isReadOnly", () => {
41
+ const a = new chat_tab_coordinator_js_1.ChatTabCoordinator();
42
+ const b = new chat_tab_coordinator_js_1.ChatTabCoordinator();
43
+ (0, vitest_1.expect)(b.isReadOnly("chat-1")).toBe(false);
44
+ a.claim("chat-1");
45
+ (0, vitest_1.expect)(b.isReadOnly("chat-1")).toBe(true);
46
+ (0, vitest_1.expect)(a.isReadOnly("chat-1")).toBe(false); // Owner is not read-only
47
+ a.dispose();
48
+ b.dispose();
49
+ });
50
+ (0, vitest_1.it)("tab A releases, tab B sees isReadOnly = false", () => {
51
+ const a = new chat_tab_coordinator_js_1.ChatTabCoordinator();
52
+ const b = new chat_tab_coordinator_js_1.ChatTabCoordinator();
53
+ a.claim("chat-1");
54
+ (0, vitest_1.expect)(b.isReadOnly("chat-1")).toBe(true);
55
+ a.release("chat-1");
56
+ (0, vitest_1.expect)(b.isReadOnly("chat-1")).toBe(false);
57
+ a.dispose();
58
+ b.dispose();
59
+ });
60
+ (0, vitest_1.it)("fires listener on claim and release", () => {
61
+ const a = new chat_tab_coordinator_js_1.ChatTabCoordinator();
62
+ const b = new chat_tab_coordinator_js_1.ChatTabCoordinator();
63
+ const listener = vitest_1.vi.fn();
64
+ b.addListener(listener);
65
+ a.claim("chat-1");
66
+ (0, vitest_1.expect)(listener).toHaveBeenCalledWith("chat-1", true);
67
+ a.release("chat-1");
68
+ (0, vitest_1.expect)(listener).toHaveBeenCalledWith("chat-1", false);
69
+ a.dispose();
70
+ b.dispose();
71
+ });
72
+ (0, vitest_1.it)("removeListener stops notifications", () => {
73
+ const a = new chat_tab_coordinator_js_1.ChatTabCoordinator();
74
+ const b = new chat_tab_coordinator_js_1.ChatTabCoordinator();
75
+ const listener = vitest_1.vi.fn();
76
+ b.addListener(listener);
77
+ b.removeListener(listener);
78
+ a.claim("chat-1");
79
+ (0, vitest_1.expect)(listener).not.toHaveBeenCalled();
80
+ a.dispose();
81
+ b.dispose();
82
+ });
83
+ (0, vitest_1.it)("claim returns false when another tab holds the chatId", () => {
84
+ const a = new chat_tab_coordinator_js_1.ChatTabCoordinator();
85
+ const b = new chat_tab_coordinator_js_1.ChatTabCoordinator();
86
+ (0, vitest_1.expect)(a.claim("chat-1")).toBe(true);
87
+ (0, vitest_1.expect)(b.claim("chat-1")).toBe(false);
88
+ a.dispose();
89
+ b.dispose();
90
+ });
91
+ (0, vitest_1.it)("supports multiple independent chatIds", () => {
92
+ const a = new chat_tab_coordinator_js_1.ChatTabCoordinator();
93
+ const b = new chat_tab_coordinator_js_1.ChatTabCoordinator();
94
+ a.claim("chat-1");
95
+ b.claim("chat-2");
96
+ (0, vitest_1.expect)(a.isReadOnly("chat-1")).toBe(false);
97
+ (0, vitest_1.expect)(a.isReadOnly("chat-2")).toBe(true);
98
+ (0, vitest_1.expect)(b.isReadOnly("chat-1")).toBe(true);
99
+ (0, vitest_1.expect)(b.isReadOnly("chat-2")).toBe(false);
100
+ a.dispose();
101
+ b.dispose();
102
+ });
103
+ (0, vitest_1.it)("heartbeat timeout clears stale claim from crashed tab", () => {
104
+ const a = new chat_tab_coordinator_js_1.ChatTabCoordinator();
105
+ const b = new chat_tab_coordinator_js_1.ChatTabCoordinator();
106
+ const listener = vitest_1.vi.fn();
107
+ b.addListener(listener);
108
+ a.claim("chat-1");
109
+ (0, vitest_1.expect)(b.isReadOnly("chat-1")).toBe(true);
110
+ // Simulate tab A crashing (close its channel, stop heartbeats)
111
+ a.dispose();
112
+ // Advance past heartbeat timeout (10s)
113
+ vitest_1.vi.advanceTimersByTime(11_000);
114
+ (0, vitest_1.expect)(b.isReadOnly("chat-1")).toBe(false);
115
+ (0, vitest_1.expect)(listener).toHaveBeenCalledWith("chat-1", false);
116
+ b.dispose();
117
+ });
118
+ (0, vitest_1.it)("dispose releases all claims", () => {
119
+ const a = new chat_tab_coordinator_js_1.ChatTabCoordinator();
120
+ const b = new chat_tab_coordinator_js_1.ChatTabCoordinator();
121
+ a.claim("chat-1");
122
+ a.claim("chat-2");
123
+ (0, vitest_1.expect)(b.isReadOnly("chat-1")).toBe(true);
124
+ (0, vitest_1.expect)(b.isReadOnly("chat-2")).toBe(true);
125
+ a.dispose();
126
+ (0, vitest_1.expect)(b.isReadOnly("chat-1")).toBe(false);
127
+ (0, vitest_1.expect)(b.isReadOnly("chat-2")).toBe(false);
128
+ b.dispose();
129
+ });
130
+ (0, vitest_1.it)("gracefully degrades when BroadcastChannel is unavailable", () => {
131
+ vitest_1.vi.stubGlobal("BroadcastChannel", undefined);
132
+ const coord = new chat_tab_coordinator_js_1.ChatTabCoordinator();
133
+ // All operations are no-ops
134
+ (0, vitest_1.expect)(coord.claim("chat-1")).toBe(true);
135
+ (0, vitest_1.expect)(coord.isReadOnly("chat-1")).toBe(false);
136
+ coord.release("chat-1"); // No error
137
+ coord.dispose(); // No error
138
+ });
139
+ });
140
+ //# sourceMappingURL=chat-tab-coordinator.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-tab-coordinator.test.js","sourceRoot":"","sources":["../../../src/v3/chat-tab-coordinator.test.ts"],"names":[],"mappings":"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"}
@@ -222,6 +222,19 @@ type TriggerChatTransportOptionsBase<TClientData = unknown> = {
222
222
  * Receives `chatId` and `runId` so your server action can persist the new PAT keyed by conversation.
223
223
  */
224
224
  renewRunAccessToken?: (params: RenewRunAccessTokenParams) => string | undefined | null | Promise<string | undefined | null>;
225
+ /**
226
+ * Enable multi-tab coordination. When `true`, only one browser tab
227
+ * can send messages to a given chatId at a time. Other tabs enter
228
+ * read-only mode. Uses `BroadcastChannel` for cross-tab communication.
229
+ *
230
+ * Use `transport.isReadOnly(chatId)` or the `useChatTabCoordination` hook
231
+ * to check read-only state and disable the input UI.
232
+ *
233
+ * No-op when `BroadcastChannel` is unavailable (SSR, Node.js).
234
+ *
235
+ * @default false
236
+ */
237
+ multiTab?: boolean;
225
238
  /**
226
239
  * Read-only "watch" mode for observing an existing chat run from the
227
240
  * outside (e.g. a dashboard viewer that wants to show an agent run's
@@ -322,6 +335,7 @@ export declare class TriggerChatTransport implements ChatTransport<UIMessage> {
322
335
  private readonly defaultMetadata;
323
336
  private readonly triggerOptions;
324
337
  private readonly watchMode;
338
+ private coordinator;
325
339
  private _onSessionChange;
326
340
  private renewRunAccessToken;
327
341
  private sessions;
@@ -434,6 +448,34 @@ export declare class TriggerChatTransport implements ChatTransport<UIMessage> {
434
448
  * Update the server-side trigger callback without recreating the transport.
435
449
  */
436
450
  setTriggerTask(fn: ((params: TriggerChatTaskParams) => Promise<TriggerChatTaskResult>) | undefined): void;
451
+ /**
452
+ * Check if a chatId is read-only (another tab is actively sending).
453
+ * Always returns `false` when `multiTab` is not enabled.
454
+ */
455
+ isReadOnly(chatId: string): boolean;
456
+ /**
457
+ * Check if THIS tab currently holds a claim for the chatId
458
+ * (i.e. this tab called sendMessages and the turn is in-flight).
459
+ */
460
+ hasClaim(chatId: string): boolean;
461
+ /**
462
+ * Listen for read-only state changes across tabs.
463
+ * The listener receives `(chatId, isReadOnly)`.
464
+ */
465
+ addReadOnlyListener(fn: (chatId: string, isReadOnly: boolean) => void): void;
466
+ /** Remove a previously added read-only listener. */
467
+ removeReadOnlyListener(fn: (chatId: string, isReadOnly: boolean) => void): void;
468
+ /** Broadcast messages to other tabs for real-time sync. */
469
+ broadcastMessages(chatId: string, messages: unknown[]): void;
470
+ /** Listen for message updates from other tabs. */
471
+ addMessagesListener(fn: (chatId: string, messages: unknown[]) => void): void;
472
+ /** Remove a previously added messages listener. */
473
+ removeMessagesListener(fn: (chatId: string, messages: unknown[]) => void): void;
474
+ /**
475
+ * Clean up resources (BroadcastChannel, heartbeat timers).
476
+ * Call on component unmount when using `multiTab`.
477
+ */
478
+ dispose(): void;
437
479
  /**
438
480
  * Inject or update a session for a chat. Useful for resuming conversations
439
481
  * from persisted state without recreating the transport.