| @@ -787,6 +787,81 @@ describe("CheckpointReactor", () => { · 786 unmodified lines |
| 787 | ).toBe("v2\n"); | 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 | it("ignores non-v2 checkpoint.captured runtime events", async () => { | 865 | it("ignores non-v2 checkpoint.captured runtime events", async () => { |
| 791 | const harness = await createHarness(); | 866 | const harness = await createHarness(); |
| 792 | const createdAt = "2026-01-01T00:00:00.000Z"; | 867 | const createdAt = "2026-01-01T00:00:00.000Z"; |