{"sha":"9902a08dc5b9e50e524cadfd1620b53252a2b391","node_id":"C_kwDOIiC6AdoAKDk5MDJhMDhkYzViOWU1MGU1MjRjYWRmZDE2MjBiNTMyNTJhMmIzOTE","commit":{"author":{"name":"wei-wei","email":"weiweiwuster714@gmail.com","date":"2026-08-21T19:19:07Z"},"committer":{"name":"wei-wei","email":"weiweiwuster714@gmail.com","date":"2026-08-21T19:19:07Z"},"message":"test: cover new turn after a stop without turn-complete\n\nCo-authored-by: Wei-Wei Wu <wei-wei@momentic.ai>","tree":{"sha":"f384acca476b18759533e2f076866e6a2d7addb1","url":"https://api.github.com/repos/triggerdotdev/trigger.dev/git/trees/f384acca476b18759533e2f076866e6a2d7addb1"},"url":"https://api.github.com/repos/triggerdotdev/trigger.dev/git/commits/9902a08dc5b9e50e524cadfd1620b53252a2b391","comment_count":0,"verification":{"verified":false,"reason":"unsigned","signature":null,"payload":null,"verified_at":null}},"url":"https://api.github.com/repos/triggerdotdev/trigger.dev/commits/9902a08dc5b9e50e524cadfd1620b53252a2b391","html_url":"https://github.com/triggerdotdev/trigger.dev/commit/9902a08dc5b9e50e524cadfd1620b53252a2b391","comments_url":"https://api.github.com/repos/triggerdotdev/trigger.dev/commits/9902a08dc5b9e50e524cadfd1620b53252a2b391/comments","author":{"login":"wuweiweiwu","id":35270620,"node_id":"MDQ6VXNlcjM1MjcwNjIw","avatar_url":"https://avatars.githubusercontent.com/u/35270620?v=4","gravatar_id":"","url":"https://api.github.com/users/wuweiweiwu","html_url":"https://github.com/wuweiweiwu","followers_url":"https://api.github.com/users/wuweiweiwu/followers","following_url":"https://api.github.com/users/wuweiweiwu/following{/other_user}","gists_url":"https://api.github.com/users/wuweiweiwu/gists{/gist_id}","starred_url":"https://api.github.com/users/wuweiweiwu/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/wuweiweiwu/subscriptions","organizations_url":"https://api.github.com/users/wuweiweiwu/orgs","repos_url":"https://api.github.com/users/wuweiweiwu/repos","events_url":"https://api.github.com/users/wuweiweiwu/events{/privacy}","received_events_url":"https://api.github.com/users/wuweiweiwu/received_events","type":"User","user_view_type":"public","site_admin":false},"committer":{"login":"wuweiweiwu","id":35270620,"node_id":"MDQ6VXNlcjM1MjcwNjIw","avatar_url":"https://avatars.githubusercontent.com/u/35270620?v=4","gravatar_id":"","url":"https://api.github.com/users/wuweiweiwu","html_url":"https://github.com/wuweiweiwu","followers_url":"https://api.github.com/users/wuweiweiwu/followers","following_url":"https://api.github.com/users/wuweiweiwu/following{/other_user}","gists_url":"https://api.github.com/users/wuweiweiwu/gists{/gist_id}","starred_url":"https://api.github.com/users/wuweiweiwu/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/wuweiweiwu/subscriptions","organizations_url":"https://api.github.com/users/wuweiweiwu/orgs","repos_url":"https://api.github.com/users/wuweiweiwu/repos","events_url":"https://api.github.com/users/wuweiweiwu/events{/privacy}","received_events_url":"https://api.github.com/users/wuweiweiwu/received_events","type":"User","user_view_type":"public","site_admin":false},"parents":[{"sha":"d721dbdb67a4071180b69456b6c77ae7dea997ce","url":"https://api.github.com/repos/triggerdotdev/trigger.dev/commits/d721dbdb67a4071180b69456b6c77ae7dea997ce","html_url":"https://github.com/triggerdotdev/trigger.dev/commit/d721dbdb67a4071180b69456b6c77ae7dea997ce"}],"stats":{"total":64,"additions":64,"deletions":0},"files":[{"sha":"a35e78ad519791d7d661a7049561328bbc291324","filename":"packages/trigger-sdk/test/chat-transport-events.test.ts","status":"modified","additions":64,"deletions":0,"changes":64,"blob_url":"https://github.com/triggerdotdev/trigger.dev/blob/9902a08dc5b9e50e524cadfd1620b53252a2b391/packages%2Ftrigger-sdk%2Ftest%2Fchat-transport-events.test.ts","raw_url":"https://github.com/triggerdotdev/trigger.dev/raw/9902a08dc5b9e50e524cadfd1620b53252a2b391/packages%2Ftrigger-sdk%2Ftest%2Fchat-transport-events.test.ts","contents_url":"https://api.github.com/repos/triggerdotdev/trigger.dev/contents/packages%2Ftrigger-sdk%2Ftest%2Fchat-transport-events.test.ts?ref=9902a08dc5b9e50e524cadfd1620b53252a2b391","patch":"@@ -174,6 +174,70 @@ describe(\"transport send events\", () => {\n   });\n });\n \n+describe(\"stopped turn followed by a new turn\", () => {\n+  /**\n+   * `.out` stub that honours the `Last-Event-ID` cursor like the server does, so\n+   * a resubscribe cannot replay records the reader already consumed. A stop that\n+   * never saw its turn-complete is therefore unrecoverable unless the new send\n+   * clears the skip state.\n+   */\n+  function cursoredOneTurnTransport() {\n+    const frames = [\n+      { id: \"1\", data: `{\"type\":\"text-delta\",\"id\":\"t1\",\"delta\":\"hello\"}` },\n+      { id: \"2\", data: `{\"type\":\"trigger:turn-complete\"}` },\n+    ];\n+\n+    return makeTransport({\n+      fetch: async (_url, init, ctx) => {\n+        if (ctx.endpoint === \"in\") return jsonOk();\n+\n+        const cursor = new Headers(init.headers).get(\"Last-Event-ID\");\n+        const from = cursor ? frames.findIndex((f) => f.id === cursor) + 1 : 0;\n+        const remaining = frames.slice(from);\n+        const response = sseResponse(\n+          remaining.map((f) => `id: ${f.id}\\ndata: ${f.data}\\n\\n`).join(\"\")\n+        );\n+        // Nothing left to send: the session is settled, so the reader stops\n+        // instead of resubscribing.\n+        if (remaining.length === 0) response.headers.set(\"X-Session-Settled\", \"true\");\n+        return response;\n+      },\n+    });\n+  }\n+\n+  it(\"streams a sendMessages turn after a stop that never saw turn-complete\", async () => {\n+    const { transport, events } = cursoredOneTurnTransport();\n+\n+    expect(await transport.stopGeneration(\"c1\")).toBe(true);\n+    events.length = 0;\n+\n+    const stream = await transport.sendMessages({\n+      trigger: \"submit-message\",\n+      chatId: \"c1\",\n+      messageId: undefined,\n+      messages: [user(\"after stop\", \"u-2\")],\n+      abortSignal: undefined,\n+    });\n+    const chunks = await readAll(stream);\n+\n+    expect(chunks).toEqual([{ type: \"text-delta\", id: \"t1\", delta: \"hello\" }]);\n+    expect(events.some((e) => e.type === \"turn-completed\")).toBe(true);\n+  });\n+\n+  it(\"streams a sendAction turn after a stop that never saw turn-complete\", async () => {\n+    const { transport, events } = cursoredOneTurnTransport();\n+\n+    expect(await transport.stopGeneration(\"c1\")).toBe(true);\n+    events.length = 0;\n+\n+    const stream = await transport.sendAction(\"c1\", { type: \"undo\" });\n+    const chunks = await readAll(stream);\n+\n+    expect(chunks).toEqual([{ type: \"text-delta\", id: \"t1\", delta: \"hello\" }]);\n+    expect(events.some((e) => e.type === \"turn-completed\")).toBe(true);\n+  });\n+});\n+\n describe(\"transport stream events\", () => {\n   it(\"marks reconnectToStream subscriptions as resumed\", async () => {\n     const { transport, events } = makeTransport({"}]}