| @@ -787,6 +787,81 @@ describe("CheckpointReactor", () => { · 786 unmodified lines |
| 787 | 787 | ).toBe("v2\n"); |
| 788 | 788 | }); |
| 789 | 789 | |
| 790 | + it("keeps the authoritative turn count when a placeholder is processed late", async () => { |
| 791 | + const harness = await createHarness({ seedFilesystemCheckpoints: false }); |
| 792 | + const threadId = ThreadId.make("thread-1"); |
| 793 | + const createdAt = "2026-01-01T00:00:00.000Z"; |
| 794 | + |
| 795 | + for (const turnNumber of [1, 2]) { |
| 796 | + const turnId = asTurnId(`late-placeholder-turn-${turnNumber}`); |
| 797 | + await harness.runPromise( |
| 798 | + harness.engine.dispatch({ |
| 799 | + type: "thread.turn.start", |
| 800 | + commandId: CommandId.make(`cmd-late-placeholder-user-${turnNumber}`), |
| 801 | + threadId, |
| 802 | + message: { |
| 803 | + messageId: MessageId.make(`late-placeholder-user-${turnNumber}`), |
| 804 | + role: "user", |
| 805 | + text: `User ${turnNumber}`, |
| 806 | + attachments: [], |
| 807 | + }, |
| 808 | + runtimeMode: "approval-required", |
| 809 | + interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, |
| 810 | + createdAt, |
| 811 | + }), |
| 812 | + ); |
| 813 | + await harness.runPromise( |
| 814 | + harness.engine.dispatch({ |
| 815 | + type: "thread.message.assistant.delta", |
| 816 | + commandId: CommandId.make(`cmd-late-placeholder-assistant-delta-${turnNumber}`), |
| 817 | + threadId, |
| 818 | + messageId: MessageId.make(`late-placeholder-assistant-${turnNumber}`), |
| 819 | + delta: `Assistant ${turnNumber}`, |
| 820 | + turnId, |
| 821 | + createdAt, |
| 822 | + }), |
| 823 | + ); |
| 824 | + await harness.runPromise( |
| 825 | + harness.engine.dispatch({ |
| 826 | + type: "thread.message.assistant.complete", |
| 827 | + commandId: CommandId.make(`cmd-late-placeholder-assistant-complete-${turnNumber}`), |
| 828 | + threadId, |
| 829 | + messageId: MessageId.make(`late-placeholder-assistant-${turnNumber}`), |
| 830 | + turnId, |
| 831 | + createdAt, |
| 832 | + }), |
| 833 | + ); |
| 834 | + } |
| 835 | + |
| 836 | + await harness.runPromise( |
| 837 | + harness.engine.dispatch({ |
| 838 | + type: "thread.turn.diff.complete", |
| 839 | + commandId: CommandId.make("cmd-late-placeholder-diff"), |
| 840 | + threadId, |
| 841 | + turnId: asTurnId("late-placeholder-turn-1"), |
| 842 | + completedAt: createdAt, |
| 843 | + checkpointRef: checkpointRefForThreadTurn(threadId, 1), |
| 844 | + status: "missing", |
| 845 | + files: [], |
| 846 | + checkpointTurnCount: 1, |
| 847 | + createdAt, |
| 848 | + }), |
| 849 | + ); |
| 850 | + |
| 851 | + const thread = await waitForThread( |
| 852 | + harness.readModel, |
| 853 | + (entry) => |
| 854 | + entry.checkpoints.some((checkpoint) => checkpoint.checkpointTurnCount === 1) && |
| 855 | + entry.activities.some((activity) => activity.kind === "checkpoint.captured"), |
| 856 | + ); |
| 857 | + expect(thread.checkpoints.some((checkpoint) => checkpoint.checkpointTurnCount === 1)).toBe( |
| 858 | + true, |
| 859 | + ); |
| 860 | + expect(thread.checkpoints.some((checkpoint) => checkpoint.checkpointTurnCount === 2)).toBe( |
| 861 | + false, |
| 862 | + ); |
| 863 | + }); |
| 864 | + |
| 790 | 865 | it("ignores non-v2 checkpoint.captured runtime events", async () => { |
| 791 | 866 | const harness = await createHarness(); |
| 792 | 867 | const createdAt = "2026-01-01T00:00:00.000Z"; |