From 19ba124fd669f70d87b8c8dad1af2a05c20ce2d8 Mon Sep 17 00:00:00 2001 From: Julius Marminge <51714798+juliusmarminge@users.noreply.github.com> Date: Fri, 9 Oct 2026 01:24:51 -0700 Subject: [PATCH] refactor(provider-core): MCP provider sessions live in a McpProviderSessions service The per-thread MCP credentials were a module-level Map in mcpSession.ts. The session manager now writes them through McpProviderSessions and adapters yield it, reading the session once per launch and handing it to their pure option builders. The server provides one instance; tests get a fresh one per layer. Co-Authored-By: Claude Opus 5.5 --- ...OrchestratorMcpToolkit.integration.test.ts | 5 +- .../AcpRegistryOrchestratorV2.live.test.ts | 2 + .../Adapters/AcpAdapterV2.test.ts | 36 +- .../Adapters/AcpRegistryAdapterV2.test.ts | 2 + .../Adapters/AntigravityAdapterV2.test.ts | 2 + .../Adapters/ClaudeAdapterV2.test.ts | 854 ++++++++++++------ .../Adapters/ClaudeAdapterV2.ts | 20 +- .../Adapters/CodexAdapterV2.test.ts | 479 ++++++++-- .../Adapters/CodexAdapterV2.ts | 36 +- .../Adapters/CursorAdapterV2.testkit.ts | 1 + .../Adapters/GrokAdapterV2.test.ts | 8 +- .../Adapters/OpenCode2AdapterV2.test.ts | 68 +- ...laudeAutomaticDelivery.integration.test.ts | 9 +- .../CursorOrchestratorV2.live.test.ts | 2 + .../DelegatedCompletionDelivery.test.ts | 2 + .../GrokOrchestratorV2.live.test.ts | 2 + .../OpenCode2OrchestratorV2.live.test.ts | 2 + ...ProjectSettingsUpgrade.integration.test.ts | 2 + .../ProviderSessionManager.test.ts | 76 +- .../ProviderSessionManager.ts | 30 +- .../src/orchestration-v2/runtimeLayer.test.ts | 5 + .../testkit/ProviderReplayHarness.ts | 18 +- .../Drivers/AntigravityDriver.test.ts | 2 + .../src/provider/Drivers/AntigravityDriver.ts | 2 + .../src/provider/Drivers/CodexDriver.test.ts | 2 + .../provider/ProviderInstanceRegistry.test.ts | 3 + .../src/provider/ProviderRegistry.test.ts | 5 + apps/server/src/server.ts | 2 + .../src/server/adapter.ts | 2 + packages/provider-acp/src/server/adapter.ts | 101 ++- packages/provider-core/package.json | 4 + .../src/server/McpProviderSessions.ts | 40 + .../provider-core/src/server/mcpSession.ts | 18 - .../src/server/adapter.test.ts | 83 +- .../provider-cursor/src/server/adapter.ts | 20 +- .../provider-cursor/src/server/driver.test.ts | 2 + packages/provider-grok/src/server/adapter.ts | 3 + .../provider-grok/src/server/driver.test.ts | 2 + .../provider-muse/src/server/adapter.test.ts | 13 +- packages/provider-muse/src/server/adapter.ts | 6 +- packages/provider-muse/src/server/driver.ts | 2 + .../src/server/adapter.test.ts | 114 ++- .../provider-opencode/src/server/adapter.ts | 6 +- .../src/server/driver.test.ts | 3 + .../src/server/v2/adapter.ts | 5 +- .../provider-pi/src/server/adapter.test.ts | 11 +- packages/provider-pi/src/server/adapter.ts | 6 +- .../provider-pi/src/server/driver.test.ts | 2 + 48 files changed, 1467 insertions(+), 653 deletions(-) create mode 100644 packages/provider-core/src/server/McpProviderSessions.ts diff --git a/apps/server/src/mcp/OrchestratorMcpToolkit.integration.test.ts b/apps/server/src/mcp/OrchestratorMcpToolkit.integration.test.ts index 02c780b35946..d232386dec88 100644 --- a/apps/server/src/mcp/OrchestratorMcpToolkit.integration.test.ts +++ b/apps/server/src/mcp/OrchestratorMcpToolkit.integration.test.ts @@ -59,6 +59,7 @@ import * as Orchestrator from "../orchestration-v2/Orchestrator.ts"; import * as ThreadManagementService from "../orchestration-v2/ThreadManagementService.ts"; import * as ProviderAdapterRegistry from "../orchestration-v2/ProviderAdapterRegistry.ts"; import * as ProviderContinuationRequests from "@t3tools/provider-core/server/ProviderContinuationRequests"; +import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions"; import { checkpointWorkspace } from "@t3tools/provider-testing/replayWorkspace"; import * as ProviderReplayHarness from "../orchestration-v2/testkit/ProviderReplayHarness.ts"; import { @@ -3810,7 +3811,9 @@ describe("orchestrator MCP toolkit", () => { Layer.provideMerge(McpServer.McpServer.layer), Layer.provideMerge(layerOrchestration), Layer.provide( - CodexOrchestratorReplayHarness.makeProviderAdapterRegistryLayer(transcript), + CodexOrchestratorReplayHarness.makeProviderAdapterRegistryLayer(transcript).pipe( + Layer.provide(McpProviderSessions.layer), + ), ), Layer.provide(layerProviderRegistry), Layer.provide(layerUnusedScheduledTaskStub), diff --git a/apps/server/src/orchestration-v2/AcpRegistryOrchestratorV2.live.test.ts b/apps/server/src/orchestration-v2/AcpRegistryOrchestratorV2.live.test.ts index 0b9cd6b5cd2a..da89b3bc6227 100644 --- a/apps/server/src/orchestration-v2/AcpRegistryOrchestratorV2.live.test.ts +++ b/apps/server/src/orchestration-v2/AcpRegistryOrchestratorV2.live.test.ts @@ -30,6 +30,7 @@ import * as ServerEnvironment from "../environment/ServerEnvironment.ts"; import * as ModelManifest from "../provider/ModelManifest.ts"; import * as ProviderInstanceRegistryHydration from "../provider/ProviderInstanceRegistryHydration.ts"; import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions"; +import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions"; import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers"; import * as OpenCodeRuntime from "@t3tools/provider-opencode/server/OpenCodeRuntime"; import * as OpenCodeServerLedger from "@t3tools/provider-opencode/server/OpenCodeServerLedger"; @@ -144,6 +145,7 @@ const layerLive = RuntimeLayer.layer.pipe( Layer.provide(layerBackgroundPolicy), Layer.provide(ProviderTurnStartServiceTestkit.layer), Layer.provide(layerPlatformTest), + Layer.provide(McpProviderSessions.layer), ); const waitForIdle = Effect.fn("AcpRegistryOrchestratorV2Live.waitForIdle")(function* ( diff --git a/apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.test.ts index f1f33d43233f..697da040bef6 100644 --- a/apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.test.ts +++ b/apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.test.ts @@ -54,7 +54,7 @@ import type * as EffectAcpProtocol from "effect-acp/protocol"; import type * as EffectAcpSchema from "effect-acp/compat"; import { layerTestProviderHost } from "@t3tools/provider-testing/host"; -import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession"; +import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions"; import * as AcpSessionRuntime from "@t3tools/provider-acp/server/AcpSessionRuntime"; import { extractXAiAcpSubagentEndNotice, @@ -96,7 +96,12 @@ const DEFAULT_GROK_SETTINGS = Schema.decodeSync(GrokSettings)({}); const layerHost = layerTestProviderHost().pipe(Layer.provide(NodeServices.layer)); -const layerTest = Layer.mergeAll(NodeServices.layer, IdAllocator.layer, layerHost); +const layerTest = Layer.mergeAll( + NodeServices.layer, + IdAllocator.layer, + McpProviderSessions.layer, + layerHost, +); const ACP_TEST_DRIVER = ProviderDriverKind.make("acp-test"); const decodeUnknownJson = Schema.decodeUnknownOption(Schema.fromJsonString(Schema.Unknown)); @@ -708,7 +713,7 @@ describe("AcpAdapterV2", () => { const instanceId = ProviderInstanceId.make("acp-test-self-contained-mcp-bridge"); const threadId = ThreadId.make("thread-acp-self-contained-mcp-bridge"); - McpProviderSession.setMcpProviderSession({ + yield* (yield* McpProviderSessions.McpProviderSessions).set({ environmentId: EnvironmentId.make("environment-acp-self-contained-mcp-bridge"), threadId, providerSessionId: "mcp-session-acp-self-contained-mcp-bridge", @@ -717,11 +722,6 @@ describe("AcpAdapterV2", () => { authorizationHeader: "Bearer self-contained-mcp-bridge-token", browserToolsAvailable: false, }); - yield* Effect.addFinalizer(() => - Effect.sync(() => { - McpProviderSession.clearMcpProviderSession(threadId); - }), - ); let runtimeInput: AcpAdapterV2RuntimeInput | undefined; const makeRuntime = makeMockRuntime({ childProcessSpawner, mockAgentPath }); @@ -772,7 +772,7 @@ describe("AcpAdapterV2", () => { const protocolEvents = yield* Queue.unbounded(); const instanceId = ProviderInstanceId.make("acp-test-instruction-transitions"); const threadId = ThreadId.make("thread-acp-instruction-transitions"); - McpProviderSession.setMcpProviderSession({ + yield* (yield* McpProviderSessions.McpProviderSessions).set({ environmentId: EnvironmentId.make("environment-acp-instruction-transitions"), threadId, providerSessionId: "mcp-session-acp-instruction-transitions", @@ -781,9 +781,6 @@ describe("AcpAdapterV2", () => { authorizationHeader: "Bearer instruction-transition-token", browserToolsAvailable: false, }); - yield* Effect.addFinalizer(() => - Effect.sync(() => McpProviderSession.clearMcpProviderSession(threadId)), - ); const adapter = yield* makeAcpAdapterV2({ instanceId, flavor: { @@ -2197,7 +2194,7 @@ describe("AcpAdapterV2", () => { }); const sourceThreadId = ThreadId.make("thread-acp-native-fork-source"); const targetThreadId = ThreadId.make("thread-acp-native-fork-target"); - McpProviderSession.setMcpProviderSession({ + yield* (yield* McpProviderSessions.McpProviderSessions).set({ environmentId: EnvironmentId.make("environment-acp-native-fork-source"), threadId: sourceThreadId, providerSessionId: "mcp-session-acp-native-fork-source", @@ -2206,7 +2203,7 @@ describe("AcpAdapterV2", () => { authorizationHeader: "Bearer source-thread-token", browserToolsAvailable: true, }); - McpProviderSession.setMcpProviderSession({ + yield* (yield* McpProviderSessions.McpProviderSessions).set({ environmentId: EnvironmentId.make("environment-acp-native-fork"), threadId: targetThreadId, providerSessionId: "mcp-session-acp-native-fork", @@ -2215,12 +2212,6 @@ describe("AcpAdapterV2", () => { authorizationHeader: "Bearer target-thread-token", browserToolsAvailable: true, }); - yield* Effect.addFinalizer(() => - Effect.sync(() => { - McpProviderSession.clearMcpProviderSession(sourceThreadId); - McpProviderSession.clearMcpProviderSession(targetThreadId); - }), - ); const runtimePolicy = ProviderAdapter.ProviderAdapterV2RuntimePolicy.make({ runtimeMode: "full-access", interactionMode: "default", @@ -2640,7 +2631,7 @@ describe("AcpAdapterV2", () => { ); const instanceId = ProviderInstanceId.make("acp-test-rollback-session"); const rollbackThreadId = ThreadId.make("thread-acp-rollback-session-target"); - McpProviderSession.setMcpProviderSession({ + yield* (yield* McpProviderSessions.McpProviderSessions).set({ environmentId: EnvironmentId.make("environment-acp-rollback-session-target"), threadId: rollbackThreadId, providerSessionId: "mcp-session-acp-rollback-session-target", @@ -2649,9 +2640,6 @@ describe("AcpAdapterV2", () => { authorizationHeader: "Bearer rollback-target-token", browserToolsAvailable: false, }); - yield* Effect.addFinalizer(() => - Effect.sync(() => McpProviderSession.clearMcpProviderSession(rollbackThreadId)), - ); const runtimeInputs: Array = []; const makeRuntime = makeMockRuntime({ childProcessSpawner, diff --git a/apps/server/src/orchestration-v2/Adapters/AcpRegistryAdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/AcpRegistryAdapterV2.test.ts index c640ae354919..17ff9b76ff44 100644 --- a/apps/server/src/orchestration-v2/Adapters/AcpRegistryAdapterV2.test.ts +++ b/apps/server/src/orchestration-v2/Adapters/AcpRegistryAdapterV2.test.ts @@ -14,6 +14,7 @@ import { ChildProcessSpawner } from "effect/process"; import * as EffectAcpErrors from "effect-acp/errors"; import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost"; +import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions"; import { layerTestProviderHost } from "@t3tools/provider-testing/host"; import * as ServerSettings from "../../serverSettings.ts"; import type { @@ -84,6 +85,7 @@ const layerRegistry = Layer.succeed( const layerTest = Layer.mergeAll( NodeServices.layer, IdAllocator.layer, + McpProviderSessions.layer, layerHost, layerRegistry, ServerSettings.layerTest(), diff --git a/apps/server/src/orchestration-v2/Adapters/AntigravityAdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/AntigravityAdapterV2.test.ts index 9579da1fa91c..ca19c1f86c50 100644 --- a/apps/server/src/orchestration-v2/Adapters/AntigravityAdapterV2.test.ts +++ b/apps/server/src/orchestration-v2/Adapters/AntigravityAdapterV2.test.ts @@ -23,6 +23,7 @@ import { ChildProcessSpawner } from "effect/process"; import type * as EffectAcpSchema from "effect-acp/compat"; import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost"; +import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions"; import { layerTestProviderHost } from "@t3tools/provider-testing/host"; import type * as AcpSessionRuntime from "@t3tools/provider-acp/server/AcpSessionRuntime"; import { makeAntigravityAcpRuntime } from "../../provider/acp/AntigravityAcpSupport.ts"; @@ -129,6 +130,7 @@ describe("AntigravityAdapterV2 flavor", () => { const layerSession = Layer.mergeAll( NodeServices.layer, IdAllocator.layer, + McpProviderSessions.layer, layerTestProviderHost().pipe(Layer.provide(NodeServices.layer)), ); diff --git a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts index eed58e1aa416..9c4e336815f7 100644 --- a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts +++ b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts @@ -53,6 +53,7 @@ import { SpawnExecutableResolution } from "@t3tools/shared/shell"; import { attachmentRelativePath } from "../../attachmentStore.ts"; import * as ServerConfig from "../../config.ts"; import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession"; +import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions"; import { PreviewControlsToolkit } from "../../mcp/toolkits/previewControls/tools.ts"; import { HtmlToolkit } from "../../mcp/toolkits/html/tools.ts"; import { EnvironmentToolkit } from "../../mcp/toolkits/environment/tools.ts"; @@ -483,26 +484,22 @@ describe("ClaudeAdapterV2 MCP query overrides", () => { T3_CODE_MCP_AUTHORIZATION: "Bearer secret-claude-token", } as const; - const withMcpSession = (threadId: ThreadId, run: () => void) => { - McpProviderSession.setMcpProviderSession({ - environmentId: EnvironmentId.make(`environment-${threadId}`), - threadId, - providerSessionId: `mcp-session-${threadId}`, - providerInstanceId: ProviderInstanceId.make("claudeAgent"), - endpoint: "http://127.0.0.1:43123/mcp", - authorizationHeader: "Bearer secret-claude-token", - browserToolsAvailable: true, - }); - try { - run(); - } finally { - McpProviderSession.clearMcpProviderSession(threadId); - } - }; + const mcpSessionFor = ( + threadId: ThreadId, + authorizationHeader = "Bearer secret-claude-token", + ): McpProviderSession.McpProviderSessionConfig => ({ + environmentId: EnvironmentId.make(`environment-${threadId}`), + threadId, + providerSessionId: `mcp-session-${threadId}`, + providerInstanceId: ProviderInstanceId.make("claudeAgent"), + endpoint: "http://127.0.0.1:43123/mcp", + authorizationHeader, + browserToolsAvailable: true, + }); it("leaves an absent allowlist absent when no MCP session exists", () => { const overrides = ClaudeAdapterV2.claudeMcpQueryOverrides({ - threadId: ThreadId.make("thread-claude-no-mcp-no-allowlist"), + mcpSession: undefined, readOnlySandbox: false, }); @@ -511,7 +508,7 @@ describe("ClaudeAdapterV2 MCP query overrides", () => { it("preserves an explicit allowlist when no MCP session exists", () => { const overrides = ClaudeAdapterV2.claudeMcpQueryOverrides({ - threadId: ThreadId.make("thread-claude-no-mcp-with-allowlist"), + mcpSession: undefined, readOnlySandbox: false, allowedTools: ["Read"], }); @@ -521,135 +518,122 @@ describe("ClaudeAdapterV2 MCP query overrides", () => { it("pre-approves all t3-code tools when attaching an MCP session without an allowlist", () => { const threadId = ThreadId.make("thread-claude-mcp-no-allowlist"); - withMcpSession(threadId, () => { - const overrides = ClaudeAdapterV2.claudeMcpQueryOverrides({ - threadId, - readOnlySandbox: false, - }); + const mcpSession = mcpSessionFor(threadId); + const overrides = ClaudeAdapterV2.claudeMcpQueryOverrides({ + mcpSession, + readOnlySandbox: false, + }); - assert.deepEqual(overrides, { - allowedTools: [ClaudeAdapterV2.CLAUDE_T3_MCP_TOOL_WILDCARD], - mcpServers: T3_MCP_SERVERS, - mcpEnvironment: T3_MCP_ENVIRONMENT, - }); + assert.deepEqual(overrides, { + allowedTools: [ClaudeAdapterV2.CLAUDE_T3_MCP_TOOL_WILDCARD], + mcpServers: T3_MCP_SERVERS, + mcpEnvironment: T3_MCP_ENVIRONMENT, }); }); it("extends an explicit allowlist with the t3-code wildcard", () => { const threadId = ThreadId.make("thread-claude-mcp-with-allowlist"); - withMcpSession(threadId, () => { - const overrides = ClaudeAdapterV2.claudeMcpQueryOverrides({ - threadId, - readOnlySandbox: false, - allowedTools: ["Read", "mcp__t3-code__*"], - }); + const mcpSession = mcpSessionFor(threadId); + const overrides = ClaudeAdapterV2.claudeMcpQueryOverrides({ + mcpSession, + readOnlySandbox: false, + allowedTools: ["Read", "mcp__t3-code__*"], + }); - assert.deepEqual(overrides, { - allowedTools: ["Read", "mcp__t3-code__*"], - mcpServers: T3_MCP_SERVERS, - mcpEnvironment: T3_MCP_ENVIRONMENT, - }); + assert.deepEqual(overrides, { + allowedTools: ["Read", "mcp__t3-code__*"], + mcpServers: T3_MCP_SERVERS, + mcpEnvironment: T3_MCP_ENVIRONMENT, }); }); it("pre-approves only read-only t3-code tools in a read-only sandbox", () => { const threadId = ThreadId.make("thread-claude-mcp-read-only"); - withMcpSession(threadId, () => { - const overrides = ClaudeAdapterV2.claudeMcpQueryOverrides({ - threadId, - readOnlySandbox: true, - allowedTools: [...ClaudeAdapterV2.CLAUDE_READ_ONLY_ALLOWED_TOOLS], - }); + const mcpSession = mcpSessionFor(threadId); + const overrides = ClaudeAdapterV2.claudeMcpQueryOverrides({ + mcpSession, + readOnlySandbox: true, + allowedTools: [...ClaudeAdapterV2.CLAUDE_READ_ONLY_ALLOWED_TOOLS], + }); - assert.deepEqual(overrides, { - allowedTools: [ - ...ClaudeAdapterV2.CLAUDE_READ_ONLY_ALLOWED_TOOLS, - ...ClaudeAdapterV2.CLAUDE_READ_ONLY_T3_MCP_ALLOWED_TOOLS, - ], - mcpServers: T3_MCP_SERVERS, - mcpEnvironment: T3_MCP_ENVIRONMENT, - }); - assert.isFalse(overrides.allowedTools?.includes(ClaudeAdapterV2.CLAUDE_T3_MCP_TOOL_WILDCARD)); + assert.deepEqual(overrides, { + allowedTools: [ + ...ClaudeAdapterV2.CLAUDE_READ_ONLY_ALLOWED_TOOLS, + ...ClaudeAdapterV2.CLAUDE_READ_ONLY_T3_MCP_ALLOWED_TOOLS, + ], + mcpServers: T3_MCP_SERVERS, + mcpEnvironment: T3_MCP_ENVIRONMENT, }); + assert.isFalse(overrides.allowedTools?.includes(ClaudeAdapterV2.CLAUDE_T3_MCP_TOOL_WILDCARD)); }); it("pre-approves only read-only t3-code tools in a read-only sandbox without an allowlist", () => { const threadId = ThreadId.make("thread-claude-mcp-read-only-no-allowlist"); - withMcpSession(threadId, () => { - const overrides = ClaudeAdapterV2.claudeMcpQueryOverrides({ - threadId, - readOnlySandbox: true, - }); - - assert.deepEqual(overrides.allowedTools, [ - ...ClaudeAdapterV2.CLAUDE_READ_ONLY_T3_MCP_ALLOWED_TOOLS, - ]); + const mcpSession = mcpSessionFor(threadId); + const overrides = ClaudeAdapterV2.claudeMcpQueryOverrides({ + mcpSession, + readOnlySandbox: true, }); + + assert.deepEqual(overrides.allowedTools, [ + ...ClaudeAdapterV2.CLAUDE_READ_ONLY_T3_MCP_ALLOWED_TOOLS, + ]); }); it("keys live-query reuse on the MCP-derived pre-approvals", () => { const threadId = ThreadId.make("thread-claude-mcp-query-key"); - withMcpSession(threadId, () => { - const queryPolicy = ClaudeAdapterV2.claudeRuntimeQueryPolicyForRuntimePolicy( - ProviderAdapter.ProviderAdapterV2RuntimePolicy.make({ - runtimeMode: "full-access", - interactionMode: "default", - cwd: "/workspace", - approvalPolicy: "on-request", - sandboxPolicy: { - type: "readOnly", - access: { type: "fullAccess" }, - networkAccess: false, - }, - }), - ); + const mcpSession = mcpSessionFor(threadId); + const queryPolicy = ClaudeAdapterV2.claudeRuntimeQueryPolicyForRuntimePolicy( + ProviderAdapter.ProviderAdapterV2RuntimePolicy.make({ + runtimeMode: "full-access", + interactionMode: "default", + cwd: "/workspace", + approvalPolicy: "on-request", + sandboxPolicy: { + type: "readOnly", + access: { type: "fullAccess" }, + networkAccess: false, + }, + }), + ); - const readOnlyKey = ClaudeAdapterV2.claudeEffectiveQueryPolicyKey( - queryPolicy, - ClaudeAdapterV2.claudeMcpQueryOverrides({ threadId, readOnlySandbox: true }), - ); - const fullAccessKey = ClaudeAdapterV2.claudeEffectiveQueryPolicyKey( - queryPolicy, - ClaudeAdapterV2.claudeMcpQueryOverrides({ threadId, readOnlySandbox: false }), - ); - const detachedKey = ClaudeAdapterV2.claudeEffectiveQueryPolicyKey(queryPolicy, {}); + const readOnlyKey = ClaudeAdapterV2.claudeEffectiveQueryPolicyKey( + queryPolicy, + ClaudeAdapterV2.claudeMcpQueryOverrides({ mcpSession, readOnlySandbox: true }), + ); + const fullAccessKey = ClaudeAdapterV2.claudeEffectiveQueryPolicyKey( + queryPolicy, + ClaudeAdapterV2.claudeMcpQueryOverrides({ mcpSession, readOnlySandbox: false }), + ); + const detachedKey = ClaudeAdapterV2.claudeEffectiveQueryPolicyKey(queryPolicy, {}); - assert.notEqual(readOnlyKey, fullAccessKey); - assert.notEqual(fullAccessKey, detachedKey); - }); + assert.notEqual(readOnlyKey, fullAccessKey); + assert.notEqual(fullAccessKey, detachedKey); }); it("invalidates live-query reuse when MCP credentials rotate", () => { const threadId = ThreadId.make("thread-claude-mcp-credential-rotation"); - withMcpSession(threadId, () => { - const queryPolicy = ClaudeAdapterV2.claudeRuntimeQueryPolicyForRuntimePolicy( - ProviderAdapter.ProviderAdapterV2RuntimePolicy.make({ - runtimeMode: "approval-required", - interactionMode: "default", - cwd: "/workspace", - }), - ); - const initialKey = ClaudeAdapterV2.claudeEffectiveQueryPolicyKey( - queryPolicy, - ClaudeAdapterV2.claudeMcpQueryOverrides({ threadId, readOnlySandbox: false }), - ); - - McpProviderSession.setMcpProviderSession({ - environmentId: EnvironmentId.make(`environment-${threadId}`), - threadId, - providerSessionId: `mcp-session-${threadId}`, - providerInstanceId: ProviderInstanceId.make("claudeAgent"), - endpoint: "http://127.0.0.1:43123/mcp", - authorizationHeader: "Bearer rotated-claude-token", - browserToolsAvailable: true, - }); + const mcpSession = mcpSessionFor(threadId); + const queryPolicy = ClaudeAdapterV2.claudeRuntimeQueryPolicyForRuntimePolicy( + ProviderAdapter.ProviderAdapterV2RuntimePolicy.make({ + runtimeMode: "approval-required", + interactionMode: "default", + cwd: "/workspace", + }), + ); + const initialKey = ClaudeAdapterV2.claudeEffectiveQueryPolicyKey( + queryPolicy, + ClaudeAdapterV2.claudeMcpQueryOverrides({ mcpSession, readOnlySandbox: false }), + ); - const rotatedKey = ClaudeAdapterV2.claudeEffectiveQueryPolicyKey( - queryPolicy, - ClaudeAdapterV2.claudeMcpQueryOverrides({ threadId, readOnlySandbox: false }), - ); - assert.notEqual(rotatedKey, initialKey); - }); + const rotatedKey = ClaudeAdapterV2.claudeEffectiveQueryPolicyKey( + queryPolicy, + ClaudeAdapterV2.claudeMcpQueryOverrides({ + mcpSession: mcpSessionFor(threadId, "Bearer rotated-claude-token"), + readOnlySandbox: false, + }), + ); + assert.notEqual(rotatedKey, initialKey); }); it("matches the read-only allowlist to the orchestrator toolkit annotations", () => { @@ -676,7 +660,7 @@ describe("ClaudeAdapterV2 MCP query overrides", () => { describe("ClaudeAdapterV2 native protocol logging", () => { it("injects thread-scoped MCP configuration without logging the credential", () => { const threadId = ThreadId.make("thread-claude-mcp"); - McpProviderSession.setMcpProviderSession({ + const mcpSession = { environmentId: EnvironmentId.make("environment-claude-mcp"), threadId, providerSessionId: "mcp-session-claude", @@ -684,60 +668,56 @@ describe("ClaudeAdapterV2 native protocol logging", () => { endpoint: "http://127.0.0.1:43123/mcp", authorizationHeader: "Bearer secret-claude-token", browserToolsAvailable: true, - }); + }; - try { - const overrides = ClaudeAdapterV2.claudeMcpQueryOverrides({ - threadId, - readOnlySandbox: false, - allowedTools: ["Read"], - }); - assert.deepEqual(overrides, { - allowedTools: ["Read", "mcp__t3-code__*"], - mcpServers: { - "t3-code": { - type: "http", - url: "http://127.0.0.1:43123/mcp", - headers: { - Authorization: "${T3_CODE_MCP_AUTHORIZATION}", - }, - timeout: ClaudeAdapterV2.CLAUDE_T3_MCP_TOOL_TIMEOUT_MS, + const overrides = ClaudeAdapterV2.claudeMcpQueryOverrides({ + mcpSession, + readOnlySandbox: false, + allowedTools: ["Read"], + }); + assert.deepEqual(overrides, { + allowedTools: ["Read", "mcp__t3-code__*"], + mcpServers: { + "t3-code": { + type: "http", + url: "http://127.0.0.1:43123/mcp", + headers: { + Authorization: "${T3_CODE_MCP_AUTHORIZATION}", }, + timeout: ClaudeAdapterV2.CLAUDE_T3_MCP_TOOL_TIMEOUT_MS, }, - mcpEnvironment: { T3_CODE_MCP_AUTHORIZATION: "Bearer secret-claude-token" }, - }); + }, + mcpEnvironment: { T3_CODE_MCP_AUTHORIZATION: "Bearer secret-claude-token" }, + }); - const options = ClaudeAdapterV2.makeClaudeQueryOptions({ - modelSelection: { - instanceId: ProviderInstanceId.make("claudeAgent"), - model: "claude-sonnet-4-6", - }, - nativeThreadId: "native-thread-claude-mcp", - resume: false, - cwd: "/workspace", - allowedTools: overrides.allowedTools ?? [], - mcpServers: overrides.mcpServers ?? {}, - environment: { ...overrides.mcpEnvironment }, - }); - // mcpServers becomes a CLI argument, readable by every local user; the - // credential may only travel in the child's environment. - assert.notInclude(JSON.stringify(options.mcpServers), "secret-claude-token"); - assert.equal(options.env?.T3_CODE_MCP_AUTHORIZATION, "Bearer secret-claude-token"); - assert.isObject(options.systemPrompt); - const systemPrompt = options.systemPrompt as { - readonly type: string; - readonly preset: string; - readonly append?: string; - }; - assert.equal(systemPrompt.type, "preset"); - assert.equal(systemPrompt.preset, "claude_code"); - assert.include(systemPrompt.append ?? "", "Use `delegate_task`"); - const logged = ClaudeAdapterV2.loggedClaudeQueryOptions(options); - assert.equal(logged.hasMcpServers, true); - assert.notInclude(JSON.stringify(logged), "secret-claude-token"); - } finally { - McpProviderSession.clearMcpProviderSession(threadId); - } + const options = ClaudeAdapterV2.makeClaudeQueryOptions({ + modelSelection: { + instanceId: ProviderInstanceId.make("claudeAgent"), + model: "claude-sonnet-4-6", + }, + nativeThreadId: "native-thread-claude-mcp", + resume: false, + cwd: "/workspace", + allowedTools: overrides.allowedTools ?? [], + mcpServers: overrides.mcpServers ?? {}, + environment: { ...overrides.mcpEnvironment }, + }); + // mcpServers becomes a CLI argument, readable by every local user; the + // credential may only travel in the child's environment. + assert.notInclude(JSON.stringify(options.mcpServers), "secret-claude-token"); + assert.equal(options.env?.T3_CODE_MCP_AUTHORIZATION, "Bearer secret-claude-token"); + assert.isObject(options.systemPrompt); + const systemPrompt = options.systemPrompt as { + readonly type: string; + readonly preset: string; + readonly append?: string; + }; + assert.equal(systemPrompt.type, "preset"); + assert.equal(systemPrompt.preset, "claude_code"); + assert.include(systemPrompt.append ?? "", "Use `delegate_task`"); + const logged = ClaudeAdapterV2.loggedClaudeQueryOptions(options); + assert.equal(logged.hasMcpServers, true); + assert.notInclude(JSON.stringify(logged), "secret-claude-token"); }); it.effect("writes Claude Agent SDK protocol frames to the native provider log", () => @@ -961,7 +941,7 @@ describe("ClaudeAdapterV2 Auto-accept edits", () => { prefix: "t3-claude-accept-edits-", }); let openedOptions: ClaudeAdapterV2.ClaudeAgentSdkQueryOptions | undefined; - const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({ + const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({ instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID, settings: DEFAULT_CLAUDE_SETTINGS, environment: {}, @@ -1056,7 +1036,11 @@ describe("ClaudeAdapterV2 Auto-accept edits", () => { decision: "accept", }); assert.equal((yield* Fiber.join(decision))?.behavior, "allow"); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); }); @@ -1174,7 +1158,11 @@ describe("ClaudeAdapterV2 executable path", () => { assert.deepEqual(executablePaths, [path.join(NodeOS.homedir(), "bin", "claude")]); }), - ).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + ).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ); it.effect("follows a bare claude on Windows to the npm package executable", () => @@ -1190,7 +1178,11 @@ describe("ClaudeAdapterV2 executable path", () => { assert.deepEqual(executablePaths, [packageExe]); }), - ).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + ).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ); }); @@ -1204,7 +1196,7 @@ describe("ClaudeAdapterV2 resume compaction", () => { prefix: "t3-claude-resume-", }); let openedOptions: ClaudeAdapterV2.ClaudeAgentSdkQueryOptions | undefined; - const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({ + const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({ instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID, settings: DEFAULT_CLAUDE_SETTINGS, environment: {}, @@ -1408,7 +1400,11 @@ describe("ClaudeAdapterV2 resume compaction", () => { ), { behavior: "cancelled" }, ); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); }); @@ -1424,7 +1420,7 @@ describe("ClaudeAdapterV2 attachments", () => { prefix: "t3-claude-v2-attachments-", }); const offeredMessages: Array = []; - const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({ + const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({ instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID, settings: DEFAULT_CLAUDE_SETTINGS, environment: {}, @@ -1552,7 +1548,11 @@ describe("ClaudeAdapterV2 attachments", () => { text: `Ultrathink:\nFocus on the diagram labels.\n\n[Attached image "diagram.png" is saved at: ${expectedAttachmentPath}]\n\n[Attached file "requirements.pdf" is saved at: ${expectedDocumentPath}]`, }, ]); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -1565,7 +1565,7 @@ describe("ClaudeAdapterV2 attachments", () => { prefix: "t3-claude-v2-unsupported-attachment-", }); let openCount = 0; - const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({ + const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({ instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID, settings: DEFAULT_CLAUDE_SETTINGS, environment: {}, @@ -1634,7 +1634,11 @@ describe("ClaudeAdapterV2 attachments", () => { assert.equal(error._tag, "ProviderAdapterTurnStartError"); assert.include(String(error.cause), "Unsupported Claude image attachment type"); assert.equal(openCount, 0); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); }); @@ -1655,7 +1659,7 @@ describe("ClaudeAdapterV2 native fork", () => { readonly threadId: ThreadId; readonly providerSessionId: ProviderSessionId; }> = []; - const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({ + const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({ instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID, settings: DEFAULT_CLAUDE_SETTINGS, environment: {}, @@ -1813,7 +1817,11 @@ describe("ClaudeAdapterV2 native fork", () => { assert.equal(openedQueries[0]?.options.resume, "forked-native-session"); assert.equal(openedQueries[0]?.options.sessionId, undefined); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); }); @@ -1828,7 +1836,7 @@ describe("ClaudeAdapterV2 native session identity", () => { prefix: "t3-claude-v2-session-identity-", }); const openedQueries: Array = []; - const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({ + const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({ instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID, settings: DEFAULT_CLAUDE_SETTINGS, environment: {}, @@ -1883,7 +1891,11 @@ describe("ClaudeAdapterV2 native session identity", () => { }), ); return openedQueries; - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ); it.effect("creates the native session on the first provider turn", () => @@ -2127,7 +2139,7 @@ describe("ClaudeAdapterV2 background wake turns", () => { Extract >(); let openedOptions: ClaudeAdapterV2.ClaudeAgentSdkQueryOptions | undefined; - const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({ + const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({ instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID, settings: DEFAULT_CLAUDE_SETTINGS, environment: options?.environment ?? {}, @@ -2365,7 +2377,12 @@ describe("ClaudeAdapterV2 background wake turns", () => { assert.deepEqual(item.input, { city: "Berlin" }); } assert.equal(new Set(items.map((item) => item.id)).size, 1); - }).pipe(Effect.scoped, Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.scoped, + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ); it.effect( @@ -2426,7 +2443,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { assert.strictEqual(harness.getOpenedOptions(), originalOptions); assert.lengthOf(harness.offeredMessages, 2); assert.isTrue(yield* harness.hasPendingBackgroundWork); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -2545,7 +2566,12 @@ describe("ClaudeAdapterV2 background wake turns", () => { ), ); for (const item of latest.values()) assert.notInclude(item.text, "secret-signature"); - }).pipe(Effect.scoped, Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.scoped, + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ); it.effect.each(["cancelled", "denied", "permission_denied", undefined])( @@ -2615,7 +2641,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { event.type === "node.updated" && event.node.nativeItemRef?.nativeId === "tool-error", ); assert.equal(node?.type === "node.updated" ? node.node.status : undefined, failed.status); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -2688,7 +2718,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { ); } assert.lengthOf(harness.terminalEvents(), 1); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -2754,7 +2788,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { yield* Queue.take(harness.terminalReceipts); assert.lengthOf(notices(), 3); assert.notEqual(notices()[0]?.id, notices()[2]?.id); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ); it.effect("keeps usage warnings and provisioned overage silent", () => @@ -2801,7 +2839,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { (event) => event.type === "turn_item.updated" && event.turnItem.type === "system_notice", ), ); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ); it.effect("names an expired Claude login instead of the terminal API error", () => @@ -2846,7 +2888,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { assert.include(terminal.failure.message, configDir); assert.include(terminal.failure.message, cwd); assert.notInclude(terminal.failure.message, "repeated API errors"); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ); it.effect.each([ @@ -2900,7 +2946,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { if (terminal.status !== "failed") return; assert.include(terminal.failure.message, expected); assert.equal(terminal.failure.class, recovered ? "provider_error" : "usage_limit"); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ); it.effect.each([ @@ -2960,7 +3010,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { ? "Claude usage limit reached. Send the message again once the limit resets." : "Claude gave up after repeated API errors.", ); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ); it.effect.each([429, 401, 529])( @@ -3004,7 +3058,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { ); if (apiErrorStatus !== 429) assert.notInclude(terminal.failure.message.toLowerCase(), "usage limit"); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ); it.effect("surfaces a Claude safety model fallback without failing the turn", () => @@ -3063,7 +3121,12 @@ describe("ClaudeAdapterV2 background wake turns", () => { (event) => event.type === "turn_item.updated" && event.turnItem.type === "error", ), ); - }).pipe(Effect.scoped, Effect.provide(Layer.mergeAll(NodeServices.layer, IdAllocator.layer))), + }).pipe( + Effect.scoped, + Effect.provide( + Layer.mergeAll(NodeServices.layer, IdAllocator.layer, McpProviderSessions.layer), + ), + ), ); it.effect( @@ -3135,7 +3198,12 @@ describe("ClaudeAdapterV2 background wake turns", () => { cacheCreationTokens: 0, }); } - }).pipe(Effect.scoped, Effect.provide(Layer.mergeAll(NodeServices.layer, IdAllocator.layer))), + }).pipe( + Effect.scoped, + Effect.provide( + Layer.mergeAll(NodeServices.layer, IdAllocator.layer, McpProviderSessions.layer), + ), + ), ); it.effect("titles Claude reads, searches, and skills on tool completion", () => @@ -3233,7 +3301,12 @@ describe("ClaudeAdapterV2 background wake turns", () => { ); for (const item of items.filter((item) => item.nativeItemRef?.nativeId !== "image")) assert.notProperty(item, "viewedImagePath"); - }).pipe(Effect.scoped, Effect.provide(Layer.mergeAll(NodeServices.layer, IdAllocator.layer))), + }).pipe( + Effect.scoped, + Effect.provide( + Layer.mergeAll(NodeServices.layer, IdAllocator.layer, McpProviderSessions.layer), + ), + ), ); it.effect("preserves typed Claude plans and todos through generic tool completion", () => @@ -3442,7 +3515,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { // The second prompt reuses the live process, which is still in the // plan mode Claude entered, so it is put back in the thread's mode. assert.deepEqual(harness.permissionModeChanges, ["bypassPermissions"]); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -3516,7 +3593,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { }), ); yield* awaitUntil(() => harness.terminalEvents().length === 1, "recovered Claude turn"); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -3570,7 +3651,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { }); assert.isDefined(terminal.retryStartedAt); assert.equal(terminal.failure.code, "api_error_529"); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -3632,7 +3717,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { terminal.failure.code, terminalReason === "overloaded_status" ? "api_error_529" : terminalReason, ); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -3697,7 +3786,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { "empty roster clear", ); assert.isFalse(yield* harness.hasPendingBackgroundWork); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -3832,7 +3925,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { assert.deepEqual(roster, [ { taskId: longRunning.taskId, kind: "monitor", description: "Monitor 0" }, ]); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -3882,7 +3979,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { ); assert.isFalse(yield* harness.hasPendingBackgroundWork); assert.lengthOf(harness.continuationRequests, 0); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -3927,7 +4028,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { ); assert.isFalse(yield* harness.hasPendingBackgroundWork); assert.lengthOf(harness.continuationRequests, 0); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -3944,7 +4049,7 @@ describe("ClaudeAdapterV2 background wake turns", () => { const events: Array = []; const continuationRequests: Array = []; - const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({ + const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({ instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID, settings: DEFAULT_CLAUDE_SETTINGS, environment: {}, @@ -4109,7 +4214,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { }), ); yield* awaitUntil(() => continuationRequests.length === 1, "replacement wake"); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -4167,7 +4276,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { const afterFailure = providerThreadRosterEvents(harness.events).at(-1); assert.deepEqual(afterFailure?.providerThread.pendingBackgroundTasks ?? [], []); assert.isFalse(yield* harness.hasPendingBackgroundWork); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -4190,7 +4303,7 @@ describe("ClaudeAdapterV2 background wake turns", () => { readonly queue: Queue.Queue; }> = []; const events: Array = []; - const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({ + const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({ instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID, settings: DEFAULT_CLAUDE_SETTINGS, environment: {}, @@ -4429,7 +4542,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { assert.isFalse(yield* hasPendingBackgroundWorkForThread(providerThreadA)); assert.isFalse(yield* hasPendingBackgroundWorkForThread(providerThreadB)); assert.isFalse(yield* hasPendingBackgroundWork); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -4649,7 +4766,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { userRunItems.map((item) => item.type), ["assistant_message"], ); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -4732,7 +4853,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { : undefined, "On branch main\nwarning: dirty", ); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -4861,7 +4986,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { }); const result = yield* Fiber.join(permission); assert.equal(result?.behavior, "allow"); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -4957,7 +5086,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { event.message.text === STALE_TASK_NOTIFICATION_RESULT_TEXT, ), ); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -5035,7 +5168,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { yield* awaitUntil(() => harness.terminalEvents().length === 2, "second turn terminal"); assert.deepEqual(assistantTexts(), ["One.", "Two."]); assert.equal(harness.terminalEvents()[1]?.status, "failed"); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -5061,7 +5198,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { ); } assert.notEqual(original.promptUuid(0), copy.promptUuid(0)); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -5098,7 +5239,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { if (terminal?.status !== "failed") return; assert.equal(terminal.threadDisposition, "reusable"); assert.include(terminal.failure.message, "never started a turn"); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -5147,7 +5292,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { ); yield* awaitUntil(() => harness.terminalEvents().length === 1, "turn terminal"); assert.equal(harness.terminalEvents()[0]?.status, "completed"); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -5193,7 +5342,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { assert.lengthOf(harness.continuationRequests, 1); assert.lengthOf(harness.terminalEvents(), 1); assert.isTrue(yield* harness.hasPendingBackgroundWork); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -5250,7 +5403,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { (event) => event.type === "message.updated" && event.message.text === WAKE_RESULT_TEXT, ), ); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -5301,7 +5458,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { ); assert.equal(harness.terminalEvents()[1]?.status, "completed"); assert.lengthOf(harness.offeredMessages, 1); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -5389,7 +5550,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { assert.equal(pause.turnItem.type, "system_notice"); if (pause.turnItem.type !== "system_notice") return; assert.include(pause.turnItem.message, "This turn is paused until the 5-hour limit"); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -5480,7 +5645,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { ), ); assert.isFalse(yield* harness.hasPendingBackgroundWork); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -5542,7 +5711,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { ); yield* awaitUntil(() => harness.terminalEvents().length === 2, "queued turn terminal"); assert.equal(harness.terminalEvents()[1]?.status, "completed"); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -5580,7 +5753,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { (event) => event.type === "message.updated" && event.message.text === fallbackText, ), ); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -5663,7 +5840,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { assert.equal(harness.terminalEvents()[0]?.status, "completed"); assert.isTrue(hasMessageText(recoveryAssistantText)); assert.isFalse(hasMessageText(staleResultText)); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -5713,7 +5894,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { ); yield* awaitUntil(() => harness.terminalEvents().length === 2, "continuation terminal"); assert.equal(harness.terminalEvents()[1]?.status, "completed"); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -5772,7 +5957,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { assert.isTrue(terminalized); assert.equal(harness.terminalEvents()[0]?.status, "interrupted"); assert.lengthOf(harness.terminalEvents(), 1); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -5831,7 +6020,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { (event) => event.type === "message.updated" && event.message.text === staleText, ), ); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -5865,7 +6058,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { ); yield* awaitUntil(() => harness.terminalEvents().length === 1, "failed terminal"); assert.equal(harness.terminalEvents()[0]?.status, "failed"); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -5891,7 +6088,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { yield* awaitUntil(() => harness.terminalEvents().length === 1, "spurious terminal"); assert.equal(harness.terminalEvents()[0]?.status, "completed"); assert.lengthOf(harness.offeredMessages, 0); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -6088,7 +6289,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { harness.continuationRequests[1]?.notification?.summary, `Command "${WAKE_TASK_DESCRIPTION}" finished`, ); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -6149,7 +6354,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { harness.continuationRequests[0]?.notification?.summary, `Command "${WAKE_TASK_DESCRIPTION}" finished`, ); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -6305,7 +6514,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { : [], ); assert.deepEqual([...new Set(userRunItems)], ["assistant_message"]); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -6411,7 +6624,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { finalSubagent?.type === "subagent.updated" && finalSubagent.subagent.status, "completed", ); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -6572,7 +6789,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { finalSubagent?.type === "subagent.updated" && finalSubagent.subagent.status, "completed", ); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -6760,7 +6981,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { subagentRouting(harness.events, []).assistantTexts(outer?.childThreadId ?? undefined), ["OUTER_WORKING", "OUTER_DONE"], ); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -6890,7 +7115,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { ); assert.equal(nested?.type === "subagent.updated" && nested.subagent.status, "completed"); assert.lengthOf(harness.continuationRequests, 0); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -6942,7 +7171,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { yield* Queue.shutdown(harness.sdkMessages); yield* awaitUntil(() => bashStatuses().length === 2, "call ended"); assert.deepEqual(bashStatuses(), ["command_execution:running", "command_execution:failed"]); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -7004,7 +7237,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { "command_execution:running", "command_execution:interrupted", ]); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -7170,7 +7407,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { `command_execution:running:${input}`, `command_execution:completed:${input}`, ]); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -7326,7 +7567,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { `dynamic_tool:running:${input}`, `dynamic_tool:completed:${input}`, ]); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -7403,7 +7648,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { "Part two.", ]); assert.deepEqual(routing.assistantTexts(harness.threadId), ["The auditor finished."]); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -7578,7 +7827,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { ), ); assert.lengthOf(harness.continuationRequests, 1); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -7639,7 +7892,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { : [], ); assert.deepEqual(retryStatuses, ["running"]); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -7728,7 +7985,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { FINAL_REPORT, ]); assert.deepEqual(routing.assistantTexts(harness.threadId), ["The auditor finished."]); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -7835,7 +8096,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { ); const child = harness.events.find((event) => event.type === "app_thread.created"); assert.equal(child?.appThread.modelSelection?.model, initialModel ?? parentModel); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -7920,7 +8185,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { }), ); yield* awaitUntil(() => harness.terminalEvents().length === 1, "turn terminal"); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -8013,7 +8282,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { assert.equal(subagentEvents().at(-1)?.subagent.status, "cancelled"); assert.isFalse(yield* harness.hasPendingBackgroundWork); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -8299,7 +8572,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { assert.lengthOf(resumedWorkIndexes, 1); assert.isAbove(resumedWorkIndexes[0] ?? -1, reopenIndex); assert.isFalse(yield* harness.hasPendingBackgroundWork); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -8315,7 +8592,7 @@ describe("ClaudeAdapterV2 background wake turns", () => { const events: Array = []; const continuationRequests: Array = []; - const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({ + const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({ instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID, settings: DEFAULT_CLAUDE_SETTINGS, environment: {}, @@ -8482,7 +8759,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { () => events.filter((event) => event.type === "turn.terminal").length === 2, "second turn terminal", ); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -8500,7 +8781,7 @@ describe("ClaudeAdapterV2 background wake turns", () => { const events: Array = []; const continuationRequests: Array = []; - const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({ + const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({ instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID, settings: DEFAULT_CLAUDE_SETTINGS, environment: {}, @@ -8670,7 +8951,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { ), ); assert.isFalse(yield* hasPendingBackgroundWork); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -8737,7 +9022,7 @@ describe("ClaudeAdapterV2 background wake turns", () => { const events: Array = []; const continuationRequests: Array = []; - const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({ + const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({ instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID, settings: DEFAULT_CLAUDE_SETTINGS, environment: {}, @@ -8920,7 +9205,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { ); assert.equal(subagentNodeEvents.at(-1)?.node.status, "completed"); assert.isFalse(yield* hasPendingBackgroundWork); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -8936,7 +9225,7 @@ describe("ClaudeAdapterV2 background wake turns", () => { }); const processQueues: Array> = []; const events: Array = []; - const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({ + const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({ instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID, settings: DEFAULT_CLAUDE_SETTINGS, environment: {}, @@ -9086,7 +9375,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { }), ); assert.lengthOf(processQueues, 3); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -9103,7 +9396,7 @@ describe("ClaudeAdapterV2 background wake turns", () => { let openCount = 0; const processQueues: Array> = []; const events: Array = []; - const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({ + const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({ instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID, settings: DEFAULT_CLAUDE_SETTINGS, environment: {}, @@ -9215,7 +9508,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { // no replacement is opened. assert.equal(openCount, 1); assert.isTrue(yield* hasPendingBackgroundWork); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -9234,7 +9531,7 @@ describe("ClaudeAdapterV2 background wake turns", () => { const events: Array = []; const continuationRequests: Array = []; - const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({ + const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({ instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID, settings: DEFAULT_CLAUDE_SETTINGS, environment: {}, @@ -9416,7 +9713,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { "continuation request after retry", ); assert.equal(continuationRequests[1]?.detail, "Retry build completed successfully"); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -9429,7 +9730,7 @@ describe("ClaudeAdapterV2 background wake turns", () => { prefix: "t3-claude-v2-first-open-fail-", }); const events: Array = []; - const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({ + const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({ instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID, settings: DEFAULT_CLAUDE_SETTINGS, environment: {}, @@ -9491,7 +9792,11 @@ describe("ClaudeAdapterV2 background wake turns", () => { assert.isTrue(Exit.isFailure(failedStart)); // No live process ever existed: do not emit a fabricated empty roster. assert.lengthOf(providerThreadRosterEvents(events), 0); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -9572,7 +9877,12 @@ describe("ClaudeAdapterV2 background wake turns", () => { }, { objective: condition, status: "complete", checks: 1 }, ]); - }).pipe(Effect.scoped, Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.scoped, + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ); it.effect("keeps a goal active when a hook stops the turn before the goal passes", () => @@ -9606,7 +9916,12 @@ describe("ClaudeAdapterV2 background wake turns", () => { status: "active", checks: 0, }); - }).pipe(Effect.scoped, Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.scoped, + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ); it.effect("keeps a goal active after a command turn with no model output", () => @@ -9640,7 +9955,12 @@ describe("ClaudeAdapterV2 background wake turns", () => { status: "active", checks: 2, }); - }).pipe(Effect.scoped, Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.scoped, + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ); }); }); diff --git a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts index 0eb2dff9bfe3..9f1e34f8d1a0 100644 --- a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts +++ b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts @@ -117,6 +117,7 @@ import { normalizeMcpText, } from "@t3tools/provider-core/server/mcpToolPresentation"; import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession"; +import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions"; import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator"; import { makeProviderFailure, @@ -973,7 +974,7 @@ export const CLAUDE_T3_MCP_TOOL_TIMEOUT_MS = 65 * 60 * 1_000; const CLAUDE_T3_MCP_AUTHORIZATION_ENV = "T3_CODE_MCP_AUTHORIZATION"; export function claudeMcpQueryOverrides(input: { - readonly threadId: ThreadId; + readonly mcpSession: McpProviderSession.McpProviderSessionConfig | undefined; readonly readOnlySandbox: boolean; readonly allowedTools?: ReadonlyArray; }): { @@ -981,7 +982,7 @@ export function claudeMcpQueryOverrides(input: { readonly mcpServers?: ClaudeQueryOptions["mcpServers"]; readonly mcpEnvironment?: Readonly>; } { - const session = McpProviderSession.readMcpProviderSession(input.threadId); + const session = input.mcpSession; if (session === undefined) { return input.allowedTools === undefined ? {} : { allowedTools: input.allowedTools }; } @@ -3026,9 +3027,10 @@ export interface ClaudeAdapterV2Options { }; } -export function makeClaudeAdapterV2( +export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* ( adapterOptions: ClaudeAdapterV2Options, -): ProviderAdapter.ProviderAdapterV2["Service"] { +) { + const mcpSessions = yield* McpProviderSessions.McpProviderSessions; const { attachmentsDir, fileSystem, path, crypto, idAllocator, queryRunner } = adapterOptions; const continuationRequests = adapterOptions.continuationRequests ?? { offer: () => Effect.void, @@ -7261,7 +7263,7 @@ export function makeClaudeAdapterV2( ) { const queryPolicy = claudeRuntimeQueryPolicyForRuntimePolicy(turnInput.runtimePolicy); const mcpOverrides = claudeMcpQueryOverrides({ - threadId: turnInput.threadId, + mcpSession: yield* mcpSessions.read(turnInput.threadId), readOnlySandbox: sandboxPolicyKindForClaudeRuntimePolicy(turnInput.runtimePolicy) === "readOnly", ...(queryPolicy.allowedTools === undefined @@ -8156,13 +8158,14 @@ export function makeClaudeAdapterV2( ), ), }); -} +}); export type ClaudeAdapterV2DriverEnv = | ClaudeAgentSdkQueryRunner | Crypto.Crypto | FileSystem.FileSystem | IdAllocator.IdAllocatorV2 + | McpProviderSessions.McpProviderSessions | Path.Path | ServerConfig.ServerConfig; @@ -8186,7 +8189,7 @@ export const createClaudeAdapterV2 = Effect.fn("ClaudeAdapterV2Driver.create")( expandHomePath(config.binaryPath), claudeEnvironment, ); - return makeClaudeAdapterV2({ + return yield* makeClaudeAdapterV2({ instanceId, settings: { ...config, enabled, binaryPath }, environment: claudeEnvironment, @@ -8234,7 +8237,7 @@ const makeDefaultClaudeAdapterV2 = Effect.fn("ClaudeAdapterV2.layer")(function* const serverConfig = yield* ServerConfig.ServerConfig; const continuationRequests = yield* ProviderContinuationRequests.ProviderContinuationRequests; - return makeClaudeAdapterV2({ + return yield* makeClaudeAdapterV2({ instanceId: CLAUDE_DEFAULT_INSTANCE_ID, settings: DEFAULT_CLAUDE_SETTINGS, environment: hostEnvironment, @@ -8255,6 +8258,7 @@ const layer: Layer.Layer< | Crypto.Crypto | FileSystem.FileSystem | IdAllocator.IdAllocatorV2 + | McpProviderSessions.McpProviderSessions | Path.Path | ServerConfig.ServerConfig > = Layer.effect(ProviderAdapter.ProviderAdapterV2, makeDefaultClaudeAdapterV2()); diff --git a/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts index ef884b9c3abc..5b18f436c26e 100644 --- a/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts +++ b/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts @@ -54,7 +54,7 @@ import { ChildProcess, ChildProcessSpawner } from "effect/process"; import packageJson from "../../../package.json" with { type: "json" }; import * as ServerConfig from "../../config.ts"; -import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession"; +import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions"; import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers"; import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator"; import * as EffectWorker from "../EffectWorker.ts"; @@ -639,7 +639,7 @@ describe("CodexAdapterV2 runtime policy", () => { describe("CodexAdapterV2 process spawning", () => { it("injects cwd, model, and MCP authorization into thread-scoped params", () => { const threadId = ThreadId.make("thread-codex-mcp"); - McpProviderSession.setMcpProviderSession({ + const mcpSession = { environmentId: EnvironmentId.make("environment-codex-mcp"), threadId, providerSessionId: "mcp-session-codex", @@ -647,38 +647,34 @@ describe("CodexAdapterV2 process spawning", () => { endpoint: "http://127.0.0.1:43123/mcp", authorizationHeader: "Bearer secret-codex-token", browserToolsAvailable: true, - }); + }; - try { - assert.deepEqual( - CodexAdapterV2.codexThreadRuntimeParams({ - threadId, - modelSelection: { model: "gpt-5.4" }, - runtimePolicy: { - runtimeMode: "full-access", - interactionMode: "default", - cwd: "/workspace/thread-codex-mcp", - }, - }), - { + assert.deepEqual( + CodexAdapterV2.codexThreadRuntimeParams({ + mcpSession, + modelSelection: { model: "gpt-5.4" }, + runtimePolicy: { + runtimeMode: "full-access", + interactionMode: "default", cwd: "/workspace/thread-codex-mcp", - model: "gpt-5.4", - config: { - "tools.update_plan.enabled": true, - mcp_servers: { - "t3-code": { - url: "http://127.0.0.1:43123/mcp", - http_headers: { - Authorization: "Bearer secret-codex-token", - }, + }, + }), + { + cwd: "/workspace/thread-codex-mcp", + model: "gpt-5.4", + config: { + "tools.update_plan.enabled": true, + mcp_servers: { + "t3-code": { + url: "http://127.0.0.1:43123/mcp", + http_headers: { + Authorization: "Bearer secret-codex-token", }, }, }, }, - ); - } finally { - McpProviderSession.clearMcpProviderSession(threadId); - } + }, + ); }); it.effect("resolves Windows command shims through the shared spawn policy", () => @@ -822,7 +818,9 @@ describe("CodexAdapterV2 process spawning", () => { assert.deepEqual(spawnedCommands, [path.join(NodeOS.homedir(), "bin", "codex")]); }).pipe( - Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer)), + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), Effect.provideService(HostProcessPlatform, "linux"), ), ); @@ -1716,7 +1714,7 @@ describe("CodexAdapterV2 session initialize", () => { beforeEmitInbound === undefined ? {} : { beforeEmitInbound }, ); let initializeRequests = 0; - const adapter = CodexAdapterV2.makeCodexAdapterV2({ + const adapter = yield* CodexAdapterV2.makeCodexAdapterV2({ instanceId: CodexAdapterV2.CODEX_DEFAULT_INSTANCE_ID, settings: DEFAULT_CODEX_SETTINGS, environment: {}, @@ -1811,7 +1809,12 @@ describe("CodexAdapterV2 session initialize", () => { providerThreads.map((providerThread) => providerThread.nativeThreadRef?.nativeId), ["concurrent-first", "concurrent-second"], ); - }).pipe(Effect.scoped, Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.scoped, + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ); it.effect("retries initialize after a failed handshake", () => @@ -1837,7 +1840,12 @@ describe("CodexAdapterV2 session initialize", () => { const providerThread = yield* session.ensureThread("thread-initialize-retry"); assert.equal(providerThread.nativeThreadRef?.nativeId, "initialize-retry"); assert.equal(session.initializeRequests(), 2); - }).pipe(Effect.scoped, Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.scoped, + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ); it.effect("completes the handshake after a caller is interrupted mid-initialize", () => @@ -1880,7 +1888,12 @@ describe("CodexAdapterV2 session initialize", () => { const providerThread = yield* session.ensureThread("thread-initialize-interrupted"); assert.equal(providerThread.nativeThreadRef?.nativeId, "initialize-interrupted"); assert.equal(session.initializeRequests(), 2); - }).pipe(Effect.scoped, Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.scoped, + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ); }); @@ -1940,7 +1953,7 @@ describe("CodexAdapterV2 post-settle continuation", () => { ), ), }; - const adapter = CodexAdapterV2.makeCodexAdapterV2({ + const adapter = yield* CodexAdapterV2.makeCodexAdapterV2({ instanceId: CodexAdapterV2.CODEX_DEFAULT_INSTANCE_ID, settings: DEFAULT_CODEX_SETTINGS, environment: {}, @@ -2121,7 +2134,12 @@ describe("CodexAdapterV2 post-settle continuation", () => { ); assert.equal(requests.filter((method) => method === "turn/start").length, 1); assert.isBelow(requests.indexOf("thread/inject_items"), requests.indexOf("turn/start")); - }).pipe(Effect.scoped, Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.scoped, + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ); it.effect("identifies sessions to Codex with the same client info as main", () => @@ -2157,7 +2175,12 @@ describe("CodexAdapterV2 post-settle continuation", () => { }, }, ]); - }).pipe(Effect.scoped, Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.scoped, + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ); it.effect("unsubscribes from the native thread when it is unloaded", () => @@ -2195,7 +2218,12 @@ describe("CodexAdapterV2 post-settle continuation", () => { assert.isDefined(harness.runtime.unloadThread); yield* harness.runtime.unloadThread!({ providerThread: harness.providerThread }); assert.deepEqual(requests, ["initialize", "thread/start", "thread/unsubscribe"]); - }).pipe(Effect.scoped, Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.scoped, + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ); it.effect("keeps the app-server failure as the cause when an unload is rejected", () => @@ -2229,7 +2257,12 @@ describe("CodexAdapterV2 post-settle continuation", () => { assert.equal(error._tag, "ProviderAdapterProtocolError"); const cause = error._tag === "ProviderAdapterProtocolError" ? error.cause : undefined; assert.equal((cause as { _tag?: string } | undefined)?._tag, "CodexAppServerRequestError"); - }).pipe(Effect.scoped, Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.scoped, + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ); it.effect("waits for native start before interrupting an acknowledged queued turn", () => @@ -2323,7 +2356,12 @@ describe("CodexAdapterV2 post-settle continuation", () => { assert.equal(harness.terminalEvents()[0]?.status, "interrupted"); assert.lengthOf(harness.terminalEvents(), 1); assert.isFalse(yield* harness.hasPendingBackgroundWork); - }).pipe(Effect.scoped, Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.scoped, + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ); it.effect("settles Stop when a queued native turn fails before starting", () => @@ -2405,7 +2443,12 @@ describe("CodexAdapterV2 post-settle continuation", () => { assert.lengthOf(harness.terminalEvents(), 1); assert.isFalse(interruptSent, "A terminal native turn must not receive turn/interrupt"); assert.isFalse(yield* harness.hasPendingBackgroundWork); - }).pipe(Effect.scoped, Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.scoped, + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ); it.effect("bounds Stop when a queued native turn never starts", () => @@ -2465,7 +2508,12 @@ describe("CodexAdapterV2 post-settle continuation", () => { assert.lengthOf(harness.terminalEvents(), 1); assert.isFalse(interruptSent, "An unstarted native turn must not receive turn/interrupt"); assert.isFalse(yield* harness.hasPendingBackgroundWork); - }).pipe(Effect.scoped, Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.scoped, + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ); it.effect("sends currency-sigil skill mentions to Codex as $ mentions", () => @@ -2524,7 +2572,12 @@ describe("CodexAdapterV2 post-settle continuation", () => { text: "then £ship it", }, }); - }).pipe(Effect.scoped, Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.scoped, + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ); const assistantMessages = (events: ReadonlyArray) => @@ -2674,7 +2727,11 @@ describe("CodexAdapterV2 post-settle continuation", () => { }); } assert.isEmpty(assistantMessages(harness.events)); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -2748,7 +2805,7 @@ describe("CodexAdapterV2 post-settle continuation", () => { ], }); const harness = yield* makeCodexReplayHarness(transcript); - McpProviderSession.setMcpProviderSession({ + yield* (yield* McpProviderSessions.McpProviderSessions).set({ environmentId: EnvironmentId.make("test"), threadId: harness.threadId, providerSessionId: "context-session", @@ -2757,9 +2814,6 @@ describe("CodexAdapterV2 post-settle continuation", () => { authorizationHeader: "Bearer test", browserToolsAvailable: true, }); - yield* Effect.addFinalizer(() => - Effect.sync(() => McpProviderSession.clearMcpProviderSession(harness.threadId)), - ); yield* harness.runtime.startTurn( makeCodexTestTurnInput({ threadId: harness.threadId, @@ -2771,7 +2825,11 @@ describe("CodexAdapterV2 post-settle continuation", () => { ); yield* harness.firstTerminal; assert.equal(harness.terminalEvents()[0]?.status, "completed"); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -2847,7 +2905,11 @@ describe("CodexAdapterV2 post-settle continuation", () => { ); assert.equal(items[0]?.id, items[1]?.id); assert.equal(harness.terminalEvents()[0]?.status, "completed"); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -2895,7 +2957,11 @@ describe("CodexAdapterV2 post-settle continuation", () => { assert.equal(resumed.nativeThreadRef?.nativeId, nativeThreadId); assert.equal(resumed.status, "idle"); assert.equal(DateTime.toEpochMillis(resumed.updatedAt), 1782622450000); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -3050,7 +3116,11 @@ describe("CodexAdapterV2 post-settle continuation", () => { ); assert.equal(resumed.status, "idle"); assert.equal(DateTime.toEpochMillis(resumed.updatedAt), 1782622450000); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -3132,7 +3202,11 @@ describe("CodexAdapterV2 post-settle continuation", () => { restartContinuationOfRunId: RunId.make("run-before-restart"), }); assert.equal(resumed.nativeThreadRef?.nativeId, nativeThreadId); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -3280,7 +3354,11 @@ describe("CodexAdapterV2 post-settle continuation", () => { assert.isAtLeast(recoveredIndex, 0); assert.isAbove(resumedCommandIndex, recoveredIndex); assert.isAbove(terminalIndex, resumedCommandIndex); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -3379,7 +3457,11 @@ describe("CodexAdapterV2 post-settle continuation", () => { ["compaction", 1782622565000], ], ); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -3543,7 +3625,11 @@ describe("CodexAdapterV2 post-settle continuation", () => { ); } assert.deepEqual(assistantMessages(harness.events), []); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -3672,7 +3758,11 @@ describe("CodexAdapterV2 post-settle continuation", () => { assistantMessages(harness.events).map((event) => event.message.text), ["CODEX_RECOVERY_OK"], ); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -3715,7 +3805,11 @@ describe("CodexAdapterV2 post-settle continuation", () => { })), [{ text: "CODEX_RECOVERY_OK", streaming: false }], ); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -3759,7 +3853,11 @@ describe("CodexAdapterV2 post-settle continuation", () => { assistantMessages(harness.events).map((event) => event.message.text), ["CODEX_RECOVERY_OK"], ); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -3878,7 +3976,11 @@ describe("CodexAdapterV2 post-settle continuation", () => { new Set(assistantMessages(harness.events).map((event) => event.message.id)).size, 1, ); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -3906,7 +4008,11 @@ describe("CodexAdapterV2 post-settle continuation", () => { assistantMessages(harness.events).map((event) => event.message.text), [""], ); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -3935,7 +4041,11 @@ describe("CodexAdapterV2 post-settle continuation", () => { assistantMessages(harness.events).map((event) => event.message.text), [""], ); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -3964,7 +4074,11 @@ describe("CodexAdapterV2 post-settle continuation", () => { assistantMessages(harness.events).map((event) => event.message.text), ["Working on it.", ""], ); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -3993,7 +4107,11 @@ describe("CodexAdapterV2 post-settle continuation", () => { assistantMessages(harness.events).map((event) => event.message.text), ["CODEX_RECOVERY_OK"], ); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -4022,7 +4140,11 @@ describe("CodexAdapterV2 post-settle continuation", () => { assistantMessages(harness.events).map((event) => event.message.text), ["CODEX_RECOVERY_OK"], ); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -4051,7 +4173,11 @@ describe("CodexAdapterV2 post-settle continuation", () => { assistantMessages(harness.events).map((event) => event.message.text), ["", "CODEX_RECOVERY_OK"], ); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -4207,7 +4333,11 @@ describe("CodexAdapterV2 post-settle continuation", () => { ); assert.lengthOf(harness.terminalEvents(), 1); assert.isFalse(yield* harness.hasPendingBackgroundWork); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -4362,7 +4492,11 @@ describe("CodexAdapterV2 post-settle continuation", () => { assert.lengthOf(harness.terminalEvents(), 1); assert.equal(harness.terminalEvents()[0]?.status, "completed"); assert.lengthOf(harness.continuationRequests, 0); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ); }, ); @@ -4722,7 +4856,11 @@ describe("CodexAdapterV2 post-settle continuation", () => { assert.lengthOf(harness.continuationRequests, 0); assert.isFalse(yield* harness.hasPendingBackgroundWork); assert.lengthOf(harness.terminalEvents(), 1); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -5011,7 +5149,11 @@ describe("CodexAdapterV2 post-settle continuation", () => { ); assert.isAtLeast(commandUpdates.length, 2, "start + interrupt terminalization"); assert.equal(commandUpdates[commandUpdates.length - 1]?.turnItem.status, "interrupted"); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -5239,7 +5381,11 @@ describe("CodexAdapterV2 post-settle continuation", () => { assert.equal(harness.subagentUpdates().at(-1)?.subagent.status, "interrupted"); assertChildProviderTerminalBeforeRoot(harness.events, harness.threadId); assert.isFalse(yield* harness.hasPendingBackgroundWork); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -5324,7 +5470,11 @@ describe("CodexAdapterV2 post-settle continuation", () => { yield* awaitUntil(() => harness.terminalEvents().length === 1, "interrupted root terminal"); assertChildProviderTerminalBeforeRoot(harness.events, harness.threadId); assert.isFalse(yield* harness.hasPendingBackgroundWork); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -5412,7 +5562,11 @@ describe("CodexAdapterV2 post-settle continuation", () => { yield* awaitUntil(() => harness.terminalEvents().length === 1, "interrupted root terminal"); assertChildProviderTerminalBeforeRoot(harness.events, harness.threadId); assert.isFalse(yield* harness.hasPendingBackgroundWork); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -5523,7 +5677,11 @@ describe("CodexAdapterV2 post-settle continuation", () => { assert.equal(lateChildUpdates.at(-1)?.providerTurn.status, "interrupted"); assertChildProviderTerminalBeforeRoot(harness.events, harness.threadId); assert.isFalse(yield* harness.hasPendingBackgroundWork); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -5688,7 +5846,11 @@ describe("CodexAdapterV2 post-settle continuation", () => { ); assertChildProviderTerminalBeforeRoot(harness.events, harness.threadId); assert.isFalse(yield* harness.hasPendingBackgroundWork); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -5953,7 +6115,11 @@ describe("CodexAdapterV2 post-settle continuation", () => { commandUpdatesBeforeLateEvents.length, "late starts and completions must not project after timeout", ); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -6060,7 +6226,11 @@ describe("CodexAdapterV2 post-settle continuation", () => { yield* awaitUntil(() => harness.terminalEvents().length === 1, "interrupted terminal"); assert.equal(harness.terminalEvents()[0]?.status, "interrupted"); assert.isFalse(yield* harness.hasPendingBackgroundWork); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -6296,7 +6466,11 @@ describe("CodexAdapterV2 post-settle continuation", () => { assert.isDefined(item); } if (scenario.name === "retry") assert.equal(terminal.retry?.attempt, 1); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -6394,7 +6568,11 @@ describe("CodexAdapterV2 post-settle continuation", () => { ); assert.isFalse(yield* harness.hasPendingBackgroundWork); assert.lengthOf(harness.continuationRequests, 0); - }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))), + }).pipe( + Effect.provide( + Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), + ), + ), ), ); @@ -6555,7 +6733,11 @@ describe("CodexAdapterV2 post-settle continuation", () => { assert.include(stored, "connect-src https://api.weather.test"); assert.isBelow(stored.indexOf("Content-Security-Policy"), stored.indexOf("