Skip to content

Commit 95b90f0

Browse files
fix(sessions): dedupe cloud completion notifications (#3572)
Co-authored-by: posthog[bot] <206114724+posthog[bot]@users.noreply.github.com> Co-authored-by: richardsolomou <2622273+richardsolomou@users.noreply.github.com>
1 parent de18c49 commit 95b90f0

2 files changed

Lines changed: 19 additions & 5 deletions

File tree

packages/core/src/sessions/cloudTaskUpdateNotifications.test.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -206,11 +206,14 @@ describe("cloud task update notifications", () => {
206206
});
207207

208208
// Each case applies a sequence of updates to a fresh harness; `expected` is
209-
// the resulting notify count. Snapshots never ring; each live turn_complete
210-
// rings once. Re-delivered stream entries are dropped upstream in
211-
// CloudTaskService by their event id (see cloud-task.test.ts), so a replay
212-
// never reaches this layer.
209+
// the resulting notify count. Snapshots never ring; each armed turn rings at
210+
// most once even if the producer writes duplicate completion entries.
213211
it.each([
212+
{
213+
label: "a completion event without an armed turn",
214+
updates: [logsUpdate([turnComplete()], 1)],
215+
expected: 0,
216+
},
214217
{
215218
label: "a live turn that starts and completes",
216219
updates: [logsUpdate([sessionPrompt(1), turnComplete()], 2)],
@@ -224,6 +227,14 @@ describe("cloud task update notifications", () => {
224227
],
225228
expected: 2,
226229
},
230+
{
231+
label: "duplicate completion events for one turn",
232+
updates: [
233+
logsUpdate([sessionPrompt(1), turnComplete()], 2),
234+
logsUpdate([turnComplete()], 3),
235+
],
236+
expected: 1,
237+
},
227238
{
228239
// Opening a task mid-turn: its session/prompt is already in history and
229240
// only the turn_complete arrives live. The completion must still ring.

packages/core/src/sessions/sessionService.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1947,6 +1947,9 @@ export class SessionService {
19471947
// above. Cloud sessions never see that response.
19481948
const session = this.getSessionByRunId(taskRunId);
19491949
if (session?.isCloud) {
1950+
const completedActiveTurn =
1951+
session.currentPromptId !== null &&
1952+
session.currentPromptId !== undefined;
19501953
const turnStartedAtTs =
19511954
this.liveTurnContent.get(taskRunId)?.startedAtTs ??
19521955
session.promptStartedAt;
@@ -1957,7 +1960,7 @@ export class SessionService {
19571960
});
19581961
if (isLive) {
19591962
// Queued messages will start a new turn — suppress the "done" notification in that case.
1960-
if (session.messageQueue.length === 0) {
1963+
if (completedActiveTurn && session.messageQueue.length === 0) {
19611964
this.d.notifyPromptComplete(
19621965
session.taskTitle,
19631966
"end_turn",

0 commit comments

Comments
 (0)