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(...@@ -57,7 +57,7 @@ function createProviderServiceHarness(
57 cwd: string,57 cwd: string,
58 hasSession = true,58 hasSession = true,
59 sessionCwd = cwd,59 sessionCwd = cwd,
60 providerName: "codex" | "claudeCode" = "codex",60 providerName: ProviderSession["provider"] = "codex",
61) {61) {
62 const now = new Date().toISOString();62 const now = new Date().toISOString();
63 const runtimeEventPubSub = Effect.runSync(PubSub.unbounded<ProviderRuntimeEvent>());63 const runtimeEventPubSub = Effect.runSync(PubSub.unbounded<ProviderRuntimeEvent>());
...@@ -235,7 +235,6 @@ describe("CheckpointReactor", () => {...@@ -235,7 +235,6 @@ describe("CheckpointReactor", () => {
235 readonly projectWorkspaceRoot?: string;235 readonly projectWorkspaceRoot?: string;
236 readonly threadWorktreePath?: string | null;236 readonly threadWorktreePath?: string | null;
237 readonly providerSessionCwd?: string;237 readonly providerSessionCwd?: string;
238 readonly providerName?: "codex" | "claudeCode";
239 }) {238 }) {
240 const cwd = createGitRepository();239 const cwd = createGitRepository();
241 tempDirs.push(cwd);240 tempDirs.push(cwd);
...@@ -243,7 +242,7 @@ describe("CheckpointReactor", () => {...@@ -243,7 +242,7 @@ describe("CheckpointReactor", () => {
243 cwd,242 cwd,
244 options?.hasSession ?? true,243 options?.hasSession ?? true,
245 options?.providerSessionCwd ?? cwd,244 options?.providerSessionCwd ?? cwd,
246 options?.providerName ?? "codex",245 "codex",
247 );246 );
248 const orchestrationLayer = OrchestrationEngineLive.pipe(247 const orchestrationLayer = OrchestrationEngineLive.pipe(
249 Layer.provide(OrchestrationProjectionPipelineLive),248 Layer.provide(OrchestrationProjectionPipelineLive),
...@@ -476,69 +475,6 @@ describe("CheckpointReactor", () => {...@@ -476,69 +475,6 @@ describe("CheckpointReactor", () => {
476 expect(thread.checkpoints[0]?.checkpointTurnCount).toBe(1);475 expect(thread.checkpoints[0]?.checkpointTurnCount).toBe(1);
477 });476 });
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
542 it("appends capture failure activity when turn diff summary cannot be derived", async () => {478 it("appends capture failure activity when turn diff summary cannot be derived", async () => {
543 const harness = await createHarness({ seedFilesystemCheckpoints: false });479 const harness = await createHarness({ seedFilesystemCheckpoints: false });
544 const createdAt = new Date().toISOString();480 const createdAt = new Date().toISOString();
...@@ -845,7 +781,7 @@ describe("CheckpointReactor", () => {...@@ -845,7 +781,7 @@ describe("CheckpointReactor", () => {
845 expect(thread.checkpoints[0]?.checkpointTurnCount).toBe(1);781 expect(thread.checkpoints[0]?.checkpointTurnCount).toBe(1);
846 expect(harness.provider.rollbackConversation).toHaveBeenCalledTimes(1);782 expect(harness.provider.rollbackConversation).toHaveBeenCalledTimes(1);
847 expect(harness.provider.rollbackConversation).toHaveBeenCalledWith({783 expect(harness.provider.rollbackConversation).toHaveBeenCalledWith({
848 784 threadId: ThreadId.makeUnsafe("thread-1"),
849 numTurns: 1,785 numTurns: 1,
850 });786 });
851 expect(fs.readFileSync(path.join(harness.cwd, "README.md"), "utf8")).toBe("v2\n");787 expect(fs.readFileSync(path.join(harness.cwd, "README.md"), "utf8")).toBe("v2\n");
...@@ -854,75 +790,6 @@ describe("CheckpointReactor", () => {...@@ -854,75 +790,6 @@ describe("CheckpointReactor", () => {
854 ).toBe(false);790 ).toBe(false);
855 });791 });
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
926 it("processes consecutive revert requests with deterministic rollback sequencing", async () => {793 it("processes consecutive revert requests with deterministic rollback sequencing", async () => {
927 const harness = await createHarness();794 const harness = await createHarness();
928 const createdAt = new Date().toISOString();795 const createdAt = new Date().toISOString();
...@@ -1008,11 +875,11 @@ describe("CheckpointReactor", () => {...@@ -1008,11 +875,11 @@ describe("CheckpointReactor", () => {
1008875
1009 expect(harness.provider.rollbackConversation).toHaveBeenCalledTimes(2);876 expect(harness.provider.rollbackConversation).toHaveBeenCalledTimes(2);
1010 expect(harness.provider.rollbackConversation.mock.calls[0]?.[0]).toEqual({877 expect(harness.provider.rollbackConversation.mock.calls[0]?.[0]).toEqual({
1011 878 threadId: ThreadId.makeUnsafe("thread-1"),
1012 numTurns: 1,879 numTurns: 1,
1013 });880 });
1014 expect(harness.provider.rollbackConversation.mock.calls[1]?.[0]).toEqual({881 expect(harness.provider.rollbackConversation.mock.calls[1]?.[0]).toEqual({
1015 882 threadId: ThreadId.makeUnsafe("thread-1"),
1016 numTurns: 1,883 numTurns: 1,
1017 });884 });
1018 });885 });
apps/server/src/orchestration/Layers/ProjectionPipeline.test.ts+10
...@@ -102,6 +102,7 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => {...@@ -102,6 +102,7 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => {
102 projectId: ProjectId.makeUnsafe("project-1"),102 projectId: ProjectId.makeUnsafe("project-1"),
103 title: "Thread 1",103 title: "Thread 1",
104 model: "gpt-5-codex",104 model: "gpt-5-codex",
105 runtimeMode: "full-access",
105 branch: null,106 branch: null,
106 worktreePath: null,107 worktreePath: null,
107 createdAt: now,108 createdAt: now,
...@@ -372,6 +373,7 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => {...@@ -372,6 +373,7 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => {
372 projectId: ProjectId.makeUnsafe("project-clear-attachments"),373 projectId: ProjectId.makeUnsafe("project-clear-attachments"),
373 title: "Thread Clear Attachments",374 title: "Thread Clear Attachments",
374 model: "gpt-5-codex",375 model: "gpt-5-codex",
376 runtimeMode: "full-access",
375 branch: null,377 branch: null,
376 worktreePath: null,378 worktreePath: null,
377 createdAt: now,379 createdAt: now,
...@@ -498,6 +500,7 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => {...@@ -498,6 +500,7 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => {
498 projectId: ProjectId.makeUnsafe("project-overwrite"),500 projectId: ProjectId.makeUnsafe("project-overwrite"),
499 title: "Thread Overwrite",501 title: "Thread Overwrite",
500 model: "gpt-5-codex",502 model: "gpt-5-codex",
503 runtimeMode: "full-access",
501 branch: null,504 branch: null,
502 worktreePath: null,505 worktreePath: null,
503 createdAt: now,506 createdAt: now,
...@@ -648,6 +651,7 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => {...@@ -648,6 +651,7 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => {
648 projectId: ProjectId.makeUnsafe("project-rollback"),651 projectId: ProjectId.makeUnsafe("project-rollback"),
649 title: "Thread Rollback",652 title: "Thread Rollback",
650 model: "gpt-5-codex",653 model: "gpt-5-codex",
654 runtimeMode: "full-access",
651 branch: null,655 branch: null,
652 worktreePath: null,656 worktreePath: null,
653 createdAt: now,657 createdAt: now,
...@@ -778,6 +782,7 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => {...@@ -778,6 +782,7 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => {
778 projectId: ProjectId.makeUnsafe("project-revert-files"),782 projectId: ProjectId.makeUnsafe("project-revert-files"),
779 title: "Thread Revert Files",783 title: "Thread Revert Files",
780 model: "gpt-5-codex",784 model: "gpt-5-codex",
785 runtimeMode: "full-access",
781 branch: null,786 branch: null,
782 worktreePath: null,787 worktreePath: null,
783 createdAt: now,788 createdAt: now,
...@@ -992,6 +997,7 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => {...@@ -992,6 +997,7 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => {
992 projectId: ProjectId.makeUnsafe("project-delete-files"),997 projectId: ProjectId.makeUnsafe("project-delete-files"),
993 title: "Thread Delete Files",998 title: "Thread Delete Files",
994 model: "gpt-5-codex",999 model: "gpt-5-codex",
1000 runtimeMode: "full-access",
995 branch: null,1001 branch: null,
996 worktreePath: null,1002 worktreePath: null,
997 createdAt: now,1003 createdAt: now,
...@@ -1154,6 +1160,7 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => {...@@ -1154,6 +1160,7 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => {
1154 projectId: ProjectId.makeUnsafe("project-a"),1160 projectId: ProjectId.makeUnsafe("project-a"),
1155 title: "Thread A",1161 title: "Thread A",
1156 model: "gpt-5-codex",1162 model: "gpt-5-codex",
1163 runtimeMode: "full-access",
1157 branch: null,1164 branch: null,
1158 worktreePath: null,1165 worktreePath: null,
1159 createdAt: now,1166 createdAt: now,
...@@ -1277,6 +1284,7 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => {...@@ -1277,6 +1284,7 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => {
1277 projectId: ProjectId.makeUnsafe("project-empty"),1284 projectId: ProjectId.makeUnsafe("project-empty"),
1278 title: "Thread Empty",1285 title: "Thread Empty",
1279 model: "gpt-5-codex",1286 model: "gpt-5-codex",
1287 runtimeMode: "full-access",
1280 branch: null,1288 branch: null,
1281 worktreePath: null,1289 worktreePath: null,
1282 createdAt: now,1290 createdAt: now,
...@@ -1413,6 +1421,7 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => {...@@ -1413,6 +1421,7 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => {
1413 projectId: ProjectId.makeUnsafe("project-conflict"),1421 projectId: ProjectId.makeUnsafe("project-conflict"),
1414 title: "Thread Conflict",1422 title: "Thread Conflict",
1415 model: "gpt-5-codex",1423 model: "gpt-5-codex",
1424 runtimeMode: "full-access",
1416 branch: null,1425 branch: null,
1417 worktreePath: null,1426 worktreePath: null,
1418 createdAt: "2026-02-26T13:00:01.000Z",1427 createdAt: "2026-02-26T13:00:01.000Z",
...@@ -1553,6 +1562,7 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => {...@@ -1553,6 +1562,7 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => {
1553 projectId: ProjectId.makeUnsafe("project-revert"),1562 projectId: ProjectId.makeUnsafe("project-revert"),
1554 title: "Thread Revert",1563 title: "Thread Revert",
1555 model: "gpt-5-codex",1564 model: "gpt-5-codex",
1565 runtimeMode: "full-access",
1556 branch: null,1566 branch: null,
1557 worktreePath: null,1567 worktreePath: null,
1558 createdAt: "2026-02-26T12:00:01.000Z",1568 createdAt: "2026-02-26T12:00:01.000Z",
apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts-98
...@@ -94,7 +94,6 @@ describe("ProviderCommandReactor", () => {...@@ -94,7 +94,6 @@ describe("ProviderCommandReactor", () => {
94 input !== null &&94 input !== null &&
95 "provider" in input &&95 "provider" in input &&
96 (input.provider === "codex" ||96 (input.provider === "codex" ||
97 input.provider === "claudeCode" ||
98 input.provider === "cursor")97 input.provider === "cursor")
99 ? input.provider98 ? input.provider
100 : "codex";99 : "codex";
...@@ -385,43 +384,6 @@ describe("ProviderCommandReactor", () => {...@@ -385,43 +384,6 @@ describe("ProviderCommandReactor", () => {
385 });384 });
386 });385 });
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
425 it("starts first turn with cursor provider when provider is specified", async () => {387 it("starts first turn with cursor provider when provider is specified", async () => {
426 const harness = await createHarness();388 const harness = await createHarness();
427 const now = new Date().toISOString();389 const now = new Date().toISOString();
...@@ -695,66 +657,6 @@ describe("ProviderCommandReactor", () => {...@@ -695,66 +657,6 @@ describe("ProviderCommandReactor", () => {
695 expect(thread?.session?.runtimeMode).toBe("approval-required");657 expect(thread?.session?.runtimeMode).toBe("approval-required");
696 });658 });
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
758 it("does not stop the active session when restart fails before rebind", async () => {660 it("does not stop the active session when restart fails before rebind", async () => {
759 const harness = await createHarness();661 const harness = await createHarness();
760 const now = new Date().toISOString();662 const now = new Date().toISOString();
apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts-54
...@@ -437,60 +437,6 @@ describe("ProviderRuntimeIngestion", () => {...@@ -437,60 +437,6 @@ describe("ProviderRuntimeIngestion", () => {
437 );437 );
438 });438 });
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
494 it("ignores non-active turn completion when runtime omits thread id", async () => {440 it("ignores non-active turn completion when runtime omits thread id", async () => {
495 const harness = await createHarness();441 const harness = await createHarness();
496 const now = new Date().toISOString();442 const now = new Date().toISOString();
apps/server/src/orchestration/projector.test.ts+2
...@@ -75,6 +75,7 @@ describe("orchestration projector", () => {...@@ -75,6 +75,7 @@ describe("orchestration projector", () => {
75 title: "demo",75 title: "demo",
76 model: "gpt-5-codex",76 model: "gpt-5-codex",
77 runtimeMode: "full-access",77 runtimeMode: "full-access",
78 interactionMode: "default",
78 branch: null,79 branch: null,
79 worktreePath: null,80 worktreePath: null,
80 latestTurn: null,81 latestTurn: null,
...@@ -82,6 +83,7 @@ describe("orchestration projector", () => {...@@ -82,6 +83,7 @@ describe("orchestration projector", () => {
82 updatedAt: now,83 updatedAt: now,
83 deletedAt: null,84 deletedAt: null,
84 messages: [],85 messages: [],
86 proposedPlans: [],
85 activities: [],87 activities: [],
86 checkpoints: [],88 checkpoints: [],
87 session: null,89 session: null,
apps/server/src/provider/Layers/ProviderService.test.ts+2-29
...@@ -216,15 +216,12 @@ const sleep = (ms: number) =>...@@ -216,15 +216,12 @@ const sleep = (ms: number) =>
216216
217function makeProviderServiceLayer() {217function makeProviderServiceLayer() {
218 const codex = makeFakeCodexAdapter();218 const codex = makeFakeCodexAdapter();
219 const claude = makeFakeCodexAdapter("claudeCode");
220 const registry: typeof ProviderAdapterRegistry.Service = {219 const registry: typeof ProviderAdapterRegistry.Service = {
221 getByProvider: (provider) =>220 getByProvider: (provider) =>
222 provider === "codex"221 provider === "codex"
223 ? Effect.succeed(codex.adapter)222 ? Effect.succeed(codex.adapter)
224 : provider === "claudeCode"223 : Effect.fail(new ProviderUnsupportedError({ provider })),
225 ? Effect.succeed(claude.adapter)224 listProviders: () => Effect.succeed(["codex"]),
226 : Effect.fail(new ProviderUnsupportedError({ provider })),
227 listProviders: () => Effect.succeed(["codex", "claudeCode"]),
228 };225 };
229226
230 const providerAdapterLayer = Layer.succeed(ProviderAdapterRegistry, registry);227 const providerAdapterLayer = Layer.succeed(ProviderAdapterRegistry, registry);
...@@ -247,7 +244,6 @@ function makeProviderServiceLayer() {...@@ -247,7 +244,6 @@ function makeProviderServiceLayer() {
247244
248 return {245 return {
249 codex,246 codex,
250 claude,
251 layer,247 layer,
252 };248 };
253}249}
...@@ -496,29 +492,6 @@ routing.layer("ProviderServiceLive routing", (it) => {...@@ -496,29 +492,6 @@ routing.layer("ProviderServiceLive routing", (it) => {
496 }),492 }),
497 );493 );
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
522 it.effect("recovers stale persisted sessions for rollback by resuming thread identity", () =>495 it.effect("recovers stale persisted sessions for rollback by resuming thread identity", () =>
523 Effect.gen(function* () {496 Effect.gen(function* () {
524 const provider = yield* ProviderService;497 const provider = yield* ProviderService;