| @@ -101,6 +101,7 @@ const projectId = ProjectId.make("project:mcp-orchestrator"); · 100 unmodified lines |
| 101 | const deletionRaceProjectId = ProjectId.make("project:mcp-orchestrator-delete-race"); | 101 | const deletionRaceProjectId = ProjectId.make("project:mcp-orchestrator-delete-race"); |
| 102 | const deletionRaceParentThreadId = ThreadId.make("thread:mcp-orchestrator-delete-race-parent"); | 102 | const deletionRaceParentThreadId = ThreadId.make("thread:mcp-orchestrator-delete-race-parent"); |
| 103 | const ownershipRaceParentThreadId = ThreadId.make("thread:mcp-orchestrator-ownership-race-parent"); | 103 | const ownershipRaceParentThreadId = ThreadId.make("thread:mcp-orchestrator-ownership-race-parent"); |
| | 104 | const lifecycleRaceParentThreadId = ThreadId.make("thread:mcp-orchestrator-lifecycle-race-parent"); |
| 104 | const targetProjectId = ProjectId.make("project:mcp-orchestrator-target"); | 105 | const targetProjectId = ProjectId.make("project:mcp-orchestrator-target"); |
| 105 | const codexInstanceId = ProviderInstanceId.make("codex"); | 106 | const codexInstanceId = ProviderInstanceId.make("codex"); |
| 106 | const claudeInstanceId = ProviderInstanceId.make("claudeAgent"); | 107 | const claudeInstanceId = ProviderInstanceId.make("claudeAgent"); |
| @@ -525,6 +526,7 @@ describe("orchestrator MCP toolkit", () => { · 418 unmodified lines |
| 525 | const allowDeletion = yield* Deferred.make<void>(); | 526 | const allowDeletion = yield* Deferred.make<void>(); |
| 526 | const deletionRaceProviderStarted = yield* Deferred.make<ProviderAdapterV2TurnInput>(); | 527 | const deletionRaceProviderStarted = yield* Deferred.make<ProviderAdapterV2TurnInput>(); |
| 527 | const ownershipRaceProviderStarted = yield* Deferred.make<ProviderAdapterV2TurnInput>(); | 528 | const ownershipRaceProviderStarted = yield* Deferred.make<ProviderAdapterV2TurnInput>(); |
| | 529 | const lifecycleRaceProviderStarted = yield* Deferred.make<ProviderAdapterV2TurnInput>(); |
| 528 | const deletionRaceCommandId = CommandId.make( | 530 | const deletionRaceCommandId = CommandId.make( |
| 529 | "command:mcp:mcp-provider-session-delete-race:create-thread:delete-create-race:0", | 531 | "command:mcp:mcp-provider-session-delete-race:create-thread:delete-create-race:0", |
| 530 | ); | 532 | ); |
| @@ -547,13 +549,16 @@ describe("orchestrator MCP toolkit", () => { · 16 unmodified lines |
| 547 | turn.threadId !== parentThreadId && | 549 | turn.threadId !== parentThreadId && |
| 548 | turn.threadId !== deletionRaceParentThreadId && | 550 | turn.threadId !== deletionRaceParentThreadId && |
| 549 | turn.threadId !== ownershipRaceParentThreadId && | 551 | turn.threadId !== ownershipRaceParentThreadId && |
| | 552 | turn.threadId !== lifecycleRaceParentThreadId && |
| 550 | turn.message.text !== cancellationPrompt, | 553 | turn.message.text !== cancellationPrompt, |
| 551 | startedSignal: (turn) => | 554 | startedSignal: (turn) => |
| 552 | turn.threadId === deletionRaceParentThreadId | 555 | turn.threadId === deletionRaceParentThreadId |
| 553 | ? deletionRaceProviderStarted | 556 | ? deletionRaceProviderStarted |
| 554 | : turn.threadId === ownershipRaceParentThreadId | 557 | : turn.threadId === ownershipRaceParentThreadId |
| 555 | ? ownershipRaceProviderStarted | 558 | ? ownershipRaceProviderStarted |
| 556 | : undefined, | 559 | : turn.threadId === lifecycleRaceParentThreadId |
| | 560 | ? lifecycleRaceProviderStarted |
| | 561 | : undefined, |
| 557 | terminalGate: (turn) => | 562 | terminalGate: (turn) => |
| 558 | turn.message.text.startsWith("Delegated task") || | 563 | turn.message.text.startsWith("Delegated task") || |
| 559 | turn.message.text.startsWith("Delegated tasks") | 564 | turn.message.text.startsWith("Delegated tasks") |
| @@ -3736,6 +3741,90 @@ describe("orchestrator MCP toolkit", () => { · 3176 unmodified lines |
| 3736 | ).toBe(true); | 3741 | ).toBe(true); |
| 3737 | }).pipe(Effect.ensuring(Deferred.succeed(inactiveParentGate.release, undefined))); | 3742 | }).pipe(Effect.ensuring(Deferred.succeed(inactiveParentGate.release, undefined))); |
| 3738 | | 3743 | |
| | 3744 | yield* orchestrator.dispatch({ |
| | 3745 | type: "thread.create", |
| | 3746 | createdBy: "user", |
| | 3747 | creationSource: "web", |
| | 3748 | commandId: CommandId.make("command:mcp-lifecycle-race-parent:create"), |
| | 3749 | threadId: lifecycleRaceParentThreadId, |
| | 3750 | projectId, |
| | 3751 | title: "MCP lifecycle race parent", |
| | 3752 | modelSelection: codexSelection, |
| | 3753 | runtimeMode: "full-access", |
| | 3754 | interactionMode: "default", |
| | 3755 | branch: null, |
| | 3756 | worktreePath: cwd, |
| | 3757 | }); |
| | 3758 | yield* orchestrator.dispatch({ |
| | 3759 | type: "message.dispatch", |
| | 3760 | createdBy: "user", |
| | 3761 | creationSource: "web", |
| | 3762 | commandId: CommandId.make("command:mcp-lifecycle-race-parent:start"), |
| | 3763 | threadId: lifecycleRaceParentThreadId, |
| | 3764 | messageId: MessageId.make("message:mcp-lifecycle-race-parent:start"), |
| | 3765 | text: "Stay active until the caller lifecycle is rechecked.", |
| | 3766 | attachments: [], |
| | 3767 | modelSelection: codexSelection, |
| | 3768 | dispatchMode: { type: "start_immediately" }, |
| | 3769 | }); |
| | 3770 | const startedLifecycleRaceTurn = yield* Deferred.await(lifecycleRaceProviderStarted); |
| | 3771 | expect(startedLifecycleRaceTurn.threadId).toBe(lifecycleRaceParentThreadId); |
| | 3772 | const lifecycleRaceInvocation: McpInvocationContext.McpInvocationScope = { |
| | 3773 | ...invocation, |
| | 3774 | threadId: lifecycleRaceParentThreadId, |
| | 3775 | providerSessionId: "mcp-provider-session-lifecycle-race", |
| | 3776 | }; |
| | 3777 | const invokeLifecycleRace = (name: string, args: Record<string, unknown>) => |
| | 3778 | server |
| | 3779 | .callTool({ name, arguments: args }) |
| | 3780 | .pipe( |
| | 3781 | Effect.provideService( |
| | 3782 | McpInvocationContext.McpInvocationContext, |
| | 3783 | lifecycleRaceInvocation, |
| | 3784 | ), |
| | 3785 | Effect.provideService(McpSchema.McpServerClient, client), |
| | 3786 | ); |
| | 3787 | const archivedParentKey = "create-after-parent-archived"; |
| | 3788 | const archivedParentGate = yield* gateLaunchReceipt( |
| | 3789 | launchCommandId(archivedParentKey, lifecycleRaceInvocation.providerSessionId), |
| | 3790 | ); |
| | 3791 | yield* Effect.gen(function* () { |
| | 3792 | const creation = yield* Effect.forkChild( |
| | 3793 | invokeLifecycleRace("create_threads", { |
| | 3794 | clientRequestId: archivedParentKey, |
| | 3795 | threads: [ |
| | 3796 | { |
| | 3797 | projectId: targetProjectId, |
| | 3798 | title: "Must not outlive its archived caller", |
| | 3799 | prompt: "Do not launch after the caller is archived.", |
| | 3800 | }, |
| | 3801 | ], |
| | 3802 | }), |
| | 3803 | { startImmediately: true }, |
| | 3804 | ); |
| | 3805 | yield* Deferred.await(archivedParentGate.entered); |
| | 3806 | yield* orchestrator.dispatch({ |
| | 3807 | type: "thread.archive", |
| | 3808 | commandId: CommandId.make("command:mcp-lifecycle-race-parent:archive"), |
| | 3809 | threadId: lifecycleRaceParentThreadId, |
| | 3810 | }); |
| | 3811 | yield* Deferred.succeed(archivedParentGate.release, undefined); |
| | 3812 | const creationCall = yield* Fiber.join(creation); |
| | 3813 | expect(creationCall.structuredContent).toMatchObject({ |
| | 3814 | code: "parent_not_active", |
| | 3815 | message: |
| | 3816 | "Thread creation requires an active run owned by this MCP provider session.", |
| | 3817 | }); |
| | 3818 | const childThreadId = ThreadId.make( |
| | 3819 | `thread:mcp:${lifecycleRaceInvocation.providerSessionId}:${archivedParentKey}:0`, |
| | 3820 | ); |
| | 3821 | expect( |
| | 3822 | Option.isNone( |
| | 3823 | yield* Effect.option(orchestrator.getThreadProjection(childThreadId)), |
| | 3824 | ), |
| | 3825 | ).toBe(true); |
| | 3826 | }).pipe(Effect.ensuring(Deferred.succeed(archivedParentGate.release, undefined))); |
| | 3827 | |
| 3739 | yield* orchestrator.dispatch({ | 3828 | yield* orchestrator.dispatch({ |
| 3740 | type: "thread.create", | 3829 | type: "thread.create", |
| 3741 | createdBy: "user", | 3830 | createdBy: "user", |