authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-10-09 09:15:22-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-10-09 09:15:22-07:00
log784b5625f9e228cca8dd67e6e24e402325672544
treeab854947597d458ebed305e249b0b4111713ab7d
parent68ab16f3084236871685bed5032cb05c7c436dbd
signature Signed by PGP key B5690EEEBB952194

refactor(provider-core): MCP provider sessions live in a McpProviderSessions service (#17446)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

48 files changed, 1467 insertions(+), 653 deletions(-)

apps/server/src/mcp/OrchestratorMcpToolkit.integration.test.ts+4-1
...@@ -59,6 +59,7 @@ import * as Orchestrator from "../orchestration-v2/Orchestrator.ts";...@@ -59,6 +59,7 @@ import * as Orchestrator from "../orchestration-v2/Orchestrator.ts";
59import * as ThreadManagementService from "../orchestration-v2/ThreadManagementService.ts";59import * as ThreadManagementService from "../orchestration-v2/ThreadManagementService.ts";
60import * as ProviderAdapterRegistry from "../orchestration-v2/ProviderAdapterRegistry.ts";60import * as ProviderAdapterRegistry from "../orchestration-v2/ProviderAdapterRegistry.ts";
61import * as ProviderContinuationRequests from "@t3tools/provider-core/server/ProviderContinuationRequests";61import * as ProviderContinuationRequests from "@t3tools/provider-core/server/ProviderContinuationRequests";
62import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
62import { checkpointWorkspace } from "@t3tools/provider-testing/replayWorkspace";63import { checkpointWorkspace } from "@t3tools/provider-testing/replayWorkspace";
63import * as ProviderReplayHarness from "../orchestration-v2/testkit/ProviderReplayHarness.ts";64import * as ProviderReplayHarness from "../orchestration-v2/testkit/ProviderReplayHarness.ts";
64import {65import {
...@@ -3810,7 +3811,9 @@ describe("orchestrator MCP toolkit", () => {...@@ -3810,7 +3811,9 @@ describe("orchestrator MCP toolkit", () => {
3810 Layer.provideMerge(McpServer.McpServer.layer),3811 Layer.provideMerge(McpServer.McpServer.layer),
3811 Layer.provideMerge(layerOrchestration),3812 Layer.provideMerge(layerOrchestration),
3812 Layer.provide(3813 Layer.provide(
3813 CodexOrchestratorReplayHarness.makeProviderAdapterRegistryLayer(transcript),3814 CodexOrchestratorReplayHarness.makeProviderAdapterRegistryLayer(transcript).pipe(
3815 Layer.provide(McpProviderSessions.layer),
3816 ),
3814 ),3817 ),
3815 Layer.provide(layerProviderRegistry),3818 Layer.provide(layerProviderRegistry),
3816 Layer.provide(layerUnusedScheduledTaskStub),3819 Layer.provide(layerUnusedScheduledTaskStub),
apps/server/src/orchestration-v2/AcpRegistryOrchestratorV2.live.test.ts+2
...@@ -30,6 +30,7 @@ import * as ServerEnvironment from "../environment/ServerEnvironment.ts";...@@ -30,6 +30,7 @@ import * as ServerEnvironment from "../environment/ServerEnvironment.ts";
30import * as ModelManifest from "../provider/ModelManifest.ts";30import * as ModelManifest from "../provider/ModelManifest.ts";
31import * as ProviderInstanceRegistryHydration from "../provider/ProviderInstanceRegistryHydration.ts";31import * as ProviderInstanceRegistryHydration from "../provider/ProviderInstanceRegistryHydration.ts";
32import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";32import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";
33import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
33import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";34import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";
34import * as OpenCodeRuntime from "@t3tools/provider-opencode/server/OpenCodeRuntime";35import * as OpenCodeRuntime from "@t3tools/provider-opencode/server/OpenCodeRuntime";
35import * as OpenCodeServerLedger from "@t3tools/provider-opencode/server/OpenCodeServerLedger";36import * as OpenCodeServerLedger from "@t3tools/provider-opencode/server/OpenCodeServerLedger";
...@@ -144,6 +145,7 @@ const layerLive = RuntimeLayer.layer.pipe(...@@ -144,6 +145,7 @@ const layerLive = RuntimeLayer.layer.pipe(
144 Layer.provide(layerBackgroundPolicy),145 Layer.provide(layerBackgroundPolicy),
145 Layer.provide(ProviderTurnStartServiceTestkit.layer),146 Layer.provide(ProviderTurnStartServiceTestkit.layer),
146 Layer.provide(layerPlatformTest),147 Layer.provide(layerPlatformTest),
148 Layer.provide(McpProviderSessions.layer),
147);149);
148150
149const waitForIdle = Effect.fn("AcpRegistryOrchestratorV2Live.waitForIdle")(function* (151const waitForIdle = Effect.fn("AcpRegistryOrchestratorV2Live.waitForIdle")(function* (
apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.test.ts+12-24
...@@ -54,7 +54,7 @@ import type * as EffectAcpProtocol from "effect-acp/protocol";...@@ -54,7 +54,7 @@ import type * as EffectAcpProtocol from "effect-acp/protocol";
54import type * as EffectAcpSchema from "effect-acp/compat";54import type * as EffectAcpSchema from "effect-acp/compat";
5555
56import { layerTestProviderHost } from "@t3tools/provider-testing/host";56import { layerTestProviderHost } from "@t3tools/provider-testing/host";
57import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession";57import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
58import * as AcpSessionRuntime from "@t3tools/provider-acp/server/AcpSessionRuntime";58import * as AcpSessionRuntime from "@t3tools/provider-acp/server/AcpSessionRuntime";
59import {59import {
60 extractXAiAcpSubagentEndNotice,60 extractXAiAcpSubagentEndNotice,
...@@ -96,7 +96,12 @@ const DEFAULT_GROK_SETTINGS = Schema.decodeSync(GrokSettings)({});...@@ -96,7 +96,12 @@ const DEFAULT_GROK_SETTINGS = Schema.decodeSync(GrokSettings)({});
9696
97const layerHost = layerTestProviderHost().pipe(Layer.provide(NodeServices.layer));97const layerHost = layerTestProviderHost().pipe(Layer.provide(NodeServices.layer));
9898
99const layerTest = Layer.mergeAll(NodeServices.layer, IdAllocator.layer, layerHost);99const layerTest = Layer.mergeAll(
100 NodeServices.layer,
101 IdAllocator.layer,
102 McpProviderSessions.layer,
103 layerHost,
104);
100const ACP_TEST_DRIVER = ProviderDriverKind.make("acp-test");105const ACP_TEST_DRIVER = ProviderDriverKind.make("acp-test");
101const decodeUnknownJson = Schema.decodeUnknownOption(Schema.fromJsonString(Schema.Unknown));106const decodeUnknownJson = Schema.decodeUnknownOption(Schema.fromJsonString(Schema.Unknown));
102107
...@@ -708,7 +713,7 @@ describe("AcpAdapterV2", () => {...@@ -708,7 +713,7 @@ describe("AcpAdapterV2", () => {
708713
709 const instanceId = ProviderInstanceId.make("acp-test-self-contained-mcp-bridge");714 const instanceId = ProviderInstanceId.make("acp-test-self-contained-mcp-bridge");
710 const threadId = ThreadId.make("thread-acp-self-contained-mcp-bridge");715 const threadId = ThreadId.make("thread-acp-self-contained-mcp-bridge");
711 McpProviderSession.setMcpProviderSession({716 yield* (yield* McpProviderSessions.McpProviderSessions).set({
712 environmentId: EnvironmentId.make("environment-acp-self-contained-mcp-bridge"),717 environmentId: EnvironmentId.make("environment-acp-self-contained-mcp-bridge"),
713 threadId,718 threadId,
714 providerSessionId: "mcp-session-acp-self-contained-mcp-bridge",719 providerSessionId: "mcp-session-acp-self-contained-mcp-bridge",
...@@ -717,11 +722,6 @@ describe("AcpAdapterV2", () => {...@@ -717,11 +722,6 @@ describe("AcpAdapterV2", () => {
717 authorizationHeader: "Bearer self-contained-mcp-bridge-token",722 authorizationHeader: "Bearer self-contained-mcp-bridge-token",
718 browserToolsAvailable: false,723 browserToolsAvailable: false,
719 });724 });
720 yield* Effect.addFinalizer(() =>
721 Effect.sync(() => {
722 McpProviderSession.clearMcpProviderSession(threadId);
723 }),
724 );
725725
726 let runtimeInput: AcpAdapterV2RuntimeInput | undefined;726 let runtimeInput: AcpAdapterV2RuntimeInput | undefined;
727 const makeRuntime = makeMockRuntime({ childProcessSpawner, mockAgentPath });727 const makeRuntime = makeMockRuntime({ childProcessSpawner, mockAgentPath });
...@@ -772,7 +772,7 @@ describe("AcpAdapterV2", () => {...@@ -772,7 +772,7 @@ describe("AcpAdapterV2", () => {
772 const protocolEvents = yield* Queue.unbounded<EffectAcpProtocol.AcpProtocolLogEvent>();772 const protocolEvents = yield* Queue.unbounded<EffectAcpProtocol.AcpProtocolLogEvent>();
773 const instanceId = ProviderInstanceId.make("acp-test-instruction-transitions");773 const instanceId = ProviderInstanceId.make("acp-test-instruction-transitions");
774 const threadId = ThreadId.make("thread-acp-instruction-transitions");774 const threadId = ThreadId.make("thread-acp-instruction-transitions");
775 McpProviderSession.setMcpProviderSession({775 yield* (yield* McpProviderSessions.McpProviderSessions).set({
776 environmentId: EnvironmentId.make("environment-acp-instruction-transitions"),776 environmentId: EnvironmentId.make("environment-acp-instruction-transitions"),
777 threadId,777 threadId,
778 providerSessionId: "mcp-session-acp-instruction-transitions",778 providerSessionId: "mcp-session-acp-instruction-transitions",
...@@ -781,9 +781,6 @@ describe("AcpAdapterV2", () => {...@@ -781,9 +781,6 @@ describe("AcpAdapterV2", () => {
781 authorizationHeader: "Bearer instruction-transition-token",781 authorizationHeader: "Bearer instruction-transition-token",
782 browserToolsAvailable: false,782 browserToolsAvailable: false,
783 });783 });
784 yield* Effect.addFinalizer(() =>
785 Effect.sync(() => McpProviderSession.clearMcpProviderSession(threadId)),
786 );
787 const adapter = yield* makeAcpAdapterV2({784 const adapter = yield* makeAcpAdapterV2({
788 instanceId,785 instanceId,
789 flavor: {786 flavor: {
...@@ -2197,7 +2194,7 @@ describe("AcpAdapterV2", () => {...@@ -2197,7 +2194,7 @@ describe("AcpAdapterV2", () => {
2197 });2194 });
2198 const sourceThreadId = ThreadId.make("thread-acp-native-fork-source");2195 const sourceThreadId = ThreadId.make("thread-acp-native-fork-source");
2199 const targetThreadId = ThreadId.make("thread-acp-native-fork-target");2196 const targetThreadId = ThreadId.make("thread-acp-native-fork-target");
2200 McpProviderSession.setMcpProviderSession({2197 yield* (yield* McpProviderSessions.McpProviderSessions).set({
2201 environmentId: EnvironmentId.make("environment-acp-native-fork-source"),2198 environmentId: EnvironmentId.make("environment-acp-native-fork-source"),
2202 threadId: sourceThreadId,2199 threadId: sourceThreadId,
2203 providerSessionId: "mcp-session-acp-native-fork-source",2200 providerSessionId: "mcp-session-acp-native-fork-source",
...@@ -2206,7 +2203,7 @@ describe("AcpAdapterV2", () => {...@@ -2206,7 +2203,7 @@ describe("AcpAdapterV2", () => {
2206 authorizationHeader: "Bearer source-thread-token",2203 authorizationHeader: "Bearer source-thread-token",
2207 browserToolsAvailable: true,2204 browserToolsAvailable: true,
2208 });2205 });
2209 McpProviderSession.setMcpProviderSession({2206 yield* (yield* McpProviderSessions.McpProviderSessions).set({
2210 environmentId: EnvironmentId.make("environment-acp-native-fork"),2207 environmentId: EnvironmentId.make("environment-acp-native-fork"),
2211 threadId: targetThreadId,2208 threadId: targetThreadId,
2212 providerSessionId: "mcp-session-acp-native-fork",2209 providerSessionId: "mcp-session-acp-native-fork",
...@@ -2215,12 +2212,6 @@ describe("AcpAdapterV2", () => {...@@ -2215,12 +2212,6 @@ describe("AcpAdapterV2", () => {
2215 authorizationHeader: "Bearer target-thread-token",2212 authorizationHeader: "Bearer target-thread-token",
2216 browserToolsAvailable: true,2213 browserToolsAvailable: true,
2217 });2214 });
2218 yield* Effect.addFinalizer(() =>
2219 Effect.sync(() => {
2220 McpProviderSession.clearMcpProviderSession(sourceThreadId);
2221 McpProviderSession.clearMcpProviderSession(targetThreadId);
2222 }),
2223 );
2224 const runtimePolicy = ProviderAdapter.ProviderAdapterV2RuntimePolicy.make({2215 const runtimePolicy = ProviderAdapter.ProviderAdapterV2RuntimePolicy.make({
2225 runtimeMode: "full-access",2216 runtimeMode: "full-access",
2226 interactionMode: "default",2217 interactionMode: "default",
...@@ -2640,7 +2631,7 @@ describe("AcpAdapterV2", () => {...@@ -2640,7 +2631,7 @@ describe("AcpAdapterV2", () => {
2640 );2631 );
2641 const instanceId = ProviderInstanceId.make("acp-test-rollback-session");2632 const instanceId = ProviderInstanceId.make("acp-test-rollback-session");
2642 const rollbackThreadId = ThreadId.make("thread-acp-rollback-session-target");2633 const rollbackThreadId = ThreadId.make("thread-acp-rollback-session-target");
2643 McpProviderSession.setMcpProviderSession({2634 yield* (yield* McpProviderSessions.McpProviderSessions).set({
2644 environmentId: EnvironmentId.make("environment-acp-rollback-session-target"),2635 environmentId: EnvironmentId.make("environment-acp-rollback-session-target"),
2645 threadId: rollbackThreadId,2636 threadId: rollbackThreadId,
2646 providerSessionId: "mcp-session-acp-rollback-session-target",2637 providerSessionId: "mcp-session-acp-rollback-session-target",
...@@ -2649,9 +2640,6 @@ describe("AcpAdapterV2", () => {...@@ -2649,9 +2640,6 @@ describe("AcpAdapterV2", () => {
2649 authorizationHeader: "Bearer rollback-target-token",2640 authorizationHeader: "Bearer rollback-target-token",
2650 browserToolsAvailable: false,2641 browserToolsAvailable: false,
2651 });2642 });
2652 yield* Effect.addFinalizer(() =>
2653 Effect.sync(() => McpProviderSession.clearMcpProviderSession(rollbackThreadId)),
2654 );
2655 const runtimeInputs: Array<AcpAdapterV2RuntimeInput> = [];2643 const runtimeInputs: Array<AcpAdapterV2RuntimeInput> = [];
2656 const makeRuntime = makeMockRuntime({2644 const makeRuntime = makeMockRuntime({
2657 childProcessSpawner,2645 childProcessSpawner,
apps/server/src/orchestration-v2/Adapters/AcpRegistryAdapterV2.test.ts+2
...@@ -14,6 +14,7 @@ import { ChildProcessSpawner } from "effect/process";...@@ -14,6 +14,7 @@ import { ChildProcessSpawner } from "effect/process";
14import * as EffectAcpErrors from "effect-acp/errors";14import * as EffectAcpErrors from "effect-acp/errors";
1515
16import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";16import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";
17import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
17import { layerTestProviderHost } from "@t3tools/provider-testing/host";18import { layerTestProviderHost } from "@t3tools/provider-testing/host";
18import * as ServerSettings from "../../serverSettings.ts";19import * as ServerSettings from "../../serverSettings.ts";
19import type {20import type {
...@@ -84,6 +85,7 @@ const layerRegistry = Layer.succeed(...@@ -84,6 +85,7 @@ const layerRegistry = Layer.succeed(
84const layerTest = Layer.mergeAll(85const layerTest = Layer.mergeAll(
85 NodeServices.layer,86 NodeServices.layer,
86 IdAllocator.layer,87 IdAllocator.layer,
88 McpProviderSessions.layer,
87 layerHost,89 layerHost,
88 layerRegistry,90 layerRegistry,
89 ServerSettings.layerTest(),91 ServerSettings.layerTest(),
apps/server/src/orchestration-v2/Adapters/AntigravityAdapterV2.test.ts+2
...@@ -23,6 +23,7 @@ import { ChildProcessSpawner } from "effect/process";...@@ -23,6 +23,7 @@ import { ChildProcessSpawner } from "effect/process";
23import type * as EffectAcpSchema from "effect-acp/compat";23import type * as EffectAcpSchema from "effect-acp/compat";
2424
25import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";25import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";
26import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
26import { layerTestProviderHost } from "@t3tools/provider-testing/host";27import { layerTestProviderHost } from "@t3tools/provider-testing/host";
27import type * as AcpSessionRuntime from "@t3tools/provider-acp/server/AcpSessionRuntime";28import type * as AcpSessionRuntime from "@t3tools/provider-acp/server/AcpSessionRuntime";
28import { makeAntigravityAcpRuntime } from "../../provider/acp/AntigravityAcpSupport.ts";29import { makeAntigravityAcpRuntime } from "../../provider/acp/AntigravityAcpSupport.ts";
...@@ -129,6 +130,7 @@ describe("AntigravityAdapterV2 flavor", () => {...@@ -129,6 +130,7 @@ describe("AntigravityAdapterV2 flavor", () => {
129const layerSession = Layer.mergeAll(130const layerSession = Layer.mergeAll(
130 NodeServices.layer,131 NodeServices.layer,
131 IdAllocator.layer,132 IdAllocator.layer,
133 McpProviderSessions.layer,
132 layerTestProviderHost().pipe(Layer.provide(NodeServices.layer)),134 layerTestProviderHost().pipe(Layer.provide(NodeServices.layer)),
133);135);
134136
apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts+587-267
...@@ -53,6 +53,7 @@ import { SpawnExecutableResolution } from "@t3tools/shared/shell";...@@ -53,6 +53,7 @@ import { SpawnExecutableResolution } from "@t3tools/shared/shell";
53import { attachmentRelativePath } from "../../attachmentStore.ts";53import { attachmentRelativePath } from "../../attachmentStore.ts";
54import * as ServerConfig from "../../config.ts";54import * as ServerConfig from "../../config.ts";
55import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession";55import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession";
56import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
56import { PreviewControlsToolkit } from "../../mcp/toolkits/previewControls/tools.ts";57import { PreviewControlsToolkit } from "../../mcp/toolkits/previewControls/tools.ts";
57import { HtmlToolkit } from "../../mcp/toolkits/html/tools.ts";58import { HtmlToolkit } from "../../mcp/toolkits/html/tools.ts";
58import { EnvironmentToolkit } from "../../mcp/toolkits/environment/tools.ts";59import { EnvironmentToolkit } from "../../mcp/toolkits/environment/tools.ts";
...@@ -483,26 +484,22 @@ describe("ClaudeAdapterV2 MCP query overrides", () => {...@@ -483,26 +484,22 @@ describe("ClaudeAdapterV2 MCP query overrides", () => {
483 T3_CODE_MCP_AUTHORIZATION: "Bearer secret-claude-token",484 T3_CODE_MCP_AUTHORIZATION: "Bearer secret-claude-token",
484 } as const;485 } as const;
485486
486 const withMcpSession = (threadId: ThreadId, run: () => void) => {487 const mcpSessionFor = (
487 McpProviderSession.setMcpProviderSession({488 threadId: ThreadId,
488 environmentId: EnvironmentId.make(`environment-${threadId}`),489 authorizationHeader = "Bearer secret-claude-token",
489 threadId,490 ): McpProviderSession.McpProviderSessionConfig => ({
490 providerSessionId: `mcp-session-${threadId}`,491 environmentId: EnvironmentId.make(`environment-${threadId}`),
491 providerInstanceId: ProviderInstanceId.make("claudeAgent"),492 threadId,
492 endpoint: "http://127.0.0.1:43123/mcp",493 providerSessionId: `mcp-session-${threadId}`,
493 authorizationHeader: "Bearer secret-claude-token",494 providerInstanceId: ProviderInstanceId.make("claudeAgent"),
494 browserToolsAvailable: true,495 endpoint: "http://127.0.0.1:43123/mcp",
495 });496 authorizationHeader,
496 try {497 browserToolsAvailable: true,
497 run();498 });
498 } finally {
499 McpProviderSession.clearMcpProviderSession(threadId);
500 }
501 };
502499
503 it("leaves an absent allowlist absent when no MCP session exists", () => {500 it("leaves an absent allowlist absent when no MCP session exists", () => {
504 const overrides = ClaudeAdapterV2.claudeMcpQueryOverrides({501 const overrides = ClaudeAdapterV2.claudeMcpQueryOverrides({
505 threadId: ThreadId.make("thread-claude-no-mcp-no-allowlist"),502 mcpSession: undefined,
506 readOnlySandbox: false,503 readOnlySandbox: false,
507 });504 });
508505
...@@ -511,7 +508,7 @@ describe("ClaudeAdapterV2 MCP query overrides", () => {...@@ -511,7 +508,7 @@ describe("ClaudeAdapterV2 MCP query overrides", () => {
511508
512 it("preserves an explicit allowlist when no MCP session exists", () => {509 it("preserves an explicit allowlist when no MCP session exists", () => {
513 const overrides = ClaudeAdapterV2.claudeMcpQueryOverrides({510 const overrides = ClaudeAdapterV2.claudeMcpQueryOverrides({
514 threadId: ThreadId.make("thread-claude-no-mcp-with-allowlist"),511 mcpSession: undefined,
515 readOnlySandbox: false,512 readOnlySandbox: false,
516 allowedTools: ["Read"],513 allowedTools: ["Read"],
517 });514 });
...@@ -521,135 +518,122 @@ describe("ClaudeAdapterV2 MCP query overrides", () => {...@@ -521,135 +518,122 @@ describe("ClaudeAdapterV2 MCP query overrides", () => {
521518
522 it("pre-approves all t3-code tools when attaching an MCP session without an allowlist", () => {519 it("pre-approves all t3-code tools when attaching an MCP session without an allowlist", () => {
523 const threadId = ThreadId.make("thread-claude-mcp-no-allowlist");520 const threadId = ThreadId.make("thread-claude-mcp-no-allowlist");
524 withMcpSession(threadId, () => {521 const mcpSession = mcpSessionFor(threadId);
525 const overrides = ClaudeAdapterV2.claudeMcpQueryOverrides({522 const overrides = ClaudeAdapterV2.claudeMcpQueryOverrides({
526 threadId,523 mcpSession,
527 readOnlySandbox: false,524 readOnlySandbox: false,
528 });525 });
529526
530 assert.deepEqual(overrides, {527 assert.deepEqual(overrides, {
531 allowedTools: [ClaudeAdapterV2.CLAUDE_T3_MCP_TOOL_WILDCARD],528 allowedTools: [ClaudeAdapterV2.CLAUDE_T3_MCP_TOOL_WILDCARD],
532 mcpServers: T3_MCP_SERVERS,529 mcpServers: T3_MCP_SERVERS,
533 mcpEnvironment: T3_MCP_ENVIRONMENT,530 mcpEnvironment: T3_MCP_ENVIRONMENT,
534 });
535 });531 });
536 });532 });
537533
538 it("extends an explicit allowlist with the t3-code wildcard", () => {534 it("extends an explicit allowlist with the t3-code wildcard", () => {
539 const threadId = ThreadId.make("thread-claude-mcp-with-allowlist");535 const threadId = ThreadId.make("thread-claude-mcp-with-allowlist");
540 withMcpSession(threadId, () => {536 const mcpSession = mcpSessionFor(threadId);
541 const overrides = ClaudeAdapterV2.claudeMcpQueryOverrides({537 const overrides = ClaudeAdapterV2.claudeMcpQueryOverrides({
542 threadId,538 mcpSession,
543 readOnlySandbox: false,539 readOnlySandbox: false,
544 allowedTools: ["Read", "mcp__t3-code__*"],540 allowedTools: ["Read", "mcp__t3-code__*"],
545 });541 });
546542
547 assert.deepEqual(overrides, {543 assert.deepEqual(overrides, {
548 allowedTools: ["Read", "mcp__t3-code__*"],544 allowedTools: ["Read", "mcp__t3-code__*"],
549 mcpServers: T3_MCP_SERVERS,545 mcpServers: T3_MCP_SERVERS,
550 mcpEnvironment: T3_MCP_ENVIRONMENT,546 mcpEnvironment: T3_MCP_ENVIRONMENT,
551 });
552 });547 });
553 });548 });
554549
555 it("pre-approves only read-only t3-code tools in a read-only sandbox", () => {550 it("pre-approves only read-only t3-code tools in a read-only sandbox", () => {
556 const threadId = ThreadId.make("thread-claude-mcp-read-only");551 const threadId = ThreadId.make("thread-claude-mcp-read-only");
557 withMcpSession(threadId, () => {552 const mcpSession = mcpSessionFor(threadId);
558 const overrides = ClaudeAdapterV2.claudeMcpQueryOverrides({553 const overrides = ClaudeAdapterV2.claudeMcpQueryOverrides({
559 threadId,554 mcpSession,
560 readOnlySandbox: true,555 readOnlySandbox: true,
561 allowedTools: [...ClaudeAdapterV2.CLAUDE_READ_ONLY_ALLOWED_TOOLS],556 allowedTools: [...ClaudeAdapterV2.CLAUDE_READ_ONLY_ALLOWED_TOOLS],
562 });557 });
563558
564 assert.deepEqual(overrides, {559 assert.deepEqual(overrides, {
565 allowedTools: [560 allowedTools: [
566 ...ClaudeAdapterV2.CLAUDE_READ_ONLY_ALLOWED_TOOLS,561 ...ClaudeAdapterV2.CLAUDE_READ_ONLY_ALLOWED_TOOLS,
567 ...ClaudeAdapterV2.CLAUDE_READ_ONLY_T3_MCP_ALLOWED_TOOLS,562 ...ClaudeAdapterV2.CLAUDE_READ_ONLY_T3_MCP_ALLOWED_TOOLS,
568 ],563 ],
569 mcpServers: T3_MCP_SERVERS,564 mcpServers: T3_MCP_SERVERS,
570 mcpEnvironment: T3_MCP_ENVIRONMENT,565 mcpEnvironment: T3_MCP_ENVIRONMENT,
571 });
572 assert.isFalse(overrides.allowedTools?.includes(ClaudeAdapterV2.CLAUDE_T3_MCP_TOOL_WILDCARD));
573 });566 });
567 assert.isFalse(overrides.allowedTools?.includes(ClaudeAdapterV2.CLAUDE_T3_MCP_TOOL_WILDCARD));
574 });568 });
575569
576 it("pre-approves only read-only t3-code tools in a read-only sandbox without an allowlist", () => {570 it("pre-approves only read-only t3-code tools in a read-only sandbox without an allowlist", () => {
577 const threadId = ThreadId.make("thread-claude-mcp-read-only-no-allowlist");571 const threadId = ThreadId.make("thread-claude-mcp-read-only-no-allowlist");
578 withMcpSession(threadId, () => {572 const mcpSession = mcpSessionFor(threadId);
579 const overrides = ClaudeAdapterV2.claudeMcpQueryOverrides({573 const overrides = ClaudeAdapterV2.claudeMcpQueryOverrides({
580 threadId,574 mcpSession,
581 readOnlySandbox: true,575 readOnlySandbox: true,
582 });
583
584 assert.deepEqual(overrides.allowedTools, [
585 ...ClaudeAdapterV2.CLAUDE_READ_ONLY_T3_MCP_ALLOWED_TOOLS,
586 ]);
587 });576 });
577
578 assert.deepEqual(overrides.allowedTools, [
579 ...ClaudeAdapterV2.CLAUDE_READ_ONLY_T3_MCP_ALLOWED_TOOLS,
580 ]);
588 });581 });
589582
590 it("keys live-query reuse on the MCP-derived pre-approvals", () => {583 it("keys live-query reuse on the MCP-derived pre-approvals", () => {
591 const threadId = ThreadId.make("thread-claude-mcp-query-key");584 const threadId = ThreadId.make("thread-claude-mcp-query-key");
592 withMcpSession(threadId, () => {585 const mcpSession = mcpSessionFor(threadId);
593 const queryPolicy = ClaudeAdapterV2.claudeRuntimeQueryPolicyForRuntimePolicy(586 const queryPolicy = ClaudeAdapterV2.claudeRuntimeQueryPolicyForRuntimePolicy(
594 ProviderAdapter.ProviderAdapterV2RuntimePolicy.make({587 ProviderAdapter.ProviderAdapterV2RuntimePolicy.make({
595 runtimeMode: "full-access",588 runtimeMode: "full-access",
596 interactionMode: "default",589 interactionMode: "default",
597 cwd: "/workspace",590 cwd: "/workspace",
598 approvalPolicy: "on-request",591 approvalPolicy: "on-request",
599 sandboxPolicy: {592 sandboxPolicy: {
600 type: "readOnly",593 type: "readOnly",
601 access: { type: "fullAccess" },594 access: { type: "fullAccess" },
602 networkAccess: false,595 networkAccess: false,
603 },596 },
604 }),597 }),
605 );598 );
606599
607 const readOnlyKey = ClaudeAdapterV2.claudeEffectiveQueryPolicyKey(600 const readOnlyKey = ClaudeAdapterV2.claudeEffectiveQueryPolicyKey(
608 queryPolicy,601 queryPolicy,
609 ClaudeAdapterV2.claudeMcpQueryOverrides({ threadId, readOnlySandbox: true }),602 ClaudeAdapterV2.claudeMcpQueryOverrides({ mcpSession, readOnlySandbox: true }),
610 );603 );
611 const fullAccessKey = ClaudeAdapterV2.claudeEffectiveQueryPolicyKey(604 const fullAccessKey = ClaudeAdapterV2.claudeEffectiveQueryPolicyKey(
612 queryPolicy,605 queryPolicy,
613 ClaudeAdapterV2.claudeMcpQueryOverrides({ threadId, readOnlySandbox: false }),606 ClaudeAdapterV2.claudeMcpQueryOverrides({ mcpSession, readOnlySandbox: false }),
614 );607 );
615 const detachedKey = ClaudeAdapterV2.claudeEffectiveQueryPolicyKey(queryPolicy, {});608 const detachedKey = ClaudeAdapterV2.claudeEffectiveQueryPolicyKey(queryPolicy, {});
616609
617 assert.notEqual(readOnlyKey, fullAccessKey);610 assert.notEqual(readOnlyKey, fullAccessKey);
618 assert.notEqual(fullAccessKey, detachedKey);611 assert.notEqual(fullAccessKey, detachedKey);
619 });
620 });612 });
621613
622 it("invalidates live-query reuse when MCP credentials rotate", () => {614 it("invalidates live-query reuse when MCP credentials rotate", () => {
623 const threadId = ThreadId.make("thread-claude-mcp-credential-rotation");615 const threadId = ThreadId.make("thread-claude-mcp-credential-rotation");
624 withMcpSession(threadId, () => {616 const mcpSession = mcpSessionFor(threadId);
625 const queryPolicy = ClaudeAdapterV2.claudeRuntimeQueryPolicyForRuntimePolicy(617 const queryPolicy = ClaudeAdapterV2.claudeRuntimeQueryPolicyForRuntimePolicy(
626 ProviderAdapter.ProviderAdapterV2RuntimePolicy.make({618 ProviderAdapter.ProviderAdapterV2RuntimePolicy.make({
627 runtimeMode: "approval-required",619 runtimeMode: "approval-required",
628 interactionMode: "default",620 interactionMode: "default",
629 cwd: "/workspace",621 cwd: "/workspace",
630 }),622 }),
631 );623 );
632 const initialKey = ClaudeAdapterV2.claudeEffectiveQueryPolicyKey(624 const initialKey = ClaudeAdapterV2.claudeEffectiveQueryPolicyKey(
633 queryPolicy,625 queryPolicy,
634 ClaudeAdapterV2.claudeMcpQueryOverrides({ threadId, readOnlySandbox: false }),626 ClaudeAdapterV2.claudeMcpQueryOverrides({ mcpSession, readOnlySandbox: false }),
635 );627 );
636
637 McpProviderSession.setMcpProviderSession({
638 environmentId: EnvironmentId.make(`environment-${threadId}`),
639 threadId,
640 providerSessionId: `mcp-session-${threadId}`,
641 providerInstanceId: ProviderInstanceId.make("claudeAgent"),
642 endpoint: "http://127.0.0.1:43123/mcp",
643 authorizationHeader: "Bearer rotated-claude-token",
644 browserToolsAvailable: true,
645 });
646628
647 const rotatedKey = ClaudeAdapterV2.claudeEffectiveQueryPolicyKey(629 const rotatedKey = ClaudeAdapterV2.claudeEffectiveQueryPolicyKey(
648 queryPolicy,630 queryPolicy,
649 ClaudeAdapterV2.claudeMcpQueryOverrides({ threadId, readOnlySandbox: false }),631 ClaudeAdapterV2.claudeMcpQueryOverrides({
650 );632 mcpSession: mcpSessionFor(threadId, "Bearer rotated-claude-token"),
651 assert.notEqual(rotatedKey, initialKey);633 readOnlySandbox: false,
652 });634 }),
635 );
636 assert.notEqual(rotatedKey, initialKey);
653 });637 });
654638
655 it("matches the read-only allowlist to the orchestrator toolkit annotations", () => {639 it("matches the read-only allowlist to the orchestrator toolkit annotations", () => {
...@@ -676,7 +660,7 @@ describe("ClaudeAdapterV2 MCP query overrides", () => {...@@ -676,7 +660,7 @@ describe("ClaudeAdapterV2 MCP query overrides", () => {
676describe("ClaudeAdapterV2 native protocol logging", () => {660describe("ClaudeAdapterV2 native protocol logging", () => {
677 it("injects thread-scoped MCP configuration without logging the credential", () => {661 it("injects thread-scoped MCP configuration without logging the credential", () => {
678 const threadId = ThreadId.make("thread-claude-mcp");662 const threadId = ThreadId.make("thread-claude-mcp");
679 McpProviderSession.setMcpProviderSession({663 const mcpSession = {
680 environmentId: EnvironmentId.make("environment-claude-mcp"),664 environmentId: EnvironmentId.make("environment-claude-mcp"),
681 threadId,665 threadId,
682 providerSessionId: "mcp-session-claude",666 providerSessionId: "mcp-session-claude",
...@@ -684,60 +668,56 @@ describe("ClaudeAdapterV2 native protocol logging", () => {...@@ -684,60 +668,56 @@ describe("ClaudeAdapterV2 native protocol logging", () => {
684 endpoint: "http://127.0.0.1:43123/mcp",668 endpoint: "http://127.0.0.1:43123/mcp",
685 authorizationHeader: "Bearer secret-claude-token",669 authorizationHeader: "Bearer secret-claude-token",
686 browserToolsAvailable: true,670 browserToolsAvailable: true,
687 });671 };
688672
689 try {673 const overrides = ClaudeAdapterV2.claudeMcpQueryOverrides({
690 const overrides = ClaudeAdapterV2.claudeMcpQueryOverrides({674 mcpSession,
691 threadId,675 readOnlySandbox: false,
692 readOnlySandbox: false,676 allowedTools: ["Read"],
693 allowedTools: ["Read"],677 });
694 });678 assert.deepEqual(overrides, {
695 assert.deepEqual(overrides, {679 allowedTools: ["Read", "mcp__t3-code__*"],
696 allowedTools: ["Read", "mcp__t3-code__*"],680 mcpServers: {
697 mcpServers: {681 "t3-code": {
698 "t3-code": {682 type: "http",
699 type: "http",683 url: "http://127.0.0.1:43123/mcp",
700 url: "http://127.0.0.1:43123/mcp",684 headers: {
701 headers: {685 Authorization: "${T3_CODE_MCP_AUTHORIZATION}",
702 Authorization: "${T3_CODE_MCP_AUTHORIZATION}",
703 },
704 timeout: ClaudeAdapterV2.CLAUDE_T3_MCP_TOOL_TIMEOUT_MS,
705 },686 },
687 timeout: ClaudeAdapterV2.CLAUDE_T3_MCP_TOOL_TIMEOUT_MS,
706 },688 },
707 mcpEnvironment: { T3_CODE_MCP_AUTHORIZATION: "Bearer secret-claude-token" },689 },
708 });690 mcpEnvironment: { T3_CODE_MCP_AUTHORIZATION: "Bearer secret-claude-token" },
691 });
709692
710 const options = ClaudeAdapterV2.makeClaudeQueryOptions({693 const options = ClaudeAdapterV2.makeClaudeQueryOptions({
711 modelSelection: {694 modelSelection: {
712 instanceId: ProviderInstanceId.make("claudeAgent"),695 instanceId: ProviderInstanceId.make("claudeAgent"),
713 model: "claude-sonnet-4-6",696 model: "claude-sonnet-4-6",
714 },697 },
715 nativeThreadId: "native-thread-claude-mcp",698 nativeThreadId: "native-thread-claude-mcp",
716 resume: false,699 resume: false,
717 cwd: "/workspace",700 cwd: "/workspace",
718 allowedTools: overrides.allowedTools ?? [],701 allowedTools: overrides.allowedTools ?? [],
719 mcpServers: overrides.mcpServers ?? {},702 mcpServers: overrides.mcpServers ?? {},
720 environment: { ...overrides.mcpEnvironment },703 environment: { ...overrides.mcpEnvironment },
721 });704 });
722 // mcpServers becomes a CLI argument, readable by every local user; the705 // mcpServers becomes a CLI argument, readable by every local user; the
723 // credential may only travel in the child's environment.706 // credential may only travel in the child's environment.
724 assert.notInclude(JSON.stringify(options.mcpServers), "secret-claude-token");707 assert.notInclude(JSON.stringify(options.mcpServers), "secret-claude-token");
725 assert.equal(options.env?.T3_CODE_MCP_AUTHORIZATION, "Bearer secret-claude-token");708 assert.equal(options.env?.T3_CODE_MCP_AUTHORIZATION, "Bearer secret-claude-token");
726 assert.isObject(options.systemPrompt);709 assert.isObject(options.systemPrompt);
727 const systemPrompt = options.systemPrompt as {710 const systemPrompt = options.systemPrompt as {
728 readonly type: string;711 readonly type: string;
729 readonly preset: string;712 readonly preset: string;
730 readonly append?: string;713 readonly append?: string;
731 };714 };
732 assert.equal(systemPrompt.type, "preset");715 assert.equal(systemPrompt.type, "preset");
733 assert.equal(systemPrompt.preset, "claude_code");716 assert.equal(systemPrompt.preset, "claude_code");
734 assert.include(systemPrompt.append ?? "", "Use `delegate_task`");717 assert.include(systemPrompt.append ?? "", "Use `delegate_task`");
735 const logged = ClaudeAdapterV2.loggedClaudeQueryOptions(options);718 const logged = ClaudeAdapterV2.loggedClaudeQueryOptions(options);
736 assert.equal(logged.hasMcpServers, true);719 assert.equal(logged.hasMcpServers, true);
737 assert.notInclude(JSON.stringify(logged), "secret-claude-token");720 assert.notInclude(JSON.stringify(logged), "secret-claude-token");
738 } finally {
739 McpProviderSession.clearMcpProviderSession(threadId);
740 }
741 });721 });
742722
743 it.effect("writes Claude Agent SDK protocol frames to the native provider log", () =>723 it.effect("writes Claude Agent SDK protocol frames to the native provider log", () =>
...@@ -961,7 +941,7 @@ describe("ClaudeAdapterV2 Auto-accept edits", () => {...@@ -961,7 +941,7 @@ describe("ClaudeAdapterV2 Auto-accept edits", () => {
961 prefix: "t3-claude-accept-edits-",941 prefix: "t3-claude-accept-edits-",
962 });942 });
963 let openedOptions: ClaudeAdapterV2.ClaudeAgentSdkQueryOptions | undefined;943 let openedOptions: ClaudeAdapterV2.ClaudeAgentSdkQueryOptions | undefined;
964 const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({944 const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({
965 instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,945 instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,
966 settings: DEFAULT_CLAUDE_SETTINGS,946 settings: DEFAULT_CLAUDE_SETTINGS,
967 environment: {},947 environment: {},
...@@ -1056,7 +1036,11 @@ describe("ClaudeAdapterV2 Auto-accept edits", () => {...@@ -1056,7 +1036,11 @@ describe("ClaudeAdapterV2 Auto-accept edits", () => {
1056 decision: "accept",1036 decision: "accept",
1057 });1037 });
1058 assert.equal((yield* Fiber.join(decision))?.behavior, "allow");1038 assert.equal((yield* Fiber.join(decision))?.behavior, "allow");
1059 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),1039 }).pipe(
1040 Effect.provide(
1041 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
1042 ),
1043 ),
1060 ),1044 ),
1061 );1045 );
1062});1046});
...@@ -1174,7 +1158,11 @@ describe("ClaudeAdapterV2 executable path", () => {...@@ -1174,7 +1158,11 @@ describe("ClaudeAdapterV2 executable path", () => {
11741158
1175 assert.deepEqual(executablePaths, [path.join(NodeOS.homedir(), "bin", "claude")]);1159 assert.deepEqual(executablePaths, [path.join(NodeOS.homedir(), "bin", "claude")]);
1176 }),1160 }),
1177 ).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),1161 ).pipe(
1162 Effect.provide(
1163 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
1164 ),
1165 ),
1178 );1166 );
11791167
1180 it.effect("follows a bare claude on Windows to the npm package executable", () =>1168 it.effect("follows a bare claude on Windows to the npm package executable", () =>
...@@ -1190,7 +1178,11 @@ describe("ClaudeAdapterV2 executable path", () => {...@@ -1190,7 +1178,11 @@ describe("ClaudeAdapterV2 executable path", () => {
11901178
1191 assert.deepEqual(executablePaths, [packageExe]);1179 assert.deepEqual(executablePaths, [packageExe]);
1192 }),1180 }),
1193 ).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),1181 ).pipe(
1182 Effect.provide(
1183 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
1184 ),
1185 ),
1194 );1186 );
1195});1187});
11961188
...@@ -1204,7 +1196,7 @@ describe("ClaudeAdapterV2 resume compaction", () => {...@@ -1204,7 +1196,7 @@ describe("ClaudeAdapterV2 resume compaction", () => {
1204 prefix: "t3-claude-resume-",1196 prefix: "t3-claude-resume-",
1205 });1197 });
1206 let openedOptions: ClaudeAdapterV2.ClaudeAgentSdkQueryOptions | undefined;1198 let openedOptions: ClaudeAdapterV2.ClaudeAgentSdkQueryOptions | undefined;
1207 const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({1199 const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({
1208 instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,1200 instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,
1209 settings: DEFAULT_CLAUDE_SETTINGS,1201 settings: DEFAULT_CLAUDE_SETTINGS,
1210 environment: {},1202 environment: {},
...@@ -1408,7 +1400,11 @@ describe("ClaudeAdapterV2 resume compaction", () => {...@@ -1408,7 +1400,11 @@ describe("ClaudeAdapterV2 resume compaction", () => {
1408 ),1400 ),
1409 { behavior: "cancelled" },1401 { behavior: "cancelled" },
1410 );1402 );
1411 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),1403 }).pipe(
1404 Effect.provide(
1405 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
1406 ),
1407 ),
1412 ),1408 ),
1413 );1409 );
1414});1410});
...@@ -1424,7 +1420,7 @@ describe("ClaudeAdapterV2 attachments", () => {...@@ -1424,7 +1420,7 @@ describe("ClaudeAdapterV2 attachments", () => {
1424 prefix: "t3-claude-v2-attachments-",1420 prefix: "t3-claude-v2-attachments-",
1425 });1421 });
1426 const offeredMessages: Array<SDKUserMessage> = [];1422 const offeredMessages: Array<SDKUserMessage> = [];
1427 const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({1423 const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({
1428 instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,1424 instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,
1429 settings: DEFAULT_CLAUDE_SETTINGS,1425 settings: DEFAULT_CLAUDE_SETTINGS,
1430 environment: {},1426 environment: {},
...@@ -1552,7 +1548,11 @@ describe("ClaudeAdapterV2 attachments", () => {...@@ -1552,7 +1548,11 @@ describe("ClaudeAdapterV2 attachments", () => {
1552 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}]`,1548 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}]`,
1553 },1549 },
1554 ]);1550 ]);
1555 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),1551 }).pipe(
1552 Effect.provide(
1553 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
1554 ),
1555 ),
1556 ),1556 ),
1557 );1557 );
15581558
...@@ -1565,7 +1565,7 @@ describe("ClaudeAdapterV2 attachments", () => {...@@ -1565,7 +1565,7 @@ describe("ClaudeAdapterV2 attachments", () => {
1565 prefix: "t3-claude-v2-unsupported-attachment-",1565 prefix: "t3-claude-v2-unsupported-attachment-",
1566 });1566 });
1567 let openCount = 0;1567 let openCount = 0;
1568 const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({1568 const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({
1569 instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,1569 instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,
1570 settings: DEFAULT_CLAUDE_SETTINGS,1570 settings: DEFAULT_CLAUDE_SETTINGS,
1571 environment: {},1571 environment: {},
...@@ -1634,7 +1634,11 @@ describe("ClaudeAdapterV2 attachments", () => {...@@ -1634,7 +1634,11 @@ describe("ClaudeAdapterV2 attachments", () => {
1634 assert.equal(error._tag, "ProviderAdapterTurnStartError");1634 assert.equal(error._tag, "ProviderAdapterTurnStartError");
1635 assert.include(String(error.cause), "Unsupported Claude image attachment type");1635 assert.include(String(error.cause), "Unsupported Claude image attachment type");
1636 assert.equal(openCount, 0);1636 assert.equal(openCount, 0);
1637 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),1637 }).pipe(
1638 Effect.provide(
1639 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
1640 ),
1641 ),
1638 ),1642 ),
1639 );1643 );
1640});1644});
...@@ -1655,7 +1659,7 @@ describe("ClaudeAdapterV2 native fork", () => {...@@ -1655,7 +1659,7 @@ describe("ClaudeAdapterV2 native fork", () => {
1655 readonly threadId: ThreadId;1659 readonly threadId: ThreadId;
1656 readonly providerSessionId: ProviderSessionId;1660 readonly providerSessionId: ProviderSessionId;
1657 }> = [];1661 }> = [];
1658 const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({1662 const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({
1659 instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,1663 instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,
1660 settings: DEFAULT_CLAUDE_SETTINGS,1664 settings: DEFAULT_CLAUDE_SETTINGS,
1661 environment: {},1665 environment: {},
...@@ -1813,7 +1817,11 @@ describe("ClaudeAdapterV2 native fork", () => {...@@ -1813,7 +1817,11 @@ describe("ClaudeAdapterV2 native fork", () => {
18131817
1814 assert.equal(openedQueries[0]?.options.resume, "forked-native-session");1818 assert.equal(openedQueries[0]?.options.resume, "forked-native-session");
1815 assert.equal(openedQueries[0]?.options.sessionId, undefined);1819 assert.equal(openedQueries[0]?.options.sessionId, undefined);
1816 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),1820 }).pipe(
1821 Effect.provide(
1822 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
1823 ),
1824 ),
1817 ),1825 ),
1818 );1826 );
1819});1827});
...@@ -1828,7 +1836,7 @@ describe("ClaudeAdapterV2 native session identity", () => {...@@ -1828,7 +1836,7 @@ describe("ClaudeAdapterV2 native session identity", () => {
1828 prefix: "t3-claude-v2-session-identity-",1836 prefix: "t3-claude-v2-session-identity-",
1829 });1837 });
1830 const openedQueries: Array<ClaudeAdapterV2.ClaudeAgentSdkQueryOpenInput> = [];1838 const openedQueries: Array<ClaudeAdapterV2.ClaudeAgentSdkQueryOpenInput> = [];
1831 const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({1839 const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({
1832 instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,1840 instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,
1833 settings: DEFAULT_CLAUDE_SETTINGS,1841 settings: DEFAULT_CLAUDE_SETTINGS,
1834 environment: {},1842 environment: {},
...@@ -1883,7 +1891,11 @@ describe("ClaudeAdapterV2 native session identity", () => {...@@ -1883,7 +1891,11 @@ describe("ClaudeAdapterV2 native session identity", () => {
1883 }),1891 }),
1884 );1892 );
1885 return openedQueries;1893 return openedQueries;
1886 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),1894 }).pipe(
1895 Effect.provide(
1896 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
1897 ),
1898 ),
1887 );1899 );
18881900
1889 it.effect("creates the native session on the first provider turn", () =>1901 it.effect("creates the native session on the first provider turn", () =>
...@@ -2127,7 +2139,7 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -2127,7 +2139,7 @@ describe("ClaudeAdapterV2 background wake turns", () => {
2127 Extract<ProviderAdapter.ProviderAdapterV2Event, { type: "turn_item.updated" }>2139 Extract<ProviderAdapter.ProviderAdapterV2Event, { type: "turn_item.updated" }>
2128 >();2140 >();
2129 let openedOptions: ClaudeAdapterV2.ClaudeAgentSdkQueryOptions | undefined;2141 let openedOptions: ClaudeAdapterV2.ClaudeAgentSdkQueryOptions | undefined;
2130 const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({2142 const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({
2131 instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,2143 instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,
2132 settings: DEFAULT_CLAUDE_SETTINGS,2144 settings: DEFAULT_CLAUDE_SETTINGS,
2133 environment: options?.environment ?? {},2145 environment: options?.environment ?? {},
...@@ -2365,7 +2377,12 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -2365,7 +2377,12 @@ describe("ClaudeAdapterV2 background wake turns", () => {
2365 assert.deepEqual(item.input, { city: "Berlin" });2377 assert.deepEqual(item.input, { city: "Berlin" });
2366 }2378 }
2367 assert.equal(new Set(items.map((item) => item.id)).size, 1);2379 assert.equal(new Set(items.map((item) => item.id)).size, 1);
2368 }).pipe(Effect.scoped, Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),2380 }).pipe(
2381 Effect.scoped,
2382 Effect.provide(
2383 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
2384 ),
2385 ),
2369 );2386 );
23702387
2371 it.effect(2388 it.effect(
...@@ -2426,7 +2443,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -2426,7 +2443,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
2426 assert.strictEqual(harness.getOpenedOptions(), originalOptions);2443 assert.strictEqual(harness.getOpenedOptions(), originalOptions);
2427 assert.lengthOf(harness.offeredMessages, 2);2444 assert.lengthOf(harness.offeredMessages, 2);
2428 assert.isTrue(yield* harness.hasPendingBackgroundWork);2445 assert.isTrue(yield* harness.hasPendingBackgroundWork);
2429 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),2446 }).pipe(
2447 Effect.provide(
2448 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
2449 ),
2450 ),
2430 ),2451 ),
2431 );2452 );
24322453
...@@ -2545,7 +2566,12 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -2545,7 +2566,12 @@ describe("ClaudeAdapterV2 background wake turns", () => {
2545 ),2566 ),
2546 );2567 );
2547 for (const item of latest.values()) assert.notInclude(item.text, "secret-signature");2568 for (const item of latest.values()) assert.notInclude(item.text, "secret-signature");
2548 }).pipe(Effect.scoped, Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),2569 }).pipe(
2570 Effect.scoped,
2571 Effect.provide(
2572 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
2573 ),
2574 ),
2549 );2575 );
25502576
2551 it.effect.each(["cancelled", "denied", "permission_denied", undefined])(2577 it.effect.each(["cancelled", "denied", "permission_denied", undefined])(
...@@ -2615,7 +2641,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -2615,7 +2641,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
2615 event.type === "node.updated" && event.node.nativeItemRef?.nativeId === "tool-error",2641 event.type === "node.updated" && event.node.nativeItemRef?.nativeId === "tool-error",
2616 );2642 );
2617 assert.equal(node?.type === "node.updated" ? node.node.status : undefined, failed.status);2643 assert.equal(node?.type === "node.updated" ? node.node.status : undefined, failed.status);
2618 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),2644 }).pipe(
2645 Effect.provide(
2646 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
2647 ),
2648 ),
2619 ),2649 ),
2620 );2650 );
26212651
...@@ -2688,7 +2718,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -2688,7 +2718,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
2688 );2718 );
2689 }2719 }
2690 assert.lengthOf(harness.terminalEvents(), 1);2720 assert.lengthOf(harness.terminalEvents(), 1);
2691 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),2721 }).pipe(
2722 Effect.provide(
2723 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
2724 ),
2725 ),
2692 ),2726 ),
2693 );2727 );
26942728
...@@ -2754,7 +2788,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -2754,7 +2788,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
2754 yield* Queue.take(harness.terminalReceipts);2788 yield* Queue.take(harness.terminalReceipts);
2755 assert.lengthOf(notices(), 3);2789 assert.lengthOf(notices(), 3);
2756 assert.notEqual(notices()[0]?.id, notices()[2]?.id);2790 assert.notEqual(notices()[0]?.id, notices()[2]?.id);
2757 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),2791 }).pipe(
2792 Effect.provide(
2793 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
2794 ),
2795 ),
2758 );2796 );
27592797
2760 it.effect("keeps usage warnings and provisioned overage silent", () =>2798 it.effect("keeps usage warnings and provisioned overage silent", () =>
...@@ -2801,7 +2839,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -2801,7 +2839,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
2801 (event) => event.type === "turn_item.updated" && event.turnItem.type === "system_notice",2839 (event) => event.type === "turn_item.updated" && event.turnItem.type === "system_notice",
2802 ),2840 ),
2803 );2841 );
2804 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),2842 }).pipe(
2843 Effect.provide(
2844 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
2845 ),
2846 ),
2805 );2847 );
28062848
2807 it.effect("names an expired Claude login instead of the terminal API error", () =>2849 it.effect("names an expired Claude login instead of the terminal API error", () =>
...@@ -2846,7 +2888,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -2846,7 +2888,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
2846 assert.include(terminal.failure.message, configDir);2888 assert.include(terminal.failure.message, configDir);
2847 assert.include(terminal.failure.message, cwd);2889 assert.include(terminal.failure.message, cwd);
2848 assert.notInclude(terminal.failure.message, "repeated API errors");2890 assert.notInclude(terminal.failure.message, "repeated API errors");
2849 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),2891 }).pipe(
2892 Effect.provide(
2893 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
2894 ),
2895 ),
2850 );2896 );
28512897
2852 it.effect.each([2898 it.effect.each([
...@@ -2900,7 +2946,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -2900,7 +2946,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
2900 if (terminal.status !== "failed") return;2946 if (terminal.status !== "failed") return;
2901 assert.include(terminal.failure.message, expected);2947 assert.include(terminal.failure.message, expected);
2902 assert.equal(terminal.failure.class, recovered ? "provider_error" : "usage_limit");2948 assert.equal(terminal.failure.class, recovered ? "provider_error" : "usage_limit");
2903 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),2949 }).pipe(
2950 Effect.provide(
2951 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
2952 ),
2953 ),
2904 );2954 );
29052955
2906 it.effect.each([2956 it.effect.each([
...@@ -2960,7 +3010,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -2960,7 +3010,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
2960 ? "Claude usage limit reached. Send the message again once the limit resets."3010 ? "Claude usage limit reached. Send the message again once the limit resets."
2961 : "Claude gave up after repeated API errors.",3011 : "Claude gave up after repeated API errors.",
2962 );3012 );
2963 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),3013 }).pipe(
3014 Effect.provide(
3015 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
3016 ),
3017 ),
2964 );3018 );
29653019
2966 it.effect.each([429, 401, 529])(3020 it.effect.each([429, 401, 529])(
...@@ -3004,7 +3058,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -3004,7 +3058,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
3004 );3058 );
3005 if (apiErrorStatus !== 429)3059 if (apiErrorStatus !== 429)
3006 assert.notInclude(terminal.failure.message.toLowerCase(), "usage limit");3060 assert.notInclude(terminal.failure.message.toLowerCase(), "usage limit");
3007 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),3061 }).pipe(
3062 Effect.provide(
3063 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
3064 ),
3065 ),
3008 );3066 );
30093067
3010 it.effect("surfaces a Claude safety model fallback without failing the turn", () =>3068 it.effect("surfaces a Claude safety model fallback without failing the turn", () =>
...@@ -3063,7 +3121,12 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -3063,7 +3121,12 @@ describe("ClaudeAdapterV2 background wake turns", () => {
3063 (event) => event.type === "turn_item.updated" && event.turnItem.type === "error",3121 (event) => event.type === "turn_item.updated" && event.turnItem.type === "error",
3064 ),3122 ),
3065 );3123 );
3066 }).pipe(Effect.scoped, Effect.provide(Layer.mergeAll(NodeServices.layer, IdAllocator.layer))),3124 }).pipe(
3125 Effect.scoped,
3126 Effect.provide(
3127 Layer.mergeAll(NodeServices.layer, IdAllocator.layer, McpProviderSessions.layer),
3128 ),
3129 ),
3067 );3130 );
30683131
3069 it.effect(3132 it.effect(
...@@ -3135,7 +3198,12 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -3135,7 +3198,12 @@ describe("ClaudeAdapterV2 background wake turns", () => {
3135 cacheCreationTokens: 0,3198 cacheCreationTokens: 0,
3136 });3199 });
3137 }3200 }
3138 }).pipe(Effect.scoped, Effect.provide(Layer.mergeAll(NodeServices.layer, IdAllocator.layer))),3201 }).pipe(
3202 Effect.scoped,
3203 Effect.provide(
3204 Layer.mergeAll(NodeServices.layer, IdAllocator.layer, McpProviderSessions.layer),
3205 ),
3206 ),
3139 );3207 );
31403208
3141 it.effect("titles Claude reads, searches, and skills on tool completion", () =>3209 it.effect("titles Claude reads, searches, and skills on tool completion", () =>
...@@ -3233,7 +3301,12 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -3233,7 +3301,12 @@ describe("ClaudeAdapterV2 background wake turns", () => {
3233 );3301 );
3234 for (const item of items.filter((item) => item.nativeItemRef?.nativeId !== "image"))3302 for (const item of items.filter((item) => item.nativeItemRef?.nativeId !== "image"))
3235 assert.notProperty(item, "viewedImagePath");3303 assert.notProperty(item, "viewedImagePath");
3236 }).pipe(Effect.scoped, Effect.provide(Layer.mergeAll(NodeServices.layer, IdAllocator.layer))),3304 }).pipe(
3305 Effect.scoped,
3306 Effect.provide(
3307 Layer.mergeAll(NodeServices.layer, IdAllocator.layer, McpProviderSessions.layer),
3308 ),
3309 ),
3237 );3310 );
32383311
3239 it.effect("preserves typed Claude plans and todos through generic tool completion", () =>3312 it.effect("preserves typed Claude plans and todos through generic tool completion", () =>
...@@ -3442,7 +3515,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -3442,7 +3515,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
3442 // The second prompt reuses the live process, which is still in the3515 // The second prompt reuses the live process, which is still in the
3443 // plan mode Claude entered, so it is put back in the thread's mode.3516 // plan mode Claude entered, so it is put back in the thread's mode.
3444 assert.deepEqual(harness.permissionModeChanges, ["bypassPermissions"]);3517 assert.deepEqual(harness.permissionModeChanges, ["bypassPermissions"]);
3445 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),3518 }).pipe(
3519 Effect.provide(
3520 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
3521 ),
3522 ),
3446 ),3523 ),
3447 );3524 );
34483525
...@@ -3516,7 +3593,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -3516,7 +3593,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
3516 }),3593 }),
3517 );3594 );
3518 yield* awaitUntil(() => harness.terminalEvents().length === 1, "recovered Claude turn");3595 yield* awaitUntil(() => harness.terminalEvents().length === 1, "recovered Claude turn");
3519 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),3596 }).pipe(
3597 Effect.provide(
3598 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
3599 ),
3600 ),
3520 ),3601 ),
3521 );3602 );
35223603
...@@ -3570,7 +3651,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -3570,7 +3651,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
3570 });3651 });
3571 assert.isDefined(terminal.retryStartedAt);3652 assert.isDefined(terminal.retryStartedAt);
3572 assert.equal(terminal.failure.code, "api_error_529");3653 assert.equal(terminal.failure.code, "api_error_529");
3573 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),3654 }).pipe(
3655 Effect.provide(
3656 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
3657 ),
3658 ),
3574 ),3659 ),
3575 );3660 );
35763661
...@@ -3632,7 +3717,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -3632,7 +3717,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
3632 terminal.failure.code,3717 terminal.failure.code,
3633 terminalReason === "overloaded_status" ? "api_error_529" : terminalReason,3718 terminalReason === "overloaded_status" ? "api_error_529" : terminalReason,
3634 );3719 );
3635 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),3720 }).pipe(
3721 Effect.provide(
3722 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
3723 ),
3724 ),
3636 ),3725 ),
3637 );3726 );
36383727
...@@ -3697,7 +3786,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -3697,7 +3786,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
3697 "empty roster clear",3786 "empty roster clear",
3698 );3787 );
3699 assert.isFalse(yield* harness.hasPendingBackgroundWork);3788 assert.isFalse(yield* harness.hasPendingBackgroundWork);
3700 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),3789 }).pipe(
3790 Effect.provide(
3791 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
3792 ),
3793 ),
3701 ),3794 ),
3702 );3795 );
37033796
...@@ -3832,7 +3925,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -3832,7 +3925,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
3832 assert.deepEqual(roster, [3925 assert.deepEqual(roster, [
3833 { taskId: longRunning.taskId, kind: "monitor", description: "Monitor 0" },3926 { taskId: longRunning.taskId, kind: "monitor", description: "Monitor 0" },
3834 ]);3927 ]);
3835 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),3928 }).pipe(
3929 Effect.provide(
3930 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
3931 ),
3932 ),
3836 ),3933 ),
3837 );3934 );
38383935
...@@ -3882,7 +3979,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -3882,7 +3979,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
3882 );3979 );
3883 assert.isFalse(yield* harness.hasPendingBackgroundWork);3980 assert.isFalse(yield* harness.hasPendingBackgroundWork);
3884 assert.lengthOf(harness.continuationRequests, 0);3981 assert.lengthOf(harness.continuationRequests, 0);
3885 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),3982 }).pipe(
3983 Effect.provide(
3984 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
3985 ),
3986 ),
3886 ),3987 ),
3887 );3988 );
38883989
...@@ -3927,7 +4028,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -3927,7 +4028,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
3927 );4028 );
3928 assert.isFalse(yield* harness.hasPendingBackgroundWork);4029 assert.isFalse(yield* harness.hasPendingBackgroundWork);
3929 assert.lengthOf(harness.continuationRequests, 0);4030 assert.lengthOf(harness.continuationRequests, 0);
3930 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),4031 }).pipe(
4032 Effect.provide(
4033 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
4034 ),
4035 ),
3931 ),4036 ),
3932 );4037 );
39334038
...@@ -3944,7 +4049,7 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -3944,7 +4049,7 @@ describe("ClaudeAdapterV2 background wake turns", () => {
3944 const events: Array<ProviderAdapter.ProviderAdapterV2Event> = [];4049 const events: Array<ProviderAdapter.ProviderAdapterV2Event> = [];
3945 const continuationRequests: Array<ProviderContinuationRequests.ProviderContinuationRequest> =4050 const continuationRequests: Array<ProviderContinuationRequests.ProviderContinuationRequest> =
3946 [];4051 [];
3947 const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({4052 const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({
3948 instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,4053 instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,
3949 settings: DEFAULT_CLAUDE_SETTINGS,4054 settings: DEFAULT_CLAUDE_SETTINGS,
3950 environment: {},4055 environment: {},
...@@ -4109,7 +4214,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -4109,7 +4214,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
4109 }),4214 }),
4110 );4215 );
4111 yield* awaitUntil(() => continuationRequests.length === 1, "replacement wake");4216 yield* awaitUntil(() => continuationRequests.length === 1, "replacement wake");
4112 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),4217 }).pipe(
4218 Effect.provide(
4219 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
4220 ),
4221 ),
4113 ),4222 ),
4114 );4223 );
41154224
...@@ -4167,7 +4276,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -4167,7 +4276,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
4167 const afterFailure = providerThreadRosterEvents(harness.events).at(-1);4276 const afterFailure = providerThreadRosterEvents(harness.events).at(-1);
4168 assert.deepEqual(afterFailure?.providerThread.pendingBackgroundTasks ?? [], []);4277 assert.deepEqual(afterFailure?.providerThread.pendingBackgroundTasks ?? [], []);
4169 assert.isFalse(yield* harness.hasPendingBackgroundWork);4278 assert.isFalse(yield* harness.hasPendingBackgroundWork);
4170 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),4279 }).pipe(
4280 Effect.provide(
4281 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
4282 ),
4283 ),
4171 ),4284 ),
4172 );4285 );
41734286
...@@ -4190,7 +4303,7 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -4190,7 +4303,7 @@ describe("ClaudeAdapterV2 background wake turns", () => {
4190 readonly queue: Queue.Queue<SDKMessage>;4303 readonly queue: Queue.Queue<SDKMessage>;
4191 }> = [];4304 }> = [];
4192 const events: Array<ProviderAdapter.ProviderAdapterV2Event> = [];4305 const events: Array<ProviderAdapter.ProviderAdapterV2Event> = [];
4193 const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({4306 const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({
4194 instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,4307 instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,
4195 settings: DEFAULT_CLAUDE_SETTINGS,4308 settings: DEFAULT_CLAUDE_SETTINGS,
4196 environment: {},4309 environment: {},
...@@ -4429,7 +4542,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -4429,7 +4542,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
4429 assert.isFalse(yield* hasPendingBackgroundWorkForThread(providerThreadA));4542 assert.isFalse(yield* hasPendingBackgroundWorkForThread(providerThreadA));
4430 assert.isFalse(yield* hasPendingBackgroundWorkForThread(providerThreadB));4543 assert.isFalse(yield* hasPendingBackgroundWorkForThread(providerThreadB));
4431 assert.isFalse(yield* hasPendingBackgroundWork);4544 assert.isFalse(yield* hasPendingBackgroundWork);
4432 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),4545 }).pipe(
4546 Effect.provide(
4547 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
4548 ),
4549 ),
4433 ),4550 ),
4434 );4551 );
44354552
...@@ -4649,7 +4766,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -4649,7 +4766,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
4649 userRunItems.map((item) => item.type),4766 userRunItems.map((item) => item.type),
4650 ["assistant_message"],4767 ["assistant_message"],
4651 );4768 );
4652 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),4769 }).pipe(
4770 Effect.provide(
4771 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
4772 ),
4773 ),
4653 ),4774 ),
4654 );4775 );
46554776
...@@ -4732,7 +4853,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -4732,7 +4853,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
4732 : undefined,4853 : undefined,
4733 "On branch main\nwarning: dirty",4854 "On branch main\nwarning: dirty",
4734 );4855 );
4735 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),4856 }).pipe(
4857 Effect.provide(
4858 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
4859 ),
4860 ),
4736 ),4861 ),
4737 );4862 );
47384863
...@@ -4861,7 +4986,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -4861,7 +4986,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
4861 });4986 });
4862 const result = yield* Fiber.join(permission);4987 const result = yield* Fiber.join(permission);
4863 assert.equal(result?.behavior, "allow");4988 assert.equal(result?.behavior, "allow");
4864 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),4989 }).pipe(
4990 Effect.provide(
4991 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
4992 ),
4993 ),
4865 ),4994 ),
4866 );4995 );
48674996
...@@ -4957,7 +5086,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -4957,7 +5086,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
4957 event.message.text === STALE_TASK_NOTIFICATION_RESULT_TEXT,5086 event.message.text === STALE_TASK_NOTIFICATION_RESULT_TEXT,
4958 ),5087 ),
4959 );5088 );
4960 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),5089 }).pipe(
5090 Effect.provide(
5091 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
5092 ),
5093 ),
4961 ),5094 ),
4962 );5095 );
49635096
...@@ -5035,7 +5168,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -5035,7 +5168,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
5035 yield* awaitUntil(() => harness.terminalEvents().length === 2, "second turn terminal");5168 yield* awaitUntil(() => harness.terminalEvents().length === 2, "second turn terminal");
5036 assert.deepEqual(assistantTexts(), ["One.", "Two."]);5169 assert.deepEqual(assistantTexts(), ["One.", "Two."]);
5037 assert.equal(harness.terminalEvents()[1]?.status, "failed");5170 assert.equal(harness.terminalEvents()[1]?.status, "failed");
5038 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),5171 }).pipe(
5172 Effect.provide(
5173 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
5174 ),
5175 ),
5039 ),5176 ),
5040 );5177 );
50415178
...@@ -5061,7 +5198,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -5061,7 +5198,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
5061 );5198 );
5062 }5199 }
5063 assert.notEqual(original.promptUuid(0), copy.promptUuid(0));5200 assert.notEqual(original.promptUuid(0), copy.promptUuid(0));
5064 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),5201 }).pipe(
5202 Effect.provide(
5203 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
5204 ),
5205 ),
5065 ),5206 ),
5066 );5207 );
50675208
...@@ -5098,7 +5239,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -5098,7 +5239,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
5098 if (terminal?.status !== "failed") return;5239 if (terminal?.status !== "failed") return;
5099 assert.equal(terminal.threadDisposition, "reusable");5240 assert.equal(terminal.threadDisposition, "reusable");
5100 assert.include(terminal.failure.message, "never started a turn");5241 assert.include(terminal.failure.message, "never started a turn");
5101 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),5242 }).pipe(
5243 Effect.provide(
5244 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
5245 ),
5246 ),
5102 ),5247 ),
5103 );5248 );
51045249
...@@ -5147,7 +5292,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -5147,7 +5292,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
5147 );5292 );
5148 yield* awaitUntil(() => harness.terminalEvents().length === 1, "turn terminal");5293 yield* awaitUntil(() => harness.terminalEvents().length === 1, "turn terminal");
5149 assert.equal(harness.terminalEvents()[0]?.status, "completed");5294 assert.equal(harness.terminalEvents()[0]?.status, "completed");
5150 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),5295 }).pipe(
5296 Effect.provide(
5297 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
5298 ),
5299 ),
5151 ),5300 ),
5152 );5301 );
51535302
...@@ -5193,7 +5342,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -5193,7 +5342,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
5193 assert.lengthOf(harness.continuationRequests, 1);5342 assert.lengthOf(harness.continuationRequests, 1);
5194 assert.lengthOf(harness.terminalEvents(), 1);5343 assert.lengthOf(harness.terminalEvents(), 1);
5195 assert.isTrue(yield* harness.hasPendingBackgroundWork);5344 assert.isTrue(yield* harness.hasPendingBackgroundWork);
5196 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),5345 }).pipe(
5346 Effect.provide(
5347 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
5348 ),
5349 ),
5197 ),5350 ),
5198 );5351 );
51995352
...@@ -5250,7 +5403,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -5250,7 +5403,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
5250 (event) => event.type === "message.updated" && event.message.text === WAKE_RESULT_TEXT,5403 (event) => event.type === "message.updated" && event.message.text === WAKE_RESULT_TEXT,
5251 ),5404 ),
5252 );5405 );
5253 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),5406 }).pipe(
5407 Effect.provide(
5408 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
5409 ),
5410 ),
5254 ),5411 ),
5255 );5412 );
52565413
...@@ -5301,7 +5458,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -5301,7 +5458,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
5301 );5458 );
5302 assert.equal(harness.terminalEvents()[1]?.status, "completed");5459 assert.equal(harness.terminalEvents()[1]?.status, "completed");
5303 assert.lengthOf(harness.offeredMessages, 1);5460 assert.lengthOf(harness.offeredMessages, 1);
5304 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),5461 }).pipe(
5462 Effect.provide(
5463 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
5464 ),
5465 ),
5305 ),5466 ),
5306 );5467 );
53075468
...@@ -5389,7 +5550,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -5389,7 +5550,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
5389 assert.equal(pause.turnItem.type, "system_notice");5550 assert.equal(pause.turnItem.type, "system_notice");
5390 if (pause.turnItem.type !== "system_notice") return;5551 if (pause.turnItem.type !== "system_notice") return;
5391 assert.include(pause.turnItem.message, "This turn is paused until the 5-hour limit");5552 assert.include(pause.turnItem.message, "This turn is paused until the 5-hour limit");
5392 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),5553 }).pipe(
5554 Effect.provide(
5555 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
5556 ),
5557 ),
5393 ),5558 ),
5394 );5559 );
53955560
...@@ -5480,7 +5645,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -5480,7 +5645,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
5480 ),5645 ),
5481 );5646 );
5482 assert.isFalse(yield* harness.hasPendingBackgroundWork);5647 assert.isFalse(yield* harness.hasPendingBackgroundWork);
5483 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),5648 }).pipe(
5649 Effect.provide(
5650 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
5651 ),
5652 ),
5484 ),5653 ),
5485 );5654 );
54865655
...@@ -5542,7 +5711,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -5542,7 +5711,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
5542 );5711 );
5543 yield* awaitUntil(() => harness.terminalEvents().length === 2, "queued turn terminal");5712 yield* awaitUntil(() => harness.terminalEvents().length === 2, "queued turn terminal");
5544 assert.equal(harness.terminalEvents()[1]?.status, "completed");5713 assert.equal(harness.terminalEvents()[1]?.status, "completed");
5545 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),5714 }).pipe(
5715 Effect.provide(
5716 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
5717 ),
5718 ),
5546 ),5719 ),
5547 );5720 );
55485721
...@@ -5580,7 +5753,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -5580,7 +5753,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
5580 (event) => event.type === "message.updated" && event.message.text === fallbackText,5753 (event) => event.type === "message.updated" && event.message.text === fallbackText,
5581 ),5754 ),
5582 );5755 );
5583 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),5756 }).pipe(
5757 Effect.provide(
5758 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
5759 ),
5760 ),
5584 ),5761 ),
5585 );5762 );
55865763
...@@ -5663,7 +5840,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -5663,7 +5840,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
5663 assert.equal(harness.terminalEvents()[0]?.status, "completed");5840 assert.equal(harness.terminalEvents()[0]?.status, "completed");
5664 assert.isTrue(hasMessageText(recoveryAssistantText));5841 assert.isTrue(hasMessageText(recoveryAssistantText));
5665 assert.isFalse(hasMessageText(staleResultText));5842 assert.isFalse(hasMessageText(staleResultText));
5666 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),5843 }).pipe(
5844 Effect.provide(
5845 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
5846 ),
5847 ),
5667 ),5848 ),
5668 );5849 );
56695850
...@@ -5713,7 +5894,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -5713,7 +5894,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
5713 );5894 );
5714 yield* awaitUntil(() => harness.terminalEvents().length === 2, "continuation terminal");5895 yield* awaitUntil(() => harness.terminalEvents().length === 2, "continuation terminal");
5715 assert.equal(harness.terminalEvents()[1]?.status, "completed");5896 assert.equal(harness.terminalEvents()[1]?.status, "completed");
5716 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),5897 }).pipe(
5898 Effect.provide(
5899 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
5900 ),
5901 ),
5717 ),5902 ),
5718 );5903 );
57195904
...@@ -5772,7 +5957,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -5772,7 +5957,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
5772 assert.isTrue(terminalized);5957 assert.isTrue(terminalized);
5773 assert.equal(harness.terminalEvents()[0]?.status, "interrupted");5958 assert.equal(harness.terminalEvents()[0]?.status, "interrupted");
5774 assert.lengthOf(harness.terminalEvents(), 1);5959 assert.lengthOf(harness.terminalEvents(), 1);
5775 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),5960 }).pipe(
5961 Effect.provide(
5962 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
5963 ),
5964 ),
5776 ),5965 ),
5777 );5966 );
57785967
...@@ -5831,7 +6020,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -5831,7 +6020,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
5831 (event) => event.type === "message.updated" && event.message.text === staleText,6020 (event) => event.type === "message.updated" && event.message.text === staleText,
5832 ),6021 ),
5833 );6022 );
5834 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),6023 }).pipe(
6024 Effect.provide(
6025 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
6026 ),
6027 ),
5835 ),6028 ),
5836 );6029 );
58376030
...@@ -5865,7 +6058,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -5865,7 +6058,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
5865 );6058 );
5866 yield* awaitUntil(() => harness.terminalEvents().length === 1, "failed terminal");6059 yield* awaitUntil(() => harness.terminalEvents().length === 1, "failed terminal");
5867 assert.equal(harness.terminalEvents()[0]?.status, "failed");6060 assert.equal(harness.terminalEvents()[0]?.status, "failed");
5868 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),6061 }).pipe(
6062 Effect.provide(
6063 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
6064 ),
6065 ),
5869 ),6066 ),
5870 );6067 );
58716068
...@@ -5891,7 +6088,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -5891,7 +6088,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
5891 yield* awaitUntil(() => harness.terminalEvents().length === 1, "spurious terminal");6088 yield* awaitUntil(() => harness.terminalEvents().length === 1, "spurious terminal");
5892 assert.equal(harness.terminalEvents()[0]?.status, "completed");6089 assert.equal(harness.terminalEvents()[0]?.status, "completed");
5893 assert.lengthOf(harness.offeredMessages, 0);6090 assert.lengthOf(harness.offeredMessages, 0);
5894 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),6091 }).pipe(
6092 Effect.provide(
6093 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
6094 ),
6095 ),
5895 ),6096 ),
5896 );6097 );
58976098
...@@ -6088,7 +6289,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -6088,7 +6289,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
6088 harness.continuationRequests[1]?.notification?.summary,6289 harness.continuationRequests[1]?.notification?.summary,
6089 `Command "${WAKE_TASK_DESCRIPTION}" finished`,6290 `Command "${WAKE_TASK_DESCRIPTION}" finished`,
6090 );6291 );
6091 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),6292 }).pipe(
6293 Effect.provide(
6294 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
6295 ),
6296 ),
6092 ),6297 ),
6093 );6298 );
60946299
...@@ -6149,7 +6354,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -6149,7 +6354,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
6149 harness.continuationRequests[0]?.notification?.summary,6354 harness.continuationRequests[0]?.notification?.summary,
6150 `Command "${WAKE_TASK_DESCRIPTION}" finished`,6355 `Command "${WAKE_TASK_DESCRIPTION}" finished`,
6151 );6356 );
6152 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),6357 }).pipe(
6358 Effect.provide(
6359 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
6360 ),
6361 ),
6153 ),6362 ),
6154 );6363 );
61556364
...@@ -6305,7 +6514,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -6305,7 +6514,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
6305 : [],6514 : [],
6306 );6515 );
6307 assert.deepEqual([...new Set(userRunItems)], ["assistant_message"]);6516 assert.deepEqual([...new Set(userRunItems)], ["assistant_message"]);
6308 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),6517 }).pipe(
6518 Effect.provide(
6519 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
6520 ),
6521 ),
6309 ),6522 ),
6310 );6523 );
63116524
...@@ -6411,7 +6624,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -6411,7 +6624,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
6411 finalSubagent?.type === "subagent.updated" && finalSubagent.subagent.status,6624 finalSubagent?.type === "subagent.updated" && finalSubagent.subagent.status,
6412 "completed",6625 "completed",
6413 );6626 );
6414 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),6627 }).pipe(
6628 Effect.provide(
6629 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
6630 ),
6631 ),
6415 ),6632 ),
6416 );6633 );
64176634
...@@ -6572,7 +6789,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -6572,7 +6789,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
6572 finalSubagent?.type === "subagent.updated" && finalSubagent.subagent.status,6789 finalSubagent?.type === "subagent.updated" && finalSubagent.subagent.status,
6573 "completed",6790 "completed",
6574 );6791 );
6575 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),6792 }).pipe(
6793 Effect.provide(
6794 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
6795 ),
6796 ),
6576 ),6797 ),
6577 );6798 );
65786799
...@@ -6760,7 +6981,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -6760,7 +6981,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
6760 subagentRouting(harness.events, []).assistantTexts(outer?.childThreadId ?? undefined),6981 subagentRouting(harness.events, []).assistantTexts(outer?.childThreadId ?? undefined),
6761 ["OUTER_WORKING", "OUTER_DONE"],6982 ["OUTER_WORKING", "OUTER_DONE"],
6762 );6983 );
6763 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),6984 }).pipe(
6985 Effect.provide(
6986 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
6987 ),
6988 ),
6764 ),6989 ),
6765 );6990 );
67666991
...@@ -6890,7 +7115,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -6890,7 +7115,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
6890 );7115 );
6891 assert.equal(nested?.type === "subagent.updated" && nested.subagent.status, "completed");7116 assert.equal(nested?.type === "subagent.updated" && nested.subagent.status, "completed");
6892 assert.lengthOf(harness.continuationRequests, 0);7117 assert.lengthOf(harness.continuationRequests, 0);
6893 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),7118 }).pipe(
7119 Effect.provide(
7120 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
7121 ),
7122 ),
6894 ),7123 ),
6895 );7124 );
68967125
...@@ -6942,7 +7171,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -6942,7 +7171,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
6942 yield* Queue.shutdown(harness.sdkMessages);7171 yield* Queue.shutdown(harness.sdkMessages);
6943 yield* awaitUntil(() => bashStatuses().length === 2, "call ended");7172 yield* awaitUntil(() => bashStatuses().length === 2, "call ended");
6944 assert.deepEqual(bashStatuses(), ["command_execution:running", "command_execution:failed"]);7173 assert.deepEqual(bashStatuses(), ["command_execution:running", "command_execution:failed"]);
6945 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),7174 }).pipe(
7175 Effect.provide(
7176 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
7177 ),
7178 ),
6946 ),7179 ),
6947 );7180 );
69487181
...@@ -7004,7 +7237,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -7004,7 +7237,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
7004 "command_execution:running",7237 "command_execution:running",
7005 "command_execution:interrupted",7238 "command_execution:interrupted",
7006 ]);7239 ]);
7007 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),7240 }).pipe(
7241 Effect.provide(
7242 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
7243 ),
7244 ),
7008 ),7245 ),
7009 );7246 );
70107247
...@@ -7170,7 +7407,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -7170,7 +7407,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
7170 `command_execution:running:${input}`,7407 `command_execution:running:${input}`,
7171 `command_execution:completed:${input}`,7408 `command_execution:completed:${input}`,
7172 ]);7409 ]);
7173 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),7410 }).pipe(
7411 Effect.provide(
7412 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
7413 ),
7414 ),
7174 ),7415 ),
7175 );7416 );
71767417
...@@ -7326,7 +7567,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -7326,7 +7567,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
7326 `dynamic_tool:running:${input}`,7567 `dynamic_tool:running:${input}`,
7327 `dynamic_tool:completed:${input}`,7568 `dynamic_tool:completed:${input}`,
7328 ]);7569 ]);
7329 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),7570 }).pipe(
7571 Effect.provide(
7572 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
7573 ),
7574 ),
7330 ),7575 ),
7331 );7576 );
73327577
...@@ -7403,7 +7648,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -7403,7 +7648,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
7403 "Part two.",7648 "Part two.",
7404 ]);7649 ]);
7405 assert.deepEqual(routing.assistantTexts(harness.threadId), ["The auditor finished."]);7650 assert.deepEqual(routing.assistantTexts(harness.threadId), ["The auditor finished."]);
7406 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),7651 }).pipe(
7652 Effect.provide(
7653 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
7654 ),
7655 ),
7407 ),7656 ),
7408 );7657 );
74097658
...@@ -7578,7 +7827,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -7578,7 +7827,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
7578 ),7827 ),
7579 );7828 );
7580 assert.lengthOf(harness.continuationRequests, 1);7829 assert.lengthOf(harness.continuationRequests, 1);
7581 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),7830 }).pipe(
7831 Effect.provide(
7832 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
7833 ),
7834 ),
7582 ),7835 ),
7583 );7836 );
75847837
...@@ -7639,7 +7892,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -7639,7 +7892,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
7639 : [],7892 : [],
7640 );7893 );
7641 assert.deepEqual(retryStatuses, ["running"]);7894 assert.deepEqual(retryStatuses, ["running"]);
7642 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),7895 }).pipe(
7896 Effect.provide(
7897 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
7898 ),
7899 ),
7643 ),7900 ),
7644 );7901 );
76457902
...@@ -7728,7 +7985,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -7728,7 +7985,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
7728 FINAL_REPORT,7985 FINAL_REPORT,
7729 ]);7986 ]);
7730 assert.deepEqual(routing.assistantTexts(harness.threadId), ["The auditor finished."]);7987 assert.deepEqual(routing.assistantTexts(harness.threadId), ["The auditor finished."]);
7731 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),7988 }).pipe(
7989 Effect.provide(
7990 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
7991 ),
7992 ),
7732 ),7993 ),
7733 );7994 );
77347995
...@@ -7835,7 +8096,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -7835,7 +8096,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
7835 );8096 );
7836 const child = harness.events.find((event) => event.type === "app_thread.created");8097 const child = harness.events.find((event) => event.type === "app_thread.created");
7837 assert.equal(child?.appThread.modelSelection?.model, initialModel ?? parentModel);8098 assert.equal(child?.appThread.modelSelection?.model, initialModel ?? parentModel);
7838 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),8099 }).pipe(
8100 Effect.provide(
8101 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
8102 ),
8103 ),
7839 ),8104 ),
7840 );8105 );
78418106
...@@ -7920,7 +8185,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -7920,7 +8185,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
7920 }),8185 }),
7921 );8186 );
7922 yield* awaitUntil(() => harness.terminalEvents().length === 1, "turn terminal");8187 yield* awaitUntil(() => harness.terminalEvents().length === 1, "turn terminal");
7923 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),8188 }).pipe(
8189 Effect.provide(
8190 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
8191 ),
8192 ),
7924 ),8193 ),
7925 );8194 );
79268195
...@@ -8013,7 +8282,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -8013,7 +8282,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
80138282
8014 assert.equal(subagentEvents().at(-1)?.subagent.status, "cancelled");8283 assert.equal(subagentEvents().at(-1)?.subagent.status, "cancelled");
8015 assert.isFalse(yield* harness.hasPendingBackgroundWork);8284 assert.isFalse(yield* harness.hasPendingBackgroundWork);
8016 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),8285 }).pipe(
8286 Effect.provide(
8287 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
8288 ),
8289 ),
8017 ),8290 ),
8018 );8291 );
80198292
...@@ -8299,7 +8572,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -8299,7 +8572,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
8299 assert.lengthOf(resumedWorkIndexes, 1);8572 assert.lengthOf(resumedWorkIndexes, 1);
8300 assert.isAbove(resumedWorkIndexes[0] ?? -1, reopenIndex);8573 assert.isAbove(resumedWorkIndexes[0] ?? -1, reopenIndex);
8301 assert.isFalse(yield* harness.hasPendingBackgroundWork);8574 assert.isFalse(yield* harness.hasPendingBackgroundWork);
8302 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),8575 }).pipe(
8576 Effect.provide(
8577 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
8578 ),
8579 ),
8303 ),8580 ),
8304 );8581 );
83058582
...@@ -8315,7 +8592,7 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -8315,7 +8592,7 @@ describe("ClaudeAdapterV2 background wake turns", () => {
8315 const events: Array<ProviderAdapter.ProviderAdapterV2Event> = [];8592 const events: Array<ProviderAdapter.ProviderAdapterV2Event> = [];
8316 const continuationRequests: Array<ProviderContinuationRequests.ProviderContinuationRequest> =8593 const continuationRequests: Array<ProviderContinuationRequests.ProviderContinuationRequest> =
8317 [];8594 [];
8318 const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({8595 const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({
8319 instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,8596 instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,
8320 settings: DEFAULT_CLAUDE_SETTINGS,8597 settings: DEFAULT_CLAUDE_SETTINGS,
8321 environment: {},8598 environment: {},
...@@ -8482,7 +8759,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -8482,7 +8759,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
8482 () => events.filter((event) => event.type === "turn.terminal").length === 2,8759 () => events.filter((event) => event.type === "turn.terminal").length === 2,
8483 "second turn terminal",8760 "second turn terminal",
8484 );8761 );
8485 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),8762 }).pipe(
8763 Effect.provide(
8764 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
8765 ),
8766 ),
8486 ),8767 ),
8487 );8768 );
84888769
...@@ -8500,7 +8781,7 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -8500,7 +8781,7 @@ describe("ClaudeAdapterV2 background wake turns", () => {
8500 const events: Array<ProviderAdapter.ProviderAdapterV2Event> = [];8781 const events: Array<ProviderAdapter.ProviderAdapterV2Event> = [];
8501 const continuationRequests: Array<ProviderContinuationRequests.ProviderContinuationRequest> =8782 const continuationRequests: Array<ProviderContinuationRequests.ProviderContinuationRequest> =
8502 [];8783 [];
8503 const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({8784 const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({
8504 instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,8785 instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,
8505 settings: DEFAULT_CLAUDE_SETTINGS,8786 settings: DEFAULT_CLAUDE_SETTINGS,
8506 environment: {},8787 environment: {},
...@@ -8670,7 +8951,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -8670,7 +8951,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
8670 ),8951 ),
8671 );8952 );
8672 assert.isFalse(yield* hasPendingBackgroundWork);8953 assert.isFalse(yield* hasPendingBackgroundWork);
8673 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),8954 }).pipe(
8955 Effect.provide(
8956 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
8957 ),
8958 ),
8674 ),8959 ),
8675 );8960 );
86768961
...@@ -8737,7 +9022,7 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -8737,7 +9022,7 @@ describe("ClaudeAdapterV2 background wake turns", () => {
8737 const events: Array<ProviderAdapter.ProviderAdapterV2Event> = [];9022 const events: Array<ProviderAdapter.ProviderAdapterV2Event> = [];
8738 const continuationRequests: Array<ProviderContinuationRequests.ProviderContinuationRequest> =9023 const continuationRequests: Array<ProviderContinuationRequests.ProviderContinuationRequest> =
8739 [];9024 [];
8740 const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({9025 const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({
8741 instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,9026 instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,
8742 settings: DEFAULT_CLAUDE_SETTINGS,9027 settings: DEFAULT_CLAUDE_SETTINGS,
8743 environment: {},9028 environment: {},
...@@ -8920,7 +9205,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -8920,7 +9205,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
8920 );9205 );
8921 assert.equal(subagentNodeEvents.at(-1)?.node.status, "completed");9206 assert.equal(subagentNodeEvents.at(-1)?.node.status, "completed");
8922 assert.isFalse(yield* hasPendingBackgroundWork);9207 assert.isFalse(yield* hasPendingBackgroundWork);
8923 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),9208 }).pipe(
9209 Effect.provide(
9210 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
9211 ),
9212 ),
8924 ),9213 ),
8925 );9214 );
89269215
...@@ -8936,7 +9225,7 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -8936,7 +9225,7 @@ describe("ClaudeAdapterV2 background wake turns", () => {
8936 });9225 });
8937 const processQueues: Array<Queue.Queue<SDKMessage>> = [];9226 const processQueues: Array<Queue.Queue<SDKMessage>> = [];
8938 const events: Array<ProviderAdapter.ProviderAdapterV2Event> = [];9227 const events: Array<ProviderAdapter.ProviderAdapterV2Event> = [];
8939 const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({9228 const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({
8940 instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,9229 instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,
8941 settings: DEFAULT_CLAUDE_SETTINGS,9230 settings: DEFAULT_CLAUDE_SETTINGS,
8942 environment: {},9231 environment: {},
...@@ -9086,7 +9375,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -9086,7 +9375,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
9086 }),9375 }),
9087 );9376 );
9088 assert.lengthOf(processQueues, 3);9377 assert.lengthOf(processQueues, 3);
9089 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),9378 }).pipe(
9379 Effect.provide(
9380 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
9381 ),
9382 ),
9090 ),9383 ),
9091 );9384 );
90929385
...@@ -9103,7 +9396,7 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -9103,7 +9396,7 @@ describe("ClaudeAdapterV2 background wake turns", () => {
9103 let openCount = 0;9396 let openCount = 0;
9104 const processQueues: Array<Queue.Queue<SDKMessage>> = [];9397 const processQueues: Array<Queue.Queue<SDKMessage>> = [];
9105 const events: Array<ProviderAdapter.ProviderAdapterV2Event> = [];9398 const events: Array<ProviderAdapter.ProviderAdapterV2Event> = [];
9106 const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({9399 const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({
9107 instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,9400 instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,
9108 settings: DEFAULT_CLAUDE_SETTINGS,9401 settings: DEFAULT_CLAUDE_SETTINGS,
9109 environment: {},9402 environment: {},
...@@ -9215,7 +9508,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -9215,7 +9508,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
9215 // no replacement is opened.9508 // no replacement is opened.
9216 assert.equal(openCount, 1);9509 assert.equal(openCount, 1);
9217 assert.isTrue(yield* hasPendingBackgroundWork);9510 assert.isTrue(yield* hasPendingBackgroundWork);
9218 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),9511 }).pipe(
9512 Effect.provide(
9513 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
9514 ),
9515 ),
9219 ),9516 ),
9220 );9517 );
92219518
...@@ -9234,7 +9531,7 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -9234,7 +9531,7 @@ describe("ClaudeAdapterV2 background wake turns", () => {
9234 const events: Array<ProviderAdapter.ProviderAdapterV2Event> = [];9531 const events: Array<ProviderAdapter.ProviderAdapterV2Event> = [];
9235 const continuationRequests: Array<ProviderContinuationRequests.ProviderContinuationRequest> =9532 const continuationRequests: Array<ProviderContinuationRequests.ProviderContinuationRequest> =
9236 [];9533 [];
9237 const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({9534 const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({
9238 instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,9535 instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,
9239 settings: DEFAULT_CLAUDE_SETTINGS,9536 settings: DEFAULT_CLAUDE_SETTINGS,
9240 environment: {},9537 environment: {},
...@@ -9416,7 +9713,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -9416,7 +9713,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
9416 "continuation request after retry",9713 "continuation request after retry",
9417 );9714 );
9418 assert.equal(continuationRequests[1]?.detail, "Retry build completed successfully");9715 assert.equal(continuationRequests[1]?.detail, "Retry build completed successfully");
9419 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),9716 }).pipe(
9717 Effect.provide(
9718 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
9719 ),
9720 ),
9420 ),9721 ),
9421 );9722 );
94229723
...@@ -9429,7 +9730,7 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -9429,7 +9730,7 @@ describe("ClaudeAdapterV2 background wake turns", () => {
9429 prefix: "t3-claude-v2-first-open-fail-",9730 prefix: "t3-claude-v2-first-open-fail-",
9430 });9731 });
9431 const events: Array<ProviderAdapter.ProviderAdapterV2Event> = [];9732 const events: Array<ProviderAdapter.ProviderAdapterV2Event> = [];
9432 const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({9733 const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({
9433 instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,9734 instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,
9434 settings: DEFAULT_CLAUDE_SETTINGS,9735 settings: DEFAULT_CLAUDE_SETTINGS,
9435 environment: {},9736 environment: {},
...@@ -9491,7 +9792,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -9491,7 +9792,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
9491 assert.isTrue(Exit.isFailure(failedStart));9792 assert.isTrue(Exit.isFailure(failedStart));
9492 // No live process ever existed: do not emit a fabricated empty roster.9793 // No live process ever existed: do not emit a fabricated empty roster.
9493 assert.lengthOf(providerThreadRosterEvents(events), 0);9794 assert.lengthOf(providerThreadRosterEvents(events), 0);
9494 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),9795 }).pipe(
9796 Effect.provide(
9797 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
9798 ),
9799 ),
9495 ),9800 ),
9496 );9801 );
94979802
...@@ -9572,7 +9877,12 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -9572,7 +9877,12 @@ describe("ClaudeAdapterV2 background wake turns", () => {
9572 },9877 },
9573 { objective: condition, status: "complete", checks: 1 },9878 { objective: condition, status: "complete", checks: 1 },
9574 ]);9879 ]);
9575 }).pipe(Effect.scoped, Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),9880 }).pipe(
9881 Effect.scoped,
9882 Effect.provide(
9883 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
9884 ),
9885 ),
9576 );9886 );
95779887
9578 it.effect("keeps a goal active when a hook stops the turn before the goal passes", () =>9888 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", () => {...@@ -9606,7 +9916,12 @@ describe("ClaudeAdapterV2 background wake turns", () => {
9606 status: "active",9916 status: "active",
9607 checks: 0,9917 checks: 0,
9608 });9918 });
9609 }).pipe(Effect.scoped, Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),9919 }).pipe(
9920 Effect.scoped,
9921 Effect.provide(
9922 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
9923 ),
9924 ),
9610 );9925 );
96119926
9612 it.effect("keeps a goal active after a command turn with no model output", () =>9927 it.effect("keeps a goal active after a command turn with no model output", () =>
...@@ -9640,7 +9955,12 @@ describe("ClaudeAdapterV2 background wake turns", () => {...@@ -9640,7 +9955,12 @@ describe("ClaudeAdapterV2 background wake turns", () => {
9640 status: "active",9955 status: "active",
9641 checks: 2,9956 checks: 2,
9642 });9957 });
9643 }).pipe(Effect.scoped, Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),9958 }).pipe(
9959 Effect.scoped,
9960 Effect.provide(
9961 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
9962 ),
9963 ),
9644 );9964 );
9645 });9965 });
9646});9966});
apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts+12-8
...@@ -117,6 +117,7 @@ import {...@@ -117,6 +117,7 @@ import {
117 normalizeMcpText,117 normalizeMcpText,
118} from "@t3tools/provider-core/server/mcpToolPresentation";118} from "@t3tools/provider-core/server/mcpToolPresentation";
119import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession";119import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession";
120import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
120import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";121import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
121import {122import {
122 makeProviderFailure,123 makeProviderFailure,
...@@ -973,7 +974,7 @@ export const CLAUDE_T3_MCP_TOOL_TIMEOUT_MS = 65 * 60 * 1_000;...@@ -973,7 +974,7 @@ export const CLAUDE_T3_MCP_TOOL_TIMEOUT_MS = 65 * 60 * 1_000;
973const CLAUDE_T3_MCP_AUTHORIZATION_ENV = "T3_CODE_MCP_AUTHORIZATION";974const CLAUDE_T3_MCP_AUTHORIZATION_ENV = "T3_CODE_MCP_AUTHORIZATION";
974975
975export function claudeMcpQueryOverrides(input: {976export function claudeMcpQueryOverrides(input: {
976 readonly threadId: ThreadId;977 readonly mcpSession: McpProviderSession.McpProviderSessionConfig | undefined;
977 readonly readOnlySandbox: boolean;978 readonly readOnlySandbox: boolean;
978 readonly allowedTools?: ReadonlyArray<string>;979 readonly allowedTools?: ReadonlyArray<string>;
979}): {980}): {
...@@ -981,7 +982,7 @@ export function claudeMcpQueryOverrides(input: {...@@ -981,7 +982,7 @@ export function claudeMcpQueryOverrides(input: {
981 readonly mcpServers?: ClaudeQueryOptions["mcpServers"];982 readonly mcpServers?: ClaudeQueryOptions["mcpServers"];
982 readonly mcpEnvironment?: Readonly<Record<string, string>>;983 readonly mcpEnvironment?: Readonly<Record<string, string>>;
983} {984} {
984 const session = McpProviderSession.readMcpProviderSession(input.threadId);985 const session = input.mcpSession;
985 if (session === undefined) {986 if (session === undefined) {
986 return input.allowedTools === undefined ? {} : { allowedTools: input.allowedTools };987 return input.allowedTools === undefined ? {} : { allowedTools: input.allowedTools };
987 }988 }
...@@ -3026,9 +3027,10 @@ export interface ClaudeAdapterV2Options {...@@ -3026,9 +3027,10 @@ export interface ClaudeAdapterV2Options {
3026 };3027 };
3027}3028}
30283029
3029export function makeClaudeAdapterV2(3030export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* (
3030 adapterOptions: ClaudeAdapterV2Options,3031 adapterOptions: ClaudeAdapterV2Options,
3031): ProviderAdapter.ProviderAdapterV2["Service"] {3032) {
3033 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
3032 const { attachmentsDir, fileSystem, path, crypto, idAllocator, queryRunner } = adapterOptions;3034 const { attachmentsDir, fileSystem, path, crypto, idAllocator, queryRunner } = adapterOptions;
3033 const continuationRequests = adapterOptions.continuationRequests ?? {3035 const continuationRequests = adapterOptions.continuationRequests ?? {
3034 offer: () => Effect.void,3036 offer: () => Effect.void,
...@@ -7261,7 +7263,7 @@ export function makeClaudeAdapterV2(...@@ -7261,7 +7263,7 @@ export function makeClaudeAdapterV2(
7261 ) {7263 ) {
7262 const queryPolicy = claudeRuntimeQueryPolicyForRuntimePolicy(turnInput.runtimePolicy);7264 const queryPolicy = claudeRuntimeQueryPolicyForRuntimePolicy(turnInput.runtimePolicy);
7263 const mcpOverrides = claudeMcpQueryOverrides({7265 const mcpOverrides = claudeMcpQueryOverrides({
7264 threadId: turnInput.threadId,7266 mcpSession: yield* mcpSessions.read(turnInput.threadId),
7265 readOnlySandbox:7267 readOnlySandbox:
7266 sandboxPolicyKindForClaudeRuntimePolicy(turnInput.runtimePolicy) === "readOnly",7268 sandboxPolicyKindForClaudeRuntimePolicy(turnInput.runtimePolicy) === "readOnly",
7267 ...(queryPolicy.allowedTools === undefined7269 ...(queryPolicy.allowedTools === undefined
...@@ -8156,13 +8158,14 @@ export function makeClaudeAdapterV2(...@@ -8156,13 +8158,14 @@ export function makeClaudeAdapterV2(
8156 ),8158 ),
8157 ),8159 ),
8158 });8160 });
8159}8161});
81608162
8161export type ClaudeAdapterV2DriverEnv =8163export type ClaudeAdapterV2DriverEnv =
8162 | ClaudeAgentSdkQueryRunner8164 | ClaudeAgentSdkQueryRunner
8163 | Crypto.Crypto8165 | Crypto.Crypto
8164 | FileSystem.FileSystem8166 | FileSystem.FileSystem
8165 | IdAllocator.IdAllocatorV28167 | IdAllocator.IdAllocatorV2
8168 | McpProviderSessions.McpProviderSessions
8166 | Path.Path8169 | Path.Path
8167 | ServerConfig.ServerConfig;8170 | ServerConfig.ServerConfig;
81688171
...@@ -8186,7 +8189,7 @@ export const createClaudeAdapterV2 = Effect.fn("ClaudeAdapterV2Driver.create")(...@@ -8186,7 +8189,7 @@ export const createClaudeAdapterV2 = Effect.fn("ClaudeAdapterV2Driver.create")(
8186 expandHomePath(config.binaryPath),8189 expandHomePath(config.binaryPath),
8187 claudeEnvironment,8190 claudeEnvironment,
8188 );8191 );
8189 return makeClaudeAdapterV2({8192 return yield* makeClaudeAdapterV2({
8190 instanceId,8193 instanceId,
8191 settings: { ...config, enabled, binaryPath },8194 settings: { ...config, enabled, binaryPath },
8192 environment: claudeEnvironment,8195 environment: claudeEnvironment,
...@@ -8234,7 +8237,7 @@ const makeDefaultClaudeAdapterV2 = Effect.fn("ClaudeAdapterV2.layer")(function*...@@ -8234,7 +8237,7 @@ const makeDefaultClaudeAdapterV2 = Effect.fn("ClaudeAdapterV2.layer")(function*
8234 const serverConfig = yield* ServerConfig.ServerConfig;8237 const serverConfig = yield* ServerConfig.ServerConfig;
8235 const continuationRequests = yield* ProviderContinuationRequests.ProviderContinuationRequests;8238 const continuationRequests = yield* ProviderContinuationRequests.ProviderContinuationRequests;
82368239
8237 return makeClaudeAdapterV2({8240 return yield* makeClaudeAdapterV2({
8238 instanceId: CLAUDE_DEFAULT_INSTANCE_ID,8241 instanceId: CLAUDE_DEFAULT_INSTANCE_ID,
8239 settings: DEFAULT_CLAUDE_SETTINGS,8242 settings: DEFAULT_CLAUDE_SETTINGS,
8240 environment: hostEnvironment,8243 environment: hostEnvironment,
...@@ -8255,6 +8258,7 @@ const layer: Layer.Layer<...@@ -8255,6 +8258,7 @@ const layer: Layer.Layer<
8255 | Crypto.Crypto8258 | Crypto.Crypto
8256 | FileSystem.FileSystem8259 | FileSystem.FileSystem
8257 | IdAllocator.IdAllocatorV28260 | IdAllocator.IdAllocatorV2
8261 | McpProviderSessions.McpProviderSessions
8258 | Path.Path8262 | Path.Path
8259 | ServerConfig.ServerConfig8263 | ServerConfig.ServerConfig
8260> = Layer.effect(ProviderAdapter.ProviderAdapterV2, makeDefaultClaudeAdapterV2());8264> = Layer.effect(ProviderAdapter.ProviderAdapterV2, makeDefaultClaudeAdapterV2());
apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts+379-100
...@@ -54,7 +54,7 @@ import { ChildProcess, ChildProcessSpawner } from "effect/process";...@@ -54,7 +54,7 @@ import { ChildProcess, ChildProcessSpawner } from "effect/process";
5454
55import packageJson from "../../../package.json" with { type: "json" };55import packageJson from "../../../package.json" with { type: "json" };
56import * as ServerConfig from "../../config.ts";56import * as ServerConfig from "../../config.ts";
57import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession";57import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
58import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";58import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";
59import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";59import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
60import * as EffectWorker from "../EffectWorker.ts";60import * as EffectWorker from "../EffectWorker.ts";
...@@ -639,7 +639,7 @@ describe("CodexAdapterV2 runtime policy", () => {...@@ -639,7 +639,7 @@ describe("CodexAdapterV2 runtime policy", () => {
639describe("CodexAdapterV2 process spawning", () => {639describe("CodexAdapterV2 process spawning", () => {
640 it("injects cwd, model, and MCP authorization into thread-scoped params", () => {640 it("injects cwd, model, and MCP authorization into thread-scoped params", () => {
641 const threadId = ThreadId.make("thread-codex-mcp");641 const threadId = ThreadId.make("thread-codex-mcp");
642 McpProviderSession.setMcpProviderSession({642 const mcpSession = {
643 environmentId: EnvironmentId.make("environment-codex-mcp"),643 environmentId: EnvironmentId.make("environment-codex-mcp"),
644 threadId,644 threadId,
645 providerSessionId: "mcp-session-codex",645 providerSessionId: "mcp-session-codex",
...@@ -647,38 +647,34 @@ describe("CodexAdapterV2 process spawning", () => {...@@ -647,38 +647,34 @@ describe("CodexAdapterV2 process spawning", () => {
647 endpoint: "http://127.0.0.1:43123/mcp",647 endpoint: "http://127.0.0.1:43123/mcp",
648 authorizationHeader: "Bearer secret-codex-token",648 authorizationHeader: "Bearer secret-codex-token",
649 browserToolsAvailable: true,649 browserToolsAvailable: true,
650 });650 };
651651
652 try {652 assert.deepEqual(
653 assert.deepEqual(653 CodexAdapterV2.codexThreadRuntimeParams({
654 CodexAdapterV2.codexThreadRuntimeParams({654 mcpSession,
655 threadId,655 modelSelection: { model: "gpt-5.4" },
656 modelSelection: { model: "gpt-5.4" },656 runtimePolicy: {
657 runtimePolicy: {657 runtimeMode: "full-access",
658 runtimeMode: "full-access",658 interactionMode: "default",
659 interactionMode: "default",
660 cwd: "/workspace/thread-codex-mcp",
661 },
662 }),
663 {
664 cwd: "/workspace/thread-codex-mcp",659 cwd: "/workspace/thread-codex-mcp",
665 model: "gpt-5.4",660 },
666 config: {661 }),
667 "tools.update_plan.enabled": true,662 {
668 mcp_servers: {663 cwd: "/workspace/thread-codex-mcp",
669 "t3-code": {664 model: "gpt-5.4",
670 url: "http://127.0.0.1:43123/mcp",665 config: {
671 http_headers: {666 "tools.update_plan.enabled": true,
672 Authorization: "Bearer secret-codex-token",667 mcp_servers: {
673 },668 "t3-code": {
669 url: "http://127.0.0.1:43123/mcp",
670 http_headers: {
671 Authorization: "Bearer secret-codex-token",
674 },672 },
675 },673 },
676 },674 },
677 },675 },
678 );676 },
679 } finally {677 );
680 McpProviderSession.clearMcpProviderSession(threadId);
681 }
682 });678 });
683679
684 it.effect("resolves Windows command shims through the shared spawn policy", () =>680 it.effect("resolves Windows command shims through the shared spawn policy", () =>
...@@ -822,7 +818,9 @@ describe("CodexAdapterV2 process spawning", () => {...@@ -822,7 +818,9 @@ describe("CodexAdapterV2 process spawning", () => {
822818
823 assert.deepEqual(spawnedCommands, [path.join(NodeOS.homedir(), "bin", "codex")]);819 assert.deepEqual(spawnedCommands, [path.join(NodeOS.homedir(), "bin", "codex")]);
824 }).pipe(820 }).pipe(
825 Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer)),821 Effect.provide(
822 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
823 ),
826 Effect.provideService(HostProcessPlatform, "linux"),824 Effect.provideService(HostProcessPlatform, "linux"),
827 ),825 ),
828 );826 );
...@@ -1716,7 +1714,7 @@ describe("CodexAdapterV2 session initialize", () => {...@@ -1716,7 +1714,7 @@ describe("CodexAdapterV2 session initialize", () => {
1716 beforeEmitInbound === undefined ? {} : { beforeEmitInbound },1714 beforeEmitInbound === undefined ? {} : { beforeEmitInbound },
1717 );1715 );
1718 let initializeRequests = 0;1716 let initializeRequests = 0;
1719 const adapter = CodexAdapterV2.makeCodexAdapterV2({1717 const adapter = yield* CodexAdapterV2.makeCodexAdapterV2({
1720 instanceId: CodexAdapterV2.CODEX_DEFAULT_INSTANCE_ID,1718 instanceId: CodexAdapterV2.CODEX_DEFAULT_INSTANCE_ID,
1721 settings: DEFAULT_CODEX_SETTINGS,1719 settings: DEFAULT_CODEX_SETTINGS,
1722 environment: {},1720 environment: {},
...@@ -1811,7 +1809,12 @@ describe("CodexAdapterV2 session initialize", () => {...@@ -1811,7 +1809,12 @@ describe("CodexAdapterV2 session initialize", () => {
1811 providerThreads.map((providerThread) => providerThread.nativeThreadRef?.nativeId),1809 providerThreads.map((providerThread) => providerThread.nativeThreadRef?.nativeId),
1812 ["concurrent-first", "concurrent-second"],1810 ["concurrent-first", "concurrent-second"],
1813 );1811 );
1814 }).pipe(Effect.scoped, Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),1812 }).pipe(
1813 Effect.scoped,
1814 Effect.provide(
1815 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
1816 ),
1817 ),
1815 );1818 );
18161819
1817 it.effect("retries initialize after a failed handshake", () =>1820 it.effect("retries initialize after a failed handshake", () =>
...@@ -1837,7 +1840,12 @@ describe("CodexAdapterV2 session initialize", () => {...@@ -1837,7 +1840,12 @@ describe("CodexAdapterV2 session initialize", () => {
1837 const providerThread = yield* session.ensureThread("thread-initialize-retry");1840 const providerThread = yield* session.ensureThread("thread-initialize-retry");
1838 assert.equal(providerThread.nativeThreadRef?.nativeId, "initialize-retry");1841 assert.equal(providerThread.nativeThreadRef?.nativeId, "initialize-retry");
1839 assert.equal(session.initializeRequests(), 2);1842 assert.equal(session.initializeRequests(), 2);
1840 }).pipe(Effect.scoped, Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),1843 }).pipe(
1844 Effect.scoped,
1845 Effect.provide(
1846 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
1847 ),
1848 ),
1841 );1849 );
18421850
1843 it.effect("completes the handshake after a caller is interrupted mid-initialize", () =>1851 it.effect("completes the handshake after a caller is interrupted mid-initialize", () =>
...@@ -1880,7 +1888,12 @@ describe("CodexAdapterV2 session initialize", () => {...@@ -1880,7 +1888,12 @@ describe("CodexAdapterV2 session initialize", () => {
1880 const providerThread = yield* session.ensureThread("thread-initialize-interrupted");1888 const providerThread = yield* session.ensureThread("thread-initialize-interrupted");
1881 assert.equal(providerThread.nativeThreadRef?.nativeId, "initialize-interrupted");1889 assert.equal(providerThread.nativeThreadRef?.nativeId, "initialize-interrupted");
1882 assert.equal(session.initializeRequests(), 2);1890 assert.equal(session.initializeRequests(), 2);
1883 }).pipe(Effect.scoped, Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),1891 }).pipe(
1892 Effect.scoped,
1893 Effect.provide(
1894 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
1895 ),
1896 ),
1884 );1897 );
1885});1898});
18861899
...@@ -1940,7 +1953,7 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -1940,7 +1953,7 @@ describe("CodexAdapterV2 post-settle continuation", () => {
1940 ),1953 ),
1941 ),1954 ),
1942 };1955 };
1943 const adapter = CodexAdapterV2.makeCodexAdapterV2({1956 const adapter = yield* CodexAdapterV2.makeCodexAdapterV2({
1944 instanceId: CodexAdapterV2.CODEX_DEFAULT_INSTANCE_ID,1957 instanceId: CodexAdapterV2.CODEX_DEFAULT_INSTANCE_ID,
1945 settings: DEFAULT_CODEX_SETTINGS,1958 settings: DEFAULT_CODEX_SETTINGS,
1946 environment: {},1959 environment: {},
...@@ -2121,7 +2134,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -2121,7 +2134,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {
2121 );2134 );
2122 assert.equal(requests.filter((method) => method === "turn/start").length, 1);2135 assert.equal(requests.filter((method) => method === "turn/start").length, 1);
2123 assert.isBelow(requests.indexOf("thread/inject_items"), requests.indexOf("turn/start"));2136 assert.isBelow(requests.indexOf("thread/inject_items"), requests.indexOf("turn/start"));
2124 }).pipe(Effect.scoped, Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),2137 }).pipe(
2138 Effect.scoped,
2139 Effect.provide(
2140 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
2141 ),
2142 ),
2125 );2143 );
21262144
2127 it.effect("identifies sessions to Codex with the same client info as main", () =>2145 it.effect("identifies sessions to Codex with the same client info as main", () =>
...@@ -2157,7 +2175,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -2157,7 +2175,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {
2157 },2175 },
2158 },2176 },
2159 ]);2177 ]);
2160 }).pipe(Effect.scoped, Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),2178 }).pipe(
2179 Effect.scoped,
2180 Effect.provide(
2181 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
2182 ),
2183 ),
2161 );2184 );
21622185
2163 it.effect("unsubscribes from the native thread when it is unloaded", () =>2186 it.effect("unsubscribes from the native thread when it is unloaded", () =>
...@@ -2195,7 +2218,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -2195,7 +2218,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {
2195 assert.isDefined(harness.runtime.unloadThread);2218 assert.isDefined(harness.runtime.unloadThread);
2196 yield* harness.runtime.unloadThread!({ providerThread: harness.providerThread });2219 yield* harness.runtime.unloadThread!({ providerThread: harness.providerThread });
2197 assert.deepEqual(requests, ["initialize", "thread/start", "thread/unsubscribe"]);2220 assert.deepEqual(requests, ["initialize", "thread/start", "thread/unsubscribe"]);
2198 }).pipe(Effect.scoped, Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),2221 }).pipe(
2222 Effect.scoped,
2223 Effect.provide(
2224 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
2225 ),
2226 ),
2199 );2227 );
22002228
2201 it.effect("keeps the app-server failure as the cause when an unload is rejected", () =>2229 it.effect("keeps the app-server failure as the cause when an unload is rejected", () =>
...@@ -2229,7 +2257,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -2229,7 +2257,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {
2229 assert.equal(error._tag, "ProviderAdapterProtocolError");2257 assert.equal(error._tag, "ProviderAdapterProtocolError");
2230 const cause = error._tag === "ProviderAdapterProtocolError" ? error.cause : undefined;2258 const cause = error._tag === "ProviderAdapterProtocolError" ? error.cause : undefined;
2231 assert.equal((cause as { _tag?: string } | undefined)?._tag, "CodexAppServerRequestError");2259 assert.equal((cause as { _tag?: string } | undefined)?._tag, "CodexAppServerRequestError");
2232 }).pipe(Effect.scoped, Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),2260 }).pipe(
2261 Effect.scoped,
2262 Effect.provide(
2263 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
2264 ),
2265 ),
2233 );2266 );
22342267
2235 it.effect("waits for native start before interrupting an acknowledged queued turn", () =>2268 it.effect("waits for native start before interrupting an acknowledged queued turn", () =>
...@@ -2323,7 +2356,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -2323,7 +2356,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {
2323 assert.equal(harness.terminalEvents()[0]?.status, "interrupted");2356 assert.equal(harness.terminalEvents()[0]?.status, "interrupted");
2324 assert.lengthOf(harness.terminalEvents(), 1);2357 assert.lengthOf(harness.terminalEvents(), 1);
2325 assert.isFalse(yield* harness.hasPendingBackgroundWork);2358 assert.isFalse(yield* harness.hasPendingBackgroundWork);
2326 }).pipe(Effect.scoped, Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),2359 }).pipe(
2360 Effect.scoped,
2361 Effect.provide(
2362 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
2363 ),
2364 ),
2327 );2365 );
23282366
2329 it.effect("settles Stop when a queued native turn fails before starting", () =>2367 it.effect("settles Stop when a queued native turn fails before starting", () =>
...@@ -2405,7 +2443,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -2405,7 +2443,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {
2405 assert.lengthOf(harness.terminalEvents(), 1);2443 assert.lengthOf(harness.terminalEvents(), 1);
2406 assert.isFalse(interruptSent, "A terminal native turn must not receive turn/interrupt");2444 assert.isFalse(interruptSent, "A terminal native turn must not receive turn/interrupt");
2407 assert.isFalse(yield* harness.hasPendingBackgroundWork);2445 assert.isFalse(yield* harness.hasPendingBackgroundWork);
2408 }).pipe(Effect.scoped, Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),2446 }).pipe(
2447 Effect.scoped,
2448 Effect.provide(
2449 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
2450 ),
2451 ),
2409 );2452 );
24102453
2411 it.effect("bounds Stop when a queued native turn never starts", () =>2454 it.effect("bounds Stop when a queued native turn never starts", () =>
...@@ -2465,7 +2508,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -2465,7 +2508,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {
2465 assert.lengthOf(harness.terminalEvents(), 1);2508 assert.lengthOf(harness.terminalEvents(), 1);
2466 assert.isFalse(interruptSent, "An unstarted native turn must not receive turn/interrupt");2509 assert.isFalse(interruptSent, "An unstarted native turn must not receive turn/interrupt");
2467 assert.isFalse(yield* harness.hasPendingBackgroundWork);2510 assert.isFalse(yield* harness.hasPendingBackgroundWork);
2468 }).pipe(Effect.scoped, Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),2511 }).pipe(
2512 Effect.scoped,
2513 Effect.provide(
2514 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
2515 ),
2516 ),
2469 );2517 );
24702518
2471 it.effect("sends currency-sigil skill mentions to Codex as $ mentions", () =>2519 it.effect("sends currency-sigil skill mentions to Codex as $ mentions", () =>
...@@ -2524,7 +2572,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -2524,7 +2572,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {
2524 text: "then £ship it",2572 text: "then £ship it",
2525 },2573 },
2526 });2574 });
2527 }).pipe(Effect.scoped, Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),2575 }).pipe(
2576 Effect.scoped,
2577 Effect.provide(
2578 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
2579 ),
2580 ),
2528 );2581 );
25292582
2530 const assistantMessages = (events: ReadonlyArray<ProviderAdapter.ProviderAdapterV2Event>) =>2583 const assistantMessages = (events: ReadonlyArray<ProviderAdapter.ProviderAdapterV2Event>) =>
...@@ -2674,7 +2727,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -2674,7 +2727,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
2674 });2727 });
2675 }2728 }
2676 assert.isEmpty(assistantMessages(harness.events));2729 assert.isEmpty(assistantMessages(harness.events));
2677 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),2730 }).pipe(
2731 Effect.provide(
2732 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
2733 ),
2734 ),
2678 ),2735 ),
2679 );2736 );
26802737
...@@ -2748,7 +2805,7 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -2748,7 +2805,7 @@ describe("CodexAdapterV2 post-settle continuation", () => {
2748 ],2805 ],
2749 });2806 });
2750 const harness = yield* makeCodexReplayHarness(transcript);2807 const harness = yield* makeCodexReplayHarness(transcript);
2751 McpProviderSession.setMcpProviderSession({2808 yield* (yield* McpProviderSessions.McpProviderSessions).set({
2752 environmentId: EnvironmentId.make("test"),2809 environmentId: EnvironmentId.make("test"),
2753 threadId: harness.threadId,2810 threadId: harness.threadId,
2754 providerSessionId: "context-session",2811 providerSessionId: "context-session",
...@@ -2757,9 +2814,6 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -2757,9 +2814,6 @@ describe("CodexAdapterV2 post-settle continuation", () => {
2757 authorizationHeader: "Bearer test",2814 authorizationHeader: "Bearer test",
2758 browserToolsAvailable: true,2815 browserToolsAvailable: true,
2759 });2816 });
2760 yield* Effect.addFinalizer(() =>
2761 Effect.sync(() => McpProviderSession.clearMcpProviderSession(harness.threadId)),
2762 );
2763 yield* harness.runtime.startTurn(2817 yield* harness.runtime.startTurn(
2764 makeCodexTestTurnInput({2818 makeCodexTestTurnInput({
2765 threadId: harness.threadId,2819 threadId: harness.threadId,
...@@ -2771,7 +2825,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -2771,7 +2825,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
2771 );2825 );
2772 yield* harness.firstTerminal;2826 yield* harness.firstTerminal;
2773 assert.equal(harness.terminalEvents()[0]?.status, "completed");2827 assert.equal(harness.terminalEvents()[0]?.status, "completed");
2774 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),2828 }).pipe(
2829 Effect.provide(
2830 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
2831 ),
2832 ),
2775 ),2833 ),
2776 );2834 );
27772835
...@@ -2847,7 +2905,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -2847,7 +2905,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
2847 );2905 );
2848 assert.equal(items[0]?.id, items[1]?.id);2906 assert.equal(items[0]?.id, items[1]?.id);
2849 assert.equal(harness.terminalEvents()[0]?.status, "completed");2907 assert.equal(harness.terminalEvents()[0]?.status, "completed");
2850 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),2908 }).pipe(
2909 Effect.provide(
2910 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
2911 ),
2912 ),
2851 ),2913 ),
2852 );2914 );
28532915
...@@ -2895,7 +2957,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -2895,7 +2957,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
2895 assert.equal(resumed.nativeThreadRef?.nativeId, nativeThreadId);2957 assert.equal(resumed.nativeThreadRef?.nativeId, nativeThreadId);
2896 assert.equal(resumed.status, "idle");2958 assert.equal(resumed.status, "idle");
2897 assert.equal(DateTime.toEpochMillis(resumed.updatedAt), 1782622450000);2959 assert.equal(DateTime.toEpochMillis(resumed.updatedAt), 1782622450000);
2898 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),2960 }).pipe(
2961 Effect.provide(
2962 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
2963 ),
2964 ),
2899 ),2965 ),
2900 );2966 );
29012967
...@@ -3050,7 +3116,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -3050,7 +3116,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
3050 );3116 );
3051 assert.equal(resumed.status, "idle");3117 assert.equal(resumed.status, "idle");
3052 assert.equal(DateTime.toEpochMillis(resumed.updatedAt), 1782622450000);3118 assert.equal(DateTime.toEpochMillis(resumed.updatedAt), 1782622450000);
3053 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),3119 }).pipe(
3120 Effect.provide(
3121 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
3122 ),
3123 ),
3054 ),3124 ),
3055 );3125 );
30563126
...@@ -3132,7 +3202,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -3132,7 +3202,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
3132 restartContinuationOfRunId: RunId.make("run-before-restart"),3202 restartContinuationOfRunId: RunId.make("run-before-restart"),
3133 });3203 });
3134 assert.equal(resumed.nativeThreadRef?.nativeId, nativeThreadId);3204 assert.equal(resumed.nativeThreadRef?.nativeId, nativeThreadId);
3135 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),3205 }).pipe(
3206 Effect.provide(
3207 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
3208 ),
3209 ),
3136 ),3210 ),
3137 );3211 );
31383212
...@@ -3280,7 +3354,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -3280,7 +3354,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
3280 assert.isAtLeast(recoveredIndex, 0);3354 assert.isAtLeast(recoveredIndex, 0);
3281 assert.isAbove(resumedCommandIndex, recoveredIndex);3355 assert.isAbove(resumedCommandIndex, recoveredIndex);
3282 assert.isAbove(terminalIndex, resumedCommandIndex);3356 assert.isAbove(terminalIndex, resumedCommandIndex);
3283 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),3357 }).pipe(
3358 Effect.provide(
3359 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
3360 ),
3361 ),
3284 ),3362 ),
3285 );3363 );
32863364
...@@ -3379,7 +3457,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -3379,7 +3457,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
3379 ["compaction", 1782622565000],3457 ["compaction", 1782622565000],
3380 ],3458 ],
3381 );3459 );
3382 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),3460 }).pipe(
3461 Effect.provide(
3462 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
3463 ),
3464 ),
3383 ),3465 ),
3384 );3466 );
33853467
...@@ -3543,7 +3625,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -3543,7 +3625,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
3543 );3625 );
3544 }3626 }
3545 assert.deepEqual(assistantMessages(harness.events), []);3627 assert.deepEqual(assistantMessages(harness.events), []);
3546 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),3628 }).pipe(
3629 Effect.provide(
3630 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
3631 ),
3632 ),
3547 ),3633 ),
3548 );3634 );
35493635
...@@ -3672,7 +3758,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -3672,7 +3758,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
3672 assistantMessages(harness.events).map((event) => event.message.text),3758 assistantMessages(harness.events).map((event) => event.message.text),
3673 ["CODEX_RECOVERY_OK"],3759 ["CODEX_RECOVERY_OK"],
3674 );3760 );
3675 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),3761 }).pipe(
3762 Effect.provide(
3763 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
3764 ),
3765 ),
3676 ),3766 ),
3677 );3767 );
36783768
...@@ -3715,7 +3805,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -3715,7 +3805,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
3715 })),3805 })),
3716 [{ text: "CODEX_RECOVERY_OK", streaming: false }],3806 [{ text: "CODEX_RECOVERY_OK", streaming: false }],
3717 );3807 );
3718 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),3808 }).pipe(
3809 Effect.provide(
3810 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
3811 ),
3812 ),
3719 ),3813 ),
3720 );3814 );
37213815
...@@ -3759,7 +3853,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -3759,7 +3853,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
3759 assistantMessages(harness.events).map((event) => event.message.text),3853 assistantMessages(harness.events).map((event) => event.message.text),
3760 ["CODEX_RECOVERY_OK"],3854 ["CODEX_RECOVERY_OK"],
3761 );3855 );
3762 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),3856 }).pipe(
3857 Effect.provide(
3858 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
3859 ),
3860 ),
3763 ),3861 ),
3764 );3862 );
37653863
...@@ -3878,7 +3976,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -3878,7 +3976,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
3878 new Set(assistantMessages(harness.events).map((event) => event.message.id)).size,3976 new Set(assistantMessages(harness.events).map((event) => event.message.id)).size,
3879 1,3977 1,
3880 );3978 );
3881 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),3979 }).pipe(
3980 Effect.provide(
3981 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
3982 ),
3983 ),
3882 ),3984 ),
3883 );3985 );
38843986
...@@ -3906,7 +4008,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -3906,7 +4008,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
3906 assistantMessages(harness.events).map((event) => event.message.text),4008 assistantMessages(harness.events).map((event) => event.message.text),
3907 [""],4009 [""],
3908 );4010 );
3909 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),4011 }).pipe(
4012 Effect.provide(
4013 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
4014 ),
4015 ),
3910 ),4016 ),
3911 );4017 );
39124018
...@@ -3935,7 +4041,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -3935,7 +4041,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
3935 assistantMessages(harness.events).map((event) => event.message.text),4041 assistantMessages(harness.events).map((event) => event.message.text),
3936 [""],4042 [""],
3937 );4043 );
3938 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),4044 }).pipe(
4045 Effect.provide(
4046 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
4047 ),
4048 ),
3939 ),4049 ),
3940 );4050 );
39414051
...@@ -3964,7 +4074,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -3964,7 +4074,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
3964 assistantMessages(harness.events).map((event) => event.message.text),4074 assistantMessages(harness.events).map((event) => event.message.text),
3965 ["Working on it.", ""],4075 ["Working on it.", ""],
3966 );4076 );
3967 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),4077 }).pipe(
4078 Effect.provide(
4079 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
4080 ),
4081 ),
3968 ),4082 ),
3969 );4083 );
39704084
...@@ -3993,7 +4107,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -3993,7 +4107,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
3993 assistantMessages(harness.events).map((event) => event.message.text),4107 assistantMessages(harness.events).map((event) => event.message.text),
3994 ["CODEX_RECOVERY_OK"],4108 ["CODEX_RECOVERY_OK"],
3995 );4109 );
3996 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),4110 }).pipe(
4111 Effect.provide(
4112 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
4113 ),
4114 ),
3997 ),4115 ),
3998 );4116 );
39994117
...@@ -4022,7 +4140,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -4022,7 +4140,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
4022 assistantMessages(harness.events).map((event) => event.message.text),4140 assistantMessages(harness.events).map((event) => event.message.text),
4023 ["CODEX_RECOVERY_OK"],4141 ["CODEX_RECOVERY_OK"],
4024 );4142 );
4025 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),4143 }).pipe(
4144 Effect.provide(
4145 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
4146 ),
4147 ),
4026 ),4148 ),
4027 );4149 );
40284150
...@@ -4051,7 +4173,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -4051,7 +4173,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
4051 assistantMessages(harness.events).map((event) => event.message.text),4173 assistantMessages(harness.events).map((event) => event.message.text),
4052 ["", "CODEX_RECOVERY_OK"],4174 ["", "CODEX_RECOVERY_OK"],
4053 );4175 );
4054 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),4176 }).pipe(
4177 Effect.provide(
4178 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
4179 ),
4180 ),
4055 ),4181 ),
4056 );4182 );
40574183
...@@ -4207,7 +4333,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -4207,7 +4333,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
4207 );4333 );
4208 assert.lengthOf(harness.terminalEvents(), 1);4334 assert.lengthOf(harness.terminalEvents(), 1);
4209 assert.isFalse(yield* harness.hasPendingBackgroundWork);4335 assert.isFalse(yield* harness.hasPendingBackgroundWork);
4210 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),4336 }).pipe(
4337 Effect.provide(
4338 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
4339 ),
4340 ),
4211 ),4341 ),
4212 );4342 );
42134343
...@@ -4362,7 +4492,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -4362,7 +4492,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
4362 assert.lengthOf(harness.terminalEvents(), 1);4492 assert.lengthOf(harness.terminalEvents(), 1);
4363 assert.equal(harness.terminalEvents()[0]?.status, "completed");4493 assert.equal(harness.terminalEvents()[0]?.status, "completed");
4364 assert.lengthOf(harness.continuationRequests, 0);4494 assert.lengthOf(harness.continuationRequests, 0);
4365 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),4495 }).pipe(
4496 Effect.provide(
4497 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
4498 ),
4499 ),
4366 );4500 );
4367 },4501 },
4368 );4502 );
...@@ -4722,7 +4856,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -4722,7 +4856,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
4722 assert.lengthOf(harness.continuationRequests, 0);4856 assert.lengthOf(harness.continuationRequests, 0);
4723 assert.isFalse(yield* harness.hasPendingBackgroundWork);4857 assert.isFalse(yield* harness.hasPendingBackgroundWork);
4724 assert.lengthOf(harness.terminalEvents(), 1);4858 assert.lengthOf(harness.terminalEvents(), 1);
4725 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),4859 }).pipe(
4860 Effect.provide(
4861 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
4862 ),
4863 ),
4726 ),4864 ),
4727 );4865 );
47284866
...@@ -5011,7 +5149,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -5011,7 +5149,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
5011 );5149 );
5012 assert.isAtLeast(commandUpdates.length, 2, "start + interrupt terminalization");5150 assert.isAtLeast(commandUpdates.length, 2, "start + interrupt terminalization");
5013 assert.equal(commandUpdates[commandUpdates.length - 1]?.turnItem.status, "interrupted");5151 assert.equal(commandUpdates[commandUpdates.length - 1]?.turnItem.status, "interrupted");
5014 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),5152 }).pipe(
5153 Effect.provide(
5154 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
5155 ),
5156 ),
5015 ),5157 ),
5016 );5158 );
50175159
...@@ -5239,7 +5381,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -5239,7 +5381,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
5239 assert.equal(harness.subagentUpdates().at(-1)?.subagent.status, "interrupted");5381 assert.equal(harness.subagentUpdates().at(-1)?.subagent.status, "interrupted");
5240 assertChildProviderTerminalBeforeRoot(harness.events, harness.threadId);5382 assertChildProviderTerminalBeforeRoot(harness.events, harness.threadId);
5241 assert.isFalse(yield* harness.hasPendingBackgroundWork);5383 assert.isFalse(yield* harness.hasPendingBackgroundWork);
5242 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),5384 }).pipe(
5385 Effect.provide(
5386 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
5387 ),
5388 ),
5243 ),5389 ),
5244 );5390 );
52455391
...@@ -5324,7 +5470,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -5324,7 +5470,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
5324 yield* awaitUntil(() => harness.terminalEvents().length === 1, "interrupted root terminal");5470 yield* awaitUntil(() => harness.terminalEvents().length === 1, "interrupted root terminal");
5325 assertChildProviderTerminalBeforeRoot(harness.events, harness.threadId);5471 assertChildProviderTerminalBeforeRoot(harness.events, harness.threadId);
5326 assert.isFalse(yield* harness.hasPendingBackgroundWork);5472 assert.isFalse(yield* harness.hasPendingBackgroundWork);
5327 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),5473 }).pipe(
5474 Effect.provide(
5475 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
5476 ),
5477 ),
5328 ),5478 ),
5329 );5479 );
53305480
...@@ -5412,7 +5562,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -5412,7 +5562,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
5412 yield* awaitUntil(() => harness.terminalEvents().length === 1, "interrupted root terminal");5562 yield* awaitUntil(() => harness.terminalEvents().length === 1, "interrupted root terminal");
5413 assertChildProviderTerminalBeforeRoot(harness.events, harness.threadId);5563 assertChildProviderTerminalBeforeRoot(harness.events, harness.threadId);
5414 assert.isFalse(yield* harness.hasPendingBackgroundWork);5564 assert.isFalse(yield* harness.hasPendingBackgroundWork);
5415 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),5565 }).pipe(
5566 Effect.provide(
5567 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
5568 ),
5569 ),
5416 ),5570 ),
5417 );5571 );
54185572
...@@ -5523,7 +5677,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -5523,7 +5677,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
5523 assert.equal(lateChildUpdates.at(-1)?.providerTurn.status, "interrupted");5677 assert.equal(lateChildUpdates.at(-1)?.providerTurn.status, "interrupted");
5524 assertChildProviderTerminalBeforeRoot(harness.events, harness.threadId);5678 assertChildProviderTerminalBeforeRoot(harness.events, harness.threadId);
5525 assert.isFalse(yield* harness.hasPendingBackgroundWork);5679 assert.isFalse(yield* harness.hasPendingBackgroundWork);
5526 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),5680 }).pipe(
5681 Effect.provide(
5682 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
5683 ),
5684 ),
5527 ),5685 ),
5528 );5686 );
55295687
...@@ -5688,7 +5846,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -5688,7 +5846,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
5688 );5846 );
5689 assertChildProviderTerminalBeforeRoot(harness.events, harness.threadId);5847 assertChildProviderTerminalBeforeRoot(harness.events, harness.threadId);
5690 assert.isFalse(yield* harness.hasPendingBackgroundWork);5848 assert.isFalse(yield* harness.hasPendingBackgroundWork);
5691 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),5849 }).pipe(
5850 Effect.provide(
5851 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
5852 ),
5853 ),
5692 ),5854 ),
5693 );5855 );
56945856
...@@ -5953,7 +6115,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -5953,7 +6115,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
5953 commandUpdatesBeforeLateEvents.length,6115 commandUpdatesBeforeLateEvents.length,
5954 "late starts and completions must not project after timeout",6116 "late starts and completions must not project after timeout",
5955 );6117 );
5956 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),6118 }).pipe(
6119 Effect.provide(
6120 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
6121 ),
6122 ),
5957 ),6123 ),
5958 );6124 );
59596125
...@@ -6060,7 +6226,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -6060,7 +6226,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
6060 yield* awaitUntil(() => harness.terminalEvents().length === 1, "interrupted terminal");6226 yield* awaitUntil(() => harness.terminalEvents().length === 1, "interrupted terminal");
6061 assert.equal(harness.terminalEvents()[0]?.status, "interrupted");6227 assert.equal(harness.terminalEvents()[0]?.status, "interrupted");
6062 assert.isFalse(yield* harness.hasPendingBackgroundWork);6228 assert.isFalse(yield* harness.hasPendingBackgroundWork);
6063 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),6229 }).pipe(
6230 Effect.provide(
6231 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
6232 ),
6233 ),
6064 ),6234 ),
6065 );6235 );
60666236
...@@ -6296,7 +6466,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -6296,7 +6466,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
6296 assert.isDefined(item);6466 assert.isDefined(item);
6297 }6467 }
6298 if (scenario.name === "retry") assert.equal(terminal.retry?.attempt, 1);6468 if (scenario.name === "retry") assert.equal(terminal.retry?.attempt, 1);
6299 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),6469 }).pipe(
6470 Effect.provide(
6471 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
6472 ),
6473 ),
6300 ),6474 ),
6301 );6475 );
63026476
...@@ -6394,7 +6568,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -6394,7 +6568,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
6394 );6568 );
6395 assert.isFalse(yield* harness.hasPendingBackgroundWork);6569 assert.isFalse(yield* harness.hasPendingBackgroundWork);
6396 assert.lengthOf(harness.continuationRequests, 0);6570 assert.lengthOf(harness.continuationRequests, 0);
6397 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),6571 }).pipe(
6572 Effect.provide(
6573 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
6574 ),
6575 ),
6398 ),6576 ),
6399 );6577 );
64006578
...@@ -6555,7 +6733,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -6555,7 +6733,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
6555 assert.include(stored, "connect-src https://api.weather.test");6733 assert.include(stored, "connect-src https://api.weather.test");
6556 assert.isBelow(stored.indexOf("Content-Security-Policy"), stored.indexOf("<script>"));6734 assert.isBelow(stored.indexOf("Content-Security-Policy"), stored.indexOf("<script>"));
6557 assert.isFalse(yield* harness.hasPendingBackgroundWork);6735 assert.isFalse(yield* harness.hasPendingBackgroundWork);
6558 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),6736 }).pipe(
6737 Effect.provide(
6738 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
6739 ),
6740 ),
6559 ),6741 ),
6560 );6742 );
65616743
...@@ -6641,7 +6823,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -6641,7 +6823,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
6641 const output = rows[0]!.turnItem.type === "dynamic_tool" ? rows[0]!.turnItem.output : null;6823 const output = rows[0]!.turnItem.type === "dynamic_tool" ? rows[0]!.turnItem.output : null;
6642 assert.isUndefined((output as Record<string, unknown> | null)?.[MCP_APP_OUTPUT_KEY]);6824 assert.isUndefined((output as Record<string, unknown> | null)?.[MCP_APP_OUTPUT_KEY]);
6643 assert.isFalse(yield* harness.hasPendingBackgroundWork);6825 assert.isFalse(yield* harness.hasPendingBackgroundWork);
6644 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),6826 }).pipe(
6827 Effect.provide(
6828 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
6829 ),
6830 ),
6645 ),6831 ),
6646 );6832 );
66476833
...@@ -6883,7 +7069,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -6883,7 +7069,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
6883 );7069 );
6884 assert.lengthOf(harness.terminalEvents(), 1);7070 assert.lengthOf(harness.terminalEvents(), 1);
6885 assert.isFalse(yield* harness.hasPendingBackgroundWork);7071 assert.isFalse(yield* harness.hasPendingBackgroundWork);
6886 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),7072 }).pipe(
7073 Effect.provide(
7074 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
7075 ),
7076 ),
6887 ),7077 ),
6888 );7078 );
68897079
...@@ -7117,7 +7307,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -7117,7 +7307,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
7117 ]);7307 ]);
7118 }7308 }
7119 assert.equal(metadataRequests, name === "current Codex Sol" ? 1 : 2);7309 assert.equal(metadataRequests, name === "current Codex Sol" ? 1 : 2);
7120 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),7310 }).pipe(
7311 Effect.provide(
7312 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
7313 ),
7314 ),
7121 ),7315 ),
7122 );7316 );
71237317
...@@ -7174,7 +7368,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -7174,7 +7368,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
7174 yield* harness.firstTerminal;7368 yield* harness.firstTerminal;
7175 assert.equal(harness.subagentUpdates().at(-1)?.subagent.model, model);7369 assert.equal(harness.subagentUpdates().at(-1)?.subagent.model, model);
7176 assert.deepEqual(metadataRequests, ["thread/read", "thread/resume"]);7370 assert.deepEqual(metadataRequests, ["thread/read", "thread/resume"]);
7177 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),7371 }).pipe(
7372 Effect.provide(
7373 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
7374 ),
7375 ),
7178 ),7376 ),
7179 );7377 );
71807378
...@@ -7273,7 +7471,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -7273,7 +7471,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
7273 { id: "reasoningEffort", value: "low" },7471 { id: "reasoningEffort", value: "low" },
7274 { id: "serviceTier", value: "ultrafast" },7472 { id: "serviceTier", value: "ultrafast" },
7275 ]);7473 ]);
7276 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),7474 }).pipe(
7475 Effect.provide(
7476 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
7477 ),
7478 ),
7277 ),7479 ),
7278 );7480 );
72797481
...@@ -7335,7 +7537,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -7335,7 +7537,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
7335 assert.isFalse(yield* harness.hasPendingBackgroundWork);7537 assert.isFalse(yield* harness.hasPendingBackgroundWork);
7336 assert.lengthOf(harness.terminalEvents(), 1);7538 assert.lengthOf(harness.terminalEvents(), 1);
7337 assert.lengthOf(harness.continuationRequests, 0);7539 assert.lengthOf(harness.continuationRequests, 0);
7338 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),7540 }).pipe(
7541 Effect.provide(
7542 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
7543 ),
7544 ),
7339 ),7545 ),
7340 );7546 );
73417547
...@@ -7415,7 +7621,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -7415,7 +7621,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
7415 assert.isNull(row?.completedAt);7621 assert.isNull(row?.completedAt);
7416 assert.isNotNull(row?.startedAt);7622 assert.isNotNull(row?.startedAt);
7417 assert.equal(DateTime.toEpochMillis(row!.startedAt!), 1782622470000);7623 assert.equal(DateTime.toEpochMillis(row!.startedAt!), 1782622470000);
7418 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),7624 }).pipe(
7625 Effect.provide(
7626 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
7627 ),
7628 ),
7419 ),7629 ),
7420 );7630 );
74217631
...@@ -7577,7 +7787,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -7577,7 +7787,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
7577 DateTime.toEpochMillis(first.subagent.completedAt!),7787 DateTime.toEpochMillis(first.subagent.completedAt!),
7578 );7788 );
7579 }7789 }
7580 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),7790 }).pipe(
7791 Effect.provide(
7792 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
7793 ),
7794 ),
7581 ),7795 ),
7582 );7796 );
75837797
...@@ -7725,7 +7939,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -7725,7 +7939,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {
7725 "thread/rollback",7939 "thread/rollback",
7726 "thread/rollback must not be sent to a legacy Codex thread",7940 "thread/rollback must not be sent to a legacy Codex thread",
7727 );7941 );
7728 }).pipe(Effect.scoped, Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),7942 }).pipe(
7943 Effect.scoped,
7944 Effect.provide(
7945 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
7946 ),
7947 ),
7729 );7948 );
77307949
7731 it.effect(7950 it.effect(
...@@ -7864,7 +8083,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -7864,7 +8083,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {
7864 assert.notEqual(forkedProviderThread.id, harness.providerThread.id);8083 assert.notEqual(forkedProviderThread.id, harness.providerThread.id);
7865 assert.equal(forkedProviderThread.forkedFrom?.providerTurnId, firstTurn.id);8084 assert.equal(forkedProviderThread.forkedFrom?.providerTurnId, firstTurn.id);
7866 assert.deepEqual(outbound.slice(-2), ["thread/turns/list", "thread/revert"]);8085 assert.deepEqual(outbound.slice(-2), ["thread/turns/list", "thread/revert"]);
7867 }).pipe(Effect.scoped, Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),8086 }).pipe(
8087 Effect.scoped,
8088 Effect.provide(
8089 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
8090 ),
8091 ),
7868 );8092 );
78698093
7870 it.effect(8094 it.effect(
...@@ -7966,7 +8190,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -7966,7 +8190,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {
7966 "thread/rollback",8190 "thread/rollback",
7967 "thread/rollback must not be sent to a legacy Codex fork",8191 "thread/rollback must not be sent to a legacy Codex fork",
7968 );8192 );
7969 }).pipe(Effect.scoped, Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),8193 }).pipe(
8194 Effect.scoped,
8195 Effect.provide(
8196 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
8197 ),
8198 ),
7970 );8199 );
79718200
7972 it.effect("propagates native thread/fork failures as typed fork errors", () =>8201 it.effect("propagates native thread/fork failures as typed fork errors", () =>
...@@ -8022,7 +8251,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -8022,7 +8251,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {
80228251
8023 assert.instanceOf(error, ProviderAdapter.ProviderAdapterForkThreadError);8252 assert.instanceOf(error, ProviderAdapter.ProviderAdapterForkThreadError);
8024 assert.include(errorCauseChainText(error), "fork exploded");8253 assert.include(errorCauseChainText(error), "fork exploded");
8025 }).pipe(Effect.scoped, Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),8254 }).pipe(
8255 Effect.scoped,
8256 Effect.provide(
8257 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
8258 ),
8259 ),
8026 );8260 );
80278261
8028 describe("native goals", () => {8262 describe("native goals", () => {
...@@ -8186,7 +8420,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -8186,7 +8420,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {
8186 ],8420 ],
8187 );8421 );
8188 assert.equal(providerGoals(harness.events).at(-1), "complete");8422 assert.equal(providerGoals(harness.events).at(-1), "complete");
8189 }).pipe(Effect.scoped, Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),8423 }).pipe(
8424 Effect.scoped,
8425 Effect.provide(
8426 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
8427 ),
8428 ),
8190 );8429 );
81918430
8192 it.effect("settles /goal pause with a reply instead of a native turn", () =>8431 it.effect("settles /goal pause with a reply instead of a native turn", () =>
...@@ -8230,7 +8469,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -8230,7 +8469,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {
8230 "Goal paused. Send /goal resume to continue.",8469 "Goal paused. Send /goal resume to continue.",
8231 );8470 );
8232 assert.equal(providerGoals(harness.events).at(-1), "paused");8471 assert.equal(providerGoals(harness.events).at(-1), "paused");
8233 }).pipe(Effect.scoped, Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),8472 }).pipe(
8473 Effect.scoped,
8474 Effect.provide(
8475 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
8476 ),
8477 ),
8234 );8478 );
82358479
8236 it.effect("settles a run held for the next goal turn when Stop arrives in between", () =>8480 it.effect("settles a run held for the next goal turn when Stop arrives in between", () =>
...@@ -8321,7 +8565,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -8321,7 +8565,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {
8321 harness.terminalEvents().map((event) => event.status),8565 harness.terminalEvents().map((event) => event.status),
8322 ["interrupted"],8566 ["interrupted"],
8323 );8567 );
8324 }).pipe(Effect.scoped, Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),8568 }).pipe(
8569 Effect.scoped,
8570 Effect.provide(
8571 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
8572 ),
8573 ),
8325 );8574 );
83268575
8327 it.effect("moves a Stop that names an earlier goal turn to the turn Codex continued with", () =>8576 it.effect("moves a Stop that names an earlier goal turn to the turn Codex continued with", () =>
...@@ -8400,7 +8649,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -8400,7 +8649,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {
8400 harness.terminalEvents().map((event) => event.status),8649 harness.terminalEvents().map((event) => event.status),
8401 ["interrupted"],8650 ["interrupted"],
8402 );8651 );
8403 }).pipe(Effect.scoped, Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),8652 }).pipe(
8653 Effect.scoped,
8654 Effect.provide(
8655 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
8656 ),
8657 ),
8404 );8658 );
84058659
8406 it.effect("stops a held goal run's background command when Stop arrives between turns", () =>8660 it.effect("stops a held goal run's background command when Stop arrives between turns", () =>
...@@ -8502,7 +8756,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -8502,7 +8756,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {
8502 ),8756 ),
8503 "the background command to be interrupted",8757 "the background command to be interrupted",
8504 );8758 );
8505 }).pipe(Effect.scoped, Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),8759 }).pipe(
8760 Effect.scoped,
8761 Effect.provide(
8762 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
8763 ),
8764 ),
8506 );8765 );
85078766
8508 it.effect("moves Stop to a continuation Codex starts while the goal pause is in flight", () =>8767 it.effect("moves Stop to a continuation Codex starts while the goal pause is in flight", () =>
...@@ -8584,7 +8843,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -8584,7 +8843,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {
8584 harness.terminalEvents().map((event) => event.status),8843 harness.terminalEvents().map((event) => event.status),
8585 ["interrupted"],8844 ["interrupted"],
8586 );8845 );
8587 }).pipe(Effect.scoped, Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),8846 }).pipe(
8847 Effect.scoped,
8848 Effect.provide(
8849 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
8850 ),
8851 ),
8588 );8852 );
85898853
8590 it.effect("stops a subagent an earlier goal turn left running when Stop settles the run", () =>8854 it.effect("stops a subagent an earlier goal turn left running when Stop settles the run", () =>
...@@ -8681,7 +8945,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -8681,7 +8945,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {
8681 ),8945 ),
8682 "the subagent turn to be interrupted",8946 "the subagent turn to be interrupted",
8683 );8947 );
8684 }).pipe(Effect.scoped, Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),8948 }).pipe(
8949 Effect.scoped,
8950 Effect.provide(
8951 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
8952 ),
8953 ),
8685 );8954 );
86868955
8687 it.effect("stops an earlier goal turn's subagent when the run settles during the pause", () =>8956 it.effect("stops an earlier goal turn's subagent when the run settles during the pause", () =>
...@@ -8794,7 +9063,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -8794,7 +9063,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {
8794 ),9063 ),
8795 "the subagent turn to be interrupted",9064 "the subagent turn to be interrupted",
8796 );9065 );
8797 }).pipe(Effect.scoped, Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),9066 }).pipe(
9067 Effect.scoped,
9068 Effect.provide(
9069 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
9070 ),
9071 ),
8798 );9072 );
87999073
8800 it.effect("pauses an active goal before Stop interrupts its turn", () =>9074 it.effect("pauses an active goal before Stop interrupts its turn", () =>
...@@ -8858,7 +9132,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {...@@ -8858,7 +9132,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {
8858 harness.terminalEvents().map((event) => event.status),9132 harness.terminalEvents().map((event) => event.status),
8859 ["interrupted"],9133 ["interrupted"],
8860 );9134 );
8861 }).pipe(Effect.scoped, Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),9135 }).pipe(
9136 Effect.scoped,
9137 Effect.provide(
9138 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
9139 ),
9140 ),
8862 );9141 );
8863 });9142 });
8864});9143});
apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts+22-14
...@@ -108,6 +108,7 @@ import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEve...@@ -108,6 +108,7 @@ import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEve
108import { codexAppServerArgs, resolveCodexLaunchArgs } from "../../provider/codexLaunchArgs.ts";108import { codexAppServerArgs, resolveCodexLaunchArgs } from "../../provider/codexLaunchArgs.ts";
109import { mergeProviderInstanceEnvironment } from "@t3tools/provider-core/server/instanceEnvironment";109import { mergeProviderInstanceEnvironment } from "@t3tools/provider-core/server/instanceEnvironment";
110import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession";110import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession";
111import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
111import {112import {
112 MCP_APP_EXTENSION_ID,113 MCP_APP_EXTENSION_ID,
113 MCP_APP_MIME_TYPE,114 MCP_APP_MIME_TYPE,
...@@ -1296,7 +1297,7 @@ export class CodexAppServerClientFactory extends Context.Service<...@@ -1296,7 +1297,7 @@ export class CodexAppServerClientFactory extends Context.Service<
1296export const CODEX_THREAD_CONFIG = { "tools.update_plan.enabled": true } as const;1297export const CODEX_THREAD_CONFIG = { "tools.update_plan.enabled": true } as const;
12971298
1298export function codexThreadRuntimeParams(input: {1299export function codexThreadRuntimeParams(input: {
1299 readonly threadId: ThreadId | null;1300 readonly mcpSession: McpProviderSession.McpProviderSessionConfig | undefined;
1300 readonly modelSelection?: { readonly model: string };1301 readonly modelSelection?: { readonly model: string };
1301 readonly runtimePolicy?: ProviderAdapter.ProviderAdapterV2RuntimePolicy;1302 readonly runtimePolicy?: ProviderAdapter.ProviderAdapterV2RuntimePolicy;
1302}): {1303}): {
...@@ -1304,8 +1305,7 @@ export function codexThreadRuntimeParams(input: {...@@ -1304,8 +1305,7 @@ export function codexThreadRuntimeParams(input: {
1304 readonly model?: string;1305 readonly model?: string;
1305 readonly config: Readonly<Record<string, Schema.Json>>;1306 readonly config: Readonly<Record<string, Schema.Json>>;
1306} {1307} {
1307 const mcpSession =1308 const { mcpSession } = input;
1308 input.threadId === null ? undefined : McpProviderSession.readMcpProviderSession(input.threadId);
1309 return {1309 return {
1310 ...(input.runtimePolicy?.cwd == null ? {} : { cwd: input.runtimePolicy.cwd }),1310 ...(input.runtimePolicy?.cwd == null ? {} : { cwd: input.runtimePolicy.cwd }),
1311 ...(input.modelSelection === undefined ? {} : { model: input.modelSelection.model }),1311 ...(input.modelSelection === undefined ? {} : { model: input.modelSelection.model }),
...@@ -1549,6 +1549,7 @@ export type CodexAdapterV2DriverEnv =...@@ -1549,6 +1549,7 @@ export type CodexAdapterV2DriverEnv =
1549 | Crypto.Crypto1549 | Crypto.Crypto
1550 | FileSystem.FileSystem1550 | FileSystem.FileSystem
1551 | IdAllocator.IdAllocatorV21551 | IdAllocator.IdAllocatorV2
1552 | McpProviderSessions.McpProviderSessions
1552 | Path.Path1553 | Path.Path
1553 | ServerConfig;1554 | ServerConfig;
15541555
...@@ -1585,7 +1586,7 @@ export const createCodexAdapterV2 = (...@@ -1585,7 +1586,7 @@ export const createCodexAdapterV2 = (
1585 homePath: homeLayout.effectiveHomePath ?? "",1586 homePath: homeLayout.effectiveHomePath ?? "",
1586 } satisfies CodexSettings;1587 } satisfies CodexSettings;
15871588
1588 return makeCodexAdapterV2({1589 return yield* makeCodexAdapterV2({
1589 instanceId,1590 instanceId,
1590 settings,1591 settings,
1591 environment: mergeProviderInstanceEnvironment(environment, hostEnvironment),1592 environment: mergeProviderInstanceEnvironment(environment, hostEnvironment),
...@@ -1613,6 +1614,7 @@ const layer: Layer.Layer<...@@ -1613,6 +1614,7 @@ const layer: Layer.Layer<
1613 | Crypto.Crypto1614 | Crypto.Crypto
1614 | FileSystem.FileSystem1615 | FileSystem.FileSystem
1615 | IdAllocator.IdAllocatorV21616 | IdAllocator.IdAllocatorV2
1617 | McpProviderSessions.McpProviderSessions
1616 | ServerConfig1618 | ServerConfig
1617> = Layer.effect(1619> = Layer.effect(
1618 ProviderAdapter.ProviderAdapterV2,1620 ProviderAdapter.ProviderAdapterV2,
...@@ -1625,7 +1627,7 @@ const layer: Layer.Layer<...@@ -1625,7 +1627,7 @@ const layer: Layer.Layer<
1625 const idAllocator = yield* IdAllocator.IdAllocatorV2;1627 const idAllocator = yield* IdAllocator.IdAllocatorV2;
1626 const serverConfig = yield* ServerConfig;1628 const serverConfig = yield* ServerConfig;
16271629
1628 return makeCodexAdapterV2({1630 return yield* makeCodexAdapterV2({
1629 instanceId: CODEX_DEFAULT_INSTANCE_ID,1631 instanceId: CODEX_DEFAULT_INSTANCE_ID,
1630 settings: DEFAULT_CODEX_SETTINGS,1632 settings: DEFAULT_CODEX_SETTINGS,
1631 environment: hostEnvironment,1633 environment: hostEnvironment,
...@@ -1667,9 +1669,12 @@ export interface CodexAdapterV2Options {...@@ -1667,9 +1669,12 @@ export interface CodexAdapterV2Options {
1667 };1669 };
1668}1670}
16691671
1670export function makeCodexAdapterV2(1672export const makeCodexAdapterV2 = Effect.fn("makeCodexAdapterV2")(function* (
1671 adapterOptions: CodexAdapterV2Options,1673 adapterOptions: CodexAdapterV2Options,
1672): ProviderAdapter.ProviderAdapterV2["Service"] {1674) {
1675 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
1676 const readMcpSession = (threadId: ThreadId | null) =>
1677 threadId === null ? Effect.succeed(undefined) : mcpSessions.read(threadId);
1673 const { clientFactory, crypto, fileSystem, idAllocator, serverConfig } = adapterOptions;1678 const { clientFactory, crypto, fileSystem, idAllocator, serverConfig } = adapterOptions;
1674 const continuationRequests = adapterOptions.continuationRequests;1679 const continuationRequests = adapterOptions.continuationRequests;
16751680
...@@ -6166,7 +6171,7 @@ export function makeCodexAdapterV2(...@@ -6166,7 +6171,7 @@ export function makeCodexAdapterV2(
6166 ) =>6171 ) =>
6167 Effect.gen(function* () {6172 Effect.gen(function* () {
6168 const threadId = yield* getNativeThreadId(turnInput.providerThread);6173 const threadId = yield* getNativeThreadId(turnInput.providerThread);
6169 const mcpSession = McpProviderSession.readMcpProviderSession(turnInput.threadId);6174 const mcpSession = yield* mcpSessions.read(turnInput.threadId);
6170 const turnStartParams = yield* buildCodexTurnStartParams({6175 const turnStartParams = yield* buildCodexTurnStartParams({
6171 nativeThreadId: threadId,6176 nativeThreadId: threadId,
6172 codexInput,6177 codexInput,
...@@ -6445,11 +6450,12 @@ export function makeCodexAdapterV2(...@@ -6445,11 +6450,12 @@ export function makeCodexAdapterV2(
6445 }),6450 }),
6446 ensureThread: (threadInput) =>6451 ensureThread: (threadInput) =>
6447 ensureInitialized.pipe(6452 ensureInitialized.pipe(
6448 Effect.andThen(6453 Effect.andThen(mcpSessions.read(threadInput.threadId)),
6454 Effect.flatMap((mcpSession) =>
6449 client.request(6455 client.request(
6450 "thread/start",6456 "thread/start",
6451 codexThreadRuntimeParams({6457 codexThreadRuntimeParams({
6452 threadId: threadInput.threadId,6458 mcpSession,
6453 modelSelection: threadInput.modelSelection,6459 modelSelection: threadInput.modelSelection,
6454 runtimePolicy: threadInput.runtimePolicy,6460 runtimePolicy: threadInput.runtimePolicy,
6455 }),6461 }),
...@@ -6482,7 +6488,9 @@ export function makeCodexAdapterV2(...@@ -6482,7 +6488,9 @@ export function makeCodexAdapterV2(
6482 threadId: nativeThreadId,6488 threadId: nativeThreadId,
6483 excludeTurns: true,6489 excludeTurns: true,
6484 ...codexThreadRuntimeParams({6490 ...codexThreadRuntimeParams({
6485 threadId: threadInput.threadId ?? threadInput.providerThread.appThreadId,6491 mcpSession: yield* readMcpSession(
6492 threadInput.threadId ?? threadInput.providerThread.appThreadId,
6493 ),
6486 ...(threadInput.modelSelection === undefined6494 ...(threadInput.modelSelection === undefined
6487 ? {}6495 ? {}
6488 : { modelSelection: threadInput.modelSelection }),6496 : { modelSelection: threadInput.modelSelection }),
...@@ -7210,7 +7218,7 @@ export function makeCodexAdapterV2(...@@ -7210,7 +7218,7 @@ export function makeCodexAdapterV2(
7210 threadId,7218 threadId,
7211 excludeTurns: true,7219 excludeTurns: true,
7212 ...codexThreadRuntimeParams({7220 ...codexThreadRuntimeParams({
7213 threadId: threadInput.providerThread.appThreadId,7221 mcpSession: yield* readMcpSession(threadInput.providerThread.appThreadId),
7214 modelSelection: input.modelSelection,7222 modelSelection: input.modelSelection,
7215 runtimePolicy: input.runtimePolicy,7223 runtimePolicy: input.runtimePolicy,
7216 }),7224 }),
...@@ -7259,7 +7267,7 @@ export function makeCodexAdapterV2(...@@ -7259,7 +7267,7 @@ export function makeCodexAdapterV2(
7259 ? {}7267 ? {}
7260 : { lastTurnId: boundary.lastTurnId }),7268 : { lastTurnId: boundary.lastTurnId }),
7261 ...codexThreadRuntimeParams({7269 ...codexThreadRuntimeParams({
7262 threadId: threadInput.targetThreadId,7270 mcpSession: yield* mcpSessions.read(threadInput.targetThreadId),
7263 ...(threadInput.modelSelection === undefined7271 ...(threadInput.modelSelection === undefined
7264 ? {}7272 ? {}
7265 : { modelSelection: threadInput.modelSelection }),7273 : { modelSelection: threadInput.modelSelection }),
...@@ -7344,4 +7352,4 @@ export function makeCodexAdapterV2(...@@ -7344,4 +7352,4 @@ export function makeCodexAdapterV2(
7344 ),7352 ),
7345 ),7353 ),
7346 });7354 });
7347}7355});
apps/server/src/orchestration-v2/Adapters/CursorAdapterV2.testkit.ts+1
...@@ -719,6 +719,7 @@ export const recordCursorAgentSdkReplayTranscript = Effect.fn(...@@ -719,6 +719,7 @@ export const recordCursorAgentSdkReplayTranscript = Effect.fn(
719 : { override: input.runtimePolicyOverride }),719 : { override: input.runtimePolicyOverride }),
720 }),720 }),
721 threadId,721 threadId,
722 mcpSession: undefined,
722 });723 });
723 const sendOptions = {724 const sendOptions = {
724 model: cursorSdkModelSelection(input.modelSelection),725 model: cursorSdkModelSelection(input.modelSelection),
apps/server/src/orchestration-v2/Adapters/GrokAdapterV2.test.ts+7-1
...@@ -26,6 +26,7 @@ import { buildInitialGrokProviderSnapshot } from "@t3tools/provider-grok/testing...@@ -26,6 +26,7 @@ import { buildInitialGrokProviderSnapshot } from "@t3tools/provider-grok/testing
26import type { ProviderInstance } from "@t3tools/provider-core/server/driver";26import type { ProviderInstance } from "@t3tools/provider-core/server/driver";
27import * as ProviderInstanceRegistry from "../../provider/ProviderInstanceRegistry.ts";27import * as ProviderInstanceRegistry from "../../provider/ProviderInstanceRegistry.ts";
28import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";28import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
29import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
29import * as ProviderAdapter from "@t3tools/provider-core/server/ProviderAdapter";30import * as ProviderAdapter from "@t3tools/provider-core/server/ProviderAdapter";
30import * as RuntimePolicy from "../RuntimePolicy.ts";31import * as RuntimePolicy from "../RuntimePolicy.ts";
31import { acpPermissionDisposition } from "@t3tools/provider-acp/server/clientPolicy";32import { acpPermissionDisposition } from "@t3tools/provider-acp/server/clientPolicy";
...@@ -309,7 +310,12 @@ describe("Grok permission prompts", () => {...@@ -309,7 +310,12 @@ describe("Grok permission prompts", () => {
309310
310describe("Grok launch permission mode", () => {311describe("Grok launch permission mode", () => {
311 const layerHost = layerTestProviderHost().pipe(Layer.provide(NodeServices.layer));312 const layerHost = layerTestProviderHost().pipe(Layer.provide(NodeServices.layer));
312 const layerTest = Layer.mergeAll(NodeServices.layer, IdAllocator.layer, layerHost);313 const layerTest = Layer.mergeAll(
314 NodeServices.layer,
315 IdAllocator.layer,
316 McpProviderSessions.layer,
317 layerHost,
318 );
313319
314 // Opens a session through the adapter's own Grok runtime factory and returns320 // Opens a session through the adapter's own Grok runtime factory and returns
315 // the argv it tried to launch. The spawn fails after recording, so no321 // the argv it tried to launch. The spawn fails after recording, so no
apps/server/src/orchestration-v2/Adapters/OpenCode2AdapterV2.test.ts+34-34
...@@ -39,7 +39,7 @@ import { TestClock } from "effect/testing";...@@ -39,7 +39,7 @@ import { TestClock } from "effect/testing";
39import { describe } from "vite-plus/test";39import { describe } from "vite-plus/test";
4040
41import type * as ProviderAdapter from "@t3tools/provider-core/server/ProviderAdapter";41import type * as ProviderAdapter from "@t3tools/provider-core/server/ProviderAdapter";
42import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession";42import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
43import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";43import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
44import * as ProviderContinuationRequests from "@t3tools/provider-core/server/ProviderContinuationRequests";44import * as ProviderContinuationRequests from "@t3tools/provider-core/server/ProviderContinuationRequests";
45import {45import {
...@@ -403,7 +403,7 @@ const history = {...@@ -403,7 +403,7 @@ const history = {
403 cursor: {},403 cursor: {},
404};404};
405405
406describe("OpenCode2 adapter", () => {406it.layer(McpProviderSessions.layer)("OpenCode2 adapter", (it) => {
407 it.effect("switches the session's model and variant before a turn that changed them", () =>407 it.effect("switches the session's model and variant before a turn that changed them", () =>
408 Effect.gen(function* () {408 Effect.gen(function* () {
409 const { runtime, thread } = yield* resumed([409 const { runtime, thread } = yield* resumed([
...@@ -2486,29 +2486,6 @@ describe("OpenCode2 adapter", () => {...@@ -2486,29 +2486,6 @@ describe("OpenCode2 adapter", () => {
2486 }).pipe(Effect.scoped),2486 }).pipe(Effect.scoped),
2487 );2487 );
24882488
2489 it.live("returns the server it reconnected to once the session closes", () =>
2490 Effect.gen(function* () {
2491 // A spawned server stops after it has no borrowers for a while, so a
2492 // session must not keep holding the connection it reconnected with.
2493 const borrowers = { current: 0 };
2494 yield* Effect.gen(function* () {
2495 yield* openCode2ReplayRuntime(
2496 [
2497 ...opening,
2498 { type: "runtime_exit", status: "success" },
2499 out("event.subscribe"),
2500 event("server.connected", {}),
2501 ],
2502 { borrowers },
2503 );
2504 // Reconnected: the dropped connection is returned and the new one is held.
2505 yield* Effect.sleep("200 millis");
2506 assert.equal(borrowers.current, 1);
2507 }).pipe(Effect.scoped);
2508 assert.equal(borrowers.current, 0);
2509 }),
2510 );
2511
2512 it.effect(2489 it.effect(
2513 "backfills and ends a subagent's turn whose execution ended while the stream was down",2490 "backfills and ends a subagent's turn whose execution ended while the stream was down",
2514 () =>2491 () =>
...@@ -2697,7 +2674,8 @@ describe("OpenCode2 adapter", () => {...@@ -2697,7 +2674,8 @@ describe("OpenCode2 adapter", () => {
2697 "registers T3's MCP server for the thread alone and removes it when the thread unloads",2674 "registers T3's MCP server for the thread alone and removes it when the thread unloads",
2698 () =>2675 () =>
2699 Effect.gen(function* () {2676 Effect.gen(function* () {
2700 McpProviderSession.setMcpProviderSession({2677 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
2678 yield* mcpSessions.set({
2701 environmentId: EnvironmentId.make("environment:opencode2-adapter"),2679 environmentId: EnvironmentId.make("environment:opencode2-adapter"),
2702 threadId,2680 threadId,
2703 providerSessionId: "mcp:opencode2-adapter",2681 providerSessionId: "mcp:opencode2-adapter",
...@@ -2706,9 +2684,7 @@ describe("OpenCode2 adapter", () => {...@@ -2706,9 +2684,7 @@ describe("OpenCode2 adapter", () => {
2706 authorizationHeader: "Bearer thread-credential",2684 authorizationHeader: "Bearer thread-credential",
2707 browserToolsAvailable: false,2685 browserToolsAvailable: false,
2708 });2686 });
2709 yield* Effect.addFinalizer(() =>2687 yield* Effect.addFinalizer(() => mcpSessions.clear(threadId));
2710 Effect.sync(() => McpProviderSession.clearMcpProviderSession(threadId)),
2711 );
2712 const server = "t3-code-thread_opencode2-adapter";2688 const server = "t3-code-thread_opencode2-adapter";
2713 const { runtime, thread } = yield* resumed([2689 const { runtime, thread } = yield* resumed([
2714 // Registered for the session's directory under the thread's own name;2690 // Registered for the session's directory under the thread's own name;
...@@ -2744,7 +2720,8 @@ describe("OpenCode2 adapter", () => {...@@ -2744,7 +2720,8 @@ describe("OpenCode2 adapter", () => {
2744 "thread:delegated-task:command%3Amcp%3A48bef2bf-6d0e-4f7a-9c3b-2e5d8a1f7c40%3Adelegate-task%3Asubproject-b-round1",2720 "thread:delegated-task:command%3Amcp%3A48bef2bf-6d0e-4f7a-9c3b-2e5d8a1f7c40%3Adelegate-task%3Asubproject-b-round1",
2745 );2721 );
2746 const server = "t3-code-aa73fa1e03099934";2722 const server = "t3-code-aa73fa1e03099934";
2747 McpProviderSession.setMcpProviderSession({2723 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
2724 yield* mcpSessions.set({
2748 environmentId: EnvironmentId.make("environment:opencode2-adapter"),2725 environmentId: EnvironmentId.make("environment:opencode2-adapter"),
2749 threadId: child,2726 threadId: child,
2750 providerSessionId: "mcp:opencode2-adapter",2727 providerSessionId: "mcp:opencode2-adapter",
...@@ -2753,9 +2730,7 @@ describe("OpenCode2 adapter", () => {...@@ -2753,9 +2730,7 @@ describe("OpenCode2 adapter", () => {
2753 authorizationHeader: "Bearer thread-credential",2730 authorizationHeader: "Bearer thread-credential",
2754 browserToolsAvailable: false,2731 browserToolsAvailable: false,
2755 });2732 });
2756 yield* Effect.addFinalizer(() =>2733 yield* Effect.addFinalizer(() => mcpSessions.clear(child));
2757 Effect.sync(() => McpProviderSession.clearMcpProviderSession(child)),
2758 );
2759 const runtime = yield* openCode2ReplayRuntimeWithInstructions([2734 const runtime = yield* openCode2ReplayRuntimeWithInstructions([
2760 ...opening,2735 ...opening,
2761 out("session.get", { sessionID: SESSION }),2736 out("session.get", { sessionID: SESSION }),
...@@ -3992,6 +3967,31 @@ describe("OpenCode2 adapter", () => {...@@ -3992,6 +3967,31 @@ describe("OpenCode2 adapter", () => {
3992 );3967 );
3993});3968});
39943969
3970describe("OpenCode2 adapter server connection", () => {
3971 it.live("returns the server it reconnected to once the session closes", () =>
3972 Effect.gen(function* () {
3973 // A spawned server stops after it has no borrowers for a while, so a
3974 // session must not keep holding the connection it reconnected with.
3975 const borrowers = { current: 0 };
3976 yield* Effect.gen(function* () {
3977 yield* openCode2ReplayRuntime(
3978 [
3979 ...opening,
3980 { type: "runtime_exit", status: "success" },
3981 out("event.subscribe"),
3982 event("server.connected", {}),
3983 ],
3984 { borrowers },
3985 );
3986 // Reconnected: the dropped connection is returned and the new one is held.
3987 yield* Effect.sleep("200 millis");
3988 assert.equal(borrowers.current, 1);
3989 }).pipe(Effect.scoped);
3990 assert.equal(borrowers.current, 0);
3991 }).pipe(Effect.provide(McpProviderSessions.layer)),
3992 );
3993});
3994
3995/** The provider turn the adapter derives for `turnInput`'s attempt. */3995/** The provider turn the adapter derives for `turnInput`'s attempt. */
3996const providerTurnId = Effect.gen(function* () {3996const providerTurnId = Effect.gen(function* () {
3997 const ids = yield* IdAllocator.IdAllocatorV2;3997 const ids = yield* IdAllocator.IdAllocatorV2;
...@@ -4001,7 +4001,7 @@ const providerTurnId = Effect.gen(function* () {...@@ -4001,7 +4001,7 @@ const providerTurnId = Effect.gen(function* () {
4001 });4001 });
4002}).pipe(Effect.provide(IdAllocator.layer));4002}).pipe(Effect.provide(IdAllocator.layer));
40034003
4004describe("OpenCode reported model variants", () => {4004it.layer(McpProviderSessions.layer)("OpenCode reported model variants", (it) => {
4005 it.effect(4005 it.effect(
4006 "updates reported variants from selected-model and step events without duplicate updates",4006 "updates reported variants from selected-model and step events without duplicate updates",
4007 () =>4007 () =>
apps/server/src/orchestration-v2/ClaudeAutomaticDelivery.integration.test.ts+7-2
...@@ -32,6 +32,7 @@ import * as ClaudeAdapterV2 from "./Adapters/ClaudeAdapterV2.ts";...@@ -32,6 +32,7 @@ import * as ClaudeAdapterV2 from "./Adapters/ClaudeAdapterV2.ts";
32import * as EffectWorker from "./EffectWorker.ts";32import * as EffectWorker from "./EffectWorker.ts";
33import * as EventSink from "./EventSink.ts";33import * as EventSink from "./EventSink.ts";
34import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";34import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
35import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
35import * as LegacyV1ThreadImporter from "./legacy/LegacyV1ThreadImporter.ts";36import * as LegacyV1ThreadImporter from "./legacy/LegacyV1ThreadImporter.ts";
36import * as Orchestrator from "./Orchestrator.ts";37import * as Orchestrator from "./Orchestrator.ts";
37import * as ProviderAdapterRegistry from "./ProviderAdapterRegistry.ts";38import * as ProviderAdapterRegistry from "./ProviderAdapterRegistry.ts";
...@@ -131,7 +132,7 @@ it.effect.each(["child completion", "scheduled message", "user steering"] as con...@@ -131,7 +132,7 @@ it.effect.each(["child completion", "scheduled message", "user steering"] as con
131 const offers: SDKUserMessage[] = [];132 const offers: SDKUserMessage[] = [];
132 const nativeQueue: SDKUserMessage[] = [];133 const nativeQueue: SDKUserMessage[] = [];
133 const batchAbort = new AbortController();134 const batchAbort = new AbortController();
134 const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({135 const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({
135 instanceId: modelSelection.instanceId,136 instanceId: modelSelection.instanceId,
136 settings,137 settings,
137 environment: {},138 environment: {},
...@@ -423,6 +424,10 @@ it.effect.each(["child completion", "scheduled message", "user steering"] as con...@@ -423,6 +424,10 @@ it.effect.each(["child completion", "scheduled message", "user steering"] as con
423 ),424 ),
424 ),425 ),
425 );426 );
426 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),427 }).pipe(
428 Effect.provide(
429 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
430 ),
431 ),
427 ),432 ),
428);433);
apps/server/src/orchestration-v2/CursorOrchestratorV2.live.test.ts+2
...@@ -30,6 +30,7 @@ import * as ServerEnvironment from "../environment/ServerEnvironment.ts";...@@ -30,6 +30,7 @@ import * as ServerEnvironment from "../environment/ServerEnvironment.ts";
30import * as ModelManifest from "../provider/ModelManifest.ts";30import * as ModelManifest from "../provider/ModelManifest.ts";
31import * as ProviderInstanceRegistryHydration from "../provider/ProviderInstanceRegistryHydration.ts";31import * as ProviderInstanceRegistryHydration from "../provider/ProviderInstanceRegistryHydration.ts";
32import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";32import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";
33import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
33import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";34import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";
34import * as OpenCodeRuntime from "@t3tools/provider-opencode/server/OpenCodeRuntime";35import * as OpenCodeRuntime from "@t3tools/provider-opencode/server/OpenCodeRuntime";
35import * as OpenCodeServerLedger from "@t3tools/provider-opencode/server/OpenCodeServerLedger";36import * as OpenCodeServerLedger from "@t3tools/provider-opencode/server/OpenCodeServerLedger";
...@@ -124,6 +125,7 @@ const layerLive = RuntimeLayer.layer.pipe(...@@ -124,6 +125,7 @@ const layerLive = RuntimeLayer.layer.pipe(
124 Layer.provide(ResetCreditCoordinator.layer),125 Layer.provide(ResetCreditCoordinator.layer),
125 Layer.provide(layerBackgroundPolicy),126 Layer.provide(layerBackgroundPolicy),
126 Layer.provide(layerPlatformTest),127 Layer.provide(layerPlatformTest),
128 Layer.provide(McpProviderSessions.layer),
127);129);
128130
129const waitForIdle = Effect.fn("CursorOrchestratorV2Live.waitForIdle")(function* (131const waitForIdle = Effect.fn("CursorOrchestratorV2Live.waitForIdle")(function* (
apps/server/src/orchestration-v2/DelegatedCompletionDelivery.test.ts+2
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1import * as SourceControlProviderRegistry from "../sourceControl/SourceControlProviderRegistry.ts";1import * as SourceControlProviderRegistry from "../sourceControl/SourceControlProviderRegistry.ts";
2import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
2import * as NodeServices from "@effect/platform-node/NodeServices";3import * as NodeServices from "@effect/platform-node/NodeServices";
3import { assert, it } from "@effect/vitest";4import { assert, it } from "@effect/vitest";
4import {5import {
...@@ -130,6 +131,7 @@ const layerTest = Layer.mergeAll(RuntimeLayer.layer, RuntimeLayer.layerEventSink...@@ -130,6 +131,7 @@ const layerTest = Layer.mergeAll(RuntimeLayer.layer, RuntimeLayer.layerEventSink
130 }),131 }),
131 ),132 ),
132 Layer.provide(McpSessionRegistryTestkit.layer),133 Layer.provide(McpSessionRegistryTestkit.layer),
134 Layer.provide(McpProviderSessions.layer),
133 Layer.provide(SqlitePersistence.layerMemory),135 Layer.provide(SqlitePersistence.layerMemory),
134 Layer.provide(layerCheckpointStoreTest),136 Layer.provide(layerCheckpointStoreTest),
135 Layer.provide(layerServerConfig),137 Layer.provide(layerServerConfig),
apps/server/src/orchestration-v2/GrokOrchestratorV2.live.test.ts+2
...@@ -30,6 +30,7 @@ import * as ServerEnvironment from "../environment/ServerEnvironment.ts";...@@ -30,6 +30,7 @@ import * as ServerEnvironment from "../environment/ServerEnvironment.ts";
30import * as ModelManifest from "../provider/ModelManifest.ts";30import * as ModelManifest from "../provider/ModelManifest.ts";
31import * as ProviderInstanceRegistryHydration from "../provider/ProviderInstanceRegistryHydration.ts";31import * as ProviderInstanceRegistryHydration from "../provider/ProviderInstanceRegistryHydration.ts";
32import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";32import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";
33import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
33import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";34import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";
34import * as OpenCodeRuntime from "@t3tools/provider-opencode/server/OpenCodeRuntime";35import * as OpenCodeRuntime from "@t3tools/provider-opencode/server/OpenCodeRuntime";
35import * as OpenCodeServerLedger from "@t3tools/provider-opencode/server/OpenCodeServerLedger";36import * as OpenCodeServerLedger from "@t3tools/provider-opencode/server/OpenCodeServerLedger";
...@@ -123,6 +124,7 @@ const layerLive = RuntimeLayer.layer.pipe(...@@ -123,6 +124,7 @@ const layerLive = RuntimeLayer.layer.pipe(
123 Layer.provide(ResetCreditCoordinator.layer),124 Layer.provide(ResetCreditCoordinator.layer),
124 Layer.provide(layerBackgroundPolicy),125 Layer.provide(layerBackgroundPolicy),
125 Layer.provide(layerPlatformTest),126 Layer.provide(layerPlatformTest),
127 Layer.provide(McpProviderSessions.layer),
126);128);
127129
128const waitForIdle = Effect.fn("GrokOrchestratorV2Live.waitForIdle")(function* (threadId: ThreadId) {130const waitForIdle = Effect.fn("GrokOrchestratorV2Live.waitForIdle")(function* (threadId: ThreadId) {
apps/server/src/orchestration-v2/OpenCode2OrchestratorV2.live.test.ts+2
...@@ -55,6 +55,7 @@ import * as ServerEnvironment from "../environment/ServerEnvironment.ts";...@@ -55,6 +55,7 @@ import * as ServerEnvironment from "../environment/ServerEnvironment.ts";
55import * as ModelManifest from "../provider/ModelManifest.ts";55import * as ModelManifest from "../provider/ModelManifest.ts";
56import * as ProviderInstanceRegistryHydration from "../provider/ProviderInstanceRegistryHydration.ts";56import * as ProviderInstanceRegistryHydration from "../provider/ProviderInstanceRegistryHydration.ts";
57import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";57import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";
58import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
58import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";59import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";
59import * as OpenCode2Client from "@t3tools/provider-opencode/server/v2/OpenCode2Client";60import * as OpenCode2Client from "@t3tools/provider-opencode/server/v2/OpenCode2Client";
60import * as OpenCodeRuntime from "@t3tools/provider-opencode/server/OpenCodeRuntime";61import * as OpenCodeRuntime from "@t3tools/provider-opencode/server/OpenCodeRuntime";
...@@ -217,6 +218,7 @@ const layerOrchestration = RuntimeLayer.layer.pipe(...@@ -217,6 +218,7 @@ const layerOrchestration = RuntimeLayer.layer.pipe(
217 Layer.provide(ResetCreditCoordinator.layer),218 Layer.provide(ResetCreditCoordinator.layer),
218 Layer.provide(layerBackgroundPolicy),219 Layer.provide(layerBackgroundPolicy),
219 Layer.provide(layerPlatformTest),220 Layer.provide(layerPlatformTest),
221 Layer.provide(McpProviderSessions.layer),
220);222);
221223
222// Starts the continuation run a provider wake asks for, as the production layer does.224// Starts the continuation run a provider wake asks for, as the production layer does.
apps/server/src/orchestration-v2/ProjectSettingsUpgrade.integration.test.ts+2
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1import * as NodeServices from "@effect/platform-node/NodeServices";1import * as NodeServices from "@effect/platform-node/NodeServices";
2import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
2import { assert, it } from "@effect/vitest";3import { assert, it } from "@effect/vitest";
3import { ProjectId } from "@t3tools/contracts";4import { ProjectId } from "@t3tools/contracts";
4import * as Effect from "effect/Effect";5import * as Effect from "effect/Effect";
...@@ -152,6 +153,7 @@ const layerRuntime = (dbPath: string) => {...@@ -152,6 +153,7 @@ const layerRuntime = (dbPath: string) => {
152 }),153 }),
153 ),154 ),
154 Layer.provide(McpSessionRegistryTestkit.layer),155 Layer.provide(McpSessionRegistryTestkit.layer),
156 Layer.provide(McpProviderSessions.layer),
155 Layer.provideMerge(SqlitePersistence.layerFromPath(dbPath)),157 Layer.provideMerge(SqlitePersistence.layerFromPath(dbPath)),
156 Layer.provide(layerCheckpointStore),158 Layer.provide(layerCheckpointStore),
157 Layer.provide(layerServerConfig),159 Layer.provide(layerServerConfig),
apps/server/src/orchestration-v2/ProviderSessionManager.test.ts+44-32
...@@ -36,6 +36,7 @@ import { ProviderWorkspaceMissingError } from "../provider/Errors.ts";...@@ -36,6 +36,7 @@ import { ProviderWorkspaceMissingError } from "../provider/Errors.ts";
36import * as ServerEnvironment from "../environment/ServerEnvironment.ts";36import * as ServerEnvironment from "../environment/ServerEnvironment.ts";
37import * as ProjectService from "../project/ProjectService.ts";37import * as ProjectService from "../project/ProjectService.ts";
38import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession";38import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession";
39import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
39import * as McpSessionRegistry from "../mcp/McpSessionRegistry.ts";40import * as McpSessionRegistry from "../mcp/McpSessionRegistry.ts";
40import * as SqlitePersistence from "../persistence/Sqlite.ts";41import * as SqlitePersistence from "../persistence/Sqlite.ts";
41import * as ServerSettings from "../serverSettings.ts";42import * as ServerSettings from "../serverSettings.ts";
...@@ -302,7 +303,7 @@ function unimplemented(detail: string) {...@@ -302,7 +303,7 @@ function unimplemented(detail: string) {
302 );303 );
303}304}
304305
305function makeProviderAdapter(306const makeProviderAdapter = Effect.fnUntraced(function* (
306 state: Ref.Ref<TestProviderRuntimeState>,307 state: Ref.Ref<TestProviderRuntimeState>,
307 options: {308 options: {
308 readonly failEventStream?: boolean;309 readonly failEventStream?: boolean;
...@@ -324,7 +325,8 @@ function makeProviderAdapter(...@@ -324,7 +325,8 @@ function makeProviderAdapter(
324 /** Completed when a hanging scope close reaches its wedged finalizer. */325 /** Completed when a hanging scope close reaches its wedged finalizer. */
325 readonly scopeCloseReached?: Deferred.Deferred<void>;326 readonly scopeCloseReached?: Deferred.Deferred<void>;
326 } = {},327 } = {},
327): ProviderAdapter.ProviderAdapterV2["Service"] {328) {
329 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
328 const countClose = Effect.addFinalizer(() =>330 const countClose = Effect.addFinalizer(() =>
329 Ref.update(state, (current) => ({331 Ref.update(state, (current) => ({
330 ...current,332 ...current,
...@@ -340,7 +342,7 @@ function makeProviderAdapter(...@@ -340,7 +342,7 @@ function makeProviderAdapter(
340 : Deferred.succeed(options.scopeCloseReached, undefined)342 : Deferred.succeed(options.scopeCloseReached, undefined)
341 ).pipe(Effect.andThen(Effect.never)),343 ).pipe(Effect.andThen(Effect.never)),
342 );344 );
343 return {345 return ProviderAdapter.ProviderAdapterV2.of({
344 instanceId: ProviderInstanceId.make("codex"),346 instanceId: ProviderInstanceId.make("codex"),
345 driver: CODEX_DRIVER,347 driver: CODEX_DRIVER,
346 getCapabilities: () => Effect.succeed(options.capabilities ?? CodexCapabilities),348 getCapabilities: () => Effect.succeed(options.capabilities ?? CodexCapabilities),
...@@ -352,19 +354,15 @@ function makeProviderAdapter(...@@ -352,19 +354,15 @@ function makeProviderAdapter(
352 if (options.hangSessionScopeClose === true) yield* hangClose;354 if (options.hangSessionScopeClose === true) yield* hangClose;
353 }355 }
354 if (options.mcpConfigs !== undefined && options.spawnBeforeOpen === true) {356 if (options.mcpConfigs !== undefined && options.spawnBeforeOpen === true) {
355 yield* Ref.update(options.mcpConfigs, (configs) => [357 const mcpConfig = yield* mcpSessions.read(input.threadId);
356 ...configs,358 yield* Ref.update(options.mcpConfigs, (configs) => [...configs, mcpConfig]);
357 McpProviderSession.readMcpProviderSession(input.threadId),
358 ]);
359 }359 }
360 if (options.beforeOpen !== undefined) {360 if (options.beforeOpen !== undefined) {
361 yield* options.beforeOpen(input);361 yield* options.beforeOpen(input);
362 }362 }
363 if (options.mcpConfigs !== undefined && options.spawnBeforeOpen !== true) {363 if (options.mcpConfigs !== undefined && options.spawnBeforeOpen !== true) {
364 yield* Ref.update(options.mcpConfigs, (configs) => [364 const mcpConfig = yield* mcpSessions.read(input.threadId);
365 ...configs,365 yield* Ref.update(options.mcpConfigs, (configs) => [...configs, mcpConfig]);
366 McpProviderSession.readMcpProviderSession(input.threadId),
367 ]);
368 }366 }
369 const now = yield* DateTime.now;367 const now = yield* DateTime.now;
370 const events = yield* Queue.unbounded<ProviderAdapter.ProviderAdapterV2Event, Cause.Done>();368 const events = yield* Queue.unbounded<ProviderAdapter.ProviderAdapterV2Event, Cause.Done>();
...@@ -440,8 +438,8 @@ function makeProviderAdapter(...@@ -440,8 +438,8 @@ function makeProviderAdapter(
440 forkThread: () => unimplemented("forkThread unused in test"),438 forkThread: () => unimplemented("forkThread unused in test"),
441 } satisfies ProviderAdapter.ProviderAdapterV2SessionRuntime;439 } satisfies ProviderAdapter.ProviderAdapterV2SessionRuntime;
442 }),440 }),
443 };441 });
444}442});
445443
446function layerTest(input: {444function layerTest(input: {
447 readonly state: Ref.Ref<TestProviderRuntimeState>;445 readonly state: Ref.Ref<TestProviderRuntimeState>;
...@@ -482,7 +480,7 @@ function layerTest(input: {...@@ -482,7 +480,7 @@ function layerTest(input: {
482 : input.failReleaseEventWrites480 : input.failReleaseEventWrites
483 ? layerFailingReleaseEventSink481 ? layerFailingReleaseEventSink
484 : layerTestEventSink;482 : layerTestEventSink;
485 const layerRegistry = ProviderAdapterRegistry.layerSingle(483 const layerRegistry = Layer.unwrap(
486 makeProviderAdapter(input.state, {484 makeProviderAdapter(input.state, {
487 failEventStream: input.failEventStream ?? false,485 failEventStream: input.failEventStream ?? false,
488 ...(input.capabilities === undefined ? {} : { capabilities: input.capabilities }),486 ...(input.capabilities === undefined ? {} : { capabilities: input.capabilities }),
...@@ -503,7 +501,7 @@ function layerTest(input: {...@@ -503,7 +501,7 @@ function layerTest(input: {
503 ...(input.scopeCloseReached === undefined501 ...(input.scopeCloseReached === undefined
504 ? {}502 ? {}
505 : { scopeCloseReached: input.scopeCloseReached }),503 : { scopeCloseReached: input.scopeCloseReached }),
506 }),504 }).pipe(Effect.map(ProviderAdapterRegistry.layerSingle)),
507 );505 );
508 const layerConfiguredMcpRegistry =506 const layerConfiguredMcpRegistry =
509 input.pauseResolve === undefined507 input.pauseResolve === undefined
...@@ -524,13 +522,14 @@ function layerTest(input: {...@@ -524,13 +522,14 @@ function layerTest(input: {
524 layerConfiguredEventSink,522 layerConfiguredEventSink,
525 IdAllocator.layer,523 IdAllocator.layer,
526 layerConfiguredMcpRegistry,524 layerConfiguredMcpRegistry,
525 McpProviderSessions.layer,
527 ProviderSessionManager.layerWithOptions({526 ProviderSessionManager.layerWithOptions({
528 idleTimeoutMs: input.idleTimeoutMs,527 idleTimeoutMs: input.idleTimeoutMs,
529 ...(input.maxIdlePinMs === undefined ? {} : { maxIdlePinMs: input.maxIdlePinMs }),528 ...(input.maxIdlePinMs === undefined ? {} : { maxIdlePinMs: input.maxIdlePinMs }),
530 }).pipe(529 }).pipe(
531 Layer.provide(530 Layer.provide(
532 Layer.mergeAll(531 Layer.mergeAll(
533 layerRegistry,532 layerRegistry.pipe(Layer.provideMerge(McpProviderSessions.layer)),
534 layerConfiguredEventSink,533 layerConfiguredEventSink,
535 IdAllocator.layer,534 IdAllocator.layer,
536 layerProviderEventIngestorTest,535 layerProviderEventIngestorTest,
...@@ -1106,6 +1105,7 @@ it.effect("ProviderSessionManagerV2 cleans up an open interrupted mid-handshake"...@@ -1106,6 +1105,7 @@ it.effect("ProviderSessionManagerV2 cleans up an open interrupted mid-handshake"
1106 const idAllocator = yield* IdAllocator.IdAllocatorV2;1105 const idAllocator = yield* IdAllocator.IdAllocatorV2;
1107 const manager = yield* ProviderSessionManager.ProviderSessionManagerV2;1106 const manager = yield* ProviderSessionManager.ProviderSessionManagerV2;
1108 const registry = yield* McpSessionRegistry.McpSessionRegistry;1107 const registry = yield* McpSessionRegistry.McpSessionRegistry;
1108 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
1109 const projectionStore = yield* ProjectionStore.ProjectionStoreV2;1109 const projectionStore = yield* ProjectionStore.ProjectionStoreV2;
1110 const now = yield* DateTime.now;1110 const now = yield* DateTime.now;
1111 const threadId = ThreadId.make("thread-provider-session-manager-interrupted-open");1111 const threadId = ThreadId.make("thread-provider-session-manager-interrupted-open");
...@@ -1133,7 +1133,7 @@ it.effect("ProviderSessionManagerV2 cleans up an open interrupted mid-handshake"...@@ -1133,7 +1133,7 @@ it.effect("ProviderSessionManagerV2 cleans up an open interrupted mid-handshake"
1133 // for it revoked.1133 // for it revoked.
1134 assert.equal((yield* Ref.get(state)).closeCount, 1);1134 assert.equal((yield* Ref.get(state)).closeCount, 1);
1135 assert.isUndefined(yield* registry.resolve(token!));1135 assert.isUndefined(yield* registry.resolve(token!));
1136 assert.isUndefined(McpProviderSession.readMcpProviderSession(threadId));1136 assert.isUndefined(yield* mcpSessions.read(threadId));
1137 assert.isTrue(Option.isNone(yield* manager.get(providerSessionId)));1137 assert.isTrue(Option.isNone(yield* manager.get(providerSessionId)));
11381138
1139 // Nothing of the interrupted open is left behind: the next open starts a1139 // Nothing of the interrupted open is left behind: the next open starts a
...@@ -1188,6 +1188,7 @@ it.effect("ProviderSessionManagerV2 cleans up an interrupted open whose scope cl...@@ -1188,6 +1188,7 @@ it.effect("ProviderSessionManagerV2 cleans up an interrupted open whose scope cl
1188 const idAllocator = yield* IdAllocator.IdAllocatorV2;1188 const idAllocator = yield* IdAllocator.IdAllocatorV2;
1189 const manager = yield* ProviderSessionManager.ProviderSessionManagerV2;1189 const manager = yield* ProviderSessionManager.ProviderSessionManagerV2;
1190 const registry = yield* McpSessionRegistry.McpSessionRegistry;1190 const registry = yield* McpSessionRegistry.McpSessionRegistry;
1191 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
1191 const now = yield* DateTime.now;1192 const now = yield* DateTime.now;
1192 const threadId = ThreadId.make("thread-provider-session-manager-interrupted-hung-open");1193 const threadId = ThreadId.make("thread-provider-session-manager-interrupted-hung-open");
1193 const providerSessionId = yield* idAllocator.allocate.providerSession({1194 const providerSessionId = yield* idAllocator.allocate.providerSession({
...@@ -1212,7 +1213,7 @@ it.effect("ProviderSessionManagerV2 cleans up an interrupted open whose scope cl...@@ -1212,7 +1213,7 @@ it.effect("ProviderSessionManagerV2 cleans up an interrupted open whose scope cl
12121213
1213 // The session cleanup already ran, ahead of the stuck close.1214 // The session cleanup already ran, ahead of the stuck close.
1214 assert.isUndefined(yield* registry.resolve(token!));1215 assert.isUndefined(yield* registry.resolve(token!));
1215 assert.isUndefined(McpProviderSession.readMcpProviderSession(threadId));1216 assert.isUndefined(yield* mcpSessions.read(threadId));
1216 assert.equal((yield* Ref.get(state)).closeCount, 0);1217 assert.equal((yield* Ref.get(state)).closeCount, 0);
12171218
1218 // The close is time-boxed, so the interrupter returns and the session's1219 // The close is time-boxed, so the interrupter returns and the session's
...@@ -1750,6 +1751,7 @@ it.effect(...@@ -1750,6 +1751,7 @@ it.effect(
1750 const idAllocator = yield* IdAllocator.IdAllocatorV2;1751 const idAllocator = yield* IdAllocator.IdAllocatorV2;
1751 const manager = yield* ProviderSessionManager.ProviderSessionManagerV2;1752 const manager = yield* ProviderSessionManager.ProviderSessionManagerV2;
1752 const registry = yield* McpSessionRegistry.McpSessionRegistry;1753 const registry = yield* McpSessionRegistry.McpSessionRegistry;
1754 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
1753 const now = yield* DateTime.now;1755 const now = yield* DateTime.now;
1754 const threadId = ThreadId.make("thread-provider-session-manager-mcp");1756 const threadId = ThreadId.make("thread-provider-session-manager-mcp");
1755 const providerSessionId = yield* idAllocator.allocate.providerSession({1757 const providerSessionId = yield* idAllocator.allocate.providerSession({
...@@ -1782,7 +1784,7 @@ it.effect(...@@ -1782,7 +1784,7 @@ it.effect(
1782 );1784 );
17831785
1784 yield* manager.close(providerSessionId);1786 yield* manager.close(providerSessionId);
1785 assert.isUndefined(McpProviderSession.readMcpProviderSession(threadId));1787 assert.isUndefined(yield* mcpSessions.read(threadId));
1786 assert.isUndefined(yield* registry.resolve(token!));1788 assert.isUndefined(yield* registry.resolve(token!));
1787 });1789 });
17881790
...@@ -1915,6 +1917,7 @@ it.effect("ProviderSessionManagerV2 revokes MCP credentials when release persist...@@ -1915,6 +1917,7 @@ it.effect("ProviderSessionManagerV2 revokes MCP credentials when release persist
1915 const idAllocator = yield* IdAllocator.IdAllocatorV2;1917 const idAllocator = yield* IdAllocator.IdAllocatorV2;
1916 const manager = yield* ProviderSessionManager.ProviderSessionManagerV2;1918 const manager = yield* ProviderSessionManager.ProviderSessionManagerV2;
1917 const registry = yield* McpSessionRegistry.McpSessionRegistry;1919 const registry = yield* McpSessionRegistry.McpSessionRegistry;
1920 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
1918 const now = yield* DateTime.now;1921 const now = yield* DateTime.now;
1919 const threadId = ThreadId.make("thread-provider-session-manager-mcp-release-failure");1922 const threadId = ThreadId.make("thread-provider-session-manager-mcp-release-failure");
1920 const providerSessionId = yield* idAllocator.allocate.providerSession({1923 const providerSessionId = yield* idAllocator.allocate.providerSession({
...@@ -1939,7 +1942,7 @@ it.effect("ProviderSessionManagerV2 revokes MCP credentials when release persist...@@ -1939,7 +1942,7 @@ it.effect("ProviderSessionManagerV2 revokes MCP credentials when release persist
19391942
1940 const closeError = yield* manager.close(providerSessionId).pipe(Effect.flip);1943 const closeError = yield* manager.close(providerSessionId).pipe(Effect.flip);
1941 assert.equal(closeError._tag, "ProviderSessionCloseError");1944 assert.equal(closeError._tag, "ProviderSessionCloseError");
1942 assert.isUndefined(McpProviderSession.readMcpProviderSession(threadId));1945 assert.isUndefined(yield* mcpSessions.read(threadId));
1943 assert.isUndefined(yield* registry.resolve(token!));1946 assert.isUndefined(yield* registry.resolve(token!));
1944 });1947 });
19451948
...@@ -1968,6 +1971,7 @@ it.effect(...@@ -1968,6 +1971,7 @@ it.effect(
1968 const idAllocator = yield* IdAllocator.IdAllocatorV2;1971 const idAllocator = yield* IdAllocator.IdAllocatorV2;
1969 const manager = yield* ProviderSessionManager.ProviderSessionManagerV2;1972 const manager = yield* ProviderSessionManager.ProviderSessionManagerV2;
1970 const registry = yield* McpSessionRegistry.McpSessionRegistry;1973 const registry = yield* McpSessionRegistry.McpSessionRegistry;
1974 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
1971 const now = yield* DateTime.now;1975 const now = yield* DateTime.now;
1972 const owner = ThreadId.make("thread-provider-session-manager-attach-race-owner");1976 const owner = ThreadId.make("thread-provider-session-manager-attach-race-owner");
1973 const threadId = ThreadId.make("thread-provider-session-manager-attach-race");1977 const threadId = ThreadId.make("thread-provider-session-manager-attach-race");
...@@ -2007,7 +2011,7 @@ it.effect(...@@ -2007,7 +2011,7 @@ it.effect(
2007 yield* Fiber.join(second);2011 yield* Fiber.join(second);
20082012
2009 // The second attach owns the thread's attachment and credential.2013 // The second attach owns the thread's attachment and credential.
2010 const config = McpProviderSession.readMcpProviderSession(threadId);2014 const config = yield* mcpSessions.read(threadId);
2011 assert.isDefined(config);2015 assert.isDefined(config);
2012 const token = config!.authorizationHeader.replace(/^Bearer\s+/, "");2016 const token = config!.authorizationHeader.replace(/^Bearer\s+/, "");
2013 assert.equal((yield* registry.resolve(token))?.thread.threadId, threadId);2017 assert.equal((yield* registry.resolve(token))?.thread.threadId, threadId);
...@@ -2031,6 +2035,7 @@ it.effect(...@@ -2031,6 +2035,7 @@ it.effect(
2031 const idAllocator = yield* IdAllocator.IdAllocatorV2;2035 const idAllocator = yield* IdAllocator.IdAllocatorV2;
2032 const manager = yield* ProviderSessionManager.ProviderSessionManagerV2;2036 const manager = yield* ProviderSessionManager.ProviderSessionManagerV2;
2033 const registry = yield* McpSessionRegistry.McpSessionRegistry;2037 const registry = yield* McpSessionRegistry.McpSessionRegistry;
2038 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
2034 const eventStore = yield* EventStore.EventStoreV2;2039 const eventStore = yield* EventStore.EventStoreV2;
2035 const now = yield* DateTime.now;2040 const now = yield* DateTime.now;
2036 const owner = ThreadId.make("thread-provider-session-manager-stale-attach-owner");2041 const owner = ThreadId.make("thread-provider-session-manager-stale-attach-owner");
...@@ -2075,14 +2080,14 @@ it.effect(...@@ -2075,14 +2080,14 @@ it.effect(
2075 modelSelection,2080 modelSelection,
2076 runtimePolicy,2081 runtimePolicy,
2077 });2082 });
2078 const config = McpProviderSession.readMcpProviderSession(threadId);2083 const config = yield* mcpSessions.read(threadId);
2079 assert.isDefined(config);2084 assert.isDefined(config);
20802085
2081 yield* Fiber.interrupt(stale);2086 yield* Fiber.interrupt(stale);
20822087
2083 // The replacement keeps the thread and its credential.2088 // The replacement keeps the thread and its credential.
2084 assert.equal(2089 assert.equal(
2085 McpProviderSession.readMcpProviderSession(threadId)?.providerSessionId,2090 (yield* mcpSessions.read(threadId))?.providerSessionId,
2086 config!.providerSessionId,2091 config!.providerSessionId,
2087 );2092 );
2088 const token = config!.authorizationHeader.replace(/^Bearer\s+/, "");2093 const token = config!.authorizationHeader.replace(/^Bearer\s+/, "");
...@@ -2126,6 +2131,7 @@ it.effect(...@@ -2126,6 +2131,7 @@ it.effect(
2126 const idAllocator = yield* IdAllocator.IdAllocatorV2;2131 const idAllocator = yield* IdAllocator.IdAllocatorV2;
2127 const manager = yield* ProviderSessionManager.ProviderSessionManagerV2;2132 const manager = yield* ProviderSessionManager.ProviderSessionManagerV2;
2128 const registry = yield* McpSessionRegistry.McpSessionRegistry;2133 const registry = yield* McpSessionRegistry.McpSessionRegistry;
2134 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
2129 const now = yield* DateTime.now;2135 const now = yield* DateTime.now;
2130 const owner = ThreadId.make("thread-provider-session-manager-resolve-stop-owner");2136 const owner = ThreadId.make("thread-provider-session-manager-resolve-stop-owner");
2131 const threadId = ThreadId.make("thread-provider-session-manager-resolve-stop");2137 const threadId = ThreadId.make("thread-provider-session-manager-resolve-stop");
...@@ -2157,7 +2163,7 @@ it.effect(...@@ -2157,7 +2163,7 @@ it.effect(
2157 // The thread gets a credential, then detaches and keeps it for a re-attach.2163 // The thread gets a credential, then detaches and keeps it for a re-attach.
2158 yield* resume;2164 yield* resume;
2159 yield* manager.detach({ providerSessionId, threadId });2165 yield* manager.detach({ providerSessionId, threadId });
2160 const config = McpProviderSession.readMcpProviderSession(threadId);2166 const config = yield* mcpSessions.read(threadId);
2161 assert.isDefined(config);2167 assert.isDefined(config);
2162 const token = config!.authorizationHeader.replace(/^Bearer\s+/, "");2168 const token = config!.authorizationHeader.replace(/^Bearer\s+/, "");
21632169
...@@ -2189,6 +2195,7 @@ it.effect("ProviderSessionManagerV2 duplicate detach preserves replacement MCP c...@@ -2189,6 +2195,7 @@ it.effect("ProviderSessionManagerV2 duplicate detach preserves replacement MCP c
2189 const idAllocator = yield* IdAllocator.IdAllocatorV2;2195 const idAllocator = yield* IdAllocator.IdAllocatorV2;
2190 const manager = yield* ProviderSessionManager.ProviderSessionManagerV2;2196 const manager = yield* ProviderSessionManager.ProviderSessionManagerV2;
2191 const registry = yield* McpSessionRegistry.McpSessionRegistry;2197 const registry = yield* McpSessionRegistry.McpSessionRegistry;
2198 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
2192 const now = yield* DateTime.now;2199 const now = yield* DateTime.now;
2193 const threadId = ThreadId.make("thread-provider-session-manager-replacement-mcp");2200 const threadId = ThreadId.make("thread-provider-session-manager-replacement-mcp");
2194 const oldSessionId = yield* idAllocator.allocate.providerSession({2201 const oldSessionId = yield* idAllocator.allocate.providerSession({
...@@ -2222,14 +2229,14 @@ it.effect("ProviderSessionManagerV2 duplicate detach preserves replacement MCP c...@@ -2222,14 +2229,14 @@ it.effect("ProviderSessionManagerV2 duplicate detach preserves replacement MCP c
2222 const replacementToken = replacement?.authorizationHeader.replace(/^Bearer\s+/, "");2229 const replacementToken = replacement?.authorizationHeader.replace(/^Bearer\s+/, "");
2223 assert.isDefined(replacementToken);2230 assert.isDefined(replacementToken);
2224 assert.equal(2231 assert.equal(
2225 McpProviderSession.readMcpProviderSession(threadId)?.providerSessionId,2232 (yield* mcpSessions.read(threadId))?.providerSessionId,
2226 replacement?.providerSessionId,2233 replacement?.providerSessionId,
2227 );2234 );
22282235
2229 yield* manager.detach({ providerSessionId: oldSessionId, threadId });2236 yield* manager.detach({ providerSessionId: oldSessionId, threadId });
22302237
2231 assert.equal(2238 assert.equal(
2232 McpProviderSession.readMcpProviderSession(threadId)?.providerSessionId,2239 (yield* mcpSessions.read(threadId))?.providerSessionId,
2233 replacement?.providerSessionId,2240 replacement?.providerSessionId,
2234 );2241 );
2235 assert.equal((yield* registry.resolve(replacementToken!))?.thread.threadId, threadId);2242 assert.equal((yield* registry.resolve(replacementToken!))?.thread.threadId, threadId);
...@@ -2261,6 +2268,7 @@ it.effect(...@@ -2261,6 +2268,7 @@ it.effect(
2261 const idAllocator = yield* IdAllocator.IdAllocatorV2;2268 const idAllocator = yield* IdAllocator.IdAllocatorV2;
2262 const manager = yield* ProviderSessionManager.ProviderSessionManagerV2;2269 const manager = yield* ProviderSessionManager.ProviderSessionManagerV2;
2263 const registry = yield* McpSessionRegistry.McpSessionRegistry;2270 const registry = yield* McpSessionRegistry.McpSessionRegistry;
2271 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
2264 const now = yield* DateTime.now;2272 const now = yield* DateTime.now;
2265 const threadId = ThreadId.make("thread-provider-session-manager-superseded-mcp");2273 const threadId = ThreadId.make("thread-provider-session-manager-superseded-mcp");
2266 const oldSessionId = yield* idAllocator.allocate.providerSession({2274 const oldSessionId = yield* idAllocator.allocate.providerSession({
...@@ -2296,7 +2304,7 @@ it.effect(...@@ -2296,7 +2304,7 @@ it.effect(
2296 const replacementToken = replacement?.authorizationHeader.replace(/^Bearer\s+/, "");2304 const replacementToken = replacement?.authorizationHeader.replace(/^Bearer\s+/, "");
2297 assert.isDefined(replacementToken);2305 assert.isDefined(replacementToken);
2298 assert.equal(2306 assert.equal(
2299 McpProviderSession.readMcpProviderSession(threadId)?.providerSessionId,2307 (yield* mcpSessions.read(threadId))?.providerSessionId,
2300 replacement?.providerSessionId,2308 replacement?.providerSessionId,
2301 );2309 );
23022310
...@@ -2305,7 +2313,7 @@ it.effect(...@@ -2305,7 +2313,7 @@ it.effect(
2305 yield* manager.detach({ providerSessionId: oldSessionId, threadId });2313 yield* manager.detach({ providerSessionId: oldSessionId, threadId });
23062314
2307 assert.equal(2315 assert.equal(
2308 McpProviderSession.readMcpProviderSession(threadId)?.providerSessionId,2316 (yield* mcpSessions.read(threadId))?.providerSessionId,
2309 replacement?.providerSessionId,2317 replacement?.providerSessionId,
2310 );2318 );
2311 assert.equal((yield* registry.resolve(replacementToken!))?.thread.threadId, threadId);2319 assert.equal((yield* registry.resolve(replacementToken!))?.thread.threadId, threadId);
...@@ -2337,6 +2345,7 @@ it.effect(...@@ -2337,6 +2345,7 @@ it.effect(
2337 const idAllocator = yield* IdAllocator.IdAllocatorV2;2345 const idAllocator = yield* IdAllocator.IdAllocatorV2;
2338 const manager = yield* ProviderSessionManager.ProviderSessionManagerV2;2346 const manager = yield* ProviderSessionManager.ProviderSessionManagerV2;
2339 const registry = yield* McpSessionRegistry.McpSessionRegistry;2347 const registry = yield* McpSessionRegistry.McpSessionRegistry;
2348 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
2340 const now = yield* DateTime.now;2349 const now = yield* DateTime.now;
2341 const threadId = ThreadId.make("thread-provider-session-manager-stable-mcp");2350 const threadId = ThreadId.make("thread-provider-session-manager-stable-mcp");
2342 const providerSessionId = yield* idAllocator.allocate.providerSession({2351 const providerSessionId = yield* idAllocator.allocate.providerSession({
...@@ -2379,7 +2388,7 @@ it.effect(...@@ -2379,7 +2388,7 @@ it.effect(
2379 runtimePolicy,2388 runtimePolicy,
2380 });2389 });
2381 assert.equal(2390 assert.equal(
2382 McpProviderSession.readMcpProviderSession(threadId)?.providerSessionId,2391 (yield* mcpSessions.read(threadId))?.providerSessionId,
2383 original?.providerSessionId,2392 original?.providerSessionId,
2384 "re-attach must reuse the existing credential, not rotate it",2393 "re-attach must reuse the existing credential, not rotate it",
2385 );2394 );
...@@ -2415,6 +2424,7 @@ it.effect(...@@ -2415,6 +2424,7 @@ it.effect(
2415 const idAllocator = yield* IdAllocator.IdAllocatorV2;2424 const idAllocator = yield* IdAllocator.IdAllocatorV2;
2416 const manager = yield* ProviderSessionManager.ProviderSessionManagerV2;2425 const manager = yield* ProviderSessionManager.ProviderSessionManagerV2;
2417 const registry = yield* McpSessionRegistry.McpSessionRegistry;2426 const registry = yield* McpSessionRegistry.McpSessionRegistry;
2427 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
2418 const now = yield* DateTime.now;2428 const now = yield* DateTime.now;
2419 const threadId = ThreadId.make("thread-provider-session-manager-stale-record");2429 const threadId = ThreadId.make("thread-provider-session-manager-stale-record");
2420 const s1 = yield* idAllocator.allocate.providerSession({2430 const s1 = yield* idAllocator.allocate.providerSession({
...@@ -2437,7 +2447,7 @@ it.effect(...@@ -2437,7 +2447,7 @@ it.effect(
2437 // The credential dies externally, so S2's attach must rotate to C2.2447 // The credential dies externally, so S2's attach must rotate to C2.
2438 yield* registry.revokeThread(threadId);2448 yield* registry.revokeThread(threadId);
2439 yield* manager.open({ threadId, providerSessionId: s2, modelSelection, runtimePolicy });2449 yield* manager.open({ threadId, providerSessionId: s2, modelSelection, runtimePolicy });
2440 const rotated = McpProviderSession.readMcpProviderSession(threadId);2450 const rotated = yield* mcpSessions.read(threadId);
2441 assert.isDefined(rotated);2451 assert.isDefined(rotated);
2442 const rotatedToken = rotated?.authorizationHeader.replace(/^Bearer\s+/, "");2452 const rotatedToken = rotated?.authorizationHeader.replace(/^Bearer\s+/, "");
2443 assert.isDefined(yield* registry.resolve(rotatedToken!));2453 assert.isDefined(yield* registry.resolve(rotatedToken!));
...@@ -2470,6 +2480,7 @@ it.effect(...@@ -2470,6 +2480,7 @@ it.effect(
2470 const idAllocator = yield* IdAllocator.IdAllocatorV2;2480 const idAllocator = yield* IdAllocator.IdAllocatorV2;
2471 const manager = yield* ProviderSessionManager.ProviderSessionManagerV2;2481 const manager = yield* ProviderSessionManager.ProviderSessionManagerV2;
2472 const registry = yield* McpSessionRegistry.McpSessionRegistry;2482 const registry = yield* McpSessionRegistry.McpSessionRegistry;
2483 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
2473 const now = yield* DateTime.now;2484 const now = yield* DateTime.now;
2474 const threadId = ThreadId.make("thread-provider-session-manager-open-race");2485 const threadId = ThreadId.make("thread-provider-session-manager-open-race");
2475 const s1 = yield* idAllocator.allocate.providerSession({2486 const s1 = yield* idAllocator.allocate.providerSession({
...@@ -2498,7 +2509,7 @@ it.effect(...@@ -2498,7 +2509,7 @@ it.effect(
2498 yield* Ref.set(duringOpen, manager.close(s1).pipe(Effect.orDie));2509 yield* Ref.set(duringOpen, manager.close(s1).pipe(Effect.orDie));
2499 yield* manager.open({ threadId, providerSessionId: s2, modelSelection, runtimePolicy });2510 yield* manager.open({ threadId, providerSessionId: s2, modelSelection, runtimePolicy });
25002511
2501 const slot = McpProviderSession.readMcpProviderSession(threadId);2512 const slot = yield* mcpSessions.read(threadId);
2502 assert.equal(2513 assert.equal(
2503 slot?.providerSessionId,2514 slot?.providerSessionId,
2504 original?.providerSessionId,2515 original?.providerSessionId,
...@@ -2542,6 +2553,7 @@ it.effect("ProviderSessionManagerV2 terminal detach revokes the thread's MCP cre...@@ -2542,6 +2553,7 @@ it.effect("ProviderSessionManagerV2 terminal detach revokes the thread's MCP cre
2542 const idAllocator = yield* IdAllocator.IdAllocatorV2;2553 const idAllocator = yield* IdAllocator.IdAllocatorV2;
2543 const manager = yield* ProviderSessionManager.ProviderSessionManagerV2;2554 const manager = yield* ProviderSessionManager.ProviderSessionManagerV2;
2544 const registry = yield* McpSessionRegistry.McpSessionRegistry;2555 const registry = yield* McpSessionRegistry.McpSessionRegistry;
2556 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
2545 const now = yield* DateTime.now;2557 const now = yield* DateTime.now;
2546 const threadId = ThreadId.make("thread-provider-session-manager-terminal-detach");2558 const threadId = ThreadId.make("thread-provider-session-manager-terminal-detach");
2547 const providerSessionId = yield* idAllocator.allocate.providerSession({2559 const providerSessionId = yield* idAllocator.allocate.providerSession({
...@@ -2566,7 +2578,7 @@ it.effect("ProviderSessionManagerV2 terminal detach revokes the thread's MCP cre...@@ -2566,7 +2578,7 @@ it.effect("ProviderSessionManagerV2 terminal detach revokes the thread's MCP cre
2566 revokeMcpCredential: true,2578 revokeMcpCredential: true,
2567 });2579 });
2568 assert.isUndefined(yield* registry.resolve(token!));2580 assert.isUndefined(yield* registry.resolve(token!));
2569 assert.isUndefined(McpProviderSession.readMcpProviderSession(threadId));2581 assert.isUndefined(yield* mcpSessions.read(threadId));
2570 });2582 });
25712583
2572 yield* effect.pipe(Effect.provide(layerTest({ state, idleTimeoutMs: 1_000, mcpConfigs })));2584 yield* effect.pipe(Effect.provide(layerTest({ state, idleTimeoutMs: 1_000, mcpConfigs })));
apps/server/src/orchestration-v2/ProviderSessionManager.ts+12-18
...@@ -40,7 +40,7 @@ import {...@@ -40,7 +40,7 @@ import {
40} from "../observability/Metrics.ts";40} from "../observability/Metrics.ts";
41import { ProviderWorkspaceMissingError } from "../provider/Errors.ts";41import { ProviderWorkspaceMissingError } from "../provider/Errors.ts";
42import * as ProjectService from "../project/ProjectService.ts";42import * as ProjectService from "../project/ProjectService.ts";
43import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession";43import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
44import * as ServerSettings from "../serverSettings.ts";44import * as ServerSettings from "../serverSettings.ts";
45import * as McpSessionRegistry from "../mcp/McpSessionRegistry.ts";45import * as McpSessionRegistry from "../mcp/McpSessionRegistry.ts";
46import * as EventSink from "./EventSink.ts";46import * as EventSink from "./EventSink.ts";
...@@ -331,6 +331,7 @@ export const layerWithOptions = (...@@ -331,6 +331,7 @@ export const layerWithOptions = (
331 | EventSink.EventSinkV2331 | EventSink.EventSinkV2
332 | FileSystem.FileSystem332 | FileSystem.FileSystem
333 | IdAllocator.IdAllocatorV2333 | IdAllocator.IdAllocatorV2
334 | McpProviderSessions.McpProviderSessions
334 | McpSessionRegistry.McpSessionRegistry335 | McpSessionRegistry.McpSessionRegistry
335 | ProjectionStore.ProjectionStoreV2336 | ProjectionStore.ProjectionStoreV2
336 | ProviderEventIngestor.ProviderEventIngestorV2337 | ProviderEventIngestor.ProviderEventIngestorV2
...@@ -342,6 +343,7 @@ export const layerWithOptions = (...@@ -342,6 +343,7 @@ export const layerWithOptions = (
342 const registry = yield* ProviderAdapterRegistry.ProviderAdapterRegistryV2;343 const registry = yield* ProviderAdapterRegistry.ProviderAdapterRegistryV2;
343 const fileSystem = yield* FileSystem.FileSystem;344 const fileSystem = yield* FileSystem.FileSystem;
344 const mcpSessionRegistry = yield* McpSessionRegistry.McpSessionRegistry;345 const mcpSessionRegistry = yield* McpSessionRegistry.McpSessionRegistry;
346 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
345 /**347 /**
346 * Optional so the many focused tests that assemble this layer by hand do348 * Optional so the many focused tests that assemble this layer by hand do
347 * not each need a settings stub; the production composition always349 * not each need a settings stub; the production composition always
...@@ -472,10 +474,9 @@ export const layerWithOptions = (...@@ -472,10 +474,9 @@ export const layerWithOptions = (
472 providerInstanceId: ProviderInstanceId,474 providerInstanceId: ProviderInstanceId,
473 ): Effect.Effect<PreparedMcpCredential> =>475 ): Effect.Effect<PreparedMcpCredential> =>
474 options.configureMcp === false476 options.configureMcp === false
475 ? Effect.sync((): PreparedMcpCredential => {477 ? mcpSessions
476 McpProviderSession.clearMcpProviderSession(threadId);478 .clear(threadId)
477 return { mcpCredentialId: undefined, issued: false };479 .pipe(Effect.as<PreparedMcpCredential>({ mcpCredentialId: undefined, issued: false }))
478 })
479 : mcpPrepareLock.withLock(480 : mcpPrepareLock.withLock(
480 threadId,481 threadId,
481 Effect.gen(function* () {482 Effect.gen(function* () {
...@@ -492,7 +493,7 @@ export const layerWithOptions = (...@@ -492,7 +493,7 @@ export const layerWithOptions = (
492 >(["orchestration", "worktree", "pull-requests"]);493 >(["orchestration", "worktree", "pull-requests"]);
493 if (browserToolsAvailable) capabilities.add("preview");494 if (browserToolsAvailable) capabilities.add("preview");
494 if (deviceToolsAvailable) capabilities.add("device");495 if (deviceToolsAvailable) capabilities.add("device");
495 const existing = McpProviderSession.readMcpProviderSession(threadId);496 const existing = yield* mcpSessions.read(threadId);
496 if (existing !== undefined) {497 if (existing !== undefined) {
497 // Reserve before the async resolve so a release cannot498 // Reserve before the async resolve so a release cannot
498 // revoke the credential between validation and reservation.499 // revoke the credential between validation and reservation.
...@@ -529,7 +530,7 @@ export const layerWithOptions = (...@@ -529,7 +530,7 @@ export const layerWithOptions = (
529 browserToolsAvailable,530 browserToolsAvailable,
530 capabilities,531 capabilities,
531 });532 });
532 McpProviderSession.setMcpProviderSession(credential.config);533 yield* mcpSessions.set(credential.config);
533 reserveMcpCredential(threadId, credential.config.providerSessionId);534 reserveMcpCredential(threadId, credential.config.providerSessionId);
534 return { mcpCredentialId: credential.config.providerSessionId, issued: true };535 return { mcpCredentialId: credential.config.providerSessionId, issued: true };
535 }),536 }),
...@@ -544,19 +545,12 @@ export const layerWithOptions = (...@@ -544,19 +545,12 @@ export const layerWithOptions = (
544 mcpCredentialId === undefined545 mcpCredentialId === undefined
545 ? mcpSessionRegistry546 ? mcpSessionRegistry
546 .revokeThread(threadId)547 .revokeThread(threadId)
547 .pipe(548 .pipe(Effect.tap(() => mcpSessions.clear(threadId)))
548 Effect.tap(() =>
549 Effect.sync(() => McpProviderSession.clearMcpProviderSession(threadId)),
550 ),
551 )
552 : mcpSessionRegistry.revokeProviderSession(mcpCredentialId).pipe(549 : mcpSessionRegistry.revokeProviderSession(mcpCredentialId).pipe(
553 Effect.tap(() =>550 Effect.tap(() =>
554 Effect.sync(() => {551 Effect.gen(function* () {
555 if (552 if ((yield* mcpSessions.read(threadId))?.providerSessionId === mcpCredentialId) {
556 McpProviderSession.readMcpProviderSession(threadId)?.providerSessionId ===553 yield* mcpSessions.clear(threadId);
557 mcpCredentialId
558 ) {
559 McpProviderSession.clearMcpProviderSession(threadId);
560 }554 }
561 }),555 }),
562 ),556 ),
apps/server/src/orchestration-v2/runtimeLayer.test.ts+5
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1import { limitRecoveryCommand } from "./UsageLimitRecoveryWorker.ts";1import { limitRecoveryCommand } from "./UsageLimitRecoveryWorker.ts";
2import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
2import * as SourceControlProviderRegistry from "../sourceControl/SourceControlProviderRegistry.ts";3import * as SourceControlProviderRegistry from "../sourceControl/SourceControlProviderRegistry.ts";
3import * as NodeServices from "@effect/platform-node/NodeServices";4import * as NodeServices from "@effect/platform-node/NodeServices";
4import { assert, it } from "@effect/vitest";5import { assert, it } from "@effect/vitest";
...@@ -272,6 +273,7 @@ const layerTest = Layer.mergeAll(...@@ -272,6 +273,7 @@ const layerTest = Layer.mergeAll(
272 ThreadCommandExecutor.layer,273 ThreadCommandExecutor.layer,
273).pipe(274).pipe(
274 Layer.provide(McpSessionRegistryTestkit.layer),275 Layer.provide(McpSessionRegistryTestkit.layer),
276 Layer.provide(McpProviderSessions.layer),
275 Layer.provide(SqlitePersistence.layerMemory),277 Layer.provide(SqlitePersistence.layerMemory),
276 Layer.provide(layerCheckpointStoreTest),278 Layer.provide(layerCheckpointStoreTest),
277 Layer.provide(layerServerConfig),279 Layer.provide(layerServerConfig),
...@@ -284,6 +286,7 @@ const layerTest = Layer.mergeAll(...@@ -284,6 +286,7 @@ const layerTest = Layer.mergeAll(
284286
285const layerLegacyImportTest = RuntimeLayer.layer.pipe(287const layerLegacyImportTest = RuntimeLayer.layer.pipe(
286 Layer.provide(McpSessionRegistryTestkit.layer),288 Layer.provide(McpSessionRegistryTestkit.layer),
289 Layer.provide(McpProviderSessions.layer),
287 Layer.provideMerge(SqlitePersistence.layerMemory),290 Layer.provideMerge(SqlitePersistence.layerMemory),
288 Layer.provide(layerCheckpointStoreTest),291 Layer.provide(layerCheckpointStoreTest),
289 Layer.provide(layerServerConfig),292 Layer.provide(layerServerConfig),
...@@ -323,6 +326,7 @@ const layerProjectDeletionTest = Layer.mergeAll(...@@ -323,6 +326,7 @@ const layerProjectDeletionTest = Layer.mergeAll(
323 }),326 }),
324 ),327 ),
325 Layer.provide(McpSessionRegistryTestkit.layer),328 Layer.provide(McpSessionRegistryTestkit.layer),
329 Layer.provide(McpProviderSessions.layer),
326 Layer.provide(SqlitePersistence.layerMemory),330 Layer.provide(SqlitePersistence.layerMemory),
327 Layer.provide(layerCheckpointStoreTest),331 Layer.provide(layerCheckpointStoreTest),
328 Layer.provide(layerServerConfig),332 Layer.provide(layerServerConfig),
...@@ -467,6 +471,7 @@ const layerSharedApplicationDataPlaneTest = Layer.mergeAll(...@@ -467,6 +471,7 @@ const layerSharedApplicationDataPlaneTest = Layer.mergeAll(
467 }),471 }),
468 ),472 ),
469 Layer.provide(McpSessionRegistryTestkit.layer),473 Layer.provide(McpSessionRegistryTestkit.layer),
474 Layer.provide(McpProviderSessions.layer),
470 Layer.provideMerge(SqlitePersistence.layerMemory),475 Layer.provideMerge(SqlitePersistence.layerMemory),
471 Layer.provide(layerCheckpointStoreTest),476 Layer.provide(layerCheckpointStoreTest),
472 Layer.provide(layerServerConfig),477 Layer.provide(layerServerConfig),
apps/server/src/orchestration-v2/testkit/ProviderReplayHarness.ts+15-3
...@@ -16,6 +16,7 @@ import * as ServerConfig from "../../config.ts";...@@ -16,6 +16,7 @@ import * as ServerConfig from "../../config.ts";
16import * as SqlitePersistence from "../../persistence/Sqlite.ts";16import * as SqlitePersistence from "../../persistence/Sqlite.ts";
17import * as ServerSettings from "../../serverSettings.ts";17import * as ServerSettings from "../../serverSettings.ts";
18import * as ThreadManagementService from "../ThreadManagementService.ts";18import * as ThreadManagementService from "../ThreadManagementService.ts";
19import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
19import * as McpSessionRegistryTestkit from "../../mcp/McpSessionRegistry.testkit.ts";20import * as McpSessionRegistryTestkit from "../../mcp/McpSessionRegistry.testkit.ts";
20import * as VcsDriverRegistry from "../../vcs/VcsDriverRegistry.ts";21import * as VcsDriverRegistry from "../../vcs/VcsDriverRegistry.ts";
21import * as VcsProcess from "../../vcs/VcsProcess.ts";22import * as VcsProcess from "../../vcs/VcsProcess.ts";
...@@ -170,7 +171,11 @@ export interface OrchestratorV2ProviderReplayHarness<...@@ -170,7 +171,11 @@ export interface OrchestratorV2ProviderReplayHarness<
170 readonly makeProviderAdapterRegistryLayer: (171 readonly makeProviderAdapterRegistryLayer: (
171 transcript: Transcript,172 transcript: Transcript,
172 options?: { readonly replayGate?: ProviderReplayGate },173 options?: { readonly replayGate?: ProviderReplayGate },
173 ) => Layer.Layer<ProviderAdapterRegistry.ProviderAdapterRegistryV2, Error>;174 ) => Layer.Layer<
175 ProviderAdapterRegistry.ProviderAdapterRegistryV2,
176 Error,
177 McpProviderSessions.McpProviderSessions
178 >;
174}179}
175180
176export function runOrchestratorV2ProviderReplayScenario<181export function runOrchestratorV2ProviderReplayScenario<
...@@ -249,7 +254,11 @@ export function layerProviderReplay<Transcript extends ProviderReplayTranscript,...@@ -249,7 +254,11 @@ export function layerProviderReplay<Transcript extends ProviderReplayTranscript,
249254
250export function layerWithRegistry<Error>(255export function layerWithRegistry<Error>(
251 scenario: Pick<OrchestratorV2ProviderReplayScenario, "name" | "runtimePolicyOverride">,256 scenario: Pick<OrchestratorV2ProviderReplayScenario, "name" | "runtimePolicyOverride">,
252 registryLayer: Layer.Layer<ProviderAdapterRegistry.ProviderAdapterRegistryV2, Error>,257 registryLayer: Layer.Layer<
258 ProviderAdapterRegistry.ProviderAdapterRegistryV2,
259 Error,
260 McpProviderSessions.McpProviderSessions
261 >,
253 options: {262 options: {
254 readonly databaseLayer?: Layer.Layer<263 readonly databaseLayer?: Layer.Layer<
255 SqlClient.SqlClient,264 SqlClient.SqlClient,
...@@ -286,7 +295,9 @@ export function layerWithRegistry<Error>(...@@ -286,7 +295,9 @@ export function layerWithRegistry<Error>(
286 // runtimeLayer.ts; layer memoization keeps it a single instance.295 // runtimeLayer.ts; layer memoization keeps it a single instance.
287 const layerContinuationRequests =296 const layerContinuationRequests =
288 options.runContinuationWorker === true ? ProviderContinuationRequests.layer : Layer.empty;297 options.runContinuationWorker === true ? ProviderContinuationRequests.layer : Layer.empty;
289 const layerProvidedRegistry = registryLayer.pipe(Layer.provide(layerContinuationRequests));298 const layerProvidedRegistry = registryLayer.pipe(
299 Layer.provide(Layer.merge(layerContinuationRequests, McpProviderSessions.layer)),
300 );
290 const layerServerSettings = ServerSettings.layerTest({301 const layerServerSettings = ServerSettings.layerTest({
291 responseStreamingMode: "turn",302 responseStreamingMode: "turn",
292 ...(options.continueThreadsAfterServerUpdate === undefined303 ...(options.continueThreadsAfterServerUpdate === undefined
...@@ -347,6 +358,7 @@ export function layerWithRegistry<Error>(...@@ -347,6 +358,7 @@ export function layerWithRegistry<Error>(
347 layerProvidedRegistry,358 layerProvidedRegistry,
348 layerEventSinkProvided,359 layerEventSinkProvided,
349 IdAllocator.layer,360 IdAllocator.layer,
361 McpProviderSessions.layer,
350 McpSessionRegistryTestkit.layer,362 McpSessionRegistryTestkit.layer,
351 layerProviderEventIngestorProvided,363 layerProviderEventIngestorProvided,
352 layerStores,364 layerStores,
apps/server/src/provider/Drivers/AntigravityDriver.test.ts+2
...@@ -30,6 +30,7 @@ import {...@@ -30,6 +30,7 @@ import {
30 resolveAntigravityInstanceDirectories,30 resolveAntigravityInstanceDirectories,
31} from "../antigravityAuthSupport.ts";31} from "../antigravityAuthSupport.ts";
32import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";32import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";
33import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
33import * as ModelManifest from "../ModelManifest.ts";34import * as ModelManifest from "../ModelManifest.ts";
34import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";35import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
35import { AntigravityDriver } from "./AntigravityDriver.ts";36import { AntigravityDriver } from "./AntigravityDriver.ts";
...@@ -264,6 +265,7 @@ const layerDeps = ServerConfig.layerTest(process.cwd(), {...@@ -264,6 +265,7 @@ const layerDeps = ServerConfig.layerTest(process.cwd(), {
264 ),265 ),
265 Layer.provideMerge(ModelManifest.layerTest),266 Layer.provideMerge(ModelManifest.layerTest),
266 Layer.provideMerge(IdAllocator.layer),267 Layer.provideMerge(IdAllocator.layer),
268 Layer.provideMerge(McpProviderSessions.layer),
267);269);
268const layerTest = ProviderHostLive.layer.pipe(270const layerTest = ProviderHostLive.layer.pipe(
269 Layer.provideMerge(ServerSecretStore.layer),271 Layer.provideMerge(ServerSecretStore.layer),
apps/server/src/provider/Drivers/AntigravityDriver.ts+2
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1import { withAgentDeviceEnvironment } from "@t3tools/provider-core/server/mcpSession";1import { withAgentDeviceEnvironment } from "@t3tools/provider-core/server/mcpSession";
2import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
2import { AntigravitySettings, ProviderDriverKind, ProviderSetupError } from "@t3tools/contracts";3import { AntigravitySettings, ProviderDriverKind, ProviderSetupError } from "@t3tools/contracts";
3import { HostProcessPlatform } from "@t3tools/shared/hostProcess";4import { HostProcessPlatform } from "@t3tools/shared/hostProcess";
4import { resolveSelfInvocation } from "@t3tools/shared/nodeRuntime";5import { resolveSelfInvocation } from "@t3tools/shared/nodeRuntime";
...@@ -73,6 +74,7 @@ export type AntigravityDriverEnv =...@@ -73,6 +74,7 @@ export type AntigravityDriverEnv =
73 | Crypto.Crypto74 | Crypto.Crypto
74 | FileSystem.FileSystem75 | FileSystem.FileSystem
75 | IdAllocator.IdAllocatorV276 | IdAllocator.IdAllocatorV2
77 | McpProviderSessions.McpProviderSessions
76 | ModelCatalog.ModelCatalog78 | ModelCatalog.ModelCatalog
77 | Path.Path79 | Path.Path
78 | ProviderEventLoggers.ProviderEventLoggers;80 | ProviderEventLoggers.ProviderEventLoggers;
apps/server/src/provider/Drivers/CodexDriver.test.ts+2
...@@ -27,6 +27,7 @@ import * as ServerConfig from "../../config.ts";...@@ -27,6 +27,7 @@ import * as ServerConfig from "../../config.ts";
27import * as ServerSettings from "../../serverSettings.ts";27import * as ServerSettings from "../../serverSettings.ts";
28import * as ResetCreditCoordinator from "../resetCreditCoordinator.ts";28import * as ResetCreditCoordinator from "../resetCreditCoordinator.ts";
29import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";29import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";
30import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
30import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";31import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";
31import * as ModelManifest from "../ModelManifest.ts";32import * as ModelManifest from "../ModelManifest.ts";
32import {33import {
...@@ -45,6 +46,7 @@ const layerDeps = ServerConfig.layerTest(process.cwd(), {...@@ -45,6 +46,7 @@ const layerDeps = ServerConfig.layerTest(process.cwd(), {
45}).pipe(46}).pipe(
46 Layer.provideMerge(NodeServices.layer),47 Layer.provideMerge(NodeServices.layer),
47 Layer.provideMerge(IdAllocator.layer),48 Layer.provideMerge(IdAllocator.layer),
49 Layer.provideMerge(McpProviderSessions.layer),
48 Layer.provideMerge(50 Layer.provideMerge(
49 Layer.mock(CodexAdapterV2.CodexAppServerClientFactory)({51 Layer.mock(CodexAdapterV2.CodexAppServerClientFactory)({
50 open: () => Effect.die("Maintenance resolution must not open a Codex session"),52 open: () => Effect.die("Maintenance resolution must not open a Codex session"),
apps/server/src/provider/ProviderInstanceRegistry.test.ts+3
...@@ -36,6 +36,7 @@ import {...@@ -36,6 +36,7 @@ import {
36 ProviderInstanceId,36 ProviderInstanceId,
37} from "@t3tools/contracts";37} from "@t3tools/contracts";
38import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";38import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";
39import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
39import type { GrokSettings } from "@t3tools/provider-grok/settings";40import type { GrokSettings } from "@t3tools/provider-grok/settings";
40import type { CursorSettings } from "@t3tools/provider-cursor/settings";41import type { CursorSettings } from "@t3tools/provider-cursor/settings";
41import { HostProcessPlatform, isHostWindows } from "@t3tools/shared/hostProcess";42import { HostProcessPlatform, isHostWindows } from "@t3tools/shared/hostProcess";
...@@ -230,6 +231,7 @@ describe("ProviderInstanceRegistry — multi-instance codex slice", () => {...@@ -230,6 +231,7 @@ describe("ProviderInstanceRegistry — multi-instance codex slice", () => {
230 ),231 ),
231 ),232 ),
232 Layer.provideMerge(ProviderLatestVersions.layer),233 Layer.provideMerge(ProviderLatestVersions.layer),
234 Layer.provideMerge(McpProviderSessions.layer),
233 Layer.provideMerge(ModelManifest.layerTest),235 Layer.provideMerge(ModelManifest.layerTest),
234 Layer.provideMerge(ResetCreditCoordinator.layerTest),236 Layer.provideMerge(ResetCreditCoordinator.layerTest),
235 );237 );
...@@ -612,6 +614,7 @@ describe("ProviderInstanceRegistry — all drivers slice", () => {...@@ -612,6 +614,7 @@ describe("ProviderInstanceRegistry — all drivers slice", () => {
612 ),614 ),
613 ),615 ),
614 Layer.provideMerge(ProviderLatestVersions.layer),616 Layer.provideMerge(ProviderLatestVersions.layer),
617 Layer.provideMerge(McpProviderSessions.layer),
615 Layer.provideMerge(ModelManifest.layerTest),618 Layer.provideMerge(ModelManifest.layerTest),
616 Layer.provideMerge(ResetCreditCoordinator.layerTest),619 Layer.provideMerge(ResetCreditCoordinator.layerTest),
617 );620 );
apps/server/src/provider/ProviderRegistry.test.ts+5
...@@ -46,6 +46,7 @@ import * as ModelManifest from "./ModelManifest.ts";...@@ -46,6 +46,7 @@ import * as ModelManifest from "./ModelManifest.ts";
46import { applyProviderCompatibility } from "./providerCompatibility.ts";46import { applyProviderCompatibility } from "./providerCompatibility.ts";
47import * as ResetCreditCoordinator from "./resetCreditCoordinator.ts";47import * as ResetCreditCoordinator from "./resetCreditCoordinator.ts";
48import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";48import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";
49import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
49import * as OpenCodeRuntime from "@t3tools/provider-opencode/server/OpenCodeRuntime";50import * as OpenCodeRuntime from "@t3tools/provider-opencode/server/OpenCodeRuntime";
50import * as OpenCodeServerLedger from "@t3tools/provider-opencode/server/OpenCodeServerLedger";51import * as OpenCodeServerLedger from "@t3tools/provider-opencode/server/OpenCodeServerLedger";
51import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";52import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";
...@@ -2761,6 +2762,7 @@ it.layer(...@@ -2761,6 +2762,7 @@ it.layer(
2761 ),2762 ),
2762 ),2763 ),
2763 Layer.provideMerge(ProviderLatestVersions.layer),2764 Layer.provideMerge(ProviderLatestVersions.layer),
2765 Layer.provideMerge(McpProviderSessions.layer),
2764 Layer.provideMerge(ModelManifest.layerTest),2766 Layer.provideMerge(ModelManifest.layerTest),
2765 Layer.provideMerge(ResetCreditCoordinator.layerTest),2767 Layer.provideMerge(ResetCreditCoordinator.layerTest),
2766 Layer.provideMerge(2768 Layer.provideMerge(
...@@ -2865,6 +2867,7 @@ it.layer(...@@ -2865,6 +2867,7 @@ it.layer(
2865 ),2867 ),
2866 ),2868 ),
2867 Layer.provideMerge(ProviderLatestVersions.layer),2869 Layer.provideMerge(ProviderLatestVersions.layer),
2870 Layer.provideMerge(McpProviderSessions.layer),
2868 Layer.provideMerge(ModelManifest.layerTest),2871 Layer.provideMerge(ModelManifest.layerTest),
2869 Layer.provideMerge(ResetCreditCoordinator.layerTest),2872 Layer.provideMerge(ResetCreditCoordinator.layerTest),
2870 Layer.provideMerge(2873 Layer.provideMerge(
...@@ -2984,6 +2987,7 @@ it.layer(...@@ -2984,6 +2987,7 @@ it.layer(
2984 ),2987 ),
2985 ),2988 ),
2986 Layer.provideMerge(ProviderLatestVersions.layer),2989 Layer.provideMerge(ProviderLatestVersions.layer),
2990 Layer.provideMerge(McpProviderSessions.layer),
2987 Layer.provideMerge(ModelManifest.layerTest),2991 Layer.provideMerge(ModelManifest.layerTest),
2988 Layer.provideMerge(ResetCreditCoordinator.layerTest),2992 Layer.provideMerge(ResetCreditCoordinator.layerTest),
2989 Layer.provideMerge(2993 Layer.provideMerge(
...@@ -3043,6 +3047,7 @@ it.layer(...@@ -3043,6 +3047,7 @@ it.layer(
3043 ),3047 ),
3044 ),3048 ),
3045 Layer.provideMerge(ProviderLatestVersions.layer),3049 Layer.provideMerge(ProviderLatestVersions.layer),
3050 Layer.provideMerge(McpProviderSessions.layer),
3046 Layer.provideMerge(ModelManifest.layerTest),3051 Layer.provideMerge(ModelManifest.layerTest),
3047 Layer.provideMerge(ResetCreditCoordinator.layerTest),3052 Layer.provideMerge(ResetCreditCoordinator.layerTest),
3048 Layer.provideMerge(3053 Layer.provideMerge(
apps/server/src/server.ts+2
...@@ -45,6 +45,7 @@ import * as ModelManifest from "./provider/ModelManifest.ts";...@@ -45,6 +45,7 @@ import * as ModelManifest from "./provider/ModelManifest.ts";
45import * as ResetCreditCoordinator from "./provider/resetCreditCoordinator.ts";45import * as ResetCreditCoordinator from "./provider/resetCreditCoordinator.ts";
46import * as EventNdjsonLogger from "./provider/EventNdjsonLogger.ts";46import * as EventNdjsonLogger from "./provider/EventNdjsonLogger.ts";
47import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";47import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";
48import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
48import * as OpenCodeRuntime from "@t3tools/provider-opencode/server/OpenCodeRuntime";49import * as OpenCodeRuntime from "@t3tools/provider-opencode/server/OpenCodeRuntime";
49import * as OpenCodeServerLedger from "@t3tools/provider-opencode/server/OpenCodeServerLedger";50import * as OpenCodeServerLedger from "@t3tools/provider-opencode/server/OpenCodeServerLedger";
50import * as ProviderHostLive from "./provider/ProviderHostLive.ts";51import * as ProviderHostLive from "./provider/ProviderHostLive.ts";
...@@ -636,6 +637,7 @@ const layerRuntimeCoreDependencies = layerRuntimeCoreDependenciesBase.pipe(...@@ -636,6 +637,7 @@ const layerRuntimeCoreDependencies = layerRuntimeCoreDependenciesBase.pipe(
636 ModelManifest.layerModelCatalog.pipe(Layer.provideMerge(ModelManifest.layer)),637 ModelManifest.layerModelCatalog.pipe(Layer.provideMerge(ModelManifest.layer)),
637 ResetCreditCoordinator.layer,638 ResetCreditCoordinator.layer,
638 ProviderLatestVersions.layer,639 ProviderLatestVersions.layer,
640 McpProviderSessions.layer,
639 ),641 ),
640 ),642 ),
641 // `OpenCodeDriver.create()` yields `OpenCodeRuntime`; previously the old643 // `OpenCodeDriver.create()` yields `OpenCodeRuntime`; previously the old
packages/provider-acp-registry/src/server/adapter.ts+2
...@@ -18,6 +18,7 @@ import type * as Scope from "effect/Scope";...@@ -18,6 +18,7 @@ import type * as Scope from "effect/Scope";
18import { ChildProcessSpawner } from "effect/process";18import { ChildProcessSpawner } from "effect/process";
19import * as EffectAcpErrors from "effect-acp/errors";19import * as EffectAcpErrors from "effect-acp/errors";
2020
21import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
21import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";22import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";
22import {23import {
23 normalizeAcpRegistryCommands,24 normalizeAcpRegistryCommands,
...@@ -259,6 +260,7 @@ export type AcpRegistryAdapterV2DriverEnv =...@@ -259,6 +260,7 @@ export type AcpRegistryAdapterV2DriverEnv =
259 | AcpRegistrySupport.AcpRegistryCatalog260 | AcpRegistrySupport.AcpRegistryCatalog
260 | IdAllocator.IdAllocatorV2261 | IdAllocator.IdAllocatorV2
261 | Path.Path262 | Path.Path
263 | McpProviderSessions.McpProviderSessions
262 | ProviderEventLoggers.ProviderEventLoggers264 | ProviderEventLoggers.ProviderEventLoggers
263 | ProviderHost.ProviderHost;265 | ProviderHost.ProviderHost;
264266
packages/provider-acp/src/server/adapter.ts+58-43
...@@ -59,6 +59,7 @@ import { formatReadToolLabel, formatSearchToolLabel } from "@t3tools/shared/tool...@@ -59,6 +59,7 @@ import { formatReadToolLabel, formatSearchToolLabel } from "@t3tools/shared/tool
59import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";59import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";
60import { makeAcpMcpOverAcpBridge, type AcpMcpOverAcpBridge } from "./mcpOverAcpBridge.ts";60import { makeAcpMcpOverAcpBridge, type AcpMcpOverAcpBridge } from "./mcpOverAcpBridge.ts";
61import { mcpToolPresentation } from "@t3tools/provider-core/server/mcpToolPresentation";61import { mcpToolPresentation } from "@t3tools/provider-core/server/mcpToolPresentation";
62import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
62import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession";63import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession";
63import {64import {
64 applyAcpAgentTerminalUpdate,65 applyAcpAgentTerminalUpdate,
...@@ -677,9 +678,10 @@ interface AcpMcpContext {...@@ -677,9 +678,10 @@ interface AcpMcpContext {
677 readonly authorization?: string;678 readonly authorization?: string;
678}679}
679680
680function acpMcpContext(threadId: ThreadId | null, self: SelfInvocation): AcpMcpContext {681function acpMcpContext(
681 if (threadId === null) return { servers: [], acpServers: [] };682 session: McpProviderSession.McpProviderSessionConfig | undefined,
682 const session = McpProviderSession.readMcpProviderSession(threadId);683 self: SelfInvocation,
684): AcpMcpContext {
683 if (session === undefined) {685 if (session === undefined) {
684 return { servers: [], acpServers: [] };686 return { servers: [], acpServers: [] };
685 }687 }
...@@ -714,15 +716,7 @@ function acpMcpContext(threadId: ThreadId | null, self: SelfInvocation): AcpMcpC...@@ -714,15 +716,7 @@ function acpMcpContext(threadId: ThreadId | null, self: SelfInvocation): AcpMcpC
714 };716 };
715}717}
716718
717function acpMcpServers(719function acpMcpActivation(context: AcpMcpContext) {
718 threadId: ThreadId | null,
719 self: SelfInvocation,
720): ReadonlyArray<EffectAcpSchema.McpServer> {
721 return acpMcpContext(threadId, self).servers;
722}
723
724function acpMcpActivation(threadId: ThreadId | null, self: SelfInvocation) {
725 const context = acpMcpContext(threadId, self);
726 return { mcpServers: context.servers, acpMcpServers: context.acpServers };720 return { mcpServers: context.servers, acpMcpServers: context.acpServers };
727}721}
728722
...@@ -1494,7 +1488,12 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* (...@@ -1494,7 +1488,12 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* (
1494 const fileSystem = yield* FileSystem.FileSystem;1488 const fileSystem = yield* FileSystem.FileSystem;
1495 const idAllocator = yield* IdAllocator.IdAllocatorV2;1489 const idAllocator = yield* IdAllocator.IdAllocatorV2;
1496 const host = yield* ProviderHost.ProviderHost;1490 const host = yield* ProviderHost.ProviderHost;
1491 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
1497 const { flavor, selfInvocation: self } = options;1492 const { flavor, selfInvocation: self } = options;
1493 const readMcpContext = (threadId: ThreadId | null) =>
1494 threadId === null
1495 ? Effect.succeed(acpMcpContext(undefined, self))
1496 : mcpSessions.read(threadId).pipe(Effect.map((session) => acpMcpContext(session, self)));
1498 const driver = flavor.driver;1497 const driver = flavor.driver;
1499 const continuationRequests = options.continuationRequests;1498 const continuationRequests = options.continuationRequests;
1500 const postSettleContinuationEnabled =1499 const postSettleContinuationEnabled =
...@@ -1550,32 +1549,32 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* (...@@ -1550,32 +1549,32 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* (
1550 readonly sessionId: string | null;1549 readonly sessionId: string | null;
1551 }1550 }
1552 let pendingTerminalEnvironment: PendingTerminalEnvironment | null = {1551 let pendingTerminalEnvironment: PendingTerminalEnvironment | null = {
1553 environment: acpMcpContext(input.threadId, self).processEnvironment,1552 environment: (yield* readMcpContext(input.threadId)).processEnvironment,
1554 claimUnknownSession: input.initialNativeThreadId === undefined,1553 claimUnknownSession: input.initialNativeThreadId === undefined,
1555 sessionId: input.initialNativeThreadId ?? null,1554 sessionId: input.initialNativeThreadId ?? null,
1556 };1555 };
1557 const prepareTerminalEnvironment = (1556 const prepareTerminalEnvironment = (
1558 threadId: ThreadId | null,1557 mcpContext: AcpMcpContext,
1559 sessionId?: string,1558 sessionId?: string,
1560 ): void => {1559 ): void => {
1561 pendingTerminalEnvironment = {1560 pendingTerminalEnvironment = {
1562 environment: acpMcpContext(threadId, self).processEnvironment,1561 environment: mcpContext.processEnvironment,
1563 claimUnknownSession: false,1562 claimUnknownSession: false,
1564 sessionId: sessionId ?? null,1563 sessionId: sessionId ?? null,
1565 };1564 };
1566 };1565 };
1567 const prepareClaimableTerminalEnvironment = (threadId: ThreadId | null): void => {1566 const prepareClaimableTerminalEnvironment = (mcpContext: AcpMcpContext): void => {
1568 pendingTerminalEnvironment = {1567 pendingTerminalEnvironment = {
1569 environment: acpMcpContext(threadId, self).processEnvironment,1568 environment: mcpContext.processEnvironment,
1570 claimUnknownSession: true,1569 claimUnknownSession: true,
1571 sessionId: null,1570 sessionId: null,
1572 };1571 };
1573 };1572 };
1574 const rememberTerminalEnvironment = (1573 const rememberTerminalEnvironment = (
1575 sessionId: string,1574 sessionId: string,
1576 threadId: ThreadId | null,1575 mcpContext: AcpMcpContext,
1577 ): void => {1576 ): void => {
1578 const environment = acpMcpContext(threadId, self).processEnvironment;1577 const environment = mcpContext.processEnvironment;
1579 pendingTerminalEnvironment = null;1578 pendingTerminalEnvironment = null;
1580 if (environment === undefined) {1579 if (environment === undefined) {
1581 terminalEnvironmentBySessionId.delete(sessionId);1580 terminalEnvironmentBySessionId.delete(sessionId);
...@@ -2097,12 +2096,11 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* (...@@ -2097,12 +2096,11 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* (
2097 ).pipe(Effect.asVoid);2096 ).pipe(Effect.asVoid);
2098 const makeRuntimeInput = (2097 const makeRuntimeInput = (
2099 runtimeGeneration: number,2098 runtimeGeneration: number,
2100 threadId: ThreadId | null,2099 mcpContext: AcpMcpContext,
2101 resumeSessionId?: string,2100 resumeSessionId?: string,
2102 onTermination: AcpAdapterV2RuntimeInput["onTermination"] = () =>2101 onTermination: AcpAdapterV2RuntimeInput["onTermination"] = () =>
2103 handleRuntimeTerminationAtGeneration(runtimeGeneration),2102 handleRuntimeTerminationAtGeneration(runtimeGeneration),
2104 ): AcpAdapterV2RuntimeInput => {2103 ): AcpAdapterV2RuntimeInput => {
2105 const mcpContext = acpMcpContext(threadId, self);
2106 return {2104 return {
2107 cwd: input.runtimePolicy.cwd ?? process.cwd(),2105 cwd: input.runtimePolicy.cwd ?? process.cwd(),
2108 runtimePolicy: input.runtimePolicy,2106 runtimePolicy: input.runtimePolicy,
...@@ -5989,10 +5987,9 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* (...@@ -5989,10 +5987,9 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* (
59895987
5990 /** Builds the MCP-over-ACP bridge for one runtime, disposed with that runtime's scope. */5988 /** Builds the MCP-over-ACP bridge for one runtime, disposed with that runtime's scope. */
5991 const makeRuntimeMcpBridge = Effect.fnUntraced(function* (5989 const makeRuntimeMcpBridge = Effect.fnUntraced(function* (
5992 threadId: ThreadId | null,5990 mcpContext: AcpMcpContext,
5993 scope: Scope.Scope,5991 scope: Scope.Scope,
5994 ) {5992 ) {
5995 const mcpContext = acpMcpContext(threadId, self);
5996 if (mcpContext.endpoint === undefined || mcpContext.authorization === undefined) {5993 if (mcpContext.endpoint === undefined || mcpContext.authorization === undefined) {
5997 return undefined;5994 return undefined;
5998 }5995 }
...@@ -6013,10 +6010,11 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* (...@@ -6013,10 +6010,11 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* (
6013 yield* Scope.close(runtimeScope, Exit.void);6010 yield* Scope.close(runtimeScope, Exit.void);
6014 }6011 }
6015 runtimeScope = yield* Scope.make();6012 runtimeScope = yield* Scope.make();
6016 runtimeMcpBridge = yield* makeRuntimeMcpBridge(threadId, runtimeScope);6013 const mcpContext = yield* readMcpContext(threadId);
6014 runtimeMcpBridge = yield* makeRuntimeMcpBridge(mcpContext, runtimeScope);
6017 const runtimeGeneration = yield* Ref.get(runtimeCallbackGeneration);6015 const runtimeGeneration = yield* Ref.get(runtimeCallbackGeneration);
6018 runtime = yield* flavor6016 runtime = yield* flavor
6019 .makeRuntime(makeRuntimeInput(runtimeGeneration, threadId, resumeSessionId))6017 .makeRuntime(makeRuntimeInput(runtimeGeneration, mcpContext, resumeSessionId))
6020 .pipe(6018 .pipe(
6021 Effect.provideService(Scope.Scope, runtimeScope),6019 Effect.provideService(Scope.Scope, runtimeScope),
6022 Effect.provideService(Crypto.Crypto, crypto),6020 Effect.provideService(Crypto.Crypto, crypto),
...@@ -6084,12 +6082,13 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* (...@@ -6084,12 +6082,13 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* (
6084 });6082 });
6085 let replacementMcpBridge: AcpMcpOverAcpBridge | undefined;6083 let replacementMcpBridge: AcpMcpOverAcpBridge | undefined;
6086 const startup = Effect.gen(function* () {6084 const startup = Effect.gen(function* () {
6087 replacementMcpBridge = yield* makeRuntimeMcpBridge(threadId, replacementScope);6085 const mcpContext = yield* readMcpContext(threadId);
6086 replacementMcpBridge = yield* makeRuntimeMcpBridge(mcpContext, replacementScope);
6088 const replacementRuntime = yield* flavor6087 const replacementRuntime = yield* flavor
6089 .makeRuntime(6088 .makeRuntime(
6090 makeRuntimeInput(6089 makeRuntimeInput(
6091 replacementGeneration,6090 replacementGeneration,
6092 threadId,6091 mcpContext,
6093 undefined,6092 undefined,
6094 handleCandidateTermination,6093 handleCandidateTermination,
6095 ),6094 ),
...@@ -6150,7 +6149,10 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* (...@@ -6150,7 +6149,10 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* (
6150 runtimeScope = replacementScope;6149 runtimeScope = replacementScope;
6151 runtimeMcpBridge = replacementMcpBridge;6150 runtimeMcpBridge = replacementMcpBridge;
6152 yield* Ref.set(runtimeCallbackGeneration, replacementGeneration);6151 yield* Ref.set(runtimeCallbackGeneration, replacementGeneration);
6153 prepareTerminalEnvironment(threadId, replacementExit.value.started.sessionId);6152 prepareTerminalEnvironment(
6153 yield* readMcpContext(threadId),
6154 replacementExit.value.started.sessionId,
6155 );
6154 yield* wireAcpRuntimeTerminalHandlers(replacementExit.value.replacementRuntime);6156 yield* wireAcpRuntimeTerminalHandlers(replacementExit.value.replacementRuntime);
6155 yield* commitSessionState(replacementExit.value.started);6157 yield* commitSessionState(replacementExit.value.started);
6156 while (true) {6158 while (true) {
...@@ -6224,12 +6226,12 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* (...@@ -6224,12 +6226,12 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* (
6224 });6226 });
6225 itemIdentityVersion = 2;6227 itemIdentityVersion = 2;
6226 yield* Ref.set(runtimeRestartRequired, false);6228 yield* Ref.set(runtimeRestartRequired, false);
6227 prepareClaimableTerminalEnvironment(input.threadId);6229 prepareClaimableTerminalEnvironment(yield* readMcpContext(input.threadId));
6228 return yield* startAcpRuntime(input.threadId);6230 return yield* startAcpRuntime(input.threadId);
6229 });6231 });
6230 yield* Ref.set(activeSessionId, started.sessionId);6232 yield* Ref.set(activeSessionId, started.sessionId);
6231 yield* Ref.set(activeSessionSetup, started);6233 yield* Ref.set(activeSessionSetup, started);
6232 rememberTerminalEnvironment(started.sessionId, input.threadId);6234 rememberTerminalEnvironment(started.sessionId, yield* readMcpContext(input.threadId));
6233 const capabilities = negotiatedCapabilities(flavor.capabilities, started);6235 const capabilities = negotiatedCapabilities(flavor.capabilities, started);
6234 const canLoadSession = started.initializeResult.agentCapabilities?.loadSession === true;6236 const canLoadSession = started.initializeResult.agentCapabilities?.loadSession === true;
6235 const canResumeSession =6237 const canResumeSession =
...@@ -6250,8 +6252,9 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* (...@@ -6250,8 +6252,9 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* (
6250 if (initialFailure !== undefined) {6252 if (initialFailure !== undefined) {
6251 return yield* initialFailure;6253 return yield* initialFailure;
6252 }6254 }
6253 const activationOptions = acpMcpActivation(threadId, self);6255 const mcpContext = yield* readMcpContext(threadId);
6254 prepareTerminalEnvironment(threadId, sessionId);6256 const activationOptions = acpMcpActivation(mcpContext);
6257 prepareTerminalEnvironment(mcpContext, sessionId);
6255 const activated = canLoadSession6258 const activated = canLoadSession
6256 ? yield* runtime.loadSession(sessionId, activationOptions)6259 ? yield* runtime.loadSession(sessionId, activationOptions)
6257 : canResumeSession6260 : canResumeSession
...@@ -6260,7 +6263,7 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* (...@@ -6260,7 +6263,7 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* (
6260 driver,6263 driver,
6261 detail: `ACP driver cannot load or resume session ${sessionId}`,6264 detail: `ACP driver cannot load or resume session ${sessionId}`,
6262 });6265 });
6263 rememberTerminalEnvironment(activated.sessionId, threadId);6266 rememberTerminalEnvironment(activated.sessionId, yield* readMcpContext(threadId));
6264 return activated;6267 return activated;
6265 });6268 });
62666269
...@@ -6771,7 +6774,7 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* (...@@ -6771,7 +6774,7 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* (
6771 const prompt: Array<EffectAcpSchema.ContentBlock> = [];6774 const prompt: Array<EffectAcpSchema.ContentBlock> = [];
6772 const instructionState = {6775 const instructionState = {
6773 interactionMode: turnInput.runtimePolicy.interactionMode,6776 interactionMode: turnInput.runtimePolicy.interactionMode,
6774 hasT3Mcp: acpMcpServers(turnInput.threadId, self).length > 0,6777 hasT3Mcp: (yield* readMcpContext(turnInput.threadId)).servers.length > 0,
6775 } satisfies T3AcpInstructionState;6778 } satisfies T3AcpInstructionState;
6776 const previousInstructionState = (yield* Ref.get(promptInstructionStates)).get(sessionId);6779 const previousInstructionState = (yield* Ref.get(promptInstructionStates)).get(sessionId);
6777 const messageText = providerMessageTextWithAttachmentPaths({6780 const messageText = providerMessageTextWithAttachmentPaths({
...@@ -7820,14 +7823,17 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* (...@@ -7820,14 +7823,17 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* (
7820 loadingMessageId: null,7823 loadingMessageId: null,
7821 loadingIndex: 0,7824 loadingIndex: 0,
7822 });7825 });
7823 prepareTerminalEnvironment(snapshotInput.providerThread.appThreadId, sessionId);7826 const mcpContext = yield* readMcpContext(
7827 snapshotInput.providerThread.appThreadId,
7828 );
7829 prepareTerminalEnvironment(mcpContext, sessionId);
7824 const activated = yield* runtime.loadSession(7830 const activated = yield* runtime.loadSession(
7825 sessionId,7831 sessionId,
7826 acpMcpActivation(snapshotInput.providerThread.appThreadId, self),7832 acpMcpActivation(mcpContext),
7827 );7833 );
7828 rememberTerminalEnvironment(7834 rememberTerminalEnvironment(
7829 activated.sessionId,7835 activated.sessionId,
7830 snapshotInput.providerThread.appThreadId,7836 yield* readMcpContext(snapshotInput.providerThread.appThreadId),
7831 );7837 );
7832 yield* Ref.set(activeSessionId, activated.sessionId);7838 yield* Ref.set(activeSessionId, activated.sessionId);
7833 yield* Ref.set(activeSessionSetup, activated);7839 yield* Ref.set(activeSessionSetup, activated);
...@@ -7883,14 +7889,16 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* (...@@ -7883,14 +7889,16 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* (
7883 // loading any of the rolled-back conversation.7889 // loading any of the rolled-back conversation.
7884 yield* awaitRuntimeTeardown();7890 yield* awaitRuntimeTeardown();
7885 itemIdentityVersion = 2;7891 itemIdentityVersion = 2;
7886 prepareTerminalEnvironment(rollbackInput.providerThread.appThreadId);7892 prepareTerminalEnvironment(
7893 yield* readMcpContext(rollbackInput.providerThread.appThreadId),
7894 );
7887 const replacement = yield* startReplacementAcpRuntime(7895 const replacement = yield* startReplacementAcpRuntime(
7888 rollbackInput.providerThread.appThreadId,7896 rollbackInput.providerThread.appThreadId,
7889 (candidate) =>7897 (candidate) =>
7890 Effect.gen(function* () {7898 Effect.gen(function* () {
7891 rememberTerminalEnvironment(7899 rememberTerminalEnvironment(
7892 candidate.sessionId,7900 candidate.sessionId,
7893 rollbackInput.providerThread.appThreadId,7901 yield* readMcpContext(rollbackInput.providerThread.appThreadId),
7894 );7902 );
7895 yield* Ref.set(runtimeRestartRequired, false);7903 yield* Ref.set(runtimeRestartRequired, false);
7896 yield* Ref.set(activeSessionId, candidate.sessionId);7904 yield* Ref.set(activeSessionId, candidate.sessionId);
...@@ -7983,12 +7991,16 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* (...@@ -7983,12 +7991,16 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* (
7983 driver,7991 driver,
7984 forkInput.sourceProviderThread,7992 forkInput.sourceProviderThread,
7985 );7993 );
7986 prepareTerminalEnvironment(forkInput.targetThreadId);7994 const mcpContext = yield* readMcpContext(forkInput.targetThreadId);
7995 prepareTerminalEnvironment(mcpContext);
7987 const forked = yield* runtime.forkSession(7996 const forked = yield* runtime.forkSession(
7988 sourceSessionId,7997 sourceSessionId,
7989 acpMcpActivation(forkInput.targetThreadId, self),7998 acpMcpActivation(mcpContext),
7999 );
8000 rememberTerminalEnvironment(
8001 forked.sessionId,
8002 yield* readMcpContext(forkInput.targetThreadId),
7990 );8003 );
7991 rememberTerminalEnvironment(forked.sessionId, forkInput.targetThreadId);
7992 yield* Ref.set(activeSessionId, forked.sessionId);8004 yield* Ref.set(activeSessionId, forked.sessionId);
7993 yield* Ref.set(activeSessionSetup, forked);8005 yield* Ref.set(activeSessionSetup, forked);
7994 yield* Ref.set(activeSelection, null);8006 yield* Ref.set(activeSelection, null);
...@@ -8051,4 +8063,7 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* (...@@ -8051,4 +8063,7 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* (
8051 });8063 });
8052});8064});
80538065
8054export type AcpAdapterV2Env = FileSystem.FileSystem | IdAllocator.IdAllocatorV2;8066export type AcpAdapterV2Env =
8067 | FileSystem.FileSystem
8068 | IdAllocator.IdAllocatorV2
8069 | McpProviderSessions.McpProviderSessions;
packages/provider-core/package.json+4
...@@ -63,6 +63,10 @@...@@ -63,6 +63,10 @@
63 "types": "./src/server/managedProvider.ts",63 "types": "./src/server/managedProvider.ts",
64 "import": "./src/server/managedProvider.ts"64 "import": "./src/server/managedProvider.ts"
65 },65 },
66 "./server/McpProviderSessions": {
67 "types": "./src/server/McpProviderSessions.ts",
68 "import": "./src/server/McpProviderSessions.ts"
69 },
66 "./server/mcpSession": {70 "./server/mcpSession": {
67 "types": "./src/server/mcpSession.ts",71 "types": "./src/server/mcpSession.ts",
68 "import": "./src/server/mcpSession.ts"72 "import": "./src/server/mcpSession.ts"
packages/provider-core/src/server/McpProviderSessions.ts created+40
...@@ -0,0 +1,40 @@
1/**
2 * The T3 MCP credential each thread's provider session should expose to its
3 * agent. The session manager writes it before a session starts; adapters read
4 * it when they build provider launch options.
5 *
6 * @module provider-core/server/McpProviderSessions
7 */
8import type { ThreadId } from "@t3tools/contracts";
9import * as Context from "effect/Context";
10import * as Effect from "effect/Effect";
11import * as Layer from "effect/Layer";
12import * as Ref from "effect/Ref";
13
14import type { McpProviderSessionConfig } from "./mcpSession.ts";
15
16export class McpProviderSessions extends Context.Service<
17 McpProviderSessions,
18 {
19 readonly set: (config: McpProviderSessionConfig) => Effect.Effect<void>;
20 readonly read: (threadId: ThreadId) => Effect.Effect<McpProviderSessionConfig | undefined>;
21 readonly clear: (threadId: ThreadId) => Effect.Effect<void>;
22 }
23>()("@t3tools/provider-core/server/McpProviderSessions") {}
24
25const make = Effect.gen(function* () {
26 const sessions = yield* Ref.make(new Map<ThreadId, McpProviderSessionConfig>());
27 return McpProviderSessions.of({
28 set: (config) =>
29 Ref.update(sessions, (current) => new Map(current).set(config.threadId, config)),
30 read: (threadId) => Ref.get(sessions).pipe(Effect.map((current) => current.get(threadId))),
31 clear: (threadId) =>
32 Ref.update(sessions, (current) => {
33 const next = new Map(current);
34 next.delete(threadId);
35 return next;
36 }),
37 });
38});
39
40export const layer = Layer.effect(McpProviderSessions, make);
packages/provider-core/src/server/mcpSession.ts-18
...@@ -40,21 +40,3 @@ export function withAgentDeviceEnvironment(...@@ -40,21 +40,3 @@ export function withAgentDeviceEnvironment(
40 ...(shimDir ? { PATH: basePath ? `${shimDir}${separator}${basePath}` : shimDir } : {}),40 ...(shimDir ? { PATH: basePath ? `${shimDir}${separator}${basePath}` : shimDir } : {}),
41 };41 };
42}42}
43
44const sessionsByThread = new Map<ThreadId, McpProviderSessionConfig>();
45
46export function setMcpProviderSession(config: McpProviderSessionConfig): void {
47 sessionsByThread.set(config.threadId, config);
48}
49
50export function readMcpProviderSession(threadId: ThreadId): McpProviderSessionConfig | undefined {
51 return sessionsByThread.get(threadId);
52}
53
54export function clearMcpProviderSession(threadId: ThreadId): void {
55 sessionsByThread.delete(threadId);
56}
57
58function clearAllMcpProviderSessions(): void {
59 sessionsByThread.clear();
60}
packages/provider-cursor/src/server/adapter.test.ts+48-35
...@@ -23,8 +23,8 @@ import * as Schema from "effect/Schema";...@@ -23,8 +23,8 @@ import * as Schema from "effect/Schema";
23import * as Stream from "effect/Stream";23import * as Stream from "effect/Stream";
2424
25import { layerTestProviderHost } from "@t3tools/provider-testing/host";25import { layerTestProviderHost } from "@t3tools/provider-testing/host";
26import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession";
27import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";26import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
27import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
28import * as ProviderAdapter from "@t3tools/provider-core/server/ProviderAdapter";28import * as ProviderAdapter from "@t3tools/provider-core/server/ProviderAdapter";
29import {29import {
30 cursorMcpServers,30 cursorMcpServers,
...@@ -199,7 +199,12 @@ describe("CursorAdapterV2", () => {...@@ -199,7 +199,12 @@ describe("CursorAdapterV2", () => {
199 : "failed",199 : "failed",
200 );200 );
201 assert.isNotNull(rows.at(-1)?.subagent.completedAt);201 assert.isNotNull(rows.at(-1)?.subagent.completedAt);
202 }).pipe(Effect.scoped, Effect.provide(Layer.merge(NodeServices.layer, IdAllocator.layer))),202 }).pipe(
203 Effect.scoped,
204 Effect.provide(
205 Layer.mergeAll(NodeServices.layer, IdAllocator.layer, McpProviderSessions.layer),
206 ),
207 ),
203 );208 );
204209
205 it.effect("fails standalone SDK transport diagnostics and sends compaction as /compress", () =>210 it.effect("fails standalone SDK transport diagnostics and sends compaction as /compress", () =>
...@@ -344,7 +349,12 @@ describe("CursorAdapterV2", () => {...@@ -344,7 +349,12 @@ describe("CursorAdapterV2", () => {
344 Stream.runHead,349 Stream.runHead,
345 );350 );
346 assert.equal(sentMessages[1], "/compress");351 assert.equal(sentMessages[1], "/compress");
347 }).pipe(Effect.scoped, Effect.provide(Layer.merge(NodeServices.layer, IdAllocator.layer))),352 }).pipe(
353 Effect.scoped,
354 Effect.provide(
355 Layer.mergeAll(NodeServices.layer, IdAllocator.layer, McpProviderSessions.layer),
356 ),
357 ),
348 );358 );
349359
350 it.effect("projects Cursor directory trees and lint diagnostics as file search results", () =>360 it.effect("projects Cursor directory trees and lint diagnostics as file search results", () =>
...@@ -746,7 +756,12 @@ describe("CursorAdapterV2", () => {...@@ -746,7 +756,12 @@ describe("CursorAdapterV2", () => {
746 },756 },
747 ],757 ],
748 );758 );
749 }).pipe(Effect.scoped, Effect.provide(Layer.merge(NodeServices.layer, IdAllocator.layer))),759 }).pipe(
760 Effect.scoped,
761 Effect.provide(
762 Layer.mergeAll(NodeServices.layer, IdAllocator.layer, McpProviderSessions.layer),
763 ),
764 ),
750 );765 );
751766
752 it("maps Cursor auto and model parameters to SDK selections", () => {767 it("maps Cursor auto and model parameters to SDK selections", () => {
...@@ -843,6 +858,7 @@ describe("CursorAdapterV2", () => {...@@ -843,6 +858,7 @@ describe("CursorAdapterV2", () => {
843 },858 },
844 runtimePolicy: { runtimeMode, interactionMode: "default", cwd: "/workspace" },859 runtimePolicy: { runtimeMode, interactionMode: "default", cwd: "/workspace" },
845 threadId: ThreadId.make("thread-cursor-setting-sources"),860 threadId: ThreadId.make("thread-cursor-setting-sources"),
861 mcpSession: undefined,
846 });862 });
847 assert.deepEqual(options.local?.settingSources, [863 assert.deepEqual(options.local?.settingSources, [
848 "project",864 "project",
...@@ -856,7 +872,7 @@ describe("CursorAdapterV2", () => {...@@ -856,7 +872,7 @@ describe("CursorAdapterV2", () => {
856872
857 it("injects thread-scoped MCP credentials without logging them", () => {873 it("injects thread-scoped MCP credentials without logging them", () => {
858 const threadId = ThreadId.make("thread-cursor-mcp");874 const threadId = ThreadId.make("thread-cursor-mcp");
859 McpProviderSession.setMcpProviderSession({875 const mcpSession = {
860 environmentId: EnvironmentId.make("environment-cursor-mcp"),876 environmentId: EnvironmentId.make("environment-cursor-mcp"),
861 threadId,877 threadId,
862 providerSessionId: "mcp-session-cursor",878 providerSessionId: "mcp-session-cursor",
...@@ -864,40 +880,37 @@ describe("CursorAdapterV2", () => {...@@ -864,40 +880,37 @@ describe("CursorAdapterV2", () => {
864 endpoint: "http://127.0.0.1:43123/mcp",880 endpoint: "http://127.0.0.1:43123/mcp",
865 authorizationHeader: "Bearer secret-cursor-mcp-token",881 authorizationHeader: "Bearer secret-cursor-mcp-token",
866 browserToolsAvailable: true,882 browserToolsAvailable: true,
867 });883 };
868884
869 try {885 assert.deepEqual(cursorMcpServers(mcpSession), {
870 assert.deepEqual(cursorMcpServers(threadId), {886 "t3-code": {
871 "t3-code": {887 type: "http",
872 type: "http",888 url: "http://127.0.0.1:43123/mcp",
873 url: "http://127.0.0.1:43123/mcp",889 headers: {
874 headers: {890 Authorization: "Bearer secret-cursor-mcp-token",
875 Authorization: "Bearer secret-cursor-mcp-token",
876 },
877 },891 },
878 });892 },
893 });
879894
880 const options = makeCursorAgentOptions({895 const options = makeCursorAgentOptions({
881 apiKey: "secret-cursor-api-key",896 apiKey: "secret-cursor-api-key",
882 modelSelection: {897 modelSelection: {
883 instanceId: ProviderInstanceId.make("cursor"),898 instanceId: ProviderInstanceId.make("cursor"),
884 model: "composer-2.5",899 model: "composer-2.5",
885 },900 },
886 runtimePolicy: {901 runtimePolicy: {
887 runtimeMode: "full-access",902 runtimeMode: "full-access",
888 interactionMode: "default",903 interactionMode: "default",
889 cwd: "/workspace",904 cwd: "/workspace",
890 },905 },
891 threadId,906 threadId,
892 });907 mcpSession,
893 assert.deepEqual(options.mcpServers, cursorMcpServers(threadId));908 });
909 assert.deepEqual(options.mcpServers, cursorMcpServers(mcpSession));
894910
895 const logged = JSON.stringify(CursorAgentSdk.loggedCursorAgentOptions(options));911 const logged = JSON.stringify(CursorAgentSdk.loggedCursorAgentOptions(options));
896 assert.notInclude(logged, "secret-cursor-api-key");912 assert.notInclude(logged, "secret-cursor-api-key");
897 assert.notInclude(logged, "secret-cursor-mcp-token");913 assert.notInclude(logged, "secret-cursor-mcp-token");
898 } finally {
899 McpProviderSession.clearMcpProviderSession(threadId);
900 }
901 });914 });
902915
903 it("recognizes direct and SDK-wrapped abort failures as cancellation", () => {916 it("recognizes direct and SDK-wrapped abort failures as cancellation", () => {
packages/provider-cursor/src/server/adapter.ts+14-6
...@@ -44,6 +44,7 @@ import * as Stream from "effect/Stream";...@@ -44,6 +44,7 @@ import * as Stream from "effect/Stream";
4444
45import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";45import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";
46import { mcpToolPresentation } from "@t3tools/provider-core/server/mcpToolPresentation";46import { mcpToolPresentation } from "@t3tools/provider-core/server/mcpToolPresentation";
47import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
47import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession";48import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession";
48import { CursorTransportFailure } from "./transportFailure.ts";49import { CursorTransportFailure } from "./transportFailure.ts";
49import { cursorSdkModelSelection } from "./sdkModel.ts";50import { cursorSdkModelSelection } from "./sdkModel.ts";
...@@ -201,8 +202,9 @@ export function cursorRuntimeAgentPolicy(...@@ -201,8 +202,9 @@ export function cursorRuntimeAgentPolicy(
201 };202 };
202}203}
203204
204export function cursorMcpServers(threadId: ThreadId): Record<string, McpServerConfig> | undefined {205export function cursorMcpServers(
205 const session = McpProviderSession.readMcpProviderSession(threadId);206 session: McpProviderSession.McpProviderSessionConfig | undefined,
207): Record<string, McpServerConfig> | undefined {
206 if (session === undefined) {208 if (session === undefined) {
207 return undefined;209 return undefined;
208 }210 }
...@@ -306,9 +308,10 @@ export function makeCursorAgentOptions(input: {...@@ -306,9 +308,10 @@ export function makeCursorAgentOptions(input: {
306 readonly modelSelection: ModelSelection;308 readonly modelSelection: ModelSelection;
307 readonly runtimePolicy: ProviderAdapter.ProviderAdapterV2RuntimePolicy;309 readonly runtimePolicy: ProviderAdapter.ProviderAdapterV2RuntimePolicy;
308 readonly threadId: ThreadId;310 readonly threadId: ThreadId;
311 readonly mcpSession: McpProviderSession.McpProviderSessionConfig | undefined;
309}): AgentOptions {312}): AgentOptions {
310 const policy = cursorRuntimeAgentPolicy(input.runtimePolicy);313 const policy = cursorRuntimeAgentPolicy(input.runtimePolicy);
311 const mcpServers = cursorMcpServers(input.threadId);314 const mcpServers = cursorMcpServers(input.mcpSession);
312 return {315 return {
313 model: cursorSdkModelSelection(input.modelSelection),316 model: cursorSdkModelSelection(input.modelSelection),
314 name: `T3 Code ${input.threadId}`,317 name: `T3 Code ${input.threadId}`,
...@@ -854,6 +857,7 @@ export const makeCursorAdapterV2 = Effect.fn("makeCursorAdapterV2")(function* (...@@ -854,6 +857,7 @@ export const makeCursorAdapterV2 = Effect.fn("makeCursorAdapterV2")(function* (
854 const idAllocator = yield* IdAllocator.IdAllocatorV2;857 const idAllocator = yield* IdAllocator.IdAllocatorV2;
855 const runner = yield* CursorAgentSdk.CursorAgentSdkRunner;858 const runner = yield* CursorAgentSdk.CursorAgentSdkRunner;
856 const host = yield* ProviderHost.ProviderHost;859 const host = yield* ProviderHost.ProviderHost;
860 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
857 const adapterOptions = options;861 const adapterOptions = options;
858 const apiKey = adapterOptions.environment.CURSOR_API_KEY?.trim() || undefined;862 const apiKey = adapterOptions.environment.CURSOR_API_KEY?.trim() || undefined;
859863
...@@ -2091,6 +2095,7 @@ export const makeCursorAdapterV2 = Effect.fn("makeCursorAdapterV2")(function* (...@@ -2091,6 +2095,7 @@ export const makeCursorAdapterV2 = Effect.fn("makeCursorAdapterV2")(function* (
2091 modelSelection: openInput.modelSelection,2095 modelSelection: openInput.modelSelection,
2092 runtimePolicy: openInput.runtimePolicy,2096 runtimePolicy: openInput.runtimePolicy,
2093 threadId: openInput.threadId,2097 threadId: openInput.threadId,
2098 mcpSession: yield* mcpSessions.read(openInput.threadId),
2094 }),2099 }),
2095 threadId: openInput.threadId,2100 threadId: openInput.threadId,
2096 providerSessionId: input.providerSessionId,2101 providerSessionId: input.providerSessionId,
...@@ -2106,6 +2111,7 @@ export const makeCursorAdapterV2 = Effect.fn("makeCursorAdapterV2")(function* (...@@ -2106,6 +2111,7 @@ export const makeCursorAdapterV2 = Effect.fn("makeCursorAdapterV2")(function* (
2106 let cursorSkillNames: ReadonlySet<string> | undefined;2111 let cursorSkillNames: ReadonlySet<string> | undefined;
2107 const resolveUserMessage = Effect.fnUntraced(function* (2112 const resolveUserMessage = Effect.fnUntraced(function* (
2108 turnInput: ProviderAdapter.ProviderAdapterV2TurnInput,2113 turnInput: ProviderAdapter.ProviderAdapterV2TurnInput,
2114 mcpServers: Record<string, McpServerConfig> | undefined,
2109 ) {2115 ) {
2110 const rawText = turnInput.message.text;2116 const rawText = turnInput.message.text;
2111 if (rawText.trim() === "/compress" && turnInput.message.attachments.length === 0) {2117 if (rawText.trim() === "/compress" && turnInput.message.attachments.length === 0) {
...@@ -2135,7 +2141,7 @@ export const makeCursorAdapterV2 = Effect.fn("makeCursorAdapterV2")(function* (...@@ -2135,7 +2141,7 @@ export const makeCursorAdapterV2 = Effect.fn("makeCursorAdapterV2")(function* (
2135 resolveAttachmentPath: host.resolveAttachmentPath,2141 resolveAttachmentPath: host.resolveAttachmentPath,
2136 }),2142 }),
2137 runOrdinal: turnInput.runOrdinal,2143 runOrdinal: turnInput.runOrdinal,
2138 hasT3Mcp: cursorMcpServers(turnInput.threadId) !== undefined,2144 hasT3Mcp: mcpServers !== undefined,
2139 });2145 });
2140 const images = yield* Effect.forEach(2146 const images = yield* Effect.forEach(
2141 turnInput.message.attachments.filter(isProviderNativeImageAttachment),2147 turnInput.message.attachments.filter(isProviderNativeImageAttachment),
...@@ -2197,8 +2203,8 @@ export const makeCursorAdapterV2 = Effect.fn("makeCursorAdapterV2")(function* (...@@ -2197,8 +2203,8 @@ export const makeCursorAdapterV2 = Effect.fn("makeCursorAdapterV2")(function* (
2197 modelSelection: turnInput.modelSelection,2203 modelSelection: turnInput.modelSelection,
2198 runtimePolicy: turnInput.runtimePolicy,2204 runtimePolicy: turnInput.runtimePolicy,
2199 });2205 });
2200 const message = yield* resolveUserMessage(turnInput);2206 const mcpServers = cursorMcpServers(yield* mcpSessions.read(turnInput.threadId));
2201 const mcpServers = cursorMcpServers(turnInput.threadId);2207 const message = yield* resolveUserMessage(turnInput, mcpServers);
2202 const pendingUpdates: Array<InteractionUpdate> = [];2208 const pendingUpdates: Array<InteractionUpdate> = [];
2203 let context: ActiveCursorTurn | null = null;2209 let context: ActiveCursorTurn | null = null;
2204 const sdkRun = yield* agent.session.send({2210 const sdkRun = yield* agent.session.send({
...@@ -2603,6 +2609,7 @@ export type CursorAdapterV2DriverEnv =...@@ -2603,6 +2609,7 @@ export type CursorAdapterV2DriverEnv =
2603 | FileSystem.FileSystem2609 | FileSystem.FileSystem
2604 | Path.Path2610 | Path.Path
2605 | IdAllocator.IdAllocatorV22611 | IdAllocator.IdAllocatorV2
2612 | McpProviderSessions.McpProviderSessions
2606 | ProviderHost.ProviderHost;2613 | ProviderHost.ProviderHost;
26072614
2608export const CursorAdapterV2Driver: ProviderAdapterDriver<2615export const CursorAdapterV2Driver: ProviderAdapterDriver<
...@@ -2646,6 +2653,7 @@ const layer: Layer.Layer<...@@ -2646,6 +2653,7 @@ const layer: Layer.Layer<
2646 | FileSystem.FileSystem2653 | FileSystem.FileSystem
2647 | Path.Path2654 | Path.Path
2648 | IdAllocator.IdAllocatorV22655 | IdAllocator.IdAllocatorV2
2656 | McpProviderSessions.McpProviderSessions
2649 | ProviderHost.ProviderHost2657 | ProviderHost.ProviderHost
2650> = Layer.effect(2658> = Layer.effect(
2651 ProviderAdapter.ProviderAdapterV2,2659 ProviderAdapter.ProviderAdapterV2,
packages/provider-cursor/src/server/driver.test.ts+2
...@@ -15,12 +15,14 @@ import { layerTestProviderHost } from "@t3tools/provider-testing/host";...@@ -15,12 +15,14 @@ import { layerTestProviderHost } from "@t3tools/provider-testing/host";
15import { CursorDriver } from "./driver.ts";15import { CursorDriver } from "./driver.ts";
16import * as CursorAgentSdk from "./CursorAgentSdk.ts";16import * as CursorAgentSdk from "./CursorAgentSdk.ts";
17import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";17import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
18import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
18import * as ProviderAdapter from "@t3tools/provider-core/server/ProviderAdapter";19import * as ProviderAdapter from "@t3tools/provider-core/server/ProviderAdapter";
19import { Cursor } from "./sdk.ts";20import { Cursor } from "./sdk.ts";
2021
21const layerTest = layerTestProviderHost({ runBackgroundWork: false }).pipe(22const layerTest = layerTestProviderHost({ runBackgroundWork: false }).pipe(
22 Layer.provideMerge(NodeServices.layer),23 Layer.provideMerge(NodeServices.layer),
23 Layer.provideMerge(IdAllocator.layer),24 Layer.provideMerge(IdAllocator.layer),
25 Layer.provideMerge(McpProviderSessions.layer),
24 Layer.provideMerge(26 Layer.provideMerge(
25 Layer.mock(CursorAgentSdk.CursorAgentSdkRunner)({27 Layer.mock(CursorAgentSdk.CursorAgentSdkRunner)({
26 open: () => Effect.die("Maintenance resolution must not open a Cursor session"),28 open: () => Effect.die("Maintenance resolution must not open a Cursor session"),
packages/provider-grok/src/server/adapter.ts+3
...@@ -23,6 +23,7 @@ import type * as Scope from "effect/Scope";...@@ -23,6 +23,7 @@ import type * as Scope from "effect/Scope";
23import { ChildProcessSpawner } from "effect/process";23import { ChildProcessSpawner } from "effect/process";
24import * as EffectAcpErrors from "effect-acp/errors";24import * as EffectAcpErrors from "effect-acp/errors";
2525
26import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
26import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";27import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";
27import { makeAcpNativeLoggerFactory } from "@t3tools/provider-acp/server/nativeLogging";28import { makeAcpNativeLoggerFactory } from "@t3tools/provider-acp/server/nativeLogging";
28import {29import {
...@@ -352,6 +353,7 @@ export type GrokAdapterV2DriverEnv =...@@ -352,6 +353,7 @@ export type GrokAdapterV2DriverEnv =
352 | FileSystem.FileSystem353 | FileSystem.FileSystem
353 | IdAllocator.IdAllocatorV2354 | IdAllocator.IdAllocatorV2
354 | Path.Path355 | Path.Path
356 | McpProviderSessions.McpProviderSessions
355 | ProviderEventLoggers.ProviderEventLoggers357 | ProviderEventLoggers.ProviderEventLoggers
356 | ProviderHost.ProviderHost;358 | ProviderHost.ProviderHost;
357359
...@@ -405,6 +407,7 @@ const layer: Layer.Layer<...@@ -405,6 +407,7 @@ const layer: Layer.Layer<
405 | Crypto.Crypto407 | Crypto.Crypto
406 | FileSystem.FileSystem408 | FileSystem.FileSystem
407 | IdAllocator.IdAllocatorV2409 | IdAllocator.IdAllocatorV2
410 | McpProviderSessions.McpProviderSessions
408 | ProviderEventLoggers.ProviderEventLoggers411 | ProviderEventLoggers.ProviderEventLoggers
409 | ProviderHost.ProviderHost412 | ProviderHost.ProviderHost
410> = Layer.effect(413> = Layer.effect(
packages/provider-grok/src/server/driver.test.ts+2
...@@ -15,10 +15,12 @@ import { layerTestProviderHost } from "@t3tools/provider-testing/host";...@@ -15,10 +15,12 @@ import { layerTestProviderHost } from "@t3tools/provider-testing/host";
15import { GrokDriver } from "./driver.ts";15import { GrokDriver } from "./driver.ts";
1616
17import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";17import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
18import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
1819
19const layerTest = layerTestProviderHost({ runBackgroundWork: false }).pipe(20const layerTest = layerTestProviderHost({ runBackgroundWork: false }).pipe(
20 Layer.provideMerge(NodeServices.layer),21 Layer.provideMerge(NodeServices.layer),
21 Layer.provideMerge(IdAllocator.layer),22 Layer.provideMerge(IdAllocator.layer),
23 Layer.provideMerge(McpProviderSessions.layer),
22 Layer.provideMerge(ProviderLatestVersions.layer),24 Layer.provideMerge(ProviderLatestVersions.layer),
23 Layer.provideMerge(25 Layer.provideMerge(
24 Layer.succeed(26 Layer.succeed(
packages/provider-muse/src/server/adapter.test.ts+4-9
...@@ -31,7 +31,7 @@ import * as Schema from "effect/Schema";...@@ -31,7 +31,7 @@ import * as Schema from "effect/Schema";
31import * as Stream from "effect/Stream";31import * as Stream from "effect/Stream";
3232
33import { layerTestProviderHost } from "@t3tools/provider-testing/host";33import { layerTestProviderHost } from "@t3tools/provider-testing/host";
34import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession";34import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
35import type { MuseItem } from "./protocol.ts";35import type { MuseItem } from "./protocol.ts";
36import type { MuseSdkHost } from "./sdk.ts";36import type { MuseSdkHost } from "./sdk.ts";
37import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";37import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
...@@ -42,6 +42,7 @@ import { makeMuseAdapterV2, type MuseAdapterV2Options } from "./adapter.ts";...@@ -42,6 +42,7 @@ import { makeMuseAdapterV2, type MuseAdapterV2Options } from "./adapter.ts";
42const testLayer = Layer.mergeAll(42const testLayer = Layer.mergeAll(
43 NodeServices.layer,43 NodeServices.layer,
44 IdAllocator.layer,44 IdAllocator.layer,
45 McpProviderSessions.layer,
45 layerTestProviderHost().pipe(Layer.provide(NodeServices.layer)),46 layerTestProviderHost().pipe(Layer.provide(NodeServices.layer)),
46);47);
47const MUSE_PROVIDER = ProviderDriverKind.make("muse");48const MUSE_PROVIDER = ProviderDriverKind.make("muse");
...@@ -351,10 +352,7 @@ const approval = (turnId: string) => ({...@@ -351,10 +352,7 @@ const approval = (turnId: string) => ({
351describe("MuseAdapterV2", () => {352describe("MuseAdapterV2", () => {
352 it.effect("connects the thread's MCP credential on native start and resume", () =>353 it.effect("connects the thread's MCP credential on native start and resume", () =>
353 Effect.gen(function* () {354 Effect.gen(function* () {
354 yield* Effect.addFinalizer(() =>355 yield* (yield* McpProviderSessions.McpProviderSessions).set({
355 Effect.sync(() => McpProviderSession.clearMcpProviderSession(THREAD_ID)),
356 );
357 McpProviderSession.setMcpProviderSession({
358 environmentId: EnvironmentId.make("test-environment"),356 environmentId: EnvironmentId.make("test-environment"),
359 threadId: THREAD_ID,357 threadId: THREAD_ID,
360 providerSessionId: "test-session",358 providerSessionId: "test-session",
...@@ -384,10 +382,7 @@ describe("MuseAdapterV2", () => {...@@ -384,10 +382,7 @@ describe("MuseAdapterV2", () => {
384382
385 it.effect("rejects a host without session MCP support before opening a native session", () =>383 it.effect("rejects a host without session MCP support before opening a native session", () =>
386 Effect.gen(function* () {384 Effect.gen(function* () {
387 yield* Effect.addFinalizer(() =>385 yield* (yield* McpProviderSessions.McpProviderSessions).set({
388 Effect.sync(() => McpProviderSession.clearMcpProviderSession(THREAD_ID)),
389 );
390 McpProviderSession.setMcpProviderSession({
391 environmentId: EnvironmentId.make("test-environment"),386 environmentId: EnvironmentId.make("test-environment"),
392 threadId: THREAD_ID,387 threadId: THREAD_ID,
393 providerSessionId: "test-session",388 providerSessionId: "test-session",
packages/provider-muse/src/server/adapter.ts+4-2
...@@ -37,6 +37,7 @@ import * as Stream from "effect/Stream";...@@ -37,6 +37,7 @@ import * as Stream from "effect/Stream";
3737
38import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";38import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";
39import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession";39import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession";
40import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
40import { buildRuntimeInstructions } from "@t3tools/provider-core/server/runtimeInstructions";41import { buildRuntimeInstructions } from "@t3tools/provider-core/server/runtimeInstructions";
41import { museModelCapabilities, resolveMuseReasoningEffort } from "./modelCatalog.ts";42import { museModelCapabilities, resolveMuseReasoningEffort } from "./modelCatalog.ts";
42import {43import {
...@@ -248,6 +249,7 @@ export const makeMuseAdapterV2 = Effect.fn("makeMuseAdapterV2")(function* (...@@ -248,6 +249,7 @@ export const makeMuseAdapterV2 = Effect.fn("makeMuseAdapterV2")(function* (
248 const idAllocator = yield* IdAllocator.IdAllocatorV2;249 const idAllocator = yield* IdAllocator.IdAllocatorV2;
249 const providerHost = yield* ProviderHost.ProviderHost;250 const providerHost = yield* ProviderHost.ProviderHost;
250 const fileSystem = yield* FileSystem.FileSystem;251 const fileSystem = yield* FileSystem.FileSystem;
252 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
251253
252 const protocolError = (detail: string, payload?: unknown) =>254 const protocolError = (detail: string, payload?: unknown) =>
253 new ProviderAdapter.ProviderAdapterProtocolError({255 new ProviderAdapter.ProviderAdapterProtocolError({
...@@ -1364,7 +1366,7 @@ export const makeMuseAdapterV2 = Effect.fn("makeMuseAdapterV2")(function* (...@@ -1364,7 +1366,7 @@ export const makeMuseAdapterV2 = Effect.fn("makeMuseAdapterV2")(function* (
1364 );1366 );
1365 const launchHost = Effect.fnUntraced(function* () {1367 const launchHost = Effect.fnUntraced(function* () {
1366 const epoch = ++hostEpoch;1368 const epoch = ++hostEpoch;
1367 const mcpSession = McpProviderSession.readMcpProviderSession(input.threadId);1369 const mcpSession = yield* mcpSessions.read(input.threadId);
1368 const created = yield* Effect.acquireRelease(1370 const created = yield* Effect.acquireRelease(
1369 createMuseSdkHostEffect(1371 createMuseSdkHostEffect(
1370 {1372 {
...@@ -1478,7 +1480,7 @@ export const makeMuseAdapterV2 = Effect.fn("makeMuseAdapterV2")(function* (...@@ -1478,7 +1480,7 @@ export const makeMuseAdapterV2 = Effect.fn("makeMuseAdapterV2")(function* (
1478 let missingNativeSession = false;1480 let missingNativeSession = false;
1479 return yield* Effect.gen(function* () {1481 return yield* Effect.gen(function* () {
1480 nativeSessionId = requestedId ?? host.connection.mintCommandId();1482 nativeSessionId = requestedId ?? host.connection.mintCommandId();
1481 const mcpSession = McpProviderSession.readMcpProviderSession(args.threadId);1483 const mcpSession = yield* mcpSessions.read(args.threadId);
1482 if (mcpSession && !host.initializeResult.grantedCapabilities.includes("sessionMcp"))1484 if (mcpSession && !host.initializeResult.grantedCapabilities.includes("sessionMcp"))
1483 return yield* protocolError(1485 return yield* protocolError(
1484 "Update Muse Code to a version that supports session MCP servers",1486 "Update Muse Code to a version that supports session MCP servers",
packages/provider-muse/src/server/driver.ts+2
...@@ -10,6 +10,7 @@ import { ChildProcessSpawner } from "effect/process";...@@ -10,6 +10,7 @@ import { ChildProcessSpawner } from "effect/process";
1010
11import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";11import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";
12import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";12import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";
13import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
13import { expandHomePath } from "@t3tools/provider-core/server/pathExpansion";14import { expandHomePath } from "@t3tools/provider-core/server/pathExpansion";
14import { makeMuseTextGeneration } from "./textGeneration.ts";15import { makeMuseTextGeneration } from "./textGeneration.ts";
15import { ProviderDriverError } from "@t3tools/provider-core/server/errors";16import { ProviderDriverError } from "@t3tools/provider-core/server/errors";
...@@ -43,6 +44,7 @@ const decodeMuseSettings = Schema.decodeSync(MuseSettings);...@@ -43,6 +44,7 @@ const decodeMuseSettings = Schema.decodeSync(MuseSettings);
4344
44export type MuseDriverEnv =45export type MuseDriverEnv =
45 | IdAllocator.IdAllocatorV246 | IdAllocator.IdAllocatorV2
47 | McpProviderSessions.McpProviderSessions
46 | ProviderHost.ProviderHost48 | ProviderHost.ProviderHost
47 | ChildProcessSpawner.ChildProcessSpawner49 | ChildProcessSpawner.ChildProcessSpawner
48 | FileSystem.FileSystem50 | FileSystem.FileSystem
packages/provider-opencode/src/server/adapter.test.ts+91-23
...@@ -36,6 +36,7 @@ import { layerTestProviderHost } from "@t3tools/provider-testing/host";...@@ -36,6 +36,7 @@ import { layerTestProviderHost } from "@t3tools/provider-testing/host";
36import type * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";36import type * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";
37import * as OpenCodeRuntime from "./OpenCodeRuntime.ts";37import * as OpenCodeRuntime from "./OpenCodeRuntime.ts";
38import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";38import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
39import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
3940
40import {41import {
41 advanceOpenCodePromptAdmission,42 advanceOpenCodePromptAdmission,
...@@ -378,7 +379,10 @@ describe("OpenCodeAdapterV2", () => {...@@ -378,7 +379,10 @@ describe("OpenCodeAdapterV2", () => {
378 hasSubagents: false,379 hasSubagents: false,
379 },380 },
380 );381 );
381 }).pipe(Effect.provide(IdAllocator.layer), Effect.scoped),382 }).pipe(
383 Effect.provide(Layer.merge(IdAllocator.layer, McpProviderSessions.layer)),
384 Effect.scoped,
385 ),
382 );386 );
383387
384 it.effect.each(["permission", "question"] as const)(388 it.effect.each(["permission", "question"] as const)(
...@@ -456,7 +460,10 @@ describe("OpenCodeAdapterV2", () => {...@@ -456,7 +460,10 @@ describe("OpenCodeAdapterV2", () => {
456 deliver = true;460 deliver = true;
457 // Failed delivery leaves the request available for an explicit retry.461 // Failed delivery leaves the request available for an explicit retry.
458 yield* harness.runtime.respondToRuntimeRequest(response);462 yield* harness.runtime.respondToRuntimeRequest(response);
459 }).pipe(Effect.provide(IdAllocator.layer), Effect.scoped),463 }).pipe(
464 Effect.provide(Layer.merge(IdAllocator.layer, McpProviderSessions.layer)),
465 Effect.scoped,
466 ),
460 );467 );
461468
462 it.effect("aborts external root and descendants before closing the event stream", () =>469 it.effect("aborts external root and descendants before closing the event stream", () =>
...@@ -494,7 +501,10 @@ describe("OpenCodeAdapterV2", () => {...@@ -494,7 +501,10 @@ describe("OpenCodeAdapterV2", () => {
494 "children:child",501 "children:child",
495 "stream.close",502 "stream.close",
496 ]);503 ]);
497 }).pipe(Effect.provide(IdAllocator.layer), Effect.scoped),504 }).pipe(
505 Effect.provide(Layer.merge(IdAllocator.layer, McpProviderSessions.layer)),
506 Effect.scoped,
507 ),
498 );508 );
499509
500 it.effect.each(["enumeration", "abort", "not-found", "timeout"] as const)(510 it.effect.each(["enumeration", "abort", "not-found", "timeout"] as const)(
...@@ -548,7 +558,10 @@ describe("OpenCodeAdapterV2", () => {...@@ -548,7 +558,10 @@ describe("OpenCodeAdapterV2", () => {
548 const result = yield* Fiber.join(stop);558 const result = yield* Fiber.join(stop);
549 assert.equal(Exit.isSuccess(result), failure === "not-found");559 assert.equal(Exit.isSuccess(result), failure === "not-found");
550 if (failure === "timeout") assert.isTrue(childSignal?.aborted);560 if (failure === "timeout") assert.isTrue(childSignal?.aborted);
551 }).pipe(Effect.provide(IdAllocator.layer), Effect.scoped),561 }).pipe(
562 Effect.provide(Layer.merge(IdAllocator.layer, McpProviderSessions.layer)),
563 Effect.scoped,
564 ),
552 );565 );
553566
554 it.effect(567 it.effect(
...@@ -712,7 +725,10 @@ describe("OpenCodeAdapterV2", () => {...@@ -712,7 +725,10 @@ describe("OpenCodeAdapterV2", () => {
712 const assistant = items.filter((item) => item.type === "assistant_message");725 const assistant = items.filter((item) => item.type === "assistant_message");
713 assert.equal(assistant.at(-1)?.text, "Tool results received");726 assert.equal(assistant.at(-1)?.text, "Tool results received");
714 assert.equal(assistant.at(-1)?.status, "completed");727 assert.equal(assistant.at(-1)?.status, "completed");
715 }).pipe(Effect.provide(IdAllocator.layer), Effect.scoped),728 }).pipe(
729 Effect.provide(Layer.merge(IdAllocator.layer, McpProviderSessions.layer)),
730 Effect.scoped,
731 ),
716 );732 );
717733
718 // Event order from a live OpenCode 1.18.32 run of a `task` call with734 // Event order from a live OpenCode 1.18.32 run of a `task` call with
...@@ -807,7 +823,10 @@ describe("OpenCodeAdapterV2", () => {...@@ -807,7 +823,10 @@ describe("OpenCodeAdapterV2", () => {
807 yield* push(status(child, "idle"));823 yield* push(status(child, "idle"));
808 yield* push({ type: "session.idle", properties: { sessionID: child } });824 yield* push({ type: "session.idle", properties: { sessionID: child } });
809 assert.isFalse(yield* hasPendingBackgroundWork, "an idle child must not pin idle release");825 assert.isFalse(yield* hasPendingBackgroundWork, "an idle child must not pin idle release");
810 }).pipe(Effect.provide(IdAllocator.layer), Effect.scoped),826 }).pipe(
827 Effect.provide(Layer.merge(IdAllocator.layer, McpProviderSessions.layer)),
828 Effect.scoped,
829 ),
811 );830 );
812831
813 it.effect("titles OpenCode reads and searches from their input", () =>832 it.effect("titles OpenCode reads and searches from their input", () =>
...@@ -902,7 +921,10 @@ describe("OpenCodeAdapterV2", () => {...@@ -902,7 +921,10 @@ describe("OpenCodeAdapterV2", () => {
902 );921 );
903 assert.ok(emptyWebSearch?.type === "web_search");922 assert.ok(emptyWebSearch?.type === "web_search");
904 assert.equal(emptyWebSearch.results, undefined);923 assert.equal(emptyWebSearch.results, undefined);
905 }).pipe(Effect.provide(IdAllocator.layer), Effect.scoped),924 }).pipe(
925 Effect.provide(Layer.merge(IdAllocator.layer, McpProviderSessions.layer)),
926 Effect.scoped,
927 ),
906 );928 );
907929
908 it.effect("presents OpenCode MCP calls without treating remote tools as local edits", () =>930 it.effect("presents OpenCode MCP calls without treating remote tools as local edits", () =>
...@@ -1058,7 +1080,10 @@ describe("OpenCodeAdapterV2", () => {...@@ -1058,7 +1080,10 @@ describe("OpenCodeAdapterV2", () => {
1058 assert.equal(items[3]?.toolName, "ambiguous_server_edit_document");1080 assert.equal(items[3]?.toolName, "ambiguous_server_edit_document");
1059 assert.isUndefined(items[3]?.toolSource);1081 assert.isUndefined(items[3]?.toolSource);
1060 assert.equal(statusReads, 1);1082 assert.equal(statusReads, 1);
1061 }).pipe(Effect.provide(IdAllocator.layer), Effect.scoped),1083 }).pipe(
1084 Effect.provide(Layer.merge(IdAllocator.layer, McpProviderSessions.layer)),
1085 Effect.scoped,
1086 ),
1062 );1087 );
10631088
1064 it.effect.each(["failure", "timeout"] as const)("retries MCP status after %s", (failure) =>1089 it.effect.each(["failure", "timeout"] as const)("retries MCP status after %s", (failure) =>
...@@ -1146,7 +1171,10 @@ describe("OpenCodeAdapterV2", () => {...@@ -1146,7 +1171,10 @@ describe("OpenCodeAdapterV2", () => {
1146 kind: "integration",1171 kind: "integration",
1147 });1172 });
1148 if (failure === "timeout") assert.isTrue(signal?.aborted);1173 if (failure === "timeout") assert.isTrue(signal?.aborted);
1149 }).pipe(Effect.provide(IdAllocator.layer), Effect.scoped),1174 }).pipe(
1175 Effect.provide(Layer.merge(IdAllocator.layer, McpProviderSessions.layer)),
1176 Effect.scoped,
1177 ),
1150 );1178 );
11511179
1152 it.effect("admits a native command on its user receipt before generation completes", () =>1180 it.effect("admits a native command on its user receipt before generation completes", () =>
...@@ -1209,7 +1237,10 @@ describe("OpenCodeAdapterV2", () => {...@@ -1209,7 +1237,10 @@ describe("OpenCodeAdapterV2", () => {
1209 yield* Fiber.join(start);1237 yield* Fiber.join(start);
1210 assert.equal(promptCalls, 0);1238 assert.equal(promptCalls, 0);
1211 release.resolve();1239 release.resolve();
1212 }).pipe(Effect.provide(IdAllocator.layer), Effect.scoped),1240 }).pipe(
1241 Effect.provide(Layer.merge(IdAllocator.layer, McpProviderSessions.layer)),
1242 Effect.scoped,
1243 ),
1213 );1244 );
12141245
1215 it.effect("sends unadvertised slash commands as ordinary prompts", () =>1246 it.effect("sends unadvertised slash commands as ordinary prompts", () =>
...@@ -1236,7 +1267,10 @@ describe("OpenCodeAdapterV2", () => {...@@ -1236,7 +1267,10 @@ describe("OpenCodeAdapterV2", () => {
1236 });1267 });
1237 yield* harness.startTurn("/unknown words");1268 yield* harness.startTurn("/unknown words");
1238 assert.deepEqual(prompts, [[{ type: "text", text: "/unknown words" }]]);1269 assert.deepEqual(prompts, [[{ type: "text", text: "/unknown words" }]]);
1239 }).pipe(Effect.provide(IdAllocator.layer), Effect.scoped),1270 }).pipe(
1271 Effect.provide(Layer.merge(IdAllocator.layer, McpProviderSessions.layer)),
1272 Effect.scoped,
1273 ),
1240 );1274 );
12411275
1242 it.effect("compacts with the native summarize API and emits a completed compaction", () =>1276 it.effect("compacts with the native summarize API and emits a completed compaction", () =>
...@@ -1291,7 +1325,10 @@ describe("OpenCodeAdapterV2", () => {...@@ -1291,7 +1325,10 @@ describe("OpenCodeAdapterV2", () => {
1291 assert.isTrue(1325 assert.isTrue(
1292 events.some((event) => event.type === "turn.terminal" && event.status === "completed"),1326 events.some((event) => event.type === "turn.terminal" && event.status === "completed"),
1293 );1327 );
1294 }).pipe(Effect.provide(IdAllocator.layer), Effect.scoped),1328 }).pipe(
1329 Effect.provide(Layer.merge(IdAllocator.layer, McpProviderSessions.layer)),
1330 Effect.scoped,
1331 ),
1295 );1332 );
12961333
1297 it.effect("ends a turn on the provider thread it started on", () =>1334 it.effect("ends a turn on the provider thread it started on", () =>
...@@ -1330,7 +1367,10 @@ describe("OpenCodeAdapterV2", () => {...@@ -1330,7 +1367,10 @@ describe("OpenCodeAdapterV2", () => {
1330 Stream.runHead,1367 Stream.runHead,
1331 );1368 );
1332 assert.equal(Option.getOrUndefined(terminal)?.providerThreadId, forkedRow.id);1369 assert.equal(Option.getOrUndefined(terminal)?.providerThreadId, forkedRow.id);
1333 }).pipe(Effect.provide(IdAllocator.layer), Effect.scoped),1370 }).pipe(
1371 Effect.provide(Layer.merge(IdAllocator.layer, McpProviderSessions.layer)),
1372 Effect.scoped,
1373 ),
1334 );1374 );
13351375
1336 it.effect("does not restore messages beyond OpenCode's persisted revert boundary", () =>1376 it.effect("does not restore messages beyond OpenCode's persisted revert boundary", () =>
...@@ -1415,7 +1455,10 @@ describe("OpenCodeAdapterV2", () => {...@@ -1415,7 +1455,10 @@ describe("OpenCodeAdapterV2", () => {
1415 ["user-kept", "assistant-kept"],1455 ["user-kept", "assistant-kept"],
1416 );1456 );
1417 assert.equal(snapshot.providerThread.nativeConversationHeadRef?.nativeId, "user-kept");1457 assert.equal(snapshot.providerThread.nativeConversationHeadRef?.nativeId, "user-kept");
1418 }).pipe(Effect.provide(IdAllocator.layer), Effect.scoped),1458 }).pipe(
1459 Effect.provide(Layer.merge(IdAllocator.layer, McpProviderSessions.layer)),
1460 Effect.scoped,
1461 ),
1419 );1462 );
14201463
1421 it.effect("keeps a newly admitted prompt alive across stale idle and delayed busy evidence", () =>1464 it.effect("keeps a newly admitted prompt alive across stale idle and delayed busy evidence", () =>
...@@ -1732,7 +1775,10 @@ describe("OpenCodeAdapterV2", () => {...@@ -1732,7 +1775,10 @@ describe("OpenCodeAdapterV2", () => {
1732 );1775 );
1733 const interrupted = yield* runtime.readThreadSnapshot({ providerThread });1776 const interrupted = yield* runtime.readThreadSnapshot({ providerThread });
1734 assert.equal(interrupted.providerTurns.at(-1)?.status, "interrupted");1777 assert.equal(interrupted.providerTurns.at(-1)?.status, "interrupted");
1735 }).pipe(Effect.provide(IdAllocator.layer), Effect.scoped),1778 }).pipe(
1779 Effect.provide(Layer.merge(IdAllocator.layer, McpProviderSessions.layer)),
1780 Effect.scoped,
1781 ),
1736 );1782 );
17371783
1738 it.effect("interrupts an initial prompt while its SDK request is pending", () =>1784 it.effect("interrupts an initial prompt while its SDK request is pending", () =>
...@@ -1897,7 +1943,10 @@ describe("OpenCodeAdapterV2", () => {...@@ -1897,7 +1943,10 @@ describe("OpenCodeAdapterV2", () => {
1897 (event.type === "provider_session.updated" && event.providerSession.status === "error"),1943 (event.type === "provider_session.updated" && event.providerSession.status === "error"),
1898 ),1944 ),
1899 );1945 );
1900 }).pipe(Effect.provide(IdAllocator.layer), Effect.scoped),1946 }).pipe(
1947 Effect.provide(Layer.merge(IdAllocator.layer, McpProviderSessions.layer)),
1948 Effect.scoped,
1949 ),
1901 );1950 );
19021951
1903 it.effect("fails an active turn when the OpenCode event stream ends cleanly", () =>1952 it.effect("fails an active turn when the OpenCode event stream ends cleanly", () =>
...@@ -1940,7 +1989,10 @@ describe("OpenCodeAdapterV2", () => {...@@ -1940,7 +1989,10 @@ describe("OpenCodeAdapterV2", () => {
1940 assert.equal(terminal?.failure?.class, "transport_error");1989 assert.equal(terminal?.failure?.class, "transport_error");
1941 assert.equal(terminal?.threadDisposition, "broken");1990 assert.equal(terminal?.threadDisposition, "broken");
1942 assert.equal((yield* Effect.exit(harness.startTurn()))._tag, "Failure");1991 assert.equal((yield* Effect.exit(harness.startTurn()))._tag, "Failure");
1943 }).pipe(Effect.provide(IdAllocator.layer), Effect.scoped),1992 }).pipe(
1993 Effect.provide(Layer.merge(IdAllocator.layer, McpProviderSessions.layer)),
1994 Effect.scoped,
1995 ),
1944 );1996 );
19451997
1946 it.effect("fails compaction when its response races stream termination", () =>1998 it.effect("fails compaction when its response races stream termination", () =>
...@@ -2001,7 +2053,10 @@ describe("OpenCodeAdapterV2", () => {...@@ -2001,7 +2053,10 @@ describe("OpenCodeAdapterV2", () => {
2001 event.turnItem.status === "completed",2053 event.turnItem.status === "completed",
2002 ),2054 ),
2003 );2055 );
2004 }).pipe(Effect.provide(IdAllocator.layer), Effect.scoped),2056 }).pipe(
2057 Effect.provide(Layer.merge(IdAllocator.layer, McpProviderSessions.layer)),
2058 Effect.scoped,
2059 ),
2005 );2060 );
20062061
2007 it.effect("does not register a turn after the OpenCode event stream ends", () =>2062 it.effect("does not register a turn after the OpenCode event stream ends", () =>
...@@ -2059,7 +2114,10 @@ describe("OpenCodeAdapterV2", () => {...@@ -2059,7 +2114,10 @@ describe("OpenCodeAdapterV2", () => {
20592114
2060 assert.isTrue(Exit.isFailure(yield* Fiber.join(start)));2115 assert.isTrue(Exit.isFailure(yield* Fiber.join(start)));
2061 assert.equal(promptCalls, 0);2116 assert.equal(promptCalls, 0);
2062 }).pipe(Effect.provide(IdAllocator.layer), Effect.scoped),2117 }).pipe(
2118 Effect.provide(Layer.merge(IdAllocator.layer, McpProviderSessions.layer)),
2119 Effect.scoped,
2120 ),
2063 );2121 );
20642122
2065 it("holds stale idle through prompt admission until the new user message is observed", () => {2123 it("holds stale idle through prompt admission until the new user message is observed", () => {
...@@ -2237,7 +2295,10 @@ describe("OpenCodeAdapterV2", () => {...@@ -2237,7 +2295,10 @@ describe("OpenCodeAdapterV2", () => {
2237 assert.lengthOf(terminals, 1);2295 assert.lengthOf(terminals, 1);
2238 assert.equal(terminals[0]?.status, "completed");2296 assert.equal(terminals[0]?.status, "completed");
2239 assert.isNull(terminals[0]?.failure ?? null);2297 assert.isNull(terminals[0]?.failure ?? null);
2240 }).pipe(Effect.provide(IdAllocator.layer), Effect.scoped),2298 }).pipe(
2299 Effect.provide(Layer.merge(IdAllocator.layer, McpProviderSessions.layer)),
2300 Effect.scoped,
2301 ),
2241 );2302 );
22422303
2243 it.effect("does not let a queued status retry adopt a newer steer generation", () =>2304 it.effect("does not let a queued status retry adopt a newer steer generation", () =>
...@@ -2340,7 +2401,10 @@ describe("OpenCodeAdapterV2", () => {...@@ -2340,7 +2401,10 @@ describe("OpenCodeAdapterV2", () => {
2340 });2401 });
2341 assert.equal(statusCallCount, 1);2402 assert.equal(statusCallCount, 1);
2342 assert.equal(afterRetry.providerTurns.at(-1)?.status, "running");2403 assert.equal(afterRetry.providerTurns.at(-1)?.status, "running");
2343 }).pipe(Effect.provide(IdAllocator.layer), Effect.scoped),2404 }).pipe(
2405 Effect.provide(Layer.merge(IdAllocator.layer, McpProviderSessions.layer)),
2406 Effect.scoped,
2407 ),
2344 );2408 );
23452409
2346 it.effect("ignores a delayed status reply after steering starts a newer admission", () =>2410 it.effect("ignores a delayed status reply after steering starts a newer admission", () =>
...@@ -2411,7 +2475,7 @@ describe("OpenCodeAdapterV2", () => {...@@ -2411,7 +2475,7 @@ describe("OpenCodeAdapterV2", () => {
2411 assert.include(serialized, '"protocol":"opencode-sdk.sse"');2475 assert.include(serialized, '"protocol":"opencode-sdk.sse"');
2412 assert.include(serialized, '"method":"session.prompt"');2476 assert.include(serialized, '"method":"session.prompt"');
2413 assert.include(serialized, '"fieldCount":2');2477 assert.include(serialized, '"fieldCount":2');
2414 }).pipe(Effect.provide(IdAllocator.layer)),2478 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, McpProviderSessions.layer))),
2415 );2479 );
24162480
2417 it.effect("adopts the handed-over provider thread identity on session create", () =>2481 it.effect("adopts the handed-over provider thread identity on session create", () =>
...@@ -2520,6 +2584,7 @@ describe("OpenCodeAdapterV2", () => {...@@ -2520,6 +2584,7 @@ describe("OpenCodeAdapterV2", () => {
2520 Effect.provide(2584 Effect.provide(
2521 Layer.mergeAll(2585 Layer.mergeAll(
2522 IdAllocator.layer,2586 IdAllocator.layer,
2587 McpProviderSessions.layer,
2523 layerTestProviderHost().pipe(Layer.provide(NodeServices.layer)),2588 layerTestProviderHost().pipe(Layer.provide(NodeServices.layer)),
2524 ),2589 ),
2525 ),2590 ),
...@@ -2718,5 +2783,8 @@ it.effect.each([false, true])(...@@ -2718,5 +2783,8 @@ it.effect.each([false, true])(
2718 assert.equal(result.messages.length, 0);2783 assert.equal(result.messages.length, 0);
2719 assert.deepEqual(calls, ["fork", "permissions"]);2784 assert.deepEqual(calls, ["fork", "permissions"]);
2720 }2785 }
2721 }).pipe(Effect.provide(IdAllocator.layer), Effect.scoped),2786 }).pipe(
2787 Effect.provide(Layer.merge(IdAllocator.layer, McpProviderSessions.layer)),
2788 Effect.scoped,
2789 ),
2722);2790);
packages/provider-opencode/src/server/adapter.ts+4-2
...@@ -55,7 +55,7 @@ import * as Stream from "effect/Stream";...@@ -55,7 +55,7 @@ import * as Stream from "effect/Stream";
5555
56import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";56import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";
57import { mcpToolPresentation } from "@t3tools/provider-core/server/mcpToolPresentation";57import { mcpToolPresentation } from "@t3tools/provider-core/server/mcpToolPresentation";
58import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession";58import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
59import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";59import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";
60import {60import {
61 structuralProtocolMethod,61 structuralProtocolMethod,
...@@ -947,6 +947,7 @@ export const makeOpenCodeAdapterV2 = Effect.fn("makeOpenCodeAdapterV2")(function...@@ -947,6 +947,7 @@ export const makeOpenCodeAdapterV2 = Effect.fn("makeOpenCodeAdapterV2")(function
947 const runtime = yield* OpenCodeRuntime.OpenCodeRuntime;947 const runtime = yield* OpenCodeRuntime.OpenCodeRuntime;
948 const idAllocator = yield* IdAllocator.IdAllocatorV2;948 const idAllocator = yield* IdAllocator.IdAllocatorV2;
949 const host = yield* ProviderHost.ProviderHost;949 const host = yield* ProviderHost.ProviderHost;
950 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
950951
951 return ProviderAdapter.ProviderAdapterV2.of({952 return ProviderAdapter.ProviderAdapterV2.of({
952 instanceId: options.instanceId,953 instanceId: options.instanceId,
...@@ -971,7 +972,7 @@ export const makeOpenCodeAdapterV2 = Effect.fn("makeOpenCodeAdapterV2")(function...@@ -971,7 +972,7 @@ export const makeOpenCodeAdapterV2 = Effect.fn("makeOpenCodeAdapterV2")(function
971 : {}),972 : {}),
972 });973 });
973974
974 const mcpSession = McpProviderSession.readMcpProviderSession(input.threadId);975 const mcpSession = yield* mcpSessions.read(input.threadId);
975 const hasT3Mcp = mcpSession !== undefined && !connection.external;976 const hasT3Mcp = mcpSession !== undefined && !connection.external;
976 const orchestrationSystemPrompt = t3OrchestrationSystemPrompt(hasT3Mcp);977 const orchestrationSystemPrompt = t3OrchestrationSystemPrompt(hasT3Mcp);
977 if (hasT3Mcp) {978 if (hasT3Mcp) {
...@@ -3728,6 +3729,7 @@ export const makeOpenCodeAdapterV2 = Effect.fn("makeOpenCodeAdapterV2")(function...@@ -3728,6 +3729,7 @@ export const makeOpenCodeAdapterV2 = Effect.fn("makeOpenCodeAdapterV2")(function
3728export type OpenCodeAdapterV2DriverEnv =3729export type OpenCodeAdapterV2DriverEnv =
3729 | OpenCodeRuntime.OpenCodeRuntime3730 | OpenCodeRuntime.OpenCodeRuntime
3730 | IdAllocator.IdAllocatorV23731 | IdAllocator.IdAllocatorV2
3732 | McpProviderSessions.McpProviderSessions
3731 | ProviderEventLoggers.ProviderEventLoggers3733 | ProviderEventLoggers.ProviderEventLoggers
3732 | ProviderHost.ProviderHost;3734 | ProviderHost.ProviderHost;
37333735
packages/provider-opencode/src/server/driver.test.ts+3
...@@ -17,6 +17,7 @@ import { ChildProcessSpawner } from "effect/process";...@@ -17,6 +17,7 @@ import { ChildProcessSpawner } from "effect/process";
1717
18import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";18import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";
19import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";19import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
20import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
20import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";21import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";
21import * as ProviderMaintenance from "@t3tools/provider-core/server/maintenanceResolver";22import * as ProviderMaintenance from "@t3tools/provider-core/server/maintenanceResolver";
22import * as OpenCodeRuntime from "./OpenCodeRuntime.ts";23import * as OpenCodeRuntime from "./OpenCodeRuntime.ts";
...@@ -49,6 +50,7 @@ const openCode2Runtime = {...@@ -49,6 +50,7 @@ const openCode2Runtime = {
4950
50const layer = Layer.mergeAll(51const layer = Layer.mergeAll(
51 IdAllocator.layer,52 IdAllocator.layer,
53 McpProviderSessions.layer,
52 ProviderLatestVersions.layer,54 ProviderLatestVersions.layer,
53 layerTestProviderHost(),55 layerTestProviderHost(),
54 Layer.succeed(56 Layer.succeed(
...@@ -212,6 +214,7 @@ const changingRuntime = {...@@ -212,6 +214,7 @@ const changingRuntime = {
212} as unknown as OpenCodeRuntime.OpenCodeRuntimeShape;214} as unknown as OpenCodeRuntime.OpenCodeRuntimeShape;
213const layerUpdate = Layer.mergeAll(215const layerUpdate = Layer.mergeAll(
214 IdAllocator.layer,216 IdAllocator.layer,
217 McpProviderSessions.layer,
215 ProviderLatestVersions.layer,218 ProviderLatestVersions.layer,
216 layerTestProviderHost(),219 layerTestProviderHost(),
217 Layer.succeed(220 Layer.succeed(
packages/provider-opencode/src/server/v2/adapter.ts+3-2
...@@ -74,7 +74,7 @@ import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";...@@ -74,7 +74,7 @@ import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";
74import * as OpenCode2Client from "./OpenCode2Client.ts";74import * as OpenCode2Client from "./OpenCode2Client.ts";
75import * as OpenCode2Server from "./OpenCode2Server.ts";75import * as OpenCode2Server from "./OpenCode2Server.ts";
76import * as OpenCodeRuntime from "../OpenCodeRuntime.ts";76import * as OpenCodeRuntime from "../OpenCodeRuntime.ts";
77import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession";77import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
78import { buildRuntimeInstructions } from "@t3tools/provider-core/server/runtimeInstructions";78import { buildRuntimeInstructions } from "@t3tools/provider-core/server/runtimeInstructions";
79import { t3OrchestrationSystemPrompt } from "@t3tools/provider-core/server/orchestrationInstructions";79import { t3OrchestrationSystemPrompt } from "@t3tools/provider-core/server/orchestrationInstructions";
80import { SKILL_MENTION_PATTERN } from "@t3tools/shared/composerInlineTokens";80import { SKILL_MENTION_PATTERN } from "@t3tools/shared/composerInlineTokens";
...@@ -831,6 +831,7 @@ export const make = Effect.fn("OpenCode2Adapter.make")(function* (instanceId: Pr...@@ -831,6 +831,7 @@ export const make = Effect.fn("OpenCode2Adapter.make")(function* (instanceId: Pr
831 const server = yield* OpenCode2Server.OpenCode2Server;831 const server = yield* OpenCode2Server.OpenCode2Server;
832 const idAllocator = yield* IdAllocator.IdAllocatorV2;832 const idAllocator = yield* IdAllocator.IdAllocatorV2;
833 const host = yield* ProviderHost.ProviderHost;833 const host = yield* ProviderHost.ProviderHost;
834 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
834 const continuationRequests = yield* ProviderContinuationRequests.ProviderContinuationRequests;835 const continuationRequests = yield* ProviderContinuationRequests.ProviderContinuationRequests;
835 const crypto = yield* Crypto.Crypto;836 const crypto = yield* Crypto.Crypto;
836 const driver = OPENCODE_PROVIDER;837 const driver = OPENCODE_PROVIDER;
...@@ -3251,7 +3252,7 @@ export const make = Effect.fn("OpenCode2Adapter.make")(function* (instanceId: Pr...@@ -3251,7 +3252,7 @@ export const make = Effect.fn("OpenCode2Adapter.make")(function* (instanceId: Pr
3251 state: ThreadState,3252 state: ThreadState,
3252 turnInput: ProviderAdapter.ProviderAdapterV2TurnInput,3253 turnInput: ProviderAdapter.ProviderAdapterV2TurnInput,
3253 ) {3254 ) {
3254 const mcpSession = McpProviderSession.readMcpProviderSession(turnInput.threadId);3255 const mcpSession = yield* mcpSessions.read(turnInput.threadId);
3255 const directory = turnInput.runtimePolicy.cwd ?? host.paths.cwd;3256 const directory = turnInput.runtimePolicy.cwd ?? host.paths.cwd;
3256 const name = yield* mcpServerNameFor(turnInput.threadId);3257 const name = yield* mcpServerNameFor(turnInput.threadId);
3257 // An external server may not reach T3's MCP endpoint, as with 1.x.3258 // An external server may not reach T3's MCP endpoint, as with 1.x.
packages/provider-pi/src/server/adapter.test.ts+4-7
...@@ -33,7 +33,7 @@ import { ChildProcess, ChildProcessSpawner } from "effect/process";...@@ -33,7 +33,7 @@ import { ChildProcess, ChildProcessSpawner } from "effect/process";
3333
34import { layerTestProviderHost } from "@t3tools/provider-testing/host";34import { layerTestProviderHost } from "@t3tools/provider-testing/host";
35import * as ProviderContinuationRequests from "@t3tools/provider-core/server/ProviderContinuationRequests";35import * as ProviderContinuationRequests from "@t3tools/provider-core/server/ProviderContinuationRequests";
36import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession";36import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
37import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";37import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
38import * as ProviderAdapter from "@t3tools/provider-core/server/ProviderAdapter";38import * as ProviderAdapter from "@t3tools/provider-core/server/ProviderAdapter";
39import { handoffBudget } from "@t3tools/provider-core/server/handoffBudget";39import { handoffBudget } from "@t3tools/provider-core/server/handoffBudget";
...@@ -49,6 +49,7 @@ import { makePiRpcConnection, type PiRpcRecord } from "./rpc.ts";...@@ -49,6 +49,7 @@ import { makePiRpcConnection, type PiRpcRecord } from "./rpc.ts";
49const layerTest = Layer.mergeAll(49const layerTest = Layer.mergeAll(
50 NodeServices.layer,50 NodeServices.layer,
51 IdAllocator.layer,51 IdAllocator.layer,
52 McpProviderSessions.layer,
52 layerTestProviderHost().pipe(Layer.provide(NodeServices.layer)),53 layerTestProviderHost().pipe(Layer.provide(NodeServices.layer)),
53);54);
5455
...@@ -1285,7 +1286,7 @@ describe("PiAdapterV2", () => {...@@ -1285,7 +1286,7 @@ describe("PiAdapterV2", () => {
12851286
1286 it.effect("injects the T3 MCP extension and bearer when a session exists", () =>1287 it.effect("injects the T3 MCP extension and bearer when a session exists", () =>
1287 Effect.gen(function* () {1288 Effect.gen(function* () {
1288 McpProviderSession.setMcpProviderSession({1289 yield* (yield* McpProviderSessions.McpProviderSessions).set({
1289 environmentId: EnvironmentId.make("environment-pi-mcp"),1290 environmentId: EnvironmentId.make("environment-pi-mcp"),
1290 threadId: THREAD_ID,1291 threadId: THREAD_ID,
1291 providerSessionId: "mcp-session-pi",1292 providerSessionId: "mcp-session-pi",
...@@ -1306,11 +1307,7 @@ describe("PiAdapterV2", () => {...@@ -1306,11 +1307,7 @@ describe("PiAdapterV2", () => {
1306 assert.equal(spawn.env.T3_MCP_URL, "http://127.0.0.1:43123/mcp");1307 assert.equal(spawn.env.T3_MCP_URL, "http://127.0.0.1:43123/mcp");
1307 assert.equal(spawn.env.T3_MCP_BEARER_TOKEN, "secret-pi-token");1308 assert.equal(spawn.env.T3_MCP_BEARER_TOKEN, "secret-pi-token");
1308 assert.equal(spawn.env.T3_PI_RUNTIME_MODE, "full-access");1309 assert.equal(spawn.env.T3_PI_RUNTIME_MODE, "full-access");
1309 }).pipe(1310 }).pipe(Effect.scoped, Effect.provide(layerTest)),
1310 Effect.ensuring(Effect.sync(() => McpProviderSession.clearMcpProviderSession(THREAD_ID))),
1311 Effect.scoped,
1312 Effect.provide(layerTest),
1313 ),
1314 );1311 );
13151312
1316 it.effect("rejects a resume while a turn is active", () =>1313 it.effect("rejects a resume while a turn is active", () =>
packages/provider-pi/src/server/adapter.ts+4-2
...@@ -62,7 +62,7 @@ import { ChildProcessSpawner } from "effect/process";...@@ -62,7 +62,7 @@ import { ChildProcessSpawner } from "effect/process";
6262
63import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";63import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";
64import { mcpToolPresentation } from "@t3tools/provider-core/server/mcpToolPresentation";64import { mcpToolPresentation } from "@t3tools/provider-core/server/mcpToolPresentation";
65import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession";65import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
66import { parsePiCompactCommand, type PiCompactCommand } from "./commands.ts";66import { parsePiCompactCommand, type PiCompactCommand } from "./commands.ts";
67import { mergeProviderInstanceEnvironment } from "@t3tools/provider-core/server/instanceEnvironment";67import { mergeProviderInstanceEnvironment } from "@t3tools/provider-core/server/instanceEnvironment";
68import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";68import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
...@@ -390,6 +390,7 @@ export const makePiAdapterV2 = Effect.fn("makePiAdapterV2")(function* (...@@ -390,6 +390,7 @@ export const makePiAdapterV2 = Effect.fn("makePiAdapterV2")(function* (
390 const fileSystem = yield* FileSystem.FileSystem;390 const fileSystem = yield* FileSystem.FileSystem;
391 const idAllocator = yield* IdAllocator.IdAllocatorV2;391 const idAllocator = yield* IdAllocator.IdAllocatorV2;
392 const host = yield* ProviderHost.ProviderHost;392 const host = yield* ProviderHost.ProviderHost;
393 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
393 const { continuationRequests } = options;394 const { continuationRequests } = options;
394395
395 const protocolError = (detail: string, payload?: unknown) =>396 const protocolError = (detail: string, payload?: unknown) =>
...@@ -409,7 +410,7 @@ export const makePiAdapterV2 = Effect.fn("makePiAdapterV2")(function* (...@@ -409,7 +410,7 @@ export const makePiAdapterV2 = Effect.fn("makePiAdapterV2")(function* (
409 ) {410 ) {
410 const scope = yield* Effect.scope;411 const scope = yield* Effect.scope;
411 const cwd = input.runtimePolicy.cwd ?? host.paths.cwd;412 const cwd = input.runtimePolicy.cwd ?? host.paths.cwd;
412 const mcpSession = McpProviderSession.readMcpProviderSession(input.threadId);413 const mcpSession = yield* mcpSessions.read(input.threadId);
413 const provideCacheFs = <A, E>(effect: Effect.Effect<A, E, FileSystem.FileSystem>) =>414 const provideCacheFs = <A, E>(effect: Effect.Effect<A, E, FileSystem.FileSystem>) =>
414 effect.pipe(415 effect.pipe(
415 Effect.provideService(FileSystem.FileSystem, fileSystem),416 Effect.provideService(FileSystem.FileSystem, fileSystem),
...@@ -3261,6 +3262,7 @@ export type PiAdapterV2DriverEnv =...@@ -3261,6 +3262,7 @@ export type PiAdapterV2DriverEnv =
3261 | ChildProcessSpawner.ChildProcessSpawner3262 | ChildProcessSpawner.ChildProcessSpawner
3262 | FileSystem.FileSystem3263 | FileSystem.FileSystem
3263 | IdAllocator.IdAllocatorV23264 | IdAllocator.IdAllocatorV2
3265 | McpProviderSessions.McpProviderSessions
3264 | ProviderHost.ProviderHost;3266 | ProviderHost.ProviderHost;
32653267
3266export const PiAdapterV2Driver: ProviderAdapterDriver<PiSettings, PiAdapterV2DriverEnv> = {3268export const PiAdapterV2Driver: ProviderAdapterDriver<PiSettings, PiAdapterV2DriverEnv> = {
packages/provider-pi/src/server/driver.test.ts+2
...@@ -15,6 +15,7 @@ import { ChildProcess, ChildProcessSpawner } from "effect/process";...@@ -15,6 +15,7 @@ import { ChildProcess, ChildProcessSpawner } from "effect/process";
1515
16import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";16import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";
17import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";17import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
18import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
18import { layerTestProviderHost } from "@t3tools/provider-testing/host";19import { layerTestProviderHost } from "@t3tools/provider-testing/host";
19import type { PiSettings } from "../settings.ts";20import type { PiSettings } from "../settings.ts";
20import { PiDriver } from "./driver.ts";21import { PiDriver } from "./driver.ts";
...@@ -26,6 +27,7 @@ const layerTest = Layer.mergeAll(...@@ -26,6 +27,7 @@ const layerTest = Layer.mergeAll(
26 runBackgroundWork: false,27 runBackgroundWork: false,
27 }),28 }),
28 IdAllocator.layer,29 IdAllocator.layer,
30 McpProviderSessions.layer,
29 ProviderLatestVersions.layer,31 ProviderLatestVersions.layer,
30 Layer.succeed(32 Layer.succeed(
31 HttpClient.HttpClient,33 HttpClient.HttpClient,