| ... | ... | @@ -295,6 +295,7 @@ describe("CheckpointReactor", () => { |
| 295 | 295 | readonly checkpointLookupFailure?: ( |
| 296 | 296 | cwd: string, |
| 297 | 297 | ) => VcsProcessTimeoutError | VcsProcessSpawnError | undefined; |
| 298 | readonly workspaceRefresh?: (cwd: string) => Effect.Effect<void>; |
| 298 | 299 | readonly hasSession?: boolean; |
| 299 | 300 | readonly seedFilesystemCheckpoints?: boolean; |
| 300 | 301 | readonly initializeGit?: boolean; |
| ... | ... | @@ -390,10 +391,10 @@ describe("CheckpointReactor", () => { |
| 390 | 391 | ).pipe(Layer.provide(VcsDriverRegistry.layer)), |
| 391 | 392 | ), |
| 392 | 393 | Layer.provideMerge( |
| 393 | | WorkspaceEntries.layer.pipe( |
| 394 | | Layer.provide(WorkspacePaths.layer), |
| 395 | | Layer.provideMerge(VcsDriverRegistry.layer), |
| 396 | | ), |
| 394 | (options?.workspaceRefresh |
| 395 | ? Layer.mock(WorkspaceEntries.WorkspaceEntries)({ refresh: options.workspaceRefresh }) |
| 396 | : WorkspaceEntries.layer |
| 397 | ).pipe(Layer.provide(WorkspacePaths.layer), Layer.provideMerge(VcsDriverRegistry.layer)), |
| 397 | 398 | ), |
| 398 | 399 | Layer.provideMerge(WorkspacePaths.layer), |
| 399 | 400 | Layer.provideMerge(VcsProcess.layer), |
| ... | ... | @@ -664,6 +665,99 @@ describe("CheckpointReactor", () => { |
| 664 | 665 | }), |
| 665 | 666 | ); |
| 666 | 667 | |
| 668 | effectIt.effect( |
| 669 | "finalizes checkpoints in both workspaces while entry refresh is blocked and coalesces later scans", |
| 670 | () => |
| 671 | Effect.gen(function* () { |
| 672 | const entered = yield* Deferred.make<void>(); |
| 673 | const release = yield* Deferred.make<void>(); |
| 674 | const refreshCalls: string[] = []; |
| 675 | let secondCwd = ""; |
| 676 | const harness = yield* Effect.promise(() => |
| 677 | createHarness({ |
| 678 | seedFilesystemCheckpoints: false, |
| 679 | secondThreadSharingWorktree: true, |
| 680 | secondThreadWorktreePath: () => { |
| 681 | secondCwd = createGitRepository(); |
| 682 | tempDirs.push(secondCwd); |
| 683 | return secondCwd; |
| 684 | }, |
| 685 | workspaceRefresh: (cwd) => |
| 686 | Effect.gen(function* () { |
| 687 | refreshCalls.push(cwd); |
| 688 | if (refreshCalls.length === 1) { |
| 689 | yield* Deferred.succeed(entered, undefined); |
| 690 | yield* Deferred.await(release); |
| 691 | } |
| 692 | }), |
| 693 | }), |
| 694 | ); |
| 695 | for (const [index, threadId] of [ |
| 696 | "thread-1", |
| 697 | "thread-2", |
| 698 | "thread-1", |
| 699 | "thread-1", |
| 700 | "thread-1", |
| 701 | ].entries()) { |
| 702 | const turnId = asTurnId(`turn-refresh-${index}`); |
| 703 | const id = ThreadId.make(threadId); |
| 704 | harness.provider.emit({ |
| 705 | type: "turn.started", |
| 706 | eventId: EventId.make(`evt-refresh-start-${index}`), |
| 707 | provider: ProviderDriverKind.make("codex"), |
| 708 | createdAt: "2026-01-01T00:00:00.000Z", |
| 709 | threadId: id, |
| 710 | turnId, |
| 711 | }); |
| 712 | if (index < 2) |
| 713 | expect(yield* harness.nextReceipt).toMatchObject({ |
| 714 | type: "checkpoint.baseline.captured", |
| 715 | threadId: id, |
| 716 | }); |
| 717 | const cwd = threadId === "thread-1" ? harness.cwd : secondCwd; |
| 718 | NodeFS.writeFileSync(NodePath.join(cwd, "README.md"), `snapshot ${index}\n`); |
| 719 | harness.provider.emit({ |
| 720 | type: "turn.completed", |
| 721 | eventId: EventId.make(`evt-refresh-complete-${index}`), |
| 722 | provider: ProviderDriverKind.make("codex"), |
| 723 | createdAt: "2026-01-01T00:00:01.000Z", |
| 724 | threadId: id, |
| 725 | turnId, |
| 726 | payload: { state: "completed" }, |
| 727 | }); |
| 728 | expect(yield* harness.nextReceipt).toMatchObject({ |
| 729 | type: "checkpoint.diff.finalized", |
| 730 | threadId: id, |
| 731 | turnId, |
| 732 | }); |
| 733 | expect(yield* harness.nextReceipt).toMatchObject({ |
| 734 | type: "turn.processing.quiesced", |
| 735 | threadId: id, |
| 736 | turnId, |
| 737 | }); |
| 738 | if (index === 0) yield* Deferred.await(entered); |
| 739 | } |
| 740 | expect(refreshCalls).toEqual([harness.cwd]); |
| 741 | expect( |
| 742 | gitShowFileAtRef( |
| 743 | harness.cwd, |
| 744 | checkpointRefForThreadTurn(ThreadId.make("thread-1"), 4), |
| 745 | "README.md", |
| 746 | ), |
| 747 | ).toBe("snapshot 4\n"); |
| 748 | expect( |
| 749 | gitShowFileAtRef( |
| 750 | secondCwd, |
| 751 | checkpointRefForThreadTurn(ThreadId.make("thread-2"), 1), |
| 752 | "README.md", |
| 753 | ), |
| 754 | ).toBe("snapshot 1\n"); |
| 755 | yield* Deferred.succeed(release, undefined); |
| 756 | yield* Effect.promise(harness.drain); |
| 757 | expect(refreshCalls).toEqual([harness.cwd, secondCwd, harness.cwd]); |
| 758 | }), |
| 759 | ); |
| 760 | |
| 667 | 761 | effectIt.effect("captures baseline and large turn summaries before completion receipts", () => |
| 668 | 762 | Effect.gen(function* () { |
| 669 | 763 | const harness = yield* Effect.promise(() => |