Skip to content

Commit 14ea61c

Browse files
committed
chore: merge fix/watch-mode-keepalive-tri-13065 (review fixes round 3)
2 parents 76560bb + e24e584 commit 14ea61c

4 files changed

Lines changed: 51 additions & 2 deletions

File tree

.changeset/chat-stream-supersede-race.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
"@trigger.dev/sdk": patch
33
---
44

5-
Fixed a race where quickly restarting a chat stream could break stop and reconnect for the new stream.
5+
Fixed a race where quickly restarting a chat stream could break stop and reconnect for the new stream. Stopping a chat now also hands it back to your other tabs instead of leaving them read-only.

apps/webapp/app/components/dashboard-agent/demo/fixtures/watches.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ export const demoErrorRecurrenceWatch = watch(
6868
"email-sends",
6969
{
7070
kind: "error_recurrence",
71-
fingerprint: DEMO_WORLD.errorFingerprint,
71+
// The page cites `error_<fingerprint>`, the spec keeps the bare form.
72+
fingerprint: DEMO_WORLD.errorFingerprint.replace(/^error_/, ""),
7273
note: "Tell me if the rate-limit error comes back.",
7374
maxHours: 12,
7475
checkEveryMinutes: 15,

packages/trigger-sdk/src/v3/chat.test.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1657,6 +1657,49 @@ describe("TriggerChatTransport", () => {
16571657
vi.useRealTimers();
16581658
}
16591659
});
1660+
1661+
it("releases the tab claim when the user stops generation (multi-tab)", async () => {
1662+
vi.useFakeTimers();
1663+
try {
1664+
global.fetch = vi.fn().mockImplementation(async (url: string | URL, init?: RequestInit) => {
1665+
const urlStr = typeof url === "string" ? url : url.toString();
1666+
if (isSessionStreamAppendUrl(urlStr)) return defaultAppendResponse();
1667+
if (isSessionOutSubscribeUrl(urlStr)) return openSseResponse(init?.signal);
1668+
throw new Error(`Unexpected URL: ${urlStr}`);
1669+
});
1670+
1671+
const transport = new TriggerChatTransport({
1672+
task: "my-chat-task",
1673+
accessToken: () => "pat",
1674+
multiTab: true,
1675+
sessions: { "chat-stop-tab": { publicAccessToken: "p", isStreaming: true } },
1676+
});
1677+
1678+
const drain = drainChunks(
1679+
await transport.sendMessages({
1680+
trigger: "submit-message" as const,
1681+
chatId: "chat-stop-tab",
1682+
messageId: undefined,
1683+
messages: [createUserMessage("hi")],
1684+
abortSignal: undefined,
1685+
})
1686+
);
1687+
await vi.advanceTimersByTimeAsync(1_000);
1688+
expect(transport.hasClaim("chat-stop-tab")).toBe(true);
1689+
1690+
expect(await transport.stopGeneration("chat-stop-tab")).toBe(true);
1691+
await vi.advanceTimersByTimeAsync(1_000);
1692+
1693+
// The turn ends here with no successor stream, so the claim must be
1694+
// freed or other tabs stay read-only until this one closes.
1695+
expect(transport.hasClaim("chat-stop-tab")).toBe(false);
1696+
1697+
transport.dispose();
1698+
await drain;
1699+
} finally {
1700+
vi.useRealTimers();
1701+
}
1702+
});
16601703
});
16611704

16621705
describe("multi-tab coordination", () => {

packages/trigger-sdk/src/v3/chat.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1218,6 +1218,11 @@ export class TriggerChatTransport implements ChatTransport<UIMessage> {
12181218
activeStream.abort();
12191219
this.activeStreams.delete(chatId);
12201220
}
1221+
// Release here, not in the stream teardown: that only releases while it
1222+
// still owns the map entry, and we just deleted it. Unlike a supersede,
1223+
// no successor stream follows a stop, so the claim would never be freed
1224+
// and other tabs would stay read-only until this one closes.
1225+
this.coordinator?.release(chatId);
12211226

12221227
// The turn won't reach its turn-complete on this client (we just
12231228
// aborted the reader), so clear the streaming flag here and persist —

0 commit comments

Comments
 (0)