authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-03-05 20:54:50-08:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-03-05 23:00:41-08:00
loga1b75fe17fee89a45d2a4d3688ac2b52e1fdac92
treefab7cb054108bcaa394cbf81f5e60080b2a3afb8
parent2e5e75699ef57f93967dffa64be9a70a7eec63a4

test: move Claude coverage out of core PR

Co-authored-by: codex <codex@users.noreply.github.com>

6 files changed, 19 insertions(+), 319 deletions(-)

apps/server/src/orchestration/Layers/CheckpointReactor.test.ts+5-138
......@@ -57,7 +57,7 @@ function createProviderServiceHarness(
5757 cwd: string,
5858 hasSession = true,
5959 sessionCwd = cwd,
60 providerName: "codex" | "claudeCode" = "codex",
60 providerName: ProviderSession["provider"] = "codex",
6161) {
6262 const now = new Date().toISOString();
6363 const runtimeEventPubSub = Effect.runSync(PubSub.unbounded<ProviderRuntimeEvent>());
......@@ -235,7 +235,6 @@ describe("CheckpointReactor", () => {
235235 readonly projectWorkspaceRoot?: string;
236236 readonly threadWorktreePath?: string | null;
237237 readonly providerSessionCwd?: string;
238 readonly providerName?: "codex" | "claudeCode";
239238 }) {
240239 const cwd = createGitRepository();
241240 tempDirs.push(cwd);
......@@ -243,7 +242,7 @@ describe("CheckpointReactor", () => {
243242 cwd,
244243 options?.hasSession ?? true,
245244 options?.providerSessionCwd ?? cwd,
246 options?.providerName ?? "codex",
245 "codex",
247246 );
248247 const orchestrationLayer = OrchestrationEngineLive.pipe(
249248 Layer.provide(OrchestrationProjectionPipelineLive),
......@@ -476,69 +475,6 @@ describe("CheckpointReactor", () => {
476475 expect(thread.checkpoints[0]?.checkpointTurnCount).toBe(1);
477476 });
478477
479 it("captures pre-turn and completion checkpoints for claudeCode runtime events", async () => {
480 const harness = await createHarness({
481 seedFilesystemCheckpoints: false,
482 providerName: "claudeCode",
483 });
484 const createdAt = new Date().toISOString();
485
486 await Effect.runPromise(
487 harness.engine.dispatch({
488 type: "thread.session.set",
489 commandId: CommandId.makeUnsafe("cmd-session-set-capture-claude"),
490 threadId: ThreadId.makeUnsafe("thread-1"),
491 session: {
492 threadId: ThreadId.makeUnsafe("thread-1"),
493 status: "ready",
494 providerName: "claudeCode",
495 runtimeMode: "approval-required",
496 activeTurnId: null,
497 lastError: null,
498 updatedAt: createdAt,
499 },
500 createdAt,
501 }),
502 );
503
504 harness.provider.emit({
505 type: "turn.started",
506 eventId: EventId.makeUnsafe("evt-turn-started-claude-1"),
507 provider: "claudeCode",
508
509 createdAt: new Date().toISOString(),
510 threadId: ThreadId.makeUnsafe("thread-1"),
511 turnId: asTurnId("turn-claude-1"),
512 });
513 await waitForGitRefExists(
514 harness.cwd,
515 checkpointRefForThreadTurn(ThreadId.makeUnsafe("thread-1"), 0),
516 );
517
518 fs.writeFileSync(path.join(harness.cwd, "README.md"), "v2\n", "utf8");
519 harness.provider.emit({
520 type: "turn.completed",
521 eventId: EventId.makeUnsafe("evt-turn-completed-claude-1"),
522 provider: "claudeCode",
523
524 createdAt: new Date().toISOString(),
525 threadId: ThreadId.makeUnsafe("thread-1"),
526 turnId: asTurnId("turn-claude-1"),
527 payload: { state: "completed" },
528 });
529
530 await waitForEvent(harness.engine, (event) => event.type === "thread.turn-diff-completed");
531 const thread = await waitForThread(
532 harness.engine,
533 (entry) => entry.latestTurn?.turnId === "turn-claude-1" && entry.checkpoints.length === 1,
534 );
535
536 expect(thread.checkpoints[0]?.checkpointTurnCount).toBe(1);
537 expect(
538 gitRefExists(harness.cwd, checkpointRefForThreadTurn(ThreadId.makeUnsafe("thread-1"), 1)),
539 ).toBe(true);
540 });
541
542478 it("appends capture failure activity when turn diff summary cannot be derived", async () => {
543479 const harness = await createHarness({ seedFilesystemCheckpoints: false });
544480 const createdAt = new Date().toISOString();
......@@ -845,7 +781,7 @@ describe("CheckpointReactor", () => {
845781 expect(thread.checkpoints[0]?.checkpointTurnCount).toBe(1);
846782 expect(harness.provider.rollbackConversation).toHaveBeenCalledTimes(1);
847783 expect(harness.provider.rollbackConversation).toHaveBeenCalledWith({
848
784 threadId: ThreadId.makeUnsafe("thread-1"),
849785 numTurns: 1,
850786 });
851787 expect(fs.readFileSync(path.join(harness.cwd, "README.md"), "utf8")).toBe("v2\n");
......@@ -854,75 +790,6 @@ describe("CheckpointReactor", () => {
854790 ).toBe(false);
855791 });
856792
857 it("executes provider revert and emits thread.reverted for claudeCode sessions", async () => {
858 const harness = await createHarness({ providerName: "claudeCode" });
859 const createdAt = new Date().toISOString();
860
861 await Effect.runPromise(
862 harness.engine.dispatch({
863 type: "thread.session.set",
864 commandId: CommandId.makeUnsafe("cmd-session-set-claude"),
865 threadId: ThreadId.makeUnsafe("thread-1"),
866 session: {
867 threadId: ThreadId.makeUnsafe("thread-1"),
868 status: "ready",
869 providerName: "claudeCode",
870 runtimeMode: "approval-required",
871 activeTurnId: null,
872 lastError: null,
873 updatedAt: createdAt,
874 },
875 createdAt,
876 }),
877 );
878
879 await Effect.runPromise(
880 harness.engine.dispatch({
881 type: "thread.turn.diff.complete",
882 commandId: CommandId.makeUnsafe("cmd-diff-claude-1"),
883 threadId: ThreadId.makeUnsafe("thread-1"),
884 turnId: asTurnId("turn-claude-1"),
885 completedAt: createdAt,
886 checkpointRef: checkpointRefForThreadTurn(ThreadId.makeUnsafe("thread-1"), 1),
887 status: "ready",
888 files: [],
889 checkpointTurnCount: 1,
890 createdAt,
891 }),
892 );
893 await Effect.runPromise(
894 harness.engine.dispatch({
895 type: "thread.turn.diff.complete",
896 commandId: CommandId.makeUnsafe("cmd-diff-claude-2"),
897 threadId: ThreadId.makeUnsafe("thread-1"),
898 turnId: asTurnId("turn-claude-2"),
899 completedAt: createdAt,
900 checkpointRef: checkpointRefForThreadTurn(ThreadId.makeUnsafe("thread-1"), 2),
901 status: "ready",
902 files: [],
903 checkpointTurnCount: 2,
904 createdAt,
905 }),
906 );
907
908 await Effect.runPromise(
909 harness.engine.dispatch({
910 type: "thread.checkpoint.revert",
911 commandId: CommandId.makeUnsafe("cmd-revert-request-claude"),
912 threadId: ThreadId.makeUnsafe("thread-1"),
913 turnCount: 1,
914 createdAt,
915 }),
916 );
917
918 await waitForEvent(harness.engine, (event) => event.type === "thread.reverted");
919 expect(harness.provider.rollbackConversation).toHaveBeenCalledTimes(1);
920 expect(harness.provider.rollbackConversation).toHaveBeenCalledWith({
921
922 numTurns: 1,
923 });
924 });
925
926793 it("processes consecutive revert requests with deterministic rollback sequencing", async () => {
927794 const harness = await createHarness();
928795 const createdAt = new Date().toISOString();
......@@ -1008,11 +875,11 @@ describe("CheckpointReactor", () => {
1008875
1009876 expect(harness.provider.rollbackConversation).toHaveBeenCalledTimes(2);
1010877 expect(harness.provider.rollbackConversation.mock.calls[0]?.[0]).toEqual({
1011
878 threadId: ThreadId.makeUnsafe("thread-1"),
1012879 numTurns: 1,
1013880 });
1014881 expect(harness.provider.rollbackConversation.mock.calls[1]?.[0]).toEqual({
1015
882 threadId: ThreadId.makeUnsafe("thread-1"),
1016883 numTurns: 1,
1017884 });
1018885 });
apps/server/src/orchestration/Layers/ProjectionPipeline.test.ts+10
......@@ -102,6 +102,7 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => {
102102 projectId: ProjectId.makeUnsafe("project-1"),
103103 title: "Thread 1",
104104 model: "gpt-5-codex",
105 runtimeMode: "full-access",
105106 branch: null,
106107 worktreePath: null,
107108 createdAt: now,
......@@ -372,6 +373,7 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => {
372373 projectId: ProjectId.makeUnsafe("project-clear-attachments"),
373374 title: "Thread Clear Attachments",
374375 model: "gpt-5-codex",
376 runtimeMode: "full-access",
375377 branch: null,
376378 worktreePath: null,
377379 createdAt: now,
......@@ -498,6 +500,7 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => {
498500 projectId: ProjectId.makeUnsafe("project-overwrite"),
499501 title: "Thread Overwrite",
500502 model: "gpt-5-codex",
503 runtimeMode: "full-access",
501504 branch: null,
502505 worktreePath: null,
503506 createdAt: now,
......@@ -648,6 +651,7 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => {
648651 projectId: ProjectId.makeUnsafe("project-rollback"),
649652 title: "Thread Rollback",
650653 model: "gpt-5-codex",
654 runtimeMode: "full-access",
651655 branch: null,
652656 worktreePath: null,
653657 createdAt: now,
......@@ -778,6 +782,7 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => {
778782 projectId: ProjectId.makeUnsafe("project-revert-files"),
779783 title: "Thread Revert Files",
780784 model: "gpt-5-codex",
785 runtimeMode: "full-access",
781786 branch: null,
782787 worktreePath: null,
783788 createdAt: now,
......@@ -992,6 +997,7 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => {
992997 projectId: ProjectId.makeUnsafe("project-delete-files"),
993998 title: "Thread Delete Files",
994999 model: "gpt-5-codex",
1000 runtimeMode: "full-access",
9951001 branch: null,
9961002 worktreePath: null,
9971003 createdAt: now,
......@@ -1154,6 +1160,7 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => {
11541160 projectId: ProjectId.makeUnsafe("project-a"),
11551161 title: "Thread A",
11561162 model: "gpt-5-codex",
1163 runtimeMode: "full-access",
11571164 branch: null,
11581165 worktreePath: null,
11591166 createdAt: now,
......@@ -1277,6 +1284,7 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => {
12771284 projectId: ProjectId.makeUnsafe("project-empty"),
12781285 title: "Thread Empty",
12791286 model: "gpt-5-codex",
1287 runtimeMode: "full-access",
12801288 branch: null,
12811289 worktreePath: null,
12821290 createdAt: now,
......@@ -1413,6 +1421,7 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => {
14131421 projectId: ProjectId.makeUnsafe("project-conflict"),
14141422 title: "Thread Conflict",
14151423 model: "gpt-5-codex",
1424 runtimeMode: "full-access",
14161425 branch: null,
14171426 worktreePath: null,
14181427 createdAt: "2026-02-26T13:00:01.000Z",
......@@ -1553,6 +1562,7 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => {
15531562 projectId: ProjectId.makeUnsafe("project-revert"),
15541563 title: "Thread Revert",
15551564 model: "gpt-5-codex",
1565 runtimeMode: "full-access",
15561566 branch: null,
15571567 worktreePath: null,
15581568 createdAt: "2026-02-26T12:00:01.000Z",
apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts-98
......@@ -94,7 +94,6 @@ describe("ProviderCommandReactor", () => {
9494 input !== null &&
9595 "provider" in input &&
9696 (input.provider === "codex" ||
97 input.provider === "claudeCode" ||
9897 input.provider === "cursor")
9998 ? input.provider
10099 : "codex";
......@@ -385,43 +384,6 @@ describe("ProviderCommandReactor", () => {
385384 });
386385 });
387386
388 it("starts first turn with requested provider when provider is specified", async () => {
389 const harness = await createHarness();
390 const now = new Date().toISOString();
391
392 await Effect.runPromise(
393 harness.engine.dispatch({
394 type: "thread.turn.start",
395 commandId: CommandId.makeUnsafe("cmd-turn-start-provider-first"),
396 threadId: ThreadId.makeUnsafe("thread-1"),
397 message: {
398 messageId: asMessageId("user-message-provider-first"),
399 role: "user",
400 text: "hello claude",
401 attachments: [],
402 },
403 provider: "claudeCode",
404 interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE,
405 runtimeMode: "approval-required",
406 createdAt: now,
407 }),
408 );
409
410 await waitFor(() => harness.startSession.mock.calls.length === 1);
411 await waitFor(() => harness.sendTurn.mock.calls.length === 1);
412 expect(harness.startSession.mock.calls[0]?.[1]).toMatchObject({
413 provider: "claudeCode",
414 cwd: "/tmp/provider-project",
415 model: "gpt-5-codex",
416 runtimeMode: "approval-required",
417 });
418
419 const readModel = await Effect.runPromise(harness.engine.getReadModel());
420 const thread = readModel.threads.find((entry) => entry.id === ThreadId.makeUnsafe("thread-1"));
421 expect(thread?.session?.providerName).toBe("claudeCode");
422 expect(thread?.session?.threadId).toBe("thread-1");
423 });
424
425387 it("starts first turn with cursor provider when provider is specified", async () => {
426388 const harness = await createHarness();
427389 const now = new Date().toISOString();
......@@ -695,66 +657,6 @@ describe("ProviderCommandReactor", () => {
695657 expect(thread?.session?.runtimeMode).toBe("approval-required");
696658 });
697659
698 it("switches provider by restarting the session when turn request provider changes", async () => {
699 const harness = await createHarness();
700 const now = new Date().toISOString();
701
702 await Effect.runPromise(
703 harness.engine.dispatch({
704 type: "thread.turn.start",
705 commandId: CommandId.makeUnsafe("cmd-turn-start-provider-switch-1"),
706 threadId: ThreadId.makeUnsafe("thread-1"),
707 message: {
708 messageId: asMessageId("user-message-provider-switch-1"),
709 role: "user",
710 text: "first",
711 attachments: [],
712 },
713 interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE,
714 runtimeMode: "approval-required",
715 createdAt: now,
716 }),
717 );
718
719 await waitFor(() => harness.startSession.mock.calls.length === 1);
720 await waitFor(() => harness.sendTurn.mock.calls.length === 1);
721
722 await Effect.runPromise(
723 harness.engine.dispatch({
724 type: "thread.turn.start",
725 commandId: CommandId.makeUnsafe("cmd-turn-start-provider-switch-2"),
726 threadId: ThreadId.makeUnsafe("thread-1"),
727 message: {
728 messageId: asMessageId("user-message-provider-switch-2"),
729 role: "user",
730 text: "second",
731 attachments: [],
732 },
733 provider: "claudeCode",
734 interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE,
735 runtimeMode: "approval-required",
736 createdAt: now,
737 }),
738 );
739
740 await waitFor(() => harness.startSession.mock.calls.length === 2);
741 await waitFor(() => harness.sendTurn.mock.calls.length === 2);
742
743 expect(harness.stopSession.mock.calls.length).toBe(0);
744 expect(harness.startSession.mock.calls[1]?.[1]).toMatchObject({
745 threadId: ThreadId.makeUnsafe("thread-1"),
746 provider: "claudeCode",
747 runtimeMode: "approval-required",
748 });
749 expect(harness.startSession.mock.calls[1]?.[1]).not.toHaveProperty("resumeCursor");
750
751 const readModel = await Effect.runPromise(harness.engine.getReadModel());
752 const thread = readModel.threads.find((entry) => entry.id === ThreadId.makeUnsafe("thread-1"));
753 expect(thread?.session?.threadId).toBe("thread-1");
754 expect(thread?.session?.providerName).toBe("claudeCode");
755 expect(thread?.session?.runtimeMode).toBe("approval-required");
756 });
757
758660 it("does not stop the active session when restart fails before rebind", async () => {
759661 const harness = await createHarness();
760662 const now = new Date().toISOString();
apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts-54
......@@ -437,60 +437,6 @@ describe("ProviderRuntimeIngestion", () => {
437437 );
438438 });
439439
440 it("accepts claude turn lifecycle when seeded thread id is a synthetic placeholder", async () => {
441 const harness = await createHarness();
442 const seededAt = new Date().toISOString();
443
444 await Effect.runPromise(
445 harness.engine.dispatch({
446 type: "thread.session.set",
447 commandId: CommandId.makeUnsafe("cmd-session-seed-claude-placeholder"),
448 threadId: ThreadId.makeUnsafe("thread-1"),
449 session: {
450 threadId: ThreadId.makeUnsafe("thread-1"),
451 status: "ready",
452 providerName: "claudeCode",
453 runtimeMode: "approval-required",
454 activeTurnId: null,
455 updatedAt: seededAt,
456 lastError: null,
457 },
458 createdAt: seededAt,
459 }),
460 );
461
462 harness.emit({
463 type: "turn.started",
464 eventId: asEventId("evt-turn-started-claude-placeholder"),
465 provider: "claudeCode",
466 createdAt: new Date().toISOString(),
467 threadId: asThreadId("thread-1"),
468 turnId: asTurnId("turn-claude-placeholder"),
469 });
470
471 await waitForThread(
472 harness.engine,
473 (thread) =>
474 thread.session?.status === "running" &&
475 thread.session?.activeTurnId === "turn-claude-placeholder",
476 );
477
478 harness.emit({
479 type: "turn.completed",
480 eventId: asEventId("evt-turn-completed-claude-placeholder"),
481 provider: "claudeCode",
482 createdAt: new Date().toISOString(),
483 threadId: asThreadId("thread-1"),
484 turnId: asTurnId("turn-claude-placeholder"),
485 status: "completed",
486 });
487
488 await waitForThread(
489 harness.engine,
490 (thread) => thread.session?.status === "ready" && thread.session?.activeTurnId === null,
491 );
492 });
493
494440 it("ignores non-active turn completion when runtime omits thread id", async () => {
495441 const harness = await createHarness();
496442 const now = new Date().toISOString();
apps/server/src/orchestration/projector.test.ts+2
......@@ -75,6 +75,7 @@ describe("orchestration projector", () => {
7575 title: "demo",
7676 model: "gpt-5-codex",
7777 runtimeMode: "full-access",
78 interactionMode: "default",
7879 branch: null,
7980 worktreePath: null,
8081 latestTurn: null,
......@@ -82,6 +83,7 @@ describe("orchestration projector", () => {
8283 updatedAt: now,
8384 deletedAt: null,
8485 messages: [],
86 proposedPlans: [],
8587 activities: [],
8688 checkpoints: [],
8789 session: null,
apps/server/src/provider/Layers/ProviderService.test.ts+2-29
......@@ -216,15 +216,12 @@ const sleep = (ms: number) =>
216216
217217function makeProviderServiceLayer() {
218218 const codex = makeFakeCodexAdapter();
219 const claude = makeFakeCodexAdapter("claudeCode");
220219 const registry: typeof ProviderAdapterRegistry.Service = {
221220 getByProvider: (provider) =>
222221 provider === "codex"
223222 ? Effect.succeed(codex.adapter)
224 : provider === "claudeCode"
225 ? Effect.succeed(claude.adapter)
226 : Effect.fail(new ProviderUnsupportedError({ provider })),
227 listProviders: () => Effect.succeed(["codex", "claudeCode"]),
223 : Effect.fail(new ProviderUnsupportedError({ provider })),
224 listProviders: () => Effect.succeed(["codex"]),
228225 };
229226
230227 const providerAdapterLayer = Layer.succeed(ProviderAdapterRegistry, registry);
......@@ -247,7 +244,6 @@ function makeProviderServiceLayer() {
247244
248245 return {
249246 codex,
250 claude,
251247 layer,
252248 };
253249}
......@@ -496,29 +492,6 @@ routing.layer("ProviderServiceLive routing", (it) => {
496492 }),
497493 );
498494
499 it.effect("routes explicit claudeCode provider session starts to the claude adapter", () =>
500 Effect.gen(function* () {
501 const provider = yield* ProviderService;
502
503 const session = yield* provider.startSession(asThreadId("thread-claude"), {
504 provider: "claudeCode",
505 threadId: asThreadId("thread-claude"),
506 cwd: "/tmp/project-claude",
507 runtimeMode: "full-access",
508 });
509
510 assert.equal(session.provider, "claudeCode");
511 assert.equal(routing.claude.startSession.mock.calls.length, 1);
512 const startInput = routing.claude.startSession.mock.calls[0]?.[0];
513 assert.equal(typeof startInput === "object" && startInput !== null, true);
514 if (startInput && typeof startInput === "object") {
515 const startPayload = startInput as { provider?: string; cwd?: string };
516 assert.equal(startPayload.provider, "claudeCode");
517 assert.equal(startPayload.cwd, "/tmp/project-claude");
518 }
519 }),
520 );
521
522495 it.effect("recovers stale persisted sessions for rollback by resuming thread identity", () =>
523496 Effect.gen(function* () {
524497 const provider = yield* ProviderService;