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";
5959import * as ThreadManagementService from "../orchestration-v2/ThreadManagementService.ts";
6060import * as ProviderAdapterRegistry from "../orchestration-v2/ProviderAdapterRegistry.ts";
6161import * as ProviderContinuationRequests from "@t3tools/provider-core/server/ProviderContinuationRequests";
62import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
6263import { checkpointWorkspace } from "@t3tools/provider-testing/replayWorkspace";
6364import * as ProviderReplayHarness from "../orchestration-v2/testkit/ProviderReplayHarness.ts";
6465import {
......@@ -3810,7 +3811,9 @@ describe("orchestrator MCP toolkit", () => {
38103811 Layer.provideMerge(McpServer.McpServer.layer),
38113812 Layer.provideMerge(layerOrchestration),
38123813 Layer.provide(
3813 CodexOrchestratorReplayHarness.makeProviderAdapterRegistryLayer(transcript),
3814 CodexOrchestratorReplayHarness.makeProviderAdapterRegistryLayer(transcript).pipe(
3815 Layer.provide(McpProviderSessions.layer),
3816 ),
38143817 ),
38153818 Layer.provide(layerProviderRegistry),
38163819 Layer.provide(layerUnusedScheduledTaskStub),
apps/server/src/orchestration-v2/AcpRegistryOrchestratorV2.live.test.ts+2
......@@ -30,6 +30,7 @@ import * as ServerEnvironment from "../environment/ServerEnvironment.ts";
3030import * as ModelManifest from "../provider/ModelManifest.ts";
3131import * as ProviderInstanceRegistryHydration from "../provider/ProviderInstanceRegistryHydration.ts";
3232import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";
33import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
3334import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";
3435import * as OpenCodeRuntime from "@t3tools/provider-opencode/server/OpenCodeRuntime";
3536import * as OpenCodeServerLedger from "@t3tools/provider-opencode/server/OpenCodeServerLedger";
......@@ -144,6 +145,7 @@ const layerLive = RuntimeLayer.layer.pipe(
144145 Layer.provide(layerBackgroundPolicy),
145146 Layer.provide(ProviderTurnStartServiceTestkit.layer),
146147 Layer.provide(layerPlatformTest),
148 Layer.provide(McpProviderSessions.layer),
147149);
148150
149151const 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";
5454import type * as EffectAcpSchema from "effect-acp/compat";
5555
5656import { layerTestProviderHost } from "@t3tools/provider-testing/host";
57import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession";
57import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
5858import * as AcpSessionRuntime from "@t3tools/provider-acp/server/AcpSessionRuntime";
5959import {
6060 extractXAiAcpSubagentEndNotice,
......@@ -96,7 +96,12 @@ const DEFAULT_GROK_SETTINGS = Schema.decodeSync(GrokSettings)({});
9696
9797const 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);
100105const ACP_TEST_DRIVER = ProviderDriverKind.make("acp-test");
101106const decodeUnknownJson = Schema.decodeUnknownOption(Schema.fromJsonString(Schema.Unknown));
102107
......@@ -708,7 +713,7 @@ describe("AcpAdapterV2", () => {
708713
709714 const instanceId = ProviderInstanceId.make("acp-test-self-contained-mcp-bridge");
710715 const threadId = ThreadId.make("thread-acp-self-contained-mcp-bridge");
711 McpProviderSession.setMcpProviderSession({
716 yield* (yield* McpProviderSessions.McpProviderSessions).set({
712717 environmentId: EnvironmentId.make("environment-acp-self-contained-mcp-bridge"),
713718 threadId,
714719 providerSessionId: "mcp-session-acp-self-contained-mcp-bridge",
......@@ -717,11 +722,6 @@ describe("AcpAdapterV2", () => {
717722 authorizationHeader: "Bearer self-contained-mcp-bridge-token",
718723 browserToolsAvailable: false,
719724 });
720 yield* Effect.addFinalizer(() =>
721 Effect.sync(() => {
722 McpProviderSession.clearMcpProviderSession(threadId);
723 }),
724 );
725725
726726 let runtimeInput: AcpAdapterV2RuntimeInput | undefined;
727727 const makeRuntime = makeMockRuntime({ childProcessSpawner, mockAgentPath });
......@@ -772,7 +772,7 @@ describe("AcpAdapterV2", () => {
772772 const protocolEvents = yield* Queue.unbounded<EffectAcpProtocol.AcpProtocolLogEvent>();
773773 const instanceId = ProviderInstanceId.make("acp-test-instruction-transitions");
774774 const threadId = ThreadId.make("thread-acp-instruction-transitions");
775 McpProviderSession.setMcpProviderSession({
775 yield* (yield* McpProviderSessions.McpProviderSessions).set({
776776 environmentId: EnvironmentId.make("environment-acp-instruction-transitions"),
777777 threadId,
778778 providerSessionId: "mcp-session-acp-instruction-transitions",
......@@ -781,9 +781,6 @@ describe("AcpAdapterV2", () => {
781781 authorizationHeader: "Bearer instruction-transition-token",
782782 browserToolsAvailable: false,
783783 });
784 yield* Effect.addFinalizer(() =>
785 Effect.sync(() => McpProviderSession.clearMcpProviderSession(threadId)),
786 );
787784 const adapter = yield* makeAcpAdapterV2({
788785 instanceId,
789786 flavor: {
......@@ -2197,7 +2194,7 @@ describe("AcpAdapterV2", () => {
21972194 });
21982195 const sourceThreadId = ThreadId.make("thread-acp-native-fork-source");
21992196 const targetThreadId = ThreadId.make("thread-acp-native-fork-target");
2200 McpProviderSession.setMcpProviderSession({
2197 yield* (yield* McpProviderSessions.McpProviderSessions).set({
22012198 environmentId: EnvironmentId.make("environment-acp-native-fork-source"),
22022199 threadId: sourceThreadId,
22032200 providerSessionId: "mcp-session-acp-native-fork-source",
......@@ -2206,7 +2203,7 @@ describe("AcpAdapterV2", () => {
22062203 authorizationHeader: "Bearer source-thread-token",
22072204 browserToolsAvailable: true,
22082205 });
2209 McpProviderSession.setMcpProviderSession({
2206 yield* (yield* McpProviderSessions.McpProviderSessions).set({
22102207 environmentId: EnvironmentId.make("environment-acp-native-fork"),
22112208 threadId: targetThreadId,
22122209 providerSessionId: "mcp-session-acp-native-fork",
......@@ -2215,12 +2212,6 @@ describe("AcpAdapterV2", () => {
22152212 authorizationHeader: "Bearer target-thread-token",
22162213 browserToolsAvailable: true,
22172214 });
2218 yield* Effect.addFinalizer(() =>
2219 Effect.sync(() => {
2220 McpProviderSession.clearMcpProviderSession(sourceThreadId);
2221 McpProviderSession.clearMcpProviderSession(targetThreadId);
2222 }),
2223 );
22242215 const runtimePolicy = ProviderAdapter.ProviderAdapterV2RuntimePolicy.make({
22252216 runtimeMode: "full-access",
22262217 interactionMode: "default",
......@@ -2640,7 +2631,7 @@ describe("AcpAdapterV2", () => {
26402631 );
26412632 const instanceId = ProviderInstanceId.make("acp-test-rollback-session");
26422633 const rollbackThreadId = ThreadId.make("thread-acp-rollback-session-target");
2643 McpProviderSession.setMcpProviderSession({
2634 yield* (yield* McpProviderSessions.McpProviderSessions).set({
26442635 environmentId: EnvironmentId.make("environment-acp-rollback-session-target"),
26452636 threadId: rollbackThreadId,
26462637 providerSessionId: "mcp-session-acp-rollback-session-target",
......@@ -2649,9 +2640,6 @@ describe("AcpAdapterV2", () => {
26492640 authorizationHeader: "Bearer rollback-target-token",
26502641 browserToolsAvailable: false,
26512642 });
2652 yield* Effect.addFinalizer(() =>
2653 Effect.sync(() => McpProviderSession.clearMcpProviderSession(rollbackThreadId)),
2654 );
26552643 const runtimeInputs: Array<AcpAdapterV2RuntimeInput> = [];
26562644 const makeRuntime = makeMockRuntime({
26572645 childProcessSpawner,
apps/server/src/orchestration-v2/Adapters/AcpRegistryAdapterV2.test.ts+2
......@@ -14,6 +14,7 @@ import { ChildProcessSpawner } from "effect/process";
1414import * as EffectAcpErrors from "effect-acp/errors";
1515
1616import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";
17import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
1718import { layerTestProviderHost } from "@t3tools/provider-testing/host";
1819import * as ServerSettings from "../../serverSettings.ts";
1920import type {
......@@ -84,6 +85,7 @@ const layerRegistry = Layer.succeed(
8485const layerTest = Layer.mergeAll(
8586 NodeServices.layer,
8687 IdAllocator.layer,
88 McpProviderSessions.layer,
8789 layerHost,
8890 layerRegistry,
8991 ServerSettings.layerTest(),
apps/server/src/orchestration-v2/Adapters/AntigravityAdapterV2.test.ts+2
......@@ -23,6 +23,7 @@ import { ChildProcessSpawner } from "effect/process";
2323import type * as EffectAcpSchema from "effect-acp/compat";
2424
2525import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";
26import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
2627import { layerTestProviderHost } from "@t3tools/provider-testing/host";
2728import type * as AcpSessionRuntime from "@t3tools/provider-acp/server/AcpSessionRuntime";
2829import { makeAntigravityAcpRuntime } from "../../provider/acp/AntigravityAcpSupport.ts";
......@@ -129,6 +130,7 @@ describe("AntigravityAdapterV2 flavor", () => {
129130const layerSession = Layer.mergeAll(
130131 NodeServices.layer,
131132 IdAllocator.layer,
133 McpProviderSessions.layer,
132134 layerTestProviderHost().pipe(Layer.provide(NodeServices.layer)),
133135);
134136
apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts+587-267
......@@ -53,6 +53,7 @@ import { SpawnExecutableResolution } from "@t3tools/shared/shell";
5353import { attachmentRelativePath } from "../../attachmentStore.ts";
5454import * as ServerConfig from "../../config.ts";
5555import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession";
56import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
5657import { PreviewControlsToolkit } from "../../mcp/toolkits/previewControls/tools.ts";
5758import { HtmlToolkit } from "../../mcp/toolkits/html/tools.ts";
5859import { EnvironmentToolkit } from "../../mcp/toolkits/environment/tools.ts";
......@@ -483,26 +484,22 @@ describe("ClaudeAdapterV2 MCP query overrides", () => {
483484 T3_CODE_MCP_AUTHORIZATION: "Bearer secret-claude-token",
484485 } as const;
485486
486 const withMcpSession = (threadId: ThreadId, run: () => void) => {
487 McpProviderSession.setMcpProviderSession({
488 environmentId: EnvironmentId.make(`environment-${threadId}`),
489 threadId,
490 providerSessionId: `mcp-session-${threadId}`,
491 providerInstanceId: ProviderInstanceId.make("claudeAgent"),
492 endpoint: "http://127.0.0.1:43123/mcp",
493 authorizationHeader: "Bearer secret-claude-token",
494 browserToolsAvailable: true,
495 });
496 try {
497 run();
498 } finally {
499 McpProviderSession.clearMcpProviderSession(threadId);
500 }
501 };
487 const mcpSessionFor = (
488 threadId: ThreadId,
489 authorizationHeader = "Bearer secret-claude-token",
490 ): McpProviderSession.McpProviderSessionConfig => ({
491 environmentId: EnvironmentId.make(`environment-${threadId}`),
492 threadId,
493 providerSessionId: `mcp-session-${threadId}`,
494 providerInstanceId: ProviderInstanceId.make("claudeAgent"),
495 endpoint: "http://127.0.0.1:43123/mcp",
496 authorizationHeader,
497 browserToolsAvailable: true,
498 });
502499
503500 it("leaves an absent allowlist absent when no MCP session exists", () => {
504501 const overrides = ClaudeAdapterV2.claudeMcpQueryOverrides({
505 threadId: ThreadId.make("thread-claude-no-mcp-no-allowlist"),
502 mcpSession: undefined,
506503 readOnlySandbox: false,
507504 });
508505
......@@ -511,7 +508,7 @@ describe("ClaudeAdapterV2 MCP query overrides", () => {
511508
512509 it("preserves an explicit allowlist when no MCP session exists", () => {
513510 const overrides = ClaudeAdapterV2.claudeMcpQueryOverrides({
514 threadId: ThreadId.make("thread-claude-no-mcp-with-allowlist"),
511 mcpSession: undefined,
515512 readOnlySandbox: false,
516513 allowedTools: ["Read"],
517514 });
......@@ -521,135 +518,122 @@ describe("ClaudeAdapterV2 MCP query overrides", () => {
521518
522519 it("pre-approves all t3-code tools when attaching an MCP session without an allowlist", () => {
523520 const threadId = ThreadId.make("thread-claude-mcp-no-allowlist");
524 withMcpSession(threadId, () => {
525 const overrides = ClaudeAdapterV2.claudeMcpQueryOverrides({
526 threadId,
527 readOnlySandbox: false,
528 });
521 const mcpSession = mcpSessionFor(threadId);
522 const overrides = ClaudeAdapterV2.claudeMcpQueryOverrides({
523 mcpSession,
524 readOnlySandbox: false,
525 });
529526
530 assert.deepEqual(overrides, {
531 allowedTools: [ClaudeAdapterV2.CLAUDE_T3_MCP_TOOL_WILDCARD],
532 mcpServers: T3_MCP_SERVERS,
533 mcpEnvironment: T3_MCP_ENVIRONMENT,
534 });
527 assert.deepEqual(overrides, {
528 allowedTools: [ClaudeAdapterV2.CLAUDE_T3_MCP_TOOL_WILDCARD],
529 mcpServers: T3_MCP_SERVERS,
530 mcpEnvironment: T3_MCP_ENVIRONMENT,
535531 });
536532 });
537533
538534 it("extends an explicit allowlist with the t3-code wildcard", () => {
539535 const threadId = ThreadId.make("thread-claude-mcp-with-allowlist");
540 withMcpSession(threadId, () => {
541 const overrides = ClaudeAdapterV2.claudeMcpQueryOverrides({
542 threadId,
543 readOnlySandbox: false,
544 allowedTools: ["Read", "mcp__t3-code__*"],
545 });
536 const mcpSession = mcpSessionFor(threadId);
537 const overrides = ClaudeAdapterV2.claudeMcpQueryOverrides({
538 mcpSession,
539 readOnlySandbox: false,
540 allowedTools: ["Read", "mcp__t3-code__*"],
541 });
546542
547 assert.deepEqual(overrides, {
548 allowedTools: ["Read", "mcp__t3-code__*"],
549 mcpServers: T3_MCP_SERVERS,
550 mcpEnvironment: T3_MCP_ENVIRONMENT,
551 });
543 assert.deepEqual(overrides, {
544 allowedTools: ["Read", "mcp__t3-code__*"],
545 mcpServers: T3_MCP_SERVERS,
546 mcpEnvironment: T3_MCP_ENVIRONMENT,
552547 });
553548 });
554549
555550 it("pre-approves only read-only t3-code tools in a read-only sandbox", () => {
556551 const threadId = ThreadId.make("thread-claude-mcp-read-only");
557 withMcpSession(threadId, () => {
558 const overrides = ClaudeAdapterV2.claudeMcpQueryOverrides({
559 threadId,
560 readOnlySandbox: true,
561 allowedTools: [...ClaudeAdapterV2.CLAUDE_READ_ONLY_ALLOWED_TOOLS],
562 });
552 const mcpSession = mcpSessionFor(threadId);
553 const overrides = ClaudeAdapterV2.claudeMcpQueryOverrides({
554 mcpSession,
555 readOnlySandbox: true,
556 allowedTools: [...ClaudeAdapterV2.CLAUDE_READ_ONLY_ALLOWED_TOOLS],
557 });
563558
564 assert.deepEqual(overrides, {
565 allowedTools: [
566 ...ClaudeAdapterV2.CLAUDE_READ_ONLY_ALLOWED_TOOLS,
567 ...ClaudeAdapterV2.CLAUDE_READ_ONLY_T3_MCP_ALLOWED_TOOLS,
568 ],
569 mcpServers: T3_MCP_SERVERS,
570 mcpEnvironment: T3_MCP_ENVIRONMENT,
571 });
572 assert.isFalse(overrides.allowedTools?.includes(ClaudeAdapterV2.CLAUDE_T3_MCP_TOOL_WILDCARD));
559 assert.deepEqual(overrides, {
560 allowedTools: [
561 ...ClaudeAdapterV2.CLAUDE_READ_ONLY_ALLOWED_TOOLS,
562 ...ClaudeAdapterV2.CLAUDE_READ_ONLY_T3_MCP_ALLOWED_TOOLS,
563 ],
564 mcpServers: T3_MCP_SERVERS,
565 mcpEnvironment: T3_MCP_ENVIRONMENT,
573566 });
567 assert.isFalse(overrides.allowedTools?.includes(ClaudeAdapterV2.CLAUDE_T3_MCP_TOOL_WILDCARD));
574568 });
575569
576570 it("pre-approves only read-only t3-code tools in a read-only sandbox without an allowlist", () => {
577571 const threadId = ThreadId.make("thread-claude-mcp-read-only-no-allowlist");
578 withMcpSession(threadId, () => {
579 const overrides = ClaudeAdapterV2.claudeMcpQueryOverrides({
580 threadId,
581 readOnlySandbox: true,
582 });
583
584 assert.deepEqual(overrides.allowedTools, [
585 ...ClaudeAdapterV2.CLAUDE_READ_ONLY_T3_MCP_ALLOWED_TOOLS,
586 ]);
572 const mcpSession = mcpSessionFor(threadId);
573 const overrides = ClaudeAdapterV2.claudeMcpQueryOverrides({
574 mcpSession,
575 readOnlySandbox: true,
587576 });
577
578 assert.deepEqual(overrides.allowedTools, [
579 ...ClaudeAdapterV2.CLAUDE_READ_ONLY_T3_MCP_ALLOWED_TOOLS,
580 ]);
588581 });
589582
590583 it("keys live-query reuse on the MCP-derived pre-approvals", () => {
591584 const threadId = ThreadId.make("thread-claude-mcp-query-key");
592 withMcpSession(threadId, () => {
593 const queryPolicy = ClaudeAdapterV2.claudeRuntimeQueryPolicyForRuntimePolicy(
594 ProviderAdapter.ProviderAdapterV2RuntimePolicy.make({
595 runtimeMode: "full-access",
596 interactionMode: "default",
597 cwd: "/workspace",
598 approvalPolicy: "on-request",
599 sandboxPolicy: {
600 type: "readOnly",
601 access: { type: "fullAccess" },
602 networkAccess: false,
603 },
604 }),
605 );
585 const mcpSession = mcpSessionFor(threadId);
586 const queryPolicy = ClaudeAdapterV2.claudeRuntimeQueryPolicyForRuntimePolicy(
587 ProviderAdapter.ProviderAdapterV2RuntimePolicy.make({
588 runtimeMode: "full-access",
589 interactionMode: "default",
590 cwd: "/workspace",
591 approvalPolicy: "on-request",
592 sandboxPolicy: {
593 type: "readOnly",
594 access: { type: "fullAccess" },
595 networkAccess: false,
596 },
597 }),
598 );
606599
607 const readOnlyKey = ClaudeAdapterV2.claudeEffectiveQueryPolicyKey(
608 queryPolicy,
609 ClaudeAdapterV2.claudeMcpQueryOverrides({ threadId, readOnlySandbox: true }),
610 );
611 const fullAccessKey = ClaudeAdapterV2.claudeEffectiveQueryPolicyKey(
612 queryPolicy,
613 ClaudeAdapterV2.claudeMcpQueryOverrides({ threadId, readOnlySandbox: false }),
614 );
615 const detachedKey = ClaudeAdapterV2.claudeEffectiveQueryPolicyKey(queryPolicy, {});
600 const readOnlyKey = ClaudeAdapterV2.claudeEffectiveQueryPolicyKey(
601 queryPolicy,
602 ClaudeAdapterV2.claudeMcpQueryOverrides({ mcpSession, readOnlySandbox: true }),
603 );
604 const fullAccessKey = ClaudeAdapterV2.claudeEffectiveQueryPolicyKey(
605 queryPolicy,
606 ClaudeAdapterV2.claudeMcpQueryOverrides({ mcpSession, readOnlySandbox: false }),
607 );
608 const detachedKey = ClaudeAdapterV2.claudeEffectiveQueryPolicyKey(queryPolicy, {});
616609
617 assert.notEqual(readOnlyKey, fullAccessKey);
618 assert.notEqual(fullAccessKey, detachedKey);
619 });
610 assert.notEqual(readOnlyKey, fullAccessKey);
611 assert.notEqual(fullAccessKey, detachedKey);
620612 });
621613
622614 it("invalidates live-query reuse when MCP credentials rotate", () => {
623615 const threadId = ThreadId.make("thread-claude-mcp-credential-rotation");
624 withMcpSession(threadId, () => {
625 const queryPolicy = ClaudeAdapterV2.claudeRuntimeQueryPolicyForRuntimePolicy(
626 ProviderAdapter.ProviderAdapterV2RuntimePolicy.make({
627 runtimeMode: "approval-required",
628 interactionMode: "default",
629 cwd: "/workspace",
630 }),
631 );
632 const initialKey = ClaudeAdapterV2.claudeEffectiveQueryPolicyKey(
633 queryPolicy,
634 ClaudeAdapterV2.claudeMcpQueryOverrides({ threadId, readOnlySandbox: false }),
635 );
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 });
616 const mcpSession = mcpSessionFor(threadId);
617 const queryPolicy = ClaudeAdapterV2.claudeRuntimeQueryPolicyForRuntimePolicy(
618 ProviderAdapter.ProviderAdapterV2RuntimePolicy.make({
619 runtimeMode: "approval-required",
620 interactionMode: "default",
621 cwd: "/workspace",
622 }),
623 );
624 const initialKey = ClaudeAdapterV2.claudeEffectiveQueryPolicyKey(
625 queryPolicy,
626 ClaudeAdapterV2.claudeMcpQueryOverrides({ mcpSession, readOnlySandbox: false }),
627 );
646628
647 const rotatedKey = ClaudeAdapterV2.claudeEffectiveQueryPolicyKey(
648 queryPolicy,
649 ClaudeAdapterV2.claudeMcpQueryOverrides({ threadId, readOnlySandbox: false }),
650 );
651 assert.notEqual(rotatedKey, initialKey);
652 });
629 const rotatedKey = ClaudeAdapterV2.claudeEffectiveQueryPolicyKey(
630 queryPolicy,
631 ClaudeAdapterV2.claudeMcpQueryOverrides({
632 mcpSession: mcpSessionFor(threadId, "Bearer rotated-claude-token"),
633 readOnlySandbox: false,
634 }),
635 );
636 assert.notEqual(rotatedKey, initialKey);
653637 });
654638
655639 it("matches the read-only allowlist to the orchestrator toolkit annotations", () => {
......@@ -676,7 +660,7 @@ describe("ClaudeAdapterV2 MCP query overrides", () => {
676660describe("ClaudeAdapterV2 native protocol logging", () => {
677661 it("injects thread-scoped MCP configuration without logging the credential", () => {
678662 const threadId = ThreadId.make("thread-claude-mcp");
679 McpProviderSession.setMcpProviderSession({
663 const mcpSession = {
680664 environmentId: EnvironmentId.make("environment-claude-mcp"),
681665 threadId,
682666 providerSessionId: "mcp-session-claude",
......@@ -684,60 +668,56 @@ describe("ClaudeAdapterV2 native protocol logging", () => {
684668 endpoint: "http://127.0.0.1:43123/mcp",
685669 authorizationHeader: "Bearer secret-claude-token",
686670 browserToolsAvailable: true,
687 });
671 };
688672
689 try {
690 const overrides = ClaudeAdapterV2.claudeMcpQueryOverrides({
691 threadId,
692 readOnlySandbox: false,
693 allowedTools: ["Read"],
694 });
695 assert.deepEqual(overrides, {
696 allowedTools: ["Read", "mcp__t3-code__*"],
697 mcpServers: {
698 "t3-code": {
699 type: "http",
700 url: "http://127.0.0.1:43123/mcp",
701 headers: {
702 Authorization: "${T3_CODE_MCP_AUTHORIZATION}",
703 },
704 timeout: ClaudeAdapterV2.CLAUDE_T3_MCP_TOOL_TIMEOUT_MS,
673 const overrides = ClaudeAdapterV2.claudeMcpQueryOverrides({
674 mcpSession,
675 readOnlySandbox: false,
676 allowedTools: ["Read"],
677 });
678 assert.deepEqual(overrides, {
679 allowedTools: ["Read", "mcp__t3-code__*"],
680 mcpServers: {
681 "t3-code": {
682 type: "http",
683 url: "http://127.0.0.1:43123/mcp",
684 headers: {
685 Authorization: "${T3_CODE_MCP_AUTHORIZATION}",
705686 },
687 timeout: ClaudeAdapterV2.CLAUDE_T3_MCP_TOOL_TIMEOUT_MS,
706688 },
707 mcpEnvironment: { T3_CODE_MCP_AUTHORIZATION: "Bearer secret-claude-token" },
708 });
689 },
690 mcpEnvironment: { T3_CODE_MCP_AUTHORIZATION: "Bearer secret-claude-token" },
691 });
709692
710 const options = ClaudeAdapterV2.makeClaudeQueryOptions({
711 modelSelection: {
712 instanceId: ProviderInstanceId.make("claudeAgent"),
713 model: "claude-sonnet-4-6",
714 },
715 nativeThreadId: "native-thread-claude-mcp",
716 resume: false,
717 cwd: "/workspace",
718 allowedTools: overrides.allowedTools ?? [],
719 mcpServers: overrides.mcpServers ?? {},
720 environment: { ...overrides.mcpEnvironment },
721 });
722 // mcpServers becomes a CLI argument, readable by every local user; the
723 // credential may only travel in the child's environment.
724 assert.notInclude(JSON.stringify(options.mcpServers), "secret-claude-token");
725 assert.equal(options.env?.T3_CODE_MCP_AUTHORIZATION, "Bearer secret-claude-token");
726 assert.isObject(options.systemPrompt);
727 const systemPrompt = options.systemPrompt as {
728 readonly type: string;
729 readonly preset: string;
730 readonly append?: string;
731 };
732 assert.equal(systemPrompt.type, "preset");
733 assert.equal(systemPrompt.preset, "claude_code");
734 assert.include(systemPrompt.append ?? "", "Use `delegate_task`");
735 const logged = ClaudeAdapterV2.loggedClaudeQueryOptions(options);
736 assert.equal(logged.hasMcpServers, true);
737 assert.notInclude(JSON.stringify(logged), "secret-claude-token");
738 } finally {
739 McpProviderSession.clearMcpProviderSession(threadId);
740 }
693 const options = ClaudeAdapterV2.makeClaudeQueryOptions({
694 modelSelection: {
695 instanceId: ProviderInstanceId.make("claudeAgent"),
696 model: "claude-sonnet-4-6",
697 },
698 nativeThreadId: "native-thread-claude-mcp",
699 resume: false,
700 cwd: "/workspace",
701 allowedTools: overrides.allowedTools ?? [],
702 mcpServers: overrides.mcpServers ?? {},
703 environment: { ...overrides.mcpEnvironment },
704 });
705 // mcpServers becomes a CLI argument, readable by every local user; the
706 // credential may only travel in the child's environment.
707 assert.notInclude(JSON.stringify(options.mcpServers), "secret-claude-token");
708 assert.equal(options.env?.T3_CODE_MCP_AUTHORIZATION, "Bearer secret-claude-token");
709 assert.isObject(options.systemPrompt);
710 const systemPrompt = options.systemPrompt as {
711 readonly type: string;
712 readonly preset: string;
713 readonly append?: string;
714 };
715 assert.equal(systemPrompt.type, "preset");
716 assert.equal(systemPrompt.preset, "claude_code");
717 assert.include(systemPrompt.append ?? "", "Use `delegate_task`");
718 const logged = ClaudeAdapterV2.loggedClaudeQueryOptions(options);
719 assert.equal(logged.hasMcpServers, true);
720 assert.notInclude(JSON.stringify(logged), "secret-claude-token");
741721 });
742722
743723 it.effect("writes Claude Agent SDK protocol frames to the native provider log", () =>
......@@ -961,7 +941,7 @@ describe("ClaudeAdapterV2 Auto-accept edits", () => {
961941 prefix: "t3-claude-accept-edits-",
962942 });
963943 let openedOptions: ClaudeAdapterV2.ClaudeAgentSdkQueryOptions | undefined;
964 const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({
944 const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({
965945 instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,
966946 settings: DEFAULT_CLAUDE_SETTINGS,
967947 environment: {},
......@@ -1056,7 +1036,11 @@ describe("ClaudeAdapterV2 Auto-accept edits", () => {
10561036 decision: "accept",
10571037 });
10581038 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 ),
10601044 ),
10611045 );
10621046});
......@@ -1174,7 +1158,11 @@ describe("ClaudeAdapterV2 executable path", () => {
11741158
11751159 assert.deepEqual(executablePaths, [path.join(NodeOS.homedir(), "bin", "claude")]);
11761160 }),
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 ),
11781166 );
11791167
11801168 it.effect("follows a bare claude on Windows to the npm package executable", () =>
......@@ -1190,7 +1178,11 @@ describe("ClaudeAdapterV2 executable path", () => {
11901178
11911179 assert.deepEqual(executablePaths, [packageExe]);
11921180 }),
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 ),
11941186 );
11951187});
11961188
......@@ -1204,7 +1196,7 @@ describe("ClaudeAdapterV2 resume compaction", () => {
12041196 prefix: "t3-claude-resume-",
12051197 });
12061198 let openedOptions: ClaudeAdapterV2.ClaudeAgentSdkQueryOptions | undefined;
1207 const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({
1199 const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({
12081200 instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,
12091201 settings: DEFAULT_CLAUDE_SETTINGS,
12101202 environment: {},
......@@ -1408,7 +1400,11 @@ describe("ClaudeAdapterV2 resume compaction", () => {
14081400 ),
14091401 { behavior: "cancelled" },
14101402 );
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 ),
14121408 ),
14131409 );
14141410});
......@@ -1424,7 +1420,7 @@ describe("ClaudeAdapterV2 attachments", () => {
14241420 prefix: "t3-claude-v2-attachments-",
14251421 });
14261422 const offeredMessages: Array<SDKUserMessage> = [];
1427 const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({
1423 const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({
14281424 instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,
14291425 settings: DEFAULT_CLAUDE_SETTINGS,
14301426 environment: {},
......@@ -1552,7 +1548,11 @@ describe("ClaudeAdapterV2 attachments", () => {
15521548 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}]`,
15531549 },
15541550 ]);
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 ),
15561556 ),
15571557 );
15581558
......@@ -1565,7 +1565,7 @@ describe("ClaudeAdapterV2 attachments", () => {
15651565 prefix: "t3-claude-v2-unsupported-attachment-",
15661566 });
15671567 let openCount = 0;
1568 const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({
1568 const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({
15691569 instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,
15701570 settings: DEFAULT_CLAUDE_SETTINGS,
15711571 environment: {},
......@@ -1634,7 +1634,11 @@ describe("ClaudeAdapterV2 attachments", () => {
16341634 assert.equal(error._tag, "ProviderAdapterTurnStartError");
16351635 assert.include(String(error.cause), "Unsupported Claude image attachment type");
16361636 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 ),
16381642 ),
16391643 );
16401644});
......@@ -1655,7 +1659,7 @@ describe("ClaudeAdapterV2 native fork", () => {
16551659 readonly threadId: ThreadId;
16561660 readonly providerSessionId: ProviderSessionId;
16571661 }> = [];
1658 const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({
1662 const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({
16591663 instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,
16601664 settings: DEFAULT_CLAUDE_SETTINGS,
16611665 environment: {},
......@@ -1813,7 +1817,11 @@ describe("ClaudeAdapterV2 native fork", () => {
18131817
18141818 assert.equal(openedQueries[0]?.options.resume, "forked-native-session");
18151819 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 ),
18171825 ),
18181826 );
18191827});
......@@ -1828,7 +1836,7 @@ describe("ClaudeAdapterV2 native session identity", () => {
18281836 prefix: "t3-claude-v2-session-identity-",
18291837 });
18301838 const openedQueries: Array<ClaudeAdapterV2.ClaudeAgentSdkQueryOpenInput> = [];
1831 const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({
1839 const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({
18321840 instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,
18331841 settings: DEFAULT_CLAUDE_SETTINGS,
18341842 environment: {},
......@@ -1883,7 +1891,11 @@ describe("ClaudeAdapterV2 native session identity", () => {
18831891 }),
18841892 );
18851893 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 ),
18871899 );
18881900
18891901 it.effect("creates the native session on the first provider turn", () =>
......@@ -2127,7 +2139,7 @@ describe("ClaudeAdapterV2 background wake turns", () => {
21272139 Extract<ProviderAdapter.ProviderAdapterV2Event, { type: "turn_item.updated" }>
21282140 >();
21292141 let openedOptions: ClaudeAdapterV2.ClaudeAgentSdkQueryOptions | undefined;
2130 const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({
2142 const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({
21312143 instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,
21322144 settings: DEFAULT_CLAUDE_SETTINGS,
21332145 environment: options?.environment ?? {},
......@@ -2365,7 +2377,12 @@ describe("ClaudeAdapterV2 background wake turns", () => {
23652377 assert.deepEqual(item.input, { city: "Berlin" });
23662378 }
23672379 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 ),
23692386 );
23702387
23712388 it.effect(
......@@ -2426,7 +2443,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
24262443 assert.strictEqual(harness.getOpenedOptions(), originalOptions);
24272444 assert.lengthOf(harness.offeredMessages, 2);
24282445 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 ),
24302451 ),
24312452 );
24322453
......@@ -2545,7 +2566,12 @@ describe("ClaudeAdapterV2 background wake turns", () => {
25452566 ),
25462567 );
25472568 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 ),
25492575 );
25502576
25512577 it.effect.each(["cancelled", "denied", "permission_denied", undefined])(
......@@ -2615,7 +2641,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
26152641 event.type === "node.updated" && event.node.nativeItemRef?.nativeId === "tool-error",
26162642 );
26172643 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 ),
26192649 ),
26202650 );
26212651
......@@ -2688,7 +2718,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
26882718 );
26892719 }
26902720 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 ),
26922726 ),
26932727 );
26942728
......@@ -2754,7 +2788,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
27542788 yield* Queue.take(harness.terminalReceipts);
27552789 assert.lengthOf(notices(), 3);
27562790 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 ),
27582796 );
27592797
27602798 it.effect("keeps usage warnings and provisioned overage silent", () =>
......@@ -2801,7 +2839,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
28012839 (event) => event.type === "turn_item.updated" && event.turnItem.type === "system_notice",
28022840 ),
28032841 );
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 ),
28052847 );
28062848
28072849 it.effect("names an expired Claude login instead of the terminal API error", () =>
......@@ -2846,7 +2888,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
28462888 assert.include(terminal.failure.message, configDir);
28472889 assert.include(terminal.failure.message, cwd);
28482890 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 ),
28502896 );
28512897
28522898 it.effect.each([
......@@ -2900,7 +2946,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
29002946 if (terminal.status !== "failed") return;
29012947 assert.include(terminal.failure.message, expected);
29022948 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 ),
29042954 );
29052955
29062956 it.effect.each([
......@@ -2960,7 +3010,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
29603010 ? "Claude usage limit reached. Send the message again once the limit resets."
29613011 : "Claude gave up after repeated API errors.",
29623012 );
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 ),
29643018 );
29653019
29663020 it.effect.each([429, 401, 529])(
......@@ -3004,7 +3058,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
30043058 );
30053059 if (apiErrorStatus !== 429)
30063060 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 ),
30083066 );
30093067
30103068 it.effect("surfaces a Claude safety model fallback without failing the turn", () =>
......@@ -3063,7 +3121,12 @@ describe("ClaudeAdapterV2 background wake turns", () => {
30633121 (event) => event.type === "turn_item.updated" && event.turnItem.type === "error",
30643122 ),
30653123 );
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 ),
30673130 );
30683131
30693132 it.effect(
......@@ -3135,7 +3198,12 @@ describe("ClaudeAdapterV2 background wake turns", () => {
31353198 cacheCreationTokens: 0,
31363199 });
31373200 }
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 ),
31393207 );
31403208
31413209 it.effect("titles Claude reads, searches, and skills on tool completion", () =>
......@@ -3233,7 +3301,12 @@ describe("ClaudeAdapterV2 background wake turns", () => {
32333301 );
32343302 for (const item of items.filter((item) => item.nativeItemRef?.nativeId !== "image"))
32353303 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 ),
32373310 );
32383311
32393312 it.effect("preserves typed Claude plans and todos through generic tool completion", () =>
......@@ -3442,7 +3515,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
34423515 // The second prompt reuses the live process, which is still in the
34433516 // plan mode Claude entered, so it is put back in the thread's mode.
34443517 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 ),
34463523 ),
34473524 );
34483525
......@@ -3516,7 +3593,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
35163593 }),
35173594 );
35183595 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 ),
35203601 ),
35213602 );
35223603
......@@ -3570,7 +3651,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
35703651 });
35713652 assert.isDefined(terminal.retryStartedAt);
35723653 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 ),
35743659 ),
35753660 );
35763661
......@@ -3632,7 +3717,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
36323717 terminal.failure.code,
36333718 terminalReason === "overloaded_status" ? "api_error_529" : terminalReason,
36343719 );
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 ),
36363725 ),
36373726 );
36383727
......@@ -3697,7 +3786,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
36973786 "empty roster clear",
36983787 );
36993788 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 ),
37013794 ),
37023795 );
37033796
......@@ -3832,7 +3925,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
38323925 assert.deepEqual(roster, [
38333926 { taskId: longRunning.taskId, kind: "monitor", description: "Monitor 0" },
38343927 ]);
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 ),
38363933 ),
38373934 );
38383935
......@@ -3882,7 +3979,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
38823979 );
38833980 assert.isFalse(yield* harness.hasPendingBackgroundWork);
38843981 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 ),
38863987 ),
38873988 );
38883989
......@@ -3927,7 +4028,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
39274028 );
39284029 assert.isFalse(yield* harness.hasPendingBackgroundWork);
39294030 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 ),
39314036 ),
39324037 );
39334038
......@@ -3944,7 +4049,7 @@ describe("ClaudeAdapterV2 background wake turns", () => {
39444049 const events: Array<ProviderAdapter.ProviderAdapterV2Event> = [];
39454050 const continuationRequests: Array<ProviderContinuationRequests.ProviderContinuationRequest> =
39464051 [];
3947 const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({
4052 const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({
39484053 instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,
39494054 settings: DEFAULT_CLAUDE_SETTINGS,
39504055 environment: {},
......@@ -4109,7 +4214,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
41094214 }),
41104215 );
41114216 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 ),
41134222 ),
41144223 );
41154224
......@@ -4167,7 +4276,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
41674276 const afterFailure = providerThreadRosterEvents(harness.events).at(-1);
41684277 assert.deepEqual(afterFailure?.providerThread.pendingBackgroundTasks ?? [], []);
41694278 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 ),
41714284 ),
41724285 );
41734286
......@@ -4190,7 +4303,7 @@ describe("ClaudeAdapterV2 background wake turns", () => {
41904303 readonly queue: Queue.Queue<SDKMessage>;
41914304 }> = [];
41924305 const events: Array<ProviderAdapter.ProviderAdapterV2Event> = [];
4193 const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({
4306 const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({
41944307 instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,
41954308 settings: DEFAULT_CLAUDE_SETTINGS,
41964309 environment: {},
......@@ -4429,7 +4542,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
44294542 assert.isFalse(yield* hasPendingBackgroundWorkForThread(providerThreadA));
44304543 assert.isFalse(yield* hasPendingBackgroundWorkForThread(providerThreadB));
44314544 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 ),
44334550 ),
44344551 );
44354552
......@@ -4649,7 +4766,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
46494766 userRunItems.map((item) => item.type),
46504767 ["assistant_message"],
46514768 );
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 ),
46534774 ),
46544775 );
46554776
......@@ -4732,7 +4853,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
47324853 : undefined,
47334854 "On branch main\nwarning: dirty",
47344855 );
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 ),
47364861 ),
47374862 );
47384863
......@@ -4861,7 +4986,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
48614986 });
48624987 const result = yield* Fiber.join(permission);
48634988 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 ),
48654994 ),
48664995 );
48674996
......@@ -4957,7 +5086,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
49575086 event.message.text === STALE_TASK_NOTIFICATION_RESULT_TEXT,
49585087 ),
49595088 );
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 ),
49615094 ),
49625095 );
49635096
......@@ -5035,7 +5168,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
50355168 yield* awaitUntil(() => harness.terminalEvents().length === 2, "second turn terminal");
50365169 assert.deepEqual(assistantTexts(), ["One.", "Two."]);
50375170 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 ),
50395176 ),
50405177 );
50415178
......@@ -5061,7 +5198,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
50615198 );
50625199 }
50635200 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 ),
50655206 ),
50665207 );
50675208
......@@ -5098,7 +5239,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
50985239 if (terminal?.status !== "failed") return;
50995240 assert.equal(terminal.threadDisposition, "reusable");
51005241 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 ),
51025247 ),
51035248 );
51045249
......@@ -5147,7 +5292,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
51475292 );
51485293 yield* awaitUntil(() => harness.terminalEvents().length === 1, "turn terminal");
51495294 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 ),
51515300 ),
51525301 );
51535302
......@@ -5193,7 +5342,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
51935342 assert.lengthOf(harness.continuationRequests, 1);
51945343 assert.lengthOf(harness.terminalEvents(), 1);
51955344 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 ),
51975350 ),
51985351 );
51995352
......@@ -5250,7 +5403,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
52505403 (event) => event.type === "message.updated" && event.message.text === WAKE_RESULT_TEXT,
52515404 ),
52525405 );
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 ),
52545411 ),
52555412 );
52565413
......@@ -5301,7 +5458,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
53015458 );
53025459 assert.equal(harness.terminalEvents()[1]?.status, "completed");
53035460 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 ),
53055466 ),
53065467 );
53075468
......@@ -5389,7 +5550,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
53895550 assert.equal(pause.turnItem.type, "system_notice");
53905551 if (pause.turnItem.type !== "system_notice") return;
53915552 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 ),
53935558 ),
53945559 );
53955560
......@@ -5480,7 +5645,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
54805645 ),
54815646 );
54825647 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 ),
54845653 ),
54855654 );
54865655
......@@ -5542,7 +5711,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
55425711 );
55435712 yield* awaitUntil(() => harness.terminalEvents().length === 2, "queued turn terminal");
55445713 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 ),
55465719 ),
55475720 );
55485721
......@@ -5580,7 +5753,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
55805753 (event) => event.type === "message.updated" && event.message.text === fallbackText,
55815754 ),
55825755 );
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 ),
55845761 ),
55855762 );
55865763
......@@ -5663,7 +5840,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
56635840 assert.equal(harness.terminalEvents()[0]?.status, "completed");
56645841 assert.isTrue(hasMessageText(recoveryAssistantText));
56655842 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 ),
56675848 ),
56685849 );
56695850
......@@ -5713,7 +5894,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
57135894 );
57145895 yield* awaitUntil(() => harness.terminalEvents().length === 2, "continuation terminal");
57155896 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 ),
57175902 ),
57185903 );
57195904
......@@ -5772,7 +5957,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
57725957 assert.isTrue(terminalized);
57735958 assert.equal(harness.terminalEvents()[0]?.status, "interrupted");
57745959 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 ),
57765965 ),
57775966 );
57785967
......@@ -5831,7 +6020,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
58316020 (event) => event.type === "message.updated" && event.message.text === staleText,
58326021 ),
58336022 );
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 ),
58356028 ),
58366029 );
58376030
......@@ -5865,7 +6058,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
58656058 );
58666059 yield* awaitUntil(() => harness.terminalEvents().length === 1, "failed terminal");
58676060 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 ),
58696066 ),
58706067 );
58716068
......@@ -5891,7 +6088,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
58916088 yield* awaitUntil(() => harness.terminalEvents().length === 1, "spurious terminal");
58926089 assert.equal(harness.terminalEvents()[0]?.status, "completed");
58936090 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 ),
58956096 ),
58966097 );
58976098
......@@ -6088,7 +6289,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
60886289 harness.continuationRequests[1]?.notification?.summary,
60896290 `Command "${WAKE_TASK_DESCRIPTION}" finished`,
60906291 );
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 ),
60926297 ),
60936298 );
60946299
......@@ -6149,7 +6354,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
61496354 harness.continuationRequests[0]?.notification?.summary,
61506355 `Command "${WAKE_TASK_DESCRIPTION}" finished`,
61516356 );
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 ),
61536362 ),
61546363 );
61556364
......@@ -6305,7 +6514,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
63056514 : [],
63066515 );
63076516 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 ),
63096522 ),
63106523 );
63116524
......@@ -6411,7 +6624,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
64116624 finalSubagent?.type === "subagent.updated" && finalSubagent.subagent.status,
64126625 "completed",
64136626 );
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 ),
64156632 ),
64166633 );
64176634
......@@ -6572,7 +6789,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
65726789 finalSubagent?.type === "subagent.updated" && finalSubagent.subagent.status,
65736790 "completed",
65746791 );
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 ),
65766797 ),
65776798 );
65786799
......@@ -6760,7 +6981,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
67606981 subagentRouting(harness.events, []).assistantTexts(outer?.childThreadId ?? undefined),
67616982 ["OUTER_WORKING", "OUTER_DONE"],
67626983 );
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 ),
67646989 ),
67656990 );
67666991
......@@ -6890,7 +7115,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
68907115 );
68917116 assert.equal(nested?.type === "subagent.updated" && nested.subagent.status, "completed");
68927117 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 ),
68947123 ),
68957124 );
68967125
......@@ -6942,7 +7171,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
69427171 yield* Queue.shutdown(harness.sdkMessages);
69437172 yield* awaitUntil(() => bashStatuses().length === 2, "call ended");
69447173 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 ),
69467179 ),
69477180 );
69487181
......@@ -7004,7 +7237,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
70047237 "command_execution:running",
70057238 "command_execution:interrupted",
70067239 ]);
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 ),
70087245 ),
70097246 );
70107247
......@@ -7170,7 +7407,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
71707407 `command_execution:running:${input}`,
71717408 `command_execution:completed:${input}`,
71727409 ]);
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 ),
71747415 ),
71757416 );
71767417
......@@ -7326,7 +7567,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
73267567 `dynamic_tool:running:${input}`,
73277568 `dynamic_tool:completed:${input}`,
73287569 ]);
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 ),
73307575 ),
73317576 );
73327577
......@@ -7403,7 +7648,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
74037648 "Part two.",
74047649 ]);
74057650 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 ),
74077656 ),
74087657 );
74097658
......@@ -7578,7 +7827,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
75787827 ),
75797828 );
75807829 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 ),
75827835 ),
75837836 );
75847837
......@@ -7639,7 +7892,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
76397892 : [],
76407893 );
76417894 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 ),
76437900 ),
76447901 );
76457902
......@@ -7728,7 +7985,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
77287985 FINAL_REPORT,
77297986 ]);
77307987 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 ),
77327993 ),
77337994 );
77347995
......@@ -7835,7 +8096,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
78358096 );
78368097 const child = harness.events.find((event) => event.type === "app_thread.created");
78378098 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 ),
78398104 ),
78408105 );
78418106
......@@ -7920,7 +8185,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
79208185 }),
79218186 );
79228187 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 ),
79248193 ),
79258194 );
79268195
......@@ -8013,7 +8282,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
80138282
80148283 assert.equal(subagentEvents().at(-1)?.subagent.status, "cancelled");
80158284 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 ),
80178290 ),
80188291 );
80198292
......@@ -8299,7 +8572,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
82998572 assert.lengthOf(resumedWorkIndexes, 1);
83008573 assert.isAbove(resumedWorkIndexes[0] ?? -1, reopenIndex);
83018574 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 ),
83038580 ),
83048581 );
83058582
......@@ -8315,7 +8592,7 @@ describe("ClaudeAdapterV2 background wake turns", () => {
83158592 const events: Array<ProviderAdapter.ProviderAdapterV2Event> = [];
83168593 const continuationRequests: Array<ProviderContinuationRequests.ProviderContinuationRequest> =
83178594 [];
8318 const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({
8595 const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({
83198596 instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,
83208597 settings: DEFAULT_CLAUDE_SETTINGS,
83218598 environment: {},
......@@ -8482,7 +8759,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
84828759 () => events.filter((event) => event.type === "turn.terminal").length === 2,
84838760 "second turn terminal",
84848761 );
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 ),
84868767 ),
84878768 );
84888769
......@@ -8500,7 +8781,7 @@ describe("ClaudeAdapterV2 background wake turns", () => {
85008781 const events: Array<ProviderAdapter.ProviderAdapterV2Event> = [];
85018782 const continuationRequests: Array<ProviderContinuationRequests.ProviderContinuationRequest> =
85028783 [];
8503 const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({
8784 const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({
85048785 instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,
85058786 settings: DEFAULT_CLAUDE_SETTINGS,
85068787 environment: {},
......@@ -8670,7 +8951,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
86708951 ),
86718952 );
86728953 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 ),
86748959 ),
86758960 );
86768961
......@@ -8737,7 +9022,7 @@ describe("ClaudeAdapterV2 background wake turns", () => {
87379022 const events: Array<ProviderAdapter.ProviderAdapterV2Event> = [];
87389023 const continuationRequests: Array<ProviderContinuationRequests.ProviderContinuationRequest> =
87399024 [];
8740 const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({
9025 const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({
87419026 instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,
87429027 settings: DEFAULT_CLAUDE_SETTINGS,
87439028 environment: {},
......@@ -8920,7 +9205,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
89209205 );
89219206 assert.equal(subagentNodeEvents.at(-1)?.node.status, "completed");
89229207 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 ),
89249213 ),
89259214 );
89269215
......@@ -8936,7 +9225,7 @@ describe("ClaudeAdapterV2 background wake turns", () => {
89369225 });
89379226 const processQueues: Array<Queue.Queue<SDKMessage>> = [];
89389227 const events: Array<ProviderAdapter.ProviderAdapterV2Event> = [];
8939 const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({
9228 const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({
89409229 instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,
89419230 settings: DEFAULT_CLAUDE_SETTINGS,
89429231 environment: {},
......@@ -9086,7 +9375,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
90869375 }),
90879376 );
90889377 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 ),
90909383 ),
90919384 );
90929385
......@@ -9103,7 +9396,7 @@ describe("ClaudeAdapterV2 background wake turns", () => {
91039396 let openCount = 0;
91049397 const processQueues: Array<Queue.Queue<SDKMessage>> = [];
91059398 const events: Array<ProviderAdapter.ProviderAdapterV2Event> = [];
9106 const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({
9399 const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({
91079400 instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,
91089401 settings: DEFAULT_CLAUDE_SETTINGS,
91099402 environment: {},
......@@ -9215,7 +9508,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
92159508 // no replacement is opened.
92169509 assert.equal(openCount, 1);
92179510 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 ),
92199516 ),
92209517 );
92219518
......@@ -9234,7 +9531,7 @@ describe("ClaudeAdapterV2 background wake turns", () => {
92349531 const events: Array<ProviderAdapter.ProviderAdapterV2Event> = [];
92359532 const continuationRequests: Array<ProviderContinuationRequests.ProviderContinuationRequest> =
92369533 [];
9237 const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({
9534 const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({
92389535 instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,
92399536 settings: DEFAULT_CLAUDE_SETTINGS,
92409537 environment: {},
......@@ -9416,7 +9713,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
94169713 "continuation request after retry",
94179714 );
94189715 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 ),
94209721 ),
94219722 );
94229723
......@@ -9429,7 +9730,7 @@ describe("ClaudeAdapterV2 background wake turns", () => {
94299730 prefix: "t3-claude-v2-first-open-fail-",
94309731 });
94319732 const events: Array<ProviderAdapter.ProviderAdapterV2Event> = [];
9432 const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({
9733 const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({
94339734 instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,
94349735 settings: DEFAULT_CLAUDE_SETTINGS,
94359736 environment: {},
......@@ -9491,7 +9792,11 @@ describe("ClaudeAdapterV2 background wake turns", () => {
94919792 assert.isTrue(Exit.isFailure(failedStart));
94929793 // No live process ever existed: do not emit a fabricated empty roster.
94939794 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 ),
94959800 ),
94969801 );
94979802
......@@ -9572,7 +9877,12 @@ describe("ClaudeAdapterV2 background wake turns", () => {
95729877 },
95739878 { objective: condition, status: "complete", checks: 1 },
95749879 ]);
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 ),
95769886 );
95779887
95789888 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", () => {
96069916 status: "active",
96079917 checks: 0,
96089918 });
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 ),
96109925 );
96119926
96129927 it.effect("keeps a goal active after a command turn with no model output", () =>
......@@ -9640,7 +9955,12 @@ describe("ClaudeAdapterV2 background wake turns", () => {
96409955 status: "active",
96419956 checks: 2,
96429957 });
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 ),
96449964 );
96459965 });
96469966});
apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts+12-8
......@@ -117,6 +117,7 @@ import {
117117 normalizeMcpText,
118118} from "@t3tools/provider-core/server/mcpToolPresentation";
119119import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession";
120import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
120121import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
121122import {
122123 makeProviderFailure,
......@@ -973,7 +974,7 @@ export const CLAUDE_T3_MCP_TOOL_TIMEOUT_MS = 65 * 60 * 1_000;
973974const CLAUDE_T3_MCP_AUTHORIZATION_ENV = "T3_CODE_MCP_AUTHORIZATION";
974975
975976export function claudeMcpQueryOverrides(input: {
976 readonly threadId: ThreadId;
977 readonly mcpSession: McpProviderSession.McpProviderSessionConfig | undefined;
977978 readonly readOnlySandbox: boolean;
978979 readonly allowedTools?: ReadonlyArray<string>;
979980}): {
......@@ -981,7 +982,7 @@ export function claudeMcpQueryOverrides(input: {
981982 readonly mcpServers?: ClaudeQueryOptions["mcpServers"];
982983 readonly mcpEnvironment?: Readonly<Record<string, string>>;
983984} {
984 const session = McpProviderSession.readMcpProviderSession(input.threadId);
985 const session = input.mcpSession;
985986 if (session === undefined) {
986987 return input.allowedTools === undefined ? {} : { allowedTools: input.allowedTools };
987988 }
......@@ -3026,9 +3027,10 @@ export interface ClaudeAdapterV2Options {
30263027 };
30273028}
30283029
3029export function makeClaudeAdapterV2(
3030export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* (
30303031 adapterOptions: ClaudeAdapterV2Options,
3031): ProviderAdapter.ProviderAdapterV2["Service"] {
3032) {
3033 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
30323034 const { attachmentsDir, fileSystem, path, crypto, idAllocator, queryRunner } = adapterOptions;
30333035 const continuationRequests = adapterOptions.continuationRequests ?? {
30343036 offer: () => Effect.void,
......@@ -7261,7 +7263,7 @@ export function makeClaudeAdapterV2(
72617263 ) {
72627264 const queryPolicy = claudeRuntimeQueryPolicyForRuntimePolicy(turnInput.runtimePolicy);
72637265 const mcpOverrides = claudeMcpQueryOverrides({
7264 threadId: turnInput.threadId,
7266 mcpSession: yield* mcpSessions.read(turnInput.threadId),
72657267 readOnlySandbox:
72667268 sandboxPolicyKindForClaudeRuntimePolicy(turnInput.runtimePolicy) === "readOnly",
72677269 ...(queryPolicy.allowedTools === undefined
......@@ -8156,13 +8158,14 @@ export function makeClaudeAdapterV2(
81568158 ),
81578159 ),
81588160 });
8159}
8161});
81608162
81618163export type ClaudeAdapterV2DriverEnv =
81628164 | ClaudeAgentSdkQueryRunner
81638165 | Crypto.Crypto
81648166 | FileSystem.FileSystem
81658167 | IdAllocator.IdAllocatorV2
8168 | McpProviderSessions.McpProviderSessions
81668169 | Path.Path
81678170 | ServerConfig.ServerConfig;
81688171
......@@ -8186,7 +8189,7 @@ export const createClaudeAdapterV2 = Effect.fn("ClaudeAdapterV2Driver.create")(
81868189 expandHomePath(config.binaryPath),
81878190 claudeEnvironment,
81888191 );
8189 return makeClaudeAdapterV2({
8192 return yield* makeClaudeAdapterV2({
81908193 instanceId,
81918194 settings: { ...config, enabled, binaryPath },
81928195 environment: claudeEnvironment,
......@@ -8234,7 +8237,7 @@ const makeDefaultClaudeAdapterV2 = Effect.fn("ClaudeAdapterV2.layer")(function*
82348237 const serverConfig = yield* ServerConfig.ServerConfig;
82358238 const continuationRequests = yield* ProviderContinuationRequests.ProviderContinuationRequests;
82368239
8237 return makeClaudeAdapterV2({
8240 return yield* makeClaudeAdapterV2({
82388241 instanceId: CLAUDE_DEFAULT_INSTANCE_ID,
82398242 settings: DEFAULT_CLAUDE_SETTINGS,
82408243 environment: hostEnvironment,
......@@ -8255,6 +8258,7 @@ const layer: Layer.Layer<
82558258 | Crypto.Crypto
82568259 | FileSystem.FileSystem
82578260 | IdAllocator.IdAllocatorV2
8261 | McpProviderSessions.McpProviderSessions
82588262 | Path.Path
82598263 | ServerConfig.ServerConfig
82608264> = 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";
5454
5555import packageJson from "../../../package.json" with { type: "json" };
5656import * as ServerConfig from "../../config.ts";
57import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession";
57import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
5858import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";
5959import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
6060import * as EffectWorker from "../EffectWorker.ts";
......@@ -639,7 +639,7 @@ describe("CodexAdapterV2 runtime policy", () => {
639639describe("CodexAdapterV2 process spawning", () => {
640640 it("injects cwd, model, and MCP authorization into thread-scoped params", () => {
641641 const threadId = ThreadId.make("thread-codex-mcp");
642 McpProviderSession.setMcpProviderSession({
642 const mcpSession = {
643643 environmentId: EnvironmentId.make("environment-codex-mcp"),
644644 threadId,
645645 providerSessionId: "mcp-session-codex",
......@@ -647,38 +647,34 @@ describe("CodexAdapterV2 process spawning", () => {
647647 endpoint: "http://127.0.0.1:43123/mcp",
648648 authorizationHeader: "Bearer secret-codex-token",
649649 browserToolsAvailable: true,
650 });
650 };
651651
652 try {
653 assert.deepEqual(
654 CodexAdapterV2.codexThreadRuntimeParams({
655 threadId,
656 modelSelection: { model: "gpt-5.4" },
657 runtimePolicy: {
658 runtimeMode: "full-access",
659 interactionMode: "default",
660 cwd: "/workspace/thread-codex-mcp",
661 },
662 }),
663 {
652 assert.deepEqual(
653 CodexAdapterV2.codexThreadRuntimeParams({
654 mcpSession,
655 modelSelection: { model: "gpt-5.4" },
656 runtimePolicy: {
657 runtimeMode: "full-access",
658 interactionMode: "default",
664659 cwd: "/workspace/thread-codex-mcp",
665 model: "gpt-5.4",
666 config: {
667 "tools.update_plan.enabled": true,
668 mcp_servers: {
669 "t3-code": {
670 url: "http://127.0.0.1:43123/mcp",
671 http_headers: {
672 Authorization: "Bearer secret-codex-token",
673 },
660 },
661 }),
662 {
663 cwd: "/workspace/thread-codex-mcp",
664 model: "gpt-5.4",
665 config: {
666 "tools.update_plan.enabled": true,
667 mcp_servers: {
668 "t3-code": {
669 url: "http://127.0.0.1:43123/mcp",
670 http_headers: {
671 Authorization: "Bearer secret-codex-token",
674672 },
675673 },
676674 },
677675 },
678 );
679 } finally {
680 McpProviderSession.clearMcpProviderSession(threadId);
681 }
676 },
677 );
682678 });
683679
684680 it.effect("resolves Windows command shims through the shared spawn policy", () =>
......@@ -822,7 +818,9 @@ describe("CodexAdapterV2 process spawning", () => {
822818
823819 assert.deepEqual(spawnedCommands, [path.join(NodeOS.homedir(), "bin", "codex")]);
824820 }).pipe(
825 Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer)),
821 Effect.provide(
822 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
823 ),
826824 Effect.provideService(HostProcessPlatform, "linux"),
827825 ),
828826 );
......@@ -1716,7 +1714,7 @@ describe("CodexAdapterV2 session initialize", () => {
17161714 beforeEmitInbound === undefined ? {} : { beforeEmitInbound },
17171715 );
17181716 let initializeRequests = 0;
1719 const adapter = CodexAdapterV2.makeCodexAdapterV2({
1717 const adapter = yield* CodexAdapterV2.makeCodexAdapterV2({
17201718 instanceId: CodexAdapterV2.CODEX_DEFAULT_INSTANCE_ID,
17211719 settings: DEFAULT_CODEX_SETTINGS,
17221720 environment: {},
......@@ -1811,7 +1809,12 @@ describe("CodexAdapterV2 session initialize", () => {
18111809 providerThreads.map((providerThread) => providerThread.nativeThreadRef?.nativeId),
18121810 ["concurrent-first", "concurrent-second"],
18131811 );
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 ),
18151818 );
18161819
18171820 it.effect("retries initialize after a failed handshake", () =>
......@@ -1837,7 +1840,12 @@ describe("CodexAdapterV2 session initialize", () => {
18371840 const providerThread = yield* session.ensureThread("thread-initialize-retry");
18381841 assert.equal(providerThread.nativeThreadRef?.nativeId, "initialize-retry");
18391842 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 ),
18411849 );
18421850
18431851 it.effect("completes the handshake after a caller is interrupted mid-initialize", () =>
......@@ -1880,7 +1888,12 @@ describe("CodexAdapterV2 session initialize", () => {
18801888 const providerThread = yield* session.ensureThread("thread-initialize-interrupted");
18811889 assert.equal(providerThread.nativeThreadRef?.nativeId, "initialize-interrupted");
18821890 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 ),
18841897 );
18851898});
18861899
......@@ -1940,7 +1953,7 @@ describe("CodexAdapterV2 post-settle continuation", () => {
19401953 ),
19411954 ),
19421955 };
1943 const adapter = CodexAdapterV2.makeCodexAdapterV2({
1956 const adapter = yield* CodexAdapterV2.makeCodexAdapterV2({
19441957 instanceId: CodexAdapterV2.CODEX_DEFAULT_INSTANCE_ID,
19451958 settings: DEFAULT_CODEX_SETTINGS,
19461959 environment: {},
......@@ -2121,7 +2134,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {
21212134 );
21222135 assert.equal(requests.filter((method) => method === "turn/start").length, 1);
21232136 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 ),
21252143 );
21262144
21272145 it.effect("identifies sessions to Codex with the same client info as main", () =>
......@@ -2157,7 +2175,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {
21572175 },
21582176 },
21592177 ]);
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 ),
21612184 );
21622185
21632186 it.effect("unsubscribes from the native thread when it is unloaded", () =>
......@@ -2195,7 +2218,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {
21952218 assert.isDefined(harness.runtime.unloadThread);
21962219 yield* harness.runtime.unloadThread!({ providerThread: harness.providerThread });
21972220 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 ),
21992227 );
22002228
22012229 it.effect("keeps the app-server failure as the cause when an unload is rejected", () =>
......@@ -2229,7 +2257,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {
22292257 assert.equal(error._tag, "ProviderAdapterProtocolError");
22302258 const cause = error._tag === "ProviderAdapterProtocolError" ? error.cause : undefined;
22312259 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 ),
22332266 );
22342267
22352268 it.effect("waits for native start before interrupting an acknowledged queued turn", () =>
......@@ -2323,7 +2356,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {
23232356 assert.equal(harness.terminalEvents()[0]?.status, "interrupted");
23242357 assert.lengthOf(harness.terminalEvents(), 1);
23252358 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 ),
23272365 );
23282366
23292367 it.effect("settles Stop when a queued native turn fails before starting", () =>
......@@ -2405,7 +2443,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {
24052443 assert.lengthOf(harness.terminalEvents(), 1);
24062444 assert.isFalse(interruptSent, "A terminal native turn must not receive turn/interrupt");
24072445 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 ),
24092452 );
24102453
24112454 it.effect("bounds Stop when a queued native turn never starts", () =>
......@@ -2465,7 +2508,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {
24652508 assert.lengthOf(harness.terminalEvents(), 1);
24662509 assert.isFalse(interruptSent, "An unstarted native turn must not receive turn/interrupt");
24672510 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 ),
24692517 );
24702518
24712519 it.effect("sends currency-sigil skill mentions to Codex as $ mentions", () =>
......@@ -2524,7 +2572,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {
25242572 text: "then £ship it",
25252573 },
25262574 });
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 ),
25282581 );
25292582
25302583 const assistantMessages = (events: ReadonlyArray<ProviderAdapter.ProviderAdapterV2Event>) =>
......@@ -2674,7 +2727,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
26742727 });
26752728 }
26762729 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 ),
26782735 ),
26792736 );
26802737
......@@ -2748,7 +2805,7 @@ describe("CodexAdapterV2 post-settle continuation", () => {
27482805 ],
27492806 });
27502807 const harness = yield* makeCodexReplayHarness(transcript);
2751 McpProviderSession.setMcpProviderSession({
2808 yield* (yield* McpProviderSessions.McpProviderSessions).set({
27522809 environmentId: EnvironmentId.make("test"),
27532810 threadId: harness.threadId,
27542811 providerSessionId: "context-session",
......@@ -2757,9 +2814,6 @@ describe("CodexAdapterV2 post-settle continuation", () => {
27572814 authorizationHeader: "Bearer test",
27582815 browserToolsAvailable: true,
27592816 });
2760 yield* Effect.addFinalizer(() =>
2761 Effect.sync(() => McpProviderSession.clearMcpProviderSession(harness.threadId)),
2762 );
27632817 yield* harness.runtime.startTurn(
27642818 makeCodexTestTurnInput({
27652819 threadId: harness.threadId,
......@@ -2771,7 +2825,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
27712825 );
27722826 yield* harness.firstTerminal;
27732827 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 ),
27752833 ),
27762834 );
27772835
......@@ -2847,7 +2905,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
28472905 );
28482906 assert.equal(items[0]?.id, items[1]?.id);
28492907 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 ),
28512913 ),
28522914 );
28532915
......@@ -2895,7 +2957,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
28952957 assert.equal(resumed.nativeThreadRef?.nativeId, nativeThreadId);
28962958 assert.equal(resumed.status, "idle");
28972959 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 ),
28992965 ),
29002966 );
29012967
......@@ -3050,7 +3116,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
30503116 );
30513117 assert.equal(resumed.status, "idle");
30523118 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 ),
30543124 ),
30553125 );
30563126
......@@ -3132,7 +3202,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
31323202 restartContinuationOfRunId: RunId.make("run-before-restart"),
31333203 });
31343204 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 ),
31363210 ),
31373211 );
31383212
......@@ -3280,7 +3354,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
32803354 assert.isAtLeast(recoveredIndex, 0);
32813355 assert.isAbove(resumedCommandIndex, recoveredIndex);
32823356 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 ),
32843362 ),
32853363 );
32863364
......@@ -3379,7 +3457,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
33793457 ["compaction", 1782622565000],
33803458 ],
33813459 );
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 ),
33833465 ),
33843466 );
33853467
......@@ -3543,7 +3625,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
35433625 );
35443626 }
35453627 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 ),
35473633 ),
35483634 );
35493635
......@@ -3672,7 +3758,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
36723758 assistantMessages(harness.events).map((event) => event.message.text),
36733759 ["CODEX_RECOVERY_OK"],
36743760 );
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 ),
36763766 ),
36773767 );
36783768
......@@ -3715,7 +3805,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
37153805 })),
37163806 [{ text: "CODEX_RECOVERY_OK", streaming: false }],
37173807 );
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 ),
37193813 ),
37203814 );
37213815
......@@ -3759,7 +3853,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
37593853 assistantMessages(harness.events).map((event) => event.message.text),
37603854 ["CODEX_RECOVERY_OK"],
37613855 );
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 ),
37633861 ),
37643862 );
37653863
......@@ -3878,7 +3976,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
38783976 new Set(assistantMessages(harness.events).map((event) => event.message.id)).size,
38793977 1,
38803978 );
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 ),
38823984 ),
38833985 );
38843986
......@@ -3906,7 +4008,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
39064008 assistantMessages(harness.events).map((event) => event.message.text),
39074009 [""],
39084010 );
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 ),
39104016 ),
39114017 );
39124018
......@@ -3935,7 +4041,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
39354041 assistantMessages(harness.events).map((event) => event.message.text),
39364042 [""],
39374043 );
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 ),
39394049 ),
39404050 );
39414051
......@@ -3964,7 +4074,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
39644074 assistantMessages(harness.events).map((event) => event.message.text),
39654075 ["Working on it.", ""],
39664076 );
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 ),
39684082 ),
39694083 );
39704084
......@@ -3993,7 +4107,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
39934107 assistantMessages(harness.events).map((event) => event.message.text),
39944108 ["CODEX_RECOVERY_OK"],
39954109 );
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 ),
39974115 ),
39984116 );
39994117
......@@ -4022,7 +4140,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
40224140 assistantMessages(harness.events).map((event) => event.message.text),
40234141 ["CODEX_RECOVERY_OK"],
40244142 );
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 ),
40264148 ),
40274149 );
40284150
......@@ -4051,7 +4173,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
40514173 assistantMessages(harness.events).map((event) => event.message.text),
40524174 ["", "CODEX_RECOVERY_OK"],
40534175 );
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 ),
40554181 ),
40564182 );
40574183
......@@ -4207,7 +4333,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
42074333 );
42084334 assert.lengthOf(harness.terminalEvents(), 1);
42094335 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 ),
42114341 ),
42124342 );
42134343
......@@ -4362,7 +4492,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
43624492 assert.lengthOf(harness.terminalEvents(), 1);
43634493 assert.equal(harness.terminalEvents()[0]?.status, "completed");
43644494 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 ),
43664500 );
43674501 },
43684502 );
......@@ -4722,7 +4856,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
47224856 assert.lengthOf(harness.continuationRequests, 0);
47234857 assert.isFalse(yield* harness.hasPendingBackgroundWork);
47244858 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 ),
47264864 ),
47274865 );
47284866
......@@ -5011,7 +5149,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
50115149 );
50125150 assert.isAtLeast(commandUpdates.length, 2, "start + interrupt terminalization");
50135151 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 ),
50155157 ),
50165158 );
50175159
......@@ -5239,7 +5381,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
52395381 assert.equal(harness.subagentUpdates().at(-1)?.subagent.status, "interrupted");
52405382 assertChildProviderTerminalBeforeRoot(harness.events, harness.threadId);
52415383 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 ),
52435389 ),
52445390 );
52455391
......@@ -5324,7 +5470,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
53245470 yield* awaitUntil(() => harness.terminalEvents().length === 1, "interrupted root terminal");
53255471 assertChildProviderTerminalBeforeRoot(harness.events, harness.threadId);
53265472 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 ),
53285478 ),
53295479 );
53305480
......@@ -5412,7 +5562,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
54125562 yield* awaitUntil(() => harness.terminalEvents().length === 1, "interrupted root terminal");
54135563 assertChildProviderTerminalBeforeRoot(harness.events, harness.threadId);
54145564 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 ),
54165570 ),
54175571 );
54185572
......@@ -5523,7 +5677,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
55235677 assert.equal(lateChildUpdates.at(-1)?.providerTurn.status, "interrupted");
55245678 assertChildProviderTerminalBeforeRoot(harness.events, harness.threadId);
55255679 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 ),
55275685 ),
55285686 );
55295687
......@@ -5688,7 +5846,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
56885846 );
56895847 assertChildProviderTerminalBeforeRoot(harness.events, harness.threadId);
56905848 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 ),
56925854 ),
56935855 );
56945856
......@@ -5953,7 +6115,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
59536115 commandUpdatesBeforeLateEvents.length,
59546116 "late starts and completions must not project after timeout",
59556117 );
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 ),
59576123 ),
59586124 );
59596125
......@@ -6060,7 +6226,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
60606226 yield* awaitUntil(() => harness.terminalEvents().length === 1, "interrupted terminal");
60616227 assert.equal(harness.terminalEvents()[0]?.status, "interrupted");
60626228 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 ),
60646234 ),
60656235 );
60666236
......@@ -6296,7 +6466,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
62966466 assert.isDefined(item);
62976467 }
62986468 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 ),
63006474 ),
63016475 );
63026476
......@@ -6394,7 +6568,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
63946568 );
63956569 assert.isFalse(yield* harness.hasPendingBackgroundWork);
63966570 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 ),
63986576 ),
63996577 );
64006578
......@@ -6555,7 +6733,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
65556733 assert.include(stored, "connect-src https://api.weather.test");
65566734 assert.isBelow(stored.indexOf("Content-Security-Policy"), stored.indexOf("<script>"));
65576735 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 ),
65596741 ),
65606742 );
65616743
......@@ -6641,7 +6823,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
66416823 const output = rows[0]!.turnItem.type === "dynamic_tool" ? rows[0]!.turnItem.output : null;
66426824 assert.isUndefined((output as Record<string, unknown> | null)?.[MCP_APP_OUTPUT_KEY]);
66436825 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 ),
66456831 ),
66466832 );
66476833
......@@ -6883,7 +7069,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
68837069 );
68847070 assert.lengthOf(harness.terminalEvents(), 1);
68857071 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 ),
68877077 ),
68887078 );
68897079
......@@ -7117,7 +7307,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
71177307 ]);
71187308 }
71197309 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 ),
71217315 ),
71227316 );
71237317
......@@ -7174,7 +7368,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
71747368 yield* harness.firstTerminal;
71757369 assert.equal(harness.subagentUpdates().at(-1)?.subagent.model, model);
71767370 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 ),
71787376 ),
71797377 );
71807378
......@@ -7273,7 +7471,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
72737471 { id: "reasoningEffort", value: "low" },
72747472 { id: "serviceTier", value: "ultrafast" },
72757473 ]);
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 ),
72777479 ),
72787480 );
72797481
......@@ -7335,7 +7537,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
73357537 assert.isFalse(yield* harness.hasPendingBackgroundWork);
73367538 assert.lengthOf(harness.terminalEvents(), 1);
73377539 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 ),
73397545 ),
73407546 );
73417547
......@@ -7415,7 +7621,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
74157621 assert.isNull(row?.completedAt);
74167622 assert.isNotNull(row?.startedAt);
74177623 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 ),
74197629 ),
74207630 );
74217631
......@@ -7577,7 +7787,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
75777787 DateTime.toEpochMillis(first.subagent.completedAt!),
75787788 );
75797789 }
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 ),
75817795 ),
75827796 );
75837797
......@@ -7725,7 +7939,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {
77257939 "thread/rollback",
77267940 "thread/rollback must not be sent to a legacy Codex thread",
77277941 );
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 ),
77297948 );
77307949
77317950 it.effect(
......@@ -7864,7 +8083,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {
78648083 assert.notEqual(forkedProviderThread.id, harness.providerThread.id);
78658084 assert.equal(forkedProviderThread.forkedFrom?.providerTurnId, firstTurn.id);
78668085 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 ),
78688092 );
78698093
78708094 it.effect(
......@@ -7966,7 +8190,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {
79668190 "thread/rollback",
79678191 "thread/rollback must not be sent to a legacy Codex fork",
79688192 );
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 ),
79708199 );
79718200
79728201 it.effect("propagates native thread/fork failures as typed fork errors", () =>
......@@ -8022,7 +8251,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {
80228251
80238252 assert.instanceOf(error, ProviderAdapter.ProviderAdapterForkThreadError);
80248253 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 ),
80268260 );
80278261
80288262 describe("native goals", () => {
......@@ -8186,7 +8420,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {
81868420 ],
81878421 );
81888422 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 ),
81908429 );
81918430
81928431 it.effect("settles /goal pause with a reply instead of a native turn", () =>
......@@ -8230,7 +8469,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {
82308469 "Goal paused. Send /goal resume to continue.",
82318470 );
82328471 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 ),
82348478 );
82358479
82368480 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", () => {
83218565 harness.terminalEvents().map((event) => event.status),
83228566 ["interrupted"],
83238567 );
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 ),
83258574 );
83268575
83278576 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", () => {
84008649 harness.terminalEvents().map((event) => event.status),
84018650 ["interrupted"],
84028651 );
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 ),
84048658 );
84058659
84068660 it.effect("stops a held goal run's background command when Stop arrives between turns", () =>
......@@ -8502,7 +8756,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {
85028756 ),
85038757 "the background command to be interrupted",
85048758 );
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 ),
85068765 );
85078766
85088767 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", () => {
85848843 harness.terminalEvents().map((event) => event.status),
85858844 ["interrupted"],
85868845 );
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 ),
85888852 );
85898853
85908854 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", () => {
86818945 ),
86828946 "the subagent turn to be interrupted",
86838947 );
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 ),
86858954 );
86868955
86878956 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", () => {
87949063 ),
87959064 "the subagent turn to be interrupted",
87969065 );
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 ),
87989072 );
87999073
88009074 it.effect("pauses an active goal before Stop interrupts its turn", () =>
......@@ -8858,7 +9132,12 @@ describe("CodexAdapterV2 post-settle continuation", () => {
88589132 harness.terminalEvents().map((event) => event.status),
88599133 ["interrupted"],
88609134 );
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 ),
88629141 );
88639142 });
88649143});
apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts+22-14
......@@ -108,6 +108,7 @@ import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEve
108108import { codexAppServerArgs, resolveCodexLaunchArgs } from "../../provider/codexLaunchArgs.ts";
109109import { mergeProviderInstanceEnvironment } from "@t3tools/provider-core/server/instanceEnvironment";
110110import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession";
111import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
111112import {
112113 MCP_APP_EXTENSION_ID,
113114 MCP_APP_MIME_TYPE,
......@@ -1296,7 +1297,7 @@ export class CodexAppServerClientFactory extends Context.Service<
12961297export const CODEX_THREAD_CONFIG = { "tools.update_plan.enabled": true } as const;
12971298
12981299export function codexThreadRuntimeParams(input: {
1299 readonly threadId: ThreadId | null;
1300 readonly mcpSession: McpProviderSession.McpProviderSessionConfig | undefined;
13001301 readonly modelSelection?: { readonly model: string };
13011302 readonly runtimePolicy?: ProviderAdapter.ProviderAdapterV2RuntimePolicy;
13021303}): {
......@@ -1304,8 +1305,7 @@ export function codexThreadRuntimeParams(input: {
13041305 readonly model?: string;
13051306 readonly config: Readonly<Record<string, Schema.Json>>;
13061307} {
1307 const mcpSession =
1308 input.threadId === null ? undefined : McpProviderSession.readMcpProviderSession(input.threadId);
1308 const { mcpSession } = input;
13091309 return {
13101310 ...(input.runtimePolicy?.cwd == null ? {} : { cwd: input.runtimePolicy.cwd }),
13111311 ...(input.modelSelection === undefined ? {} : { model: input.modelSelection.model }),
......@@ -1549,6 +1549,7 @@ export type CodexAdapterV2DriverEnv =
15491549 | Crypto.Crypto
15501550 | FileSystem.FileSystem
15511551 | IdAllocator.IdAllocatorV2
1552 | McpProviderSessions.McpProviderSessions
15521553 | Path.Path
15531554 | ServerConfig;
15541555
......@@ -1585,7 +1586,7 @@ export const createCodexAdapterV2 = (
15851586 homePath: homeLayout.effectiveHomePath ?? "",
15861587 } satisfies CodexSettings;
15871588
1588 return makeCodexAdapterV2({
1589 return yield* makeCodexAdapterV2({
15891590 instanceId,
15901591 settings,
15911592 environment: mergeProviderInstanceEnvironment(environment, hostEnvironment),
......@@ -1613,6 +1614,7 @@ const layer: Layer.Layer<
16131614 | Crypto.Crypto
16141615 | FileSystem.FileSystem
16151616 | IdAllocator.IdAllocatorV2
1617 | McpProviderSessions.McpProviderSessions
16161618 | ServerConfig
16171619> = Layer.effect(
16181620 ProviderAdapter.ProviderAdapterV2,
......@@ -1625,7 +1627,7 @@ const layer: Layer.Layer<
16251627 const idAllocator = yield* IdAllocator.IdAllocatorV2;
16261628 const serverConfig = yield* ServerConfig;
16271629
1628 return makeCodexAdapterV2({
1630 return yield* makeCodexAdapterV2({
16291631 instanceId: CODEX_DEFAULT_INSTANCE_ID,
16301632 settings: DEFAULT_CODEX_SETTINGS,
16311633 environment: hostEnvironment,
......@@ -1667,9 +1669,12 @@ export interface CodexAdapterV2Options {
16671669 };
16681670}
16691671
1670export function makeCodexAdapterV2(
1672export const makeCodexAdapterV2 = Effect.fn("makeCodexAdapterV2")(function* (
16711673 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);
16731678 const { clientFactory, crypto, fileSystem, idAllocator, serverConfig } = adapterOptions;
16741679 const continuationRequests = adapterOptions.continuationRequests;
16751680
......@@ -6166,7 +6171,7 @@ export function makeCodexAdapterV2(
61666171 ) =>
61676172 Effect.gen(function* () {
61686173 const threadId = yield* getNativeThreadId(turnInput.providerThread);
6169 const mcpSession = McpProviderSession.readMcpProviderSession(turnInput.threadId);
6174 const mcpSession = yield* mcpSessions.read(turnInput.threadId);
61706175 const turnStartParams = yield* buildCodexTurnStartParams({
61716176 nativeThreadId: threadId,
61726177 codexInput,
......@@ -6445,11 +6450,12 @@ export function makeCodexAdapterV2(
64456450 }),
64466451 ensureThread: (threadInput) =>
64476452 ensureInitialized.pipe(
6448 Effect.andThen(
6453 Effect.andThen(mcpSessions.read(threadInput.threadId)),
6454 Effect.flatMap((mcpSession) =>
64496455 client.request(
64506456 "thread/start",
64516457 codexThreadRuntimeParams({
6452 threadId: threadInput.threadId,
6458 mcpSession,
64536459 modelSelection: threadInput.modelSelection,
64546460 runtimePolicy: threadInput.runtimePolicy,
64556461 }),
......@@ -6482,7 +6488,9 @@ export function makeCodexAdapterV2(
64826488 threadId: nativeThreadId,
64836489 excludeTurns: true,
64846490 ...codexThreadRuntimeParams({
6485 threadId: threadInput.threadId ?? threadInput.providerThread.appThreadId,
6491 mcpSession: yield* readMcpSession(
6492 threadInput.threadId ?? threadInput.providerThread.appThreadId,
6493 ),
64866494 ...(threadInput.modelSelection === undefined
64876495 ? {}
64886496 : { modelSelection: threadInput.modelSelection }),
......@@ -7210,7 +7218,7 @@ export function makeCodexAdapterV2(
72107218 threadId,
72117219 excludeTurns: true,
72127220 ...codexThreadRuntimeParams({
7213 threadId: threadInput.providerThread.appThreadId,
7221 mcpSession: yield* readMcpSession(threadInput.providerThread.appThreadId),
72147222 modelSelection: input.modelSelection,
72157223 runtimePolicy: input.runtimePolicy,
72167224 }),
......@@ -7259,7 +7267,7 @@ export function makeCodexAdapterV2(
72597267 ? {}
72607268 : { lastTurnId: boundary.lastTurnId }),
72617269 ...codexThreadRuntimeParams({
7262 threadId: threadInput.targetThreadId,
7270 mcpSession: yield* mcpSessions.read(threadInput.targetThreadId),
72637271 ...(threadInput.modelSelection === undefined
72647272 ? {}
72657273 : { modelSelection: threadInput.modelSelection }),
......@@ -7344,4 +7352,4 @@ export function makeCodexAdapterV2(
73447352 ),
73457353 ),
73467354 });
7347}
7355});
apps/server/src/orchestration-v2/Adapters/CursorAdapterV2.testkit.ts+1
......@@ -719,6 +719,7 @@ export const recordCursorAgentSdkReplayTranscript = Effect.fn(
719719 : { override: input.runtimePolicyOverride }),
720720 }),
721721 threadId,
722 mcpSession: undefined,
722723 });
723724 const sendOptions = {
724725 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
2626import type { ProviderInstance } from "@t3tools/provider-core/server/driver";
2727import * as ProviderInstanceRegistry from "../../provider/ProviderInstanceRegistry.ts";
2828import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
29import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
2930import * as ProviderAdapter from "@t3tools/provider-core/server/ProviderAdapter";
3031import * as RuntimePolicy from "../RuntimePolicy.ts";
3132import { acpPermissionDisposition } from "@t3tools/provider-acp/server/clientPolicy";
......@@ -309,7 +310,12 @@ describe("Grok permission prompts", () => {
309310
310311describe("Grok launch permission mode", () => {
311312 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
314320 // Opens a session through the adapter's own Grok runtime factory and returns
315321 // 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";
3939import { describe } from "vite-plus/test";
4040
4141import 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";
4343import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
4444import * as ProviderContinuationRequests from "@t3tools/provider-core/server/ProviderContinuationRequests";
4545import {
......@@ -403,7 +403,7 @@ const history = {
403403 cursor: {},
404404};
405405
406describe("OpenCode2 adapter", () => {
406it.layer(McpProviderSessions.layer)("OpenCode2 adapter", (it) => {
407407 it.effect("switches the session's model and variant before a turn that changed them", () =>
408408 Effect.gen(function* () {
409409 const { runtime, thread } = yield* resumed([
......@@ -2486,29 +2486,6 @@ describe("OpenCode2 adapter", () => {
24862486 }).pipe(Effect.scoped),
24872487 );
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
25122489 it.effect(
25132490 "backfills and ends a subagent's turn whose execution ended while the stream was down",
25142491 () =>
......@@ -2697,7 +2674,8 @@ describe("OpenCode2 adapter", () => {
26972674 "registers T3's MCP server for the thread alone and removes it when the thread unloads",
26982675 () =>
26992676 Effect.gen(function* () {
2700 McpProviderSession.setMcpProviderSession({
2677 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
2678 yield* mcpSessions.set({
27012679 environmentId: EnvironmentId.make("environment:opencode2-adapter"),
27022680 threadId,
27032681 providerSessionId: "mcp:opencode2-adapter",
......@@ -2706,9 +2684,7 @@ describe("OpenCode2 adapter", () => {
27062684 authorizationHeader: "Bearer thread-credential",
27072685 browserToolsAvailable: false,
27082686 });
2709 yield* Effect.addFinalizer(() =>
2710 Effect.sync(() => McpProviderSession.clearMcpProviderSession(threadId)),
2711 );
2687 yield* Effect.addFinalizer(() => mcpSessions.clear(threadId));
27122688 const server = "t3-code-thread_opencode2-adapter";
27132689 const { runtime, thread } = yield* resumed([
27142690 // Registered for the session's directory under the thread's own name;
......@@ -2744,7 +2720,8 @@ describe("OpenCode2 adapter", () => {
27442720 "thread:delegated-task:command%3Amcp%3A48bef2bf-6d0e-4f7a-9c3b-2e5d8a1f7c40%3Adelegate-task%3Asubproject-b-round1",
27452721 );
27462722 const server = "t3-code-aa73fa1e03099934";
2747 McpProviderSession.setMcpProviderSession({
2723 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
2724 yield* mcpSessions.set({
27482725 environmentId: EnvironmentId.make("environment:opencode2-adapter"),
27492726 threadId: child,
27502727 providerSessionId: "mcp:opencode2-adapter",
......@@ -2753,9 +2730,7 @@ describe("OpenCode2 adapter", () => {
27532730 authorizationHeader: "Bearer thread-credential",
27542731 browserToolsAvailable: false,
27552732 });
2756 yield* Effect.addFinalizer(() =>
2757 Effect.sync(() => McpProviderSession.clearMcpProviderSession(child)),
2758 );
2733 yield* Effect.addFinalizer(() => mcpSessions.clear(child));
27592734 const runtime = yield* openCode2ReplayRuntimeWithInstructions([
27602735 ...opening,
27612736 out("session.get", { sessionID: SESSION }),
......@@ -3992,6 +3967,31 @@ describe("OpenCode2 adapter", () => {
39923967 );
39933968});
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
39953995/** The provider turn the adapter derives for `turnInput`'s attempt. */
39963996const providerTurnId = Effect.gen(function* () {
39973997 const ids = yield* IdAllocator.IdAllocatorV2;
......@@ -4001,7 +4001,7 @@ const providerTurnId = Effect.gen(function* () {
40014001 });
40024002}).pipe(Effect.provide(IdAllocator.layer));
40034003
4004describe("OpenCode reported model variants", () => {
4004it.layer(McpProviderSessions.layer)("OpenCode reported model variants", (it) => {
40054005 it.effect(
40064006 "updates reported variants from selected-model and step events without duplicate updates",
40074007 () =>
apps/server/src/orchestration-v2/ClaudeAutomaticDelivery.integration.test.ts+7-2
......@@ -32,6 +32,7 @@ import * as ClaudeAdapterV2 from "./Adapters/ClaudeAdapterV2.ts";
3232import * as EffectWorker from "./EffectWorker.ts";
3333import * as EventSink from "./EventSink.ts";
3434import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
35import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
3536import * as LegacyV1ThreadImporter from "./legacy/LegacyV1ThreadImporter.ts";
3637import * as Orchestrator from "./Orchestrator.ts";
3738import * as ProviderAdapterRegistry from "./ProviderAdapterRegistry.ts";
......@@ -131,7 +132,7 @@ it.effect.each(["child completion", "scheduled message", "user steering"] as con
131132 const offers: SDKUserMessage[] = [];
132133 const nativeQueue: SDKUserMessage[] = [];
133134 const batchAbort = new AbortController();
134 const adapter = ClaudeAdapterV2.makeClaudeAdapterV2({
135 const adapter = yield* ClaudeAdapterV2.makeClaudeAdapterV2({
135136 instanceId: modelSelection.instanceId,
136137 settings,
137138 environment: {},
......@@ -423,6 +424,10 @@ it.effect.each(["child completion", "scheduled message", "user steering"] as con
423424 ),
424425 ),
425426 );
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 ),
427432 ),
428433);
apps/server/src/orchestration-v2/CursorOrchestratorV2.live.test.ts+2
......@@ -30,6 +30,7 @@ import * as ServerEnvironment from "../environment/ServerEnvironment.ts";
3030import * as ModelManifest from "../provider/ModelManifest.ts";
3131import * as ProviderInstanceRegistryHydration from "../provider/ProviderInstanceRegistryHydration.ts";
3232import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";
33import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
3334import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";
3435import * as OpenCodeRuntime from "@t3tools/provider-opencode/server/OpenCodeRuntime";
3536import * as OpenCodeServerLedger from "@t3tools/provider-opencode/server/OpenCodeServerLedger";
......@@ -124,6 +125,7 @@ const layerLive = RuntimeLayer.layer.pipe(
124125 Layer.provide(ResetCreditCoordinator.layer),
125126 Layer.provide(layerBackgroundPolicy),
126127 Layer.provide(layerPlatformTest),
128 Layer.provide(McpProviderSessions.layer),
127129);
128130
129131const waitForIdle = Effect.fn("CursorOrchestratorV2Live.waitForIdle")(function* (
apps/server/src/orchestration-v2/DelegatedCompletionDelivery.test.ts+2
......@@ -1,4 +1,5 @@
11import * as SourceControlProviderRegistry from "../sourceControl/SourceControlProviderRegistry.ts";
2import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
23import * as NodeServices from "@effect/platform-node/NodeServices";
34import { assert, it } from "@effect/vitest";
45import {
......@@ -130,6 +131,7 @@ const layerTest = Layer.mergeAll(RuntimeLayer.layer, RuntimeLayer.layerEventSink
130131 }),
131132 ),
132133 Layer.provide(McpSessionRegistryTestkit.layer),
134 Layer.provide(McpProviderSessions.layer),
133135 Layer.provide(SqlitePersistence.layerMemory),
134136 Layer.provide(layerCheckpointStoreTest),
135137 Layer.provide(layerServerConfig),
apps/server/src/orchestration-v2/GrokOrchestratorV2.live.test.ts+2
......@@ -30,6 +30,7 @@ import * as ServerEnvironment from "../environment/ServerEnvironment.ts";
3030import * as ModelManifest from "../provider/ModelManifest.ts";
3131import * as ProviderInstanceRegistryHydration from "../provider/ProviderInstanceRegistryHydration.ts";
3232import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";
33import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
3334import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";
3435import * as OpenCodeRuntime from "@t3tools/provider-opencode/server/OpenCodeRuntime";
3536import * as OpenCodeServerLedger from "@t3tools/provider-opencode/server/OpenCodeServerLedger";
......@@ -123,6 +124,7 @@ const layerLive = RuntimeLayer.layer.pipe(
123124 Layer.provide(ResetCreditCoordinator.layer),
124125 Layer.provide(layerBackgroundPolicy),
125126 Layer.provide(layerPlatformTest),
127 Layer.provide(McpProviderSessions.layer),
126128);
127129
128130const 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";
5555import * as ModelManifest from "../provider/ModelManifest.ts";
5656import * as ProviderInstanceRegistryHydration from "../provider/ProviderInstanceRegistryHydration.ts";
5757import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";
58import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
5859import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";
5960import * as OpenCode2Client from "@t3tools/provider-opencode/server/v2/OpenCode2Client";
6061import * as OpenCodeRuntime from "@t3tools/provider-opencode/server/OpenCodeRuntime";
......@@ -217,6 +218,7 @@ const layerOrchestration = RuntimeLayer.layer.pipe(
217218 Layer.provide(ResetCreditCoordinator.layer),
218219 Layer.provide(layerBackgroundPolicy),
219220 Layer.provide(layerPlatformTest),
221 Layer.provide(McpProviderSessions.layer),
220222);
221223
222224// 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 @@
11import * as NodeServices from "@effect/platform-node/NodeServices";
2import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
23import { assert, it } from "@effect/vitest";
34import { ProjectId } from "@t3tools/contracts";
45import * as Effect from "effect/Effect";
......@@ -152,6 +153,7 @@ const layerRuntime = (dbPath: string) => {
152153 }),
153154 ),
154155 Layer.provide(McpSessionRegistryTestkit.layer),
156 Layer.provide(McpProviderSessions.layer),
155157 Layer.provideMerge(SqlitePersistence.layerFromPath(dbPath)),
156158 Layer.provide(layerCheckpointStore),
157159 Layer.provide(layerServerConfig),
apps/server/src/orchestration-v2/ProviderSessionManager.test.ts+44-32
......@@ -36,6 +36,7 @@ import { ProviderWorkspaceMissingError } from "../provider/Errors.ts";
3636import * as ServerEnvironment from "../environment/ServerEnvironment.ts";
3737import * as ProjectService from "../project/ProjectService.ts";
3838import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession";
39import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
3940import * as McpSessionRegistry from "../mcp/McpSessionRegistry.ts";
4041import * as SqlitePersistence from "../persistence/Sqlite.ts";
4142import * as ServerSettings from "../serverSettings.ts";
......@@ -302,7 +303,7 @@ function unimplemented(detail: string) {
302303 );
303304}
304305
305function makeProviderAdapter(
306const makeProviderAdapter = Effect.fnUntraced(function* (
306307 state: Ref.Ref<TestProviderRuntimeState>,
307308 options: {
308309 readonly failEventStream?: boolean;
......@@ -324,7 +325,8 @@ function makeProviderAdapter(
324325 /** Completed when a hanging scope close reaches its wedged finalizer. */
325326 readonly scopeCloseReached?: Deferred.Deferred<void>;
326327 } = {},
327): ProviderAdapter.ProviderAdapterV2["Service"] {
328) {
329 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
328330 const countClose = Effect.addFinalizer(() =>
329331 Ref.update(state, (current) => ({
330332 ...current,
......@@ -340,7 +342,7 @@ function makeProviderAdapter(
340342 : Deferred.succeed(options.scopeCloseReached, undefined)
341343 ).pipe(Effect.andThen(Effect.never)),
342344 );
343 return {
345 return ProviderAdapter.ProviderAdapterV2.of({
344346 instanceId: ProviderInstanceId.make("codex"),
345347 driver: CODEX_DRIVER,
346348 getCapabilities: () => Effect.succeed(options.capabilities ?? CodexCapabilities),
......@@ -352,19 +354,15 @@ function makeProviderAdapter(
352354 if (options.hangSessionScopeClose === true) yield* hangClose;
353355 }
354356 if (options.mcpConfigs !== undefined && options.spawnBeforeOpen === true) {
355 yield* Ref.update(options.mcpConfigs, (configs) => [
356 ...configs,
357 McpProviderSession.readMcpProviderSession(input.threadId),
358 ]);
357 const mcpConfig = yield* mcpSessions.read(input.threadId);
358 yield* Ref.update(options.mcpConfigs, (configs) => [...configs, mcpConfig]);
359359 }
360360 if (options.beforeOpen !== undefined) {
361361 yield* options.beforeOpen(input);
362362 }
363363 if (options.mcpConfigs !== undefined && options.spawnBeforeOpen !== true) {
364 yield* Ref.update(options.mcpConfigs, (configs) => [
365 ...configs,
366 McpProviderSession.readMcpProviderSession(input.threadId),
367 ]);
364 const mcpConfig = yield* mcpSessions.read(input.threadId);
365 yield* Ref.update(options.mcpConfigs, (configs) => [...configs, mcpConfig]);
368366 }
369367 const now = yield* DateTime.now;
370368 const events = yield* Queue.unbounded<ProviderAdapter.ProviderAdapterV2Event, Cause.Done>();
......@@ -440,8 +438,8 @@ function makeProviderAdapter(
440438 forkThread: () => unimplemented("forkThread unused in test"),
441439 } satisfies ProviderAdapter.ProviderAdapterV2SessionRuntime;
442440 }),
443 };
444}
441 });
442});
445443
446444function layerTest(input: {
447445 readonly state: Ref.Ref<TestProviderRuntimeState>;
......@@ -482,7 +480,7 @@ function layerTest(input: {
482480 : input.failReleaseEventWrites
483481 ? layerFailingReleaseEventSink
484482 : layerTestEventSink;
485 const layerRegistry = ProviderAdapterRegistry.layerSingle(
483 const layerRegistry = Layer.unwrap(
486484 makeProviderAdapter(input.state, {
487485 failEventStream: input.failEventStream ?? false,
488486 ...(input.capabilities === undefined ? {} : { capabilities: input.capabilities }),
......@@ -503,7 +501,7 @@ function layerTest(input: {
503501 ...(input.scopeCloseReached === undefined
504502 ? {}
505503 : { scopeCloseReached: input.scopeCloseReached }),
506 }),
504 }).pipe(Effect.map(ProviderAdapterRegistry.layerSingle)),
507505 );
508506 const layerConfiguredMcpRegistry =
509507 input.pauseResolve === undefined
......@@ -524,13 +522,14 @@ function layerTest(input: {
524522 layerConfiguredEventSink,
525523 IdAllocator.layer,
526524 layerConfiguredMcpRegistry,
525 McpProviderSessions.layer,
527526 ProviderSessionManager.layerWithOptions({
528527 idleTimeoutMs: input.idleTimeoutMs,
529528 ...(input.maxIdlePinMs === undefined ? {} : { maxIdlePinMs: input.maxIdlePinMs }),
530529 }).pipe(
531530 Layer.provide(
532531 Layer.mergeAll(
533 layerRegistry,
532 layerRegistry.pipe(Layer.provideMerge(McpProviderSessions.layer)),
534533 layerConfiguredEventSink,
535534 IdAllocator.layer,
536535 layerProviderEventIngestorTest,
......@@ -1106,6 +1105,7 @@ it.effect("ProviderSessionManagerV2 cleans up an open interrupted mid-handshake"
11061105 const idAllocator = yield* IdAllocator.IdAllocatorV2;
11071106 const manager = yield* ProviderSessionManager.ProviderSessionManagerV2;
11081107 const registry = yield* McpSessionRegistry.McpSessionRegistry;
1108 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
11091109 const projectionStore = yield* ProjectionStore.ProjectionStoreV2;
11101110 const now = yield* DateTime.now;
11111111 const threadId = ThreadId.make("thread-provider-session-manager-interrupted-open");
......@@ -1133,7 +1133,7 @@ it.effect("ProviderSessionManagerV2 cleans up an open interrupted mid-handshake"
11331133 // for it revoked.
11341134 assert.equal((yield* Ref.get(state)).closeCount, 1);
11351135 assert.isUndefined(yield* registry.resolve(token!));
1136 assert.isUndefined(McpProviderSession.readMcpProviderSession(threadId));
1136 assert.isUndefined(yield* mcpSessions.read(threadId));
11371137 assert.isTrue(Option.isNone(yield* manager.get(providerSessionId)));
11381138
11391139 // 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
11881188 const idAllocator = yield* IdAllocator.IdAllocatorV2;
11891189 const manager = yield* ProviderSessionManager.ProviderSessionManagerV2;
11901190 const registry = yield* McpSessionRegistry.McpSessionRegistry;
1191 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
11911192 const now = yield* DateTime.now;
11921193 const threadId = ThreadId.make("thread-provider-session-manager-interrupted-hung-open");
11931194 const providerSessionId = yield* idAllocator.allocate.providerSession({
......@@ -1212,7 +1213,7 @@ it.effect("ProviderSessionManagerV2 cleans up an interrupted open whose scope cl
12121213
12131214 // The session cleanup already ran, ahead of the stuck close.
12141215 assert.isUndefined(yield* registry.resolve(token!));
1215 assert.isUndefined(McpProviderSession.readMcpProviderSession(threadId));
1216 assert.isUndefined(yield* mcpSessions.read(threadId));
12161217 assert.equal((yield* Ref.get(state)).closeCount, 0);
12171218
12181219 // The close is time-boxed, so the interrupter returns and the session's
......@@ -1750,6 +1751,7 @@ it.effect(
17501751 const idAllocator = yield* IdAllocator.IdAllocatorV2;
17511752 const manager = yield* ProviderSessionManager.ProviderSessionManagerV2;
17521753 const registry = yield* McpSessionRegistry.McpSessionRegistry;
1754 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
17531755 const now = yield* DateTime.now;
17541756 const threadId = ThreadId.make("thread-provider-session-manager-mcp");
17551757 const providerSessionId = yield* idAllocator.allocate.providerSession({
......@@ -1782,7 +1784,7 @@ it.effect(
17821784 );
17831785
17841786 yield* manager.close(providerSessionId);
1785 assert.isUndefined(McpProviderSession.readMcpProviderSession(threadId));
1787 assert.isUndefined(yield* mcpSessions.read(threadId));
17861788 assert.isUndefined(yield* registry.resolve(token!));
17871789 });
17881790
......@@ -1915,6 +1917,7 @@ it.effect("ProviderSessionManagerV2 revokes MCP credentials when release persist
19151917 const idAllocator = yield* IdAllocator.IdAllocatorV2;
19161918 const manager = yield* ProviderSessionManager.ProviderSessionManagerV2;
19171919 const registry = yield* McpSessionRegistry.McpSessionRegistry;
1920 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
19181921 const now = yield* DateTime.now;
19191922 const threadId = ThreadId.make("thread-provider-session-manager-mcp-release-failure");
19201923 const providerSessionId = yield* idAllocator.allocate.providerSession({
......@@ -1939,7 +1942,7 @@ it.effect("ProviderSessionManagerV2 revokes MCP credentials when release persist
19391942
19401943 const closeError = yield* manager.close(providerSessionId).pipe(Effect.flip);
19411944 assert.equal(closeError._tag, "ProviderSessionCloseError");
1942 assert.isUndefined(McpProviderSession.readMcpProviderSession(threadId));
1945 assert.isUndefined(yield* mcpSessions.read(threadId));
19431946 assert.isUndefined(yield* registry.resolve(token!));
19441947 });
19451948
......@@ -1968,6 +1971,7 @@ it.effect(
19681971 const idAllocator = yield* IdAllocator.IdAllocatorV2;
19691972 const manager = yield* ProviderSessionManager.ProviderSessionManagerV2;
19701973 const registry = yield* McpSessionRegistry.McpSessionRegistry;
1974 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
19711975 const now = yield* DateTime.now;
19721976 const owner = ThreadId.make("thread-provider-session-manager-attach-race-owner");
19731977 const threadId = ThreadId.make("thread-provider-session-manager-attach-race");
......@@ -2007,7 +2011,7 @@ it.effect(
20072011 yield* Fiber.join(second);
20082012
20092013 // The second attach owns the thread's attachment and credential.
2010 const config = McpProviderSession.readMcpProviderSession(threadId);
2014 const config = yield* mcpSessions.read(threadId);
20112015 assert.isDefined(config);
20122016 const token = config!.authorizationHeader.replace(/^Bearer\s+/, "");
20132017 assert.equal((yield* registry.resolve(token))?.thread.threadId, threadId);
......@@ -2031,6 +2035,7 @@ it.effect(
20312035 const idAllocator = yield* IdAllocator.IdAllocatorV2;
20322036 const manager = yield* ProviderSessionManager.ProviderSessionManagerV2;
20332037 const registry = yield* McpSessionRegistry.McpSessionRegistry;
2038 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
20342039 const eventStore = yield* EventStore.EventStoreV2;
20352040 const now = yield* DateTime.now;
20362041 const owner = ThreadId.make("thread-provider-session-manager-stale-attach-owner");
......@@ -2075,14 +2080,14 @@ it.effect(
20752080 modelSelection,
20762081 runtimePolicy,
20772082 });
2078 const config = McpProviderSession.readMcpProviderSession(threadId);
2083 const config = yield* mcpSessions.read(threadId);
20792084 assert.isDefined(config);
20802085
20812086 yield* Fiber.interrupt(stale);
20822087
20832088 // The replacement keeps the thread and its credential.
20842089 assert.equal(
2085 McpProviderSession.readMcpProviderSession(threadId)?.providerSessionId,
2090 (yield* mcpSessions.read(threadId))?.providerSessionId,
20862091 config!.providerSessionId,
20872092 );
20882093 const token = config!.authorizationHeader.replace(/^Bearer\s+/, "");
......@@ -2126,6 +2131,7 @@ it.effect(
21262131 const idAllocator = yield* IdAllocator.IdAllocatorV2;
21272132 const manager = yield* ProviderSessionManager.ProviderSessionManagerV2;
21282133 const registry = yield* McpSessionRegistry.McpSessionRegistry;
2134 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
21292135 const now = yield* DateTime.now;
21302136 const owner = ThreadId.make("thread-provider-session-manager-resolve-stop-owner");
21312137 const threadId = ThreadId.make("thread-provider-session-manager-resolve-stop");
......@@ -2157,7 +2163,7 @@ it.effect(
21572163 // The thread gets a credential, then detaches and keeps it for a re-attach.
21582164 yield* resume;
21592165 yield* manager.detach({ providerSessionId, threadId });
2160 const config = McpProviderSession.readMcpProviderSession(threadId);
2166 const config = yield* mcpSessions.read(threadId);
21612167 assert.isDefined(config);
21622168 const token = config!.authorizationHeader.replace(/^Bearer\s+/, "");
21632169
......@@ -2189,6 +2195,7 @@ it.effect("ProviderSessionManagerV2 duplicate detach preserves replacement MCP c
21892195 const idAllocator = yield* IdAllocator.IdAllocatorV2;
21902196 const manager = yield* ProviderSessionManager.ProviderSessionManagerV2;
21912197 const registry = yield* McpSessionRegistry.McpSessionRegistry;
2198 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
21922199 const now = yield* DateTime.now;
21932200 const threadId = ThreadId.make("thread-provider-session-manager-replacement-mcp");
21942201 const oldSessionId = yield* idAllocator.allocate.providerSession({
......@@ -2222,14 +2229,14 @@ it.effect("ProviderSessionManagerV2 duplicate detach preserves replacement MCP c
22222229 const replacementToken = replacement?.authorizationHeader.replace(/^Bearer\s+/, "");
22232230 assert.isDefined(replacementToken);
22242231 assert.equal(
2225 McpProviderSession.readMcpProviderSession(threadId)?.providerSessionId,
2232 (yield* mcpSessions.read(threadId))?.providerSessionId,
22262233 replacement?.providerSessionId,
22272234 );
22282235
22292236 yield* manager.detach({ providerSessionId: oldSessionId, threadId });
22302237
22312238 assert.equal(
2232 McpProviderSession.readMcpProviderSession(threadId)?.providerSessionId,
2239 (yield* mcpSessions.read(threadId))?.providerSessionId,
22332240 replacement?.providerSessionId,
22342241 );
22352242 assert.equal((yield* registry.resolve(replacementToken!))?.thread.threadId, threadId);
......@@ -2261,6 +2268,7 @@ it.effect(
22612268 const idAllocator = yield* IdAllocator.IdAllocatorV2;
22622269 const manager = yield* ProviderSessionManager.ProviderSessionManagerV2;
22632270 const registry = yield* McpSessionRegistry.McpSessionRegistry;
2271 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
22642272 const now = yield* DateTime.now;
22652273 const threadId = ThreadId.make("thread-provider-session-manager-superseded-mcp");
22662274 const oldSessionId = yield* idAllocator.allocate.providerSession({
......@@ -2296,7 +2304,7 @@ it.effect(
22962304 const replacementToken = replacement?.authorizationHeader.replace(/^Bearer\s+/, "");
22972305 assert.isDefined(replacementToken);
22982306 assert.equal(
2299 McpProviderSession.readMcpProviderSession(threadId)?.providerSessionId,
2307 (yield* mcpSessions.read(threadId))?.providerSessionId,
23002308 replacement?.providerSessionId,
23012309 );
23022310
......@@ -2305,7 +2313,7 @@ it.effect(
23052313 yield* manager.detach({ providerSessionId: oldSessionId, threadId });
23062314
23072315 assert.equal(
2308 McpProviderSession.readMcpProviderSession(threadId)?.providerSessionId,
2316 (yield* mcpSessions.read(threadId))?.providerSessionId,
23092317 replacement?.providerSessionId,
23102318 );
23112319 assert.equal((yield* registry.resolve(replacementToken!))?.thread.threadId, threadId);
......@@ -2337,6 +2345,7 @@ it.effect(
23372345 const idAllocator = yield* IdAllocator.IdAllocatorV2;
23382346 const manager = yield* ProviderSessionManager.ProviderSessionManagerV2;
23392347 const registry = yield* McpSessionRegistry.McpSessionRegistry;
2348 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
23402349 const now = yield* DateTime.now;
23412350 const threadId = ThreadId.make("thread-provider-session-manager-stable-mcp");
23422351 const providerSessionId = yield* idAllocator.allocate.providerSession({
......@@ -2379,7 +2388,7 @@ it.effect(
23792388 runtimePolicy,
23802389 });
23812390 assert.equal(
2382 McpProviderSession.readMcpProviderSession(threadId)?.providerSessionId,
2391 (yield* mcpSessions.read(threadId))?.providerSessionId,
23832392 original?.providerSessionId,
23842393 "re-attach must reuse the existing credential, not rotate it",
23852394 );
......@@ -2415,6 +2424,7 @@ it.effect(
24152424 const idAllocator = yield* IdAllocator.IdAllocatorV2;
24162425 const manager = yield* ProviderSessionManager.ProviderSessionManagerV2;
24172426 const registry = yield* McpSessionRegistry.McpSessionRegistry;
2427 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
24182428 const now = yield* DateTime.now;
24192429 const threadId = ThreadId.make("thread-provider-session-manager-stale-record");
24202430 const s1 = yield* idAllocator.allocate.providerSession({
......@@ -2437,7 +2447,7 @@ it.effect(
24372447 // The credential dies externally, so S2's attach must rotate to C2.
24382448 yield* registry.revokeThread(threadId);
24392449 yield* manager.open({ threadId, providerSessionId: s2, modelSelection, runtimePolicy });
2440 const rotated = McpProviderSession.readMcpProviderSession(threadId);
2450 const rotated = yield* mcpSessions.read(threadId);
24412451 assert.isDefined(rotated);
24422452 const rotatedToken = rotated?.authorizationHeader.replace(/^Bearer\s+/, "");
24432453 assert.isDefined(yield* registry.resolve(rotatedToken!));
......@@ -2470,6 +2480,7 @@ it.effect(
24702480 const idAllocator = yield* IdAllocator.IdAllocatorV2;
24712481 const manager = yield* ProviderSessionManager.ProviderSessionManagerV2;
24722482 const registry = yield* McpSessionRegistry.McpSessionRegistry;
2483 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
24732484 const now = yield* DateTime.now;
24742485 const threadId = ThreadId.make("thread-provider-session-manager-open-race");
24752486 const s1 = yield* idAllocator.allocate.providerSession({
......@@ -2498,7 +2509,7 @@ it.effect(
24982509 yield* Ref.set(duringOpen, manager.close(s1).pipe(Effect.orDie));
24992510 yield* manager.open({ threadId, providerSessionId: s2, modelSelection, runtimePolicy });
25002511
2501 const slot = McpProviderSession.readMcpProviderSession(threadId);
2512 const slot = yield* mcpSessions.read(threadId);
25022513 assert.equal(
25032514 slot?.providerSessionId,
25042515 original?.providerSessionId,
......@@ -2542,6 +2553,7 @@ it.effect("ProviderSessionManagerV2 terminal detach revokes the thread's MCP cre
25422553 const idAllocator = yield* IdAllocator.IdAllocatorV2;
25432554 const manager = yield* ProviderSessionManager.ProviderSessionManagerV2;
25442555 const registry = yield* McpSessionRegistry.McpSessionRegistry;
2556 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
25452557 const now = yield* DateTime.now;
25462558 const threadId = ThreadId.make("thread-provider-session-manager-terminal-detach");
25472559 const providerSessionId = yield* idAllocator.allocate.providerSession({
......@@ -2566,7 +2578,7 @@ it.effect("ProviderSessionManagerV2 terminal detach revokes the thread's MCP cre
25662578 revokeMcpCredential: true,
25672579 });
25682580 assert.isUndefined(yield* registry.resolve(token!));
2569 assert.isUndefined(McpProviderSession.readMcpProviderSession(threadId));
2581 assert.isUndefined(yield* mcpSessions.read(threadId));
25702582 });
25712583
25722584 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 {
4040} from "../observability/Metrics.ts";
4141import { ProviderWorkspaceMissingError } from "../provider/Errors.ts";
4242import * as ProjectService from "../project/ProjectService.ts";
43import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession";
43import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
4444import * as ServerSettings from "../serverSettings.ts";
4545import * as McpSessionRegistry from "../mcp/McpSessionRegistry.ts";
4646import * as EventSink from "./EventSink.ts";
......@@ -331,6 +331,7 @@ export const layerWithOptions = (
331331 | EventSink.EventSinkV2
332332 | FileSystem.FileSystem
333333 | IdAllocator.IdAllocatorV2
334 | McpProviderSessions.McpProviderSessions
334335 | McpSessionRegistry.McpSessionRegistry
335336 | ProjectionStore.ProjectionStoreV2
336337 | ProviderEventIngestor.ProviderEventIngestorV2
......@@ -342,6 +343,7 @@ export const layerWithOptions = (
342343 const registry = yield* ProviderAdapterRegistry.ProviderAdapterRegistryV2;
343344 const fileSystem = yield* FileSystem.FileSystem;
344345 const mcpSessionRegistry = yield* McpSessionRegistry.McpSessionRegistry;
346 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
345347 /**
346348 * Optional so the many focused tests that assemble this layer by hand do
347349 * not each need a settings stub; the production composition always
......@@ -472,10 +474,9 @@ export const layerWithOptions = (
472474 providerInstanceId: ProviderInstanceId,
473475 ): Effect.Effect<PreparedMcpCredential> =>
474476 options.configureMcp === false
475 ? Effect.sync((): PreparedMcpCredential => {
476 McpProviderSession.clearMcpProviderSession(threadId);
477 return { mcpCredentialId: undefined, issued: false };
478 })
477 ? mcpSessions
478 .clear(threadId)
479 .pipe(Effect.as<PreparedMcpCredential>({ mcpCredentialId: undefined, issued: false }))
479480 : mcpPrepareLock.withLock(
480481 threadId,
481482 Effect.gen(function* () {
......@@ -492,7 +493,7 @@ export const layerWithOptions = (
492493 >(["orchestration", "worktree", "pull-requests"]);
493494 if (browserToolsAvailable) capabilities.add("preview");
494495 if (deviceToolsAvailable) capabilities.add("device");
495 const existing = McpProviderSession.readMcpProviderSession(threadId);
496 const existing = yield* mcpSessions.read(threadId);
496497 if (existing !== undefined) {
497498 // Reserve before the async resolve so a release cannot
498499 // revoke the credential between validation and reservation.
......@@ -529,7 +530,7 @@ export const layerWithOptions = (
529530 browserToolsAvailable,
530531 capabilities,
531532 });
532 McpProviderSession.setMcpProviderSession(credential.config);
533 yield* mcpSessions.set(credential.config);
533534 reserveMcpCredential(threadId, credential.config.providerSessionId);
534535 return { mcpCredentialId: credential.config.providerSessionId, issued: true };
535536 }),
......@@ -544,19 +545,12 @@ export const layerWithOptions = (
544545 mcpCredentialId === undefined
545546 ? mcpSessionRegistry
546547 .revokeThread(threadId)
547 .pipe(
548 Effect.tap(() =>
549 Effect.sync(() => McpProviderSession.clearMcpProviderSession(threadId)),
550 ),
551 )
548 .pipe(Effect.tap(() => mcpSessions.clear(threadId)))
552549 : mcpSessionRegistry.revokeProviderSession(mcpCredentialId).pipe(
553550 Effect.tap(() =>
554 Effect.sync(() => {
555 if (
556 McpProviderSession.readMcpProviderSession(threadId)?.providerSessionId ===
557 mcpCredentialId
558 ) {
559 McpProviderSession.clearMcpProviderSession(threadId);
551 Effect.gen(function* () {
552 if ((yield* mcpSessions.read(threadId))?.providerSessionId === mcpCredentialId) {
553 yield* mcpSessions.clear(threadId);
560554 }
561555 }),
562556 ),
apps/server/src/orchestration-v2/runtimeLayer.test.ts+5
......@@ -1,4 +1,5 @@
11import { limitRecoveryCommand } from "./UsageLimitRecoveryWorker.ts";
2import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
23import * as SourceControlProviderRegistry from "../sourceControl/SourceControlProviderRegistry.ts";
34import * as NodeServices from "@effect/platform-node/NodeServices";
45import { assert, it } from "@effect/vitest";
......@@ -272,6 +273,7 @@ const layerTest = Layer.mergeAll(
272273 ThreadCommandExecutor.layer,
273274).pipe(
274275 Layer.provide(McpSessionRegistryTestkit.layer),
276 Layer.provide(McpProviderSessions.layer),
275277 Layer.provide(SqlitePersistence.layerMemory),
276278 Layer.provide(layerCheckpointStoreTest),
277279 Layer.provide(layerServerConfig),
......@@ -284,6 +286,7 @@ const layerTest = Layer.mergeAll(
284286
285287const layerLegacyImportTest = RuntimeLayer.layer.pipe(
286288 Layer.provide(McpSessionRegistryTestkit.layer),
289 Layer.provide(McpProviderSessions.layer),
287290 Layer.provideMerge(SqlitePersistence.layerMemory),
288291 Layer.provide(layerCheckpointStoreTest),
289292 Layer.provide(layerServerConfig),
......@@ -323,6 +326,7 @@ const layerProjectDeletionTest = Layer.mergeAll(
323326 }),
324327 ),
325328 Layer.provide(McpSessionRegistryTestkit.layer),
329 Layer.provide(McpProviderSessions.layer),
326330 Layer.provide(SqlitePersistence.layerMemory),
327331 Layer.provide(layerCheckpointStoreTest),
328332 Layer.provide(layerServerConfig),
......@@ -467,6 +471,7 @@ const layerSharedApplicationDataPlaneTest = Layer.mergeAll(
467471 }),
468472 ),
469473 Layer.provide(McpSessionRegistryTestkit.layer),
474 Layer.provide(McpProviderSessions.layer),
470475 Layer.provideMerge(SqlitePersistence.layerMemory),
471476 Layer.provide(layerCheckpointStoreTest),
472477 Layer.provide(layerServerConfig),
apps/server/src/orchestration-v2/testkit/ProviderReplayHarness.ts+15-3
......@@ -16,6 +16,7 @@ import * as ServerConfig from "../../config.ts";
1616import * as SqlitePersistence from "../../persistence/Sqlite.ts";
1717import * as ServerSettings from "../../serverSettings.ts";
1818import * as ThreadManagementService from "../ThreadManagementService.ts";
19import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
1920import * as McpSessionRegistryTestkit from "../../mcp/McpSessionRegistry.testkit.ts";
2021import * as VcsDriverRegistry from "../../vcs/VcsDriverRegistry.ts";
2122import * as VcsProcess from "../../vcs/VcsProcess.ts";
......@@ -170,7 +171,11 @@ export interface OrchestratorV2ProviderReplayHarness<
170171 readonly makeProviderAdapterRegistryLayer: (
171172 transcript: Transcript,
172173 options?: { readonly replayGate?: ProviderReplayGate },
173 ) => Layer.Layer<ProviderAdapterRegistry.ProviderAdapterRegistryV2, Error>;
174 ) => Layer.Layer<
175 ProviderAdapterRegistry.ProviderAdapterRegistryV2,
176 Error,
177 McpProviderSessions.McpProviderSessions
178 >;
174179}
175180
176181export function runOrchestratorV2ProviderReplayScenario<
......@@ -249,7 +254,11 @@ export function layerProviderReplay<Transcript extends ProviderReplayTranscript,
249254
250255export function layerWithRegistry<Error>(
251256 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 >,
253262 options: {
254263 readonly databaseLayer?: Layer.Layer<
255264 SqlClient.SqlClient,
......@@ -286,7 +295,9 @@ export function layerWithRegistry<Error>(
286295 // runtimeLayer.ts; layer memoization keeps it a single instance.
287296 const layerContinuationRequests =
288297 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 );
290301 const layerServerSettings = ServerSettings.layerTest({
291302 responseStreamingMode: "turn",
292303 ...(options.continueThreadsAfterServerUpdate === undefined
......@@ -347,6 +358,7 @@ export function layerWithRegistry<Error>(
347358 layerProvidedRegistry,
348359 layerEventSinkProvided,
349360 IdAllocator.layer,
361 McpProviderSessions.layer,
350362 McpSessionRegistryTestkit.layer,
351363 layerProviderEventIngestorProvided,
352364 layerStores,
apps/server/src/provider/Drivers/AntigravityDriver.test.ts+2
......@@ -30,6 +30,7 @@ import {
3030 resolveAntigravityInstanceDirectories,
3131} from "../antigravityAuthSupport.ts";
3232import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";
33import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
3334import * as ModelManifest from "../ModelManifest.ts";
3435import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
3536import { AntigravityDriver } from "./AntigravityDriver.ts";
......@@ -264,6 +265,7 @@ const layerDeps = ServerConfig.layerTest(process.cwd(), {
264265 ),
265266 Layer.provideMerge(ModelManifest.layerTest),
266267 Layer.provideMerge(IdAllocator.layer),
268 Layer.provideMerge(McpProviderSessions.layer),
267269);
268270const layerTest = ProviderHostLive.layer.pipe(
269271 Layer.provideMerge(ServerSecretStore.layer),
apps/server/src/provider/Drivers/AntigravityDriver.ts+2
......@@ -1,4 +1,5 @@
11import { withAgentDeviceEnvironment } from "@t3tools/provider-core/server/mcpSession";
2import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
23import { AntigravitySettings, ProviderDriverKind, ProviderSetupError } from "@t3tools/contracts";
34import { HostProcessPlatform } from "@t3tools/shared/hostProcess";
45import { resolveSelfInvocation } from "@t3tools/shared/nodeRuntime";
......@@ -73,6 +74,7 @@ export type AntigravityDriverEnv =
7374 | Crypto.Crypto
7475 | FileSystem.FileSystem
7576 | IdAllocator.IdAllocatorV2
77 | McpProviderSessions.McpProviderSessions
7678 | ModelCatalog.ModelCatalog
7779 | Path.Path
7880 | ProviderEventLoggers.ProviderEventLoggers;
apps/server/src/provider/Drivers/CodexDriver.test.ts+2
......@@ -27,6 +27,7 @@ import * as ServerConfig from "../../config.ts";
2727import * as ServerSettings from "../../serverSettings.ts";
2828import * as ResetCreditCoordinator from "../resetCreditCoordinator.ts";
2929import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";
30import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
3031import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";
3132import * as ModelManifest from "../ModelManifest.ts";
3233import {
......@@ -45,6 +46,7 @@ const layerDeps = ServerConfig.layerTest(process.cwd(), {
4546}).pipe(
4647 Layer.provideMerge(NodeServices.layer),
4748 Layer.provideMerge(IdAllocator.layer),
49 Layer.provideMerge(McpProviderSessions.layer),
4850 Layer.provideMerge(
4951 Layer.mock(CodexAdapterV2.CodexAppServerClientFactory)({
5052 open: () => Effect.die("Maintenance resolution must not open a Codex session"),
apps/server/src/provider/ProviderInstanceRegistry.test.ts+3
......@@ -36,6 +36,7 @@ import {
3636 ProviderInstanceId,
3737} from "@t3tools/contracts";
3838import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";
39import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
3940import type { GrokSettings } from "@t3tools/provider-grok/settings";
4041import type { CursorSettings } from "@t3tools/provider-cursor/settings";
4142import { HostProcessPlatform, isHostWindows } from "@t3tools/shared/hostProcess";
......@@ -230,6 +231,7 @@ describe("ProviderInstanceRegistry — multi-instance codex slice", () => {
230231 ),
231232 ),
232233 Layer.provideMerge(ProviderLatestVersions.layer),
234 Layer.provideMerge(McpProviderSessions.layer),
233235 Layer.provideMerge(ModelManifest.layerTest),
234236 Layer.provideMerge(ResetCreditCoordinator.layerTest),
235237 );
......@@ -612,6 +614,7 @@ describe("ProviderInstanceRegistry — all drivers slice", () => {
612614 ),
613615 ),
614616 Layer.provideMerge(ProviderLatestVersions.layer),
617 Layer.provideMerge(McpProviderSessions.layer),
615618 Layer.provideMerge(ModelManifest.layerTest),
616619 Layer.provideMerge(ResetCreditCoordinator.layerTest),
617620 );
apps/server/src/provider/ProviderRegistry.test.ts+5
......@@ -46,6 +46,7 @@ import * as ModelManifest from "./ModelManifest.ts";
4646import { applyProviderCompatibility } from "./providerCompatibility.ts";
4747import * as ResetCreditCoordinator from "./resetCreditCoordinator.ts";
4848import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";
49import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
4950import * as OpenCodeRuntime from "@t3tools/provider-opencode/server/OpenCodeRuntime";
5051import * as OpenCodeServerLedger from "@t3tools/provider-opencode/server/OpenCodeServerLedger";
5152import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";
......@@ -2761,6 +2762,7 @@ it.layer(
27612762 ),
27622763 ),
27632764 Layer.provideMerge(ProviderLatestVersions.layer),
2765 Layer.provideMerge(McpProviderSessions.layer),
27642766 Layer.provideMerge(ModelManifest.layerTest),
27652767 Layer.provideMerge(ResetCreditCoordinator.layerTest),
27662768 Layer.provideMerge(
......@@ -2865,6 +2867,7 @@ it.layer(
28652867 ),
28662868 ),
28672869 Layer.provideMerge(ProviderLatestVersions.layer),
2870 Layer.provideMerge(McpProviderSessions.layer),
28682871 Layer.provideMerge(ModelManifest.layerTest),
28692872 Layer.provideMerge(ResetCreditCoordinator.layerTest),
28702873 Layer.provideMerge(
......@@ -2984,6 +2987,7 @@ it.layer(
29842987 ),
29852988 ),
29862989 Layer.provideMerge(ProviderLatestVersions.layer),
2990 Layer.provideMerge(McpProviderSessions.layer),
29872991 Layer.provideMerge(ModelManifest.layerTest),
29882992 Layer.provideMerge(ResetCreditCoordinator.layerTest),
29892993 Layer.provideMerge(
......@@ -3043,6 +3047,7 @@ it.layer(
30433047 ),
30443048 ),
30453049 Layer.provideMerge(ProviderLatestVersions.layer),
3050 Layer.provideMerge(McpProviderSessions.layer),
30463051 Layer.provideMerge(ModelManifest.layerTest),
30473052 Layer.provideMerge(ResetCreditCoordinator.layerTest),
30483053 Layer.provideMerge(
apps/server/src/server.ts+2
......@@ -45,6 +45,7 @@ import * as ModelManifest from "./provider/ModelManifest.ts";
4545import * as ResetCreditCoordinator from "./provider/resetCreditCoordinator.ts";
4646import * as EventNdjsonLogger from "./provider/EventNdjsonLogger.ts";
4747import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";
48import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
4849import * as OpenCodeRuntime from "@t3tools/provider-opencode/server/OpenCodeRuntime";
4950import * as OpenCodeServerLedger from "@t3tools/provider-opencode/server/OpenCodeServerLedger";
5051import * as ProviderHostLive from "./provider/ProviderHostLive.ts";
......@@ -636,6 +637,7 @@ const layerRuntimeCoreDependencies = layerRuntimeCoreDependenciesBase.pipe(
636637 ModelManifest.layerModelCatalog.pipe(Layer.provideMerge(ModelManifest.layer)),
637638 ResetCreditCoordinator.layer,
638639 ProviderLatestVersions.layer,
640 McpProviderSessions.layer,
639641 ),
640642 ),
641643 // `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";
1818import { ChildProcessSpawner } from "effect/process";
1919import * as EffectAcpErrors from "effect-acp/errors";
2020
21import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
2122import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";
2223import {
2324 normalizeAcpRegistryCommands,
......@@ -259,6 +260,7 @@ export type AcpRegistryAdapterV2DriverEnv =
259260 | AcpRegistrySupport.AcpRegistryCatalog
260261 | IdAllocator.IdAllocatorV2
261262 | Path.Path
263 | McpProviderSessions.McpProviderSessions
262264 | ProviderEventLoggers.ProviderEventLoggers
263265 | ProviderHost.ProviderHost;
264266
packages/provider-acp/src/server/adapter.ts+58-43
......@@ -59,6 +59,7 @@ import { formatReadToolLabel, formatSearchToolLabel } from "@t3tools/shared/tool
5959import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";
6060import { makeAcpMcpOverAcpBridge, type AcpMcpOverAcpBridge } from "./mcpOverAcpBridge.ts";
6161import { mcpToolPresentation } from "@t3tools/provider-core/server/mcpToolPresentation";
62import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
6263import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession";
6364import {
6465 applyAcpAgentTerminalUpdate,
......@@ -677,9 +678,10 @@ interface AcpMcpContext {
677678 readonly authorization?: string;
678679}
679680
680function acpMcpContext(threadId: ThreadId | null, self: SelfInvocation): AcpMcpContext {
681 if (threadId === null) return { servers: [], acpServers: [] };
682 const session = McpProviderSession.readMcpProviderSession(threadId);
681function acpMcpContext(
682 session: McpProviderSession.McpProviderSessionConfig | undefined,
683 self: SelfInvocation,
684): AcpMcpContext {
683685 if (session === undefined) {
684686 return { servers: [], acpServers: [] };
685687 }
......@@ -714,15 +716,7 @@ function acpMcpContext(threadId: ThreadId | null, self: SelfInvocation): AcpMcpC
714716 };
715717}
716718
717function acpMcpServers(
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);
719function acpMcpActivation(context: AcpMcpContext) {
726720 return { mcpServers: context.servers, acpMcpServers: context.acpServers };
727721}
728722
......@@ -1494,7 +1488,12 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* (
14941488 const fileSystem = yield* FileSystem.FileSystem;
14951489 const idAllocator = yield* IdAllocator.IdAllocatorV2;
14961490 const host = yield* ProviderHost.ProviderHost;
1491 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
14971492 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)));
14981497 const driver = flavor.driver;
14991498 const continuationRequests = options.continuationRequests;
15001499 const postSettleContinuationEnabled =
......@@ -1550,32 +1549,32 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* (
15501549 readonly sessionId: string | null;
15511550 }
15521551 let pendingTerminalEnvironment: PendingTerminalEnvironment | null = {
1553 environment: acpMcpContext(input.threadId, self).processEnvironment,
1552 environment: (yield* readMcpContext(input.threadId)).processEnvironment,
15541553 claimUnknownSession: input.initialNativeThreadId === undefined,
15551554 sessionId: input.initialNativeThreadId ?? null,
15561555 };
15571556 const prepareTerminalEnvironment = (
1558 threadId: ThreadId | null,
1557 mcpContext: AcpMcpContext,
15591558 sessionId?: string,
15601559 ): void => {
15611560 pendingTerminalEnvironment = {
1562 environment: acpMcpContext(threadId, self).processEnvironment,
1561 environment: mcpContext.processEnvironment,
15631562 claimUnknownSession: false,
15641563 sessionId: sessionId ?? null,
15651564 };
15661565 };
1567 const prepareClaimableTerminalEnvironment = (threadId: ThreadId | null): void => {
1566 const prepareClaimableTerminalEnvironment = (mcpContext: AcpMcpContext): void => {
15681567 pendingTerminalEnvironment = {
1569 environment: acpMcpContext(threadId, self).processEnvironment,
1568 environment: mcpContext.processEnvironment,
15701569 claimUnknownSession: true,
15711570 sessionId: null,
15721571 };
15731572 };
15741573 const rememberTerminalEnvironment = (
15751574 sessionId: string,
1576 threadId: ThreadId | null,
1575 mcpContext: AcpMcpContext,
15771576 ): void => {
1578 const environment = acpMcpContext(threadId, self).processEnvironment;
1577 const environment = mcpContext.processEnvironment;
15791578 pendingTerminalEnvironment = null;
15801579 if (environment === undefined) {
15811580 terminalEnvironmentBySessionId.delete(sessionId);
......@@ -2097,12 +2096,11 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* (
20972096 ).pipe(Effect.asVoid);
20982097 const makeRuntimeInput = (
20992098 runtimeGeneration: number,
2100 threadId: ThreadId | null,
2099 mcpContext: AcpMcpContext,
21012100 resumeSessionId?: string,
21022101 onTermination: AcpAdapterV2RuntimeInput["onTermination"] = () =>
21032102 handleRuntimeTerminationAtGeneration(runtimeGeneration),
21042103 ): AcpAdapterV2RuntimeInput => {
2105 const mcpContext = acpMcpContext(threadId, self);
21062104 return {
21072105 cwd: input.runtimePolicy.cwd ?? process.cwd(),
21082106 runtimePolicy: input.runtimePolicy,
......@@ -5989,10 +5987,9 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* (
59895987
59905988 /** Builds the MCP-over-ACP bridge for one runtime, disposed with that runtime's scope. */
59915989 const makeRuntimeMcpBridge = Effect.fnUntraced(function* (
5992 threadId: ThreadId | null,
5990 mcpContext: AcpMcpContext,
59935991 scope: Scope.Scope,
59945992 ) {
5995 const mcpContext = acpMcpContext(threadId, self);
59965993 if (mcpContext.endpoint === undefined || mcpContext.authorization === undefined) {
59975994 return undefined;
59985995 }
......@@ -6013,10 +6010,11 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* (
60136010 yield* Scope.close(runtimeScope, Exit.void);
60146011 }
60156012 runtimeScope = yield* Scope.make();
6016 runtimeMcpBridge = yield* makeRuntimeMcpBridge(threadId, runtimeScope);
6013 const mcpContext = yield* readMcpContext(threadId);
6014 runtimeMcpBridge = yield* makeRuntimeMcpBridge(mcpContext, runtimeScope);
60176015 const runtimeGeneration = yield* Ref.get(runtimeCallbackGeneration);
60186016 runtime = yield* flavor
6019 .makeRuntime(makeRuntimeInput(runtimeGeneration, threadId, resumeSessionId))
6017 .makeRuntime(makeRuntimeInput(runtimeGeneration, mcpContext, resumeSessionId))
60206018 .pipe(
60216019 Effect.provideService(Scope.Scope, runtimeScope),
60226020 Effect.provideService(Crypto.Crypto, crypto),
......@@ -6084,12 +6082,13 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* (
60846082 });
60856083 let replacementMcpBridge: AcpMcpOverAcpBridge | undefined;
60866084 const startup = Effect.gen(function* () {
6087 replacementMcpBridge = yield* makeRuntimeMcpBridge(threadId, replacementScope);
6085 const mcpContext = yield* readMcpContext(threadId);
6086 replacementMcpBridge = yield* makeRuntimeMcpBridge(mcpContext, replacementScope);
60886087 const replacementRuntime = yield* flavor
60896088 .makeRuntime(
60906089 makeRuntimeInput(
60916090 replacementGeneration,
6092 threadId,
6091 mcpContext,
60936092 undefined,
60946093 handleCandidateTermination,
60956094 ),
......@@ -6150,7 +6149,10 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* (
61506149 runtimeScope = replacementScope;
61516150 runtimeMcpBridge = replacementMcpBridge;
61526151 yield* Ref.set(runtimeCallbackGeneration, replacementGeneration);
6153 prepareTerminalEnvironment(threadId, replacementExit.value.started.sessionId);
6152 prepareTerminalEnvironment(
6153 yield* readMcpContext(threadId),
6154 replacementExit.value.started.sessionId,
6155 );
61546156 yield* wireAcpRuntimeTerminalHandlers(replacementExit.value.replacementRuntime);
61556157 yield* commitSessionState(replacementExit.value.started);
61566158 while (true) {
......@@ -6224,12 +6226,12 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* (
62246226 });
62256227 itemIdentityVersion = 2;
62266228 yield* Ref.set(runtimeRestartRequired, false);
6227 prepareClaimableTerminalEnvironment(input.threadId);
6229 prepareClaimableTerminalEnvironment(yield* readMcpContext(input.threadId));
62286230 return yield* startAcpRuntime(input.threadId);
62296231 });
62306232 yield* Ref.set(activeSessionId, started.sessionId);
62316233 yield* Ref.set(activeSessionSetup, started);
6232 rememberTerminalEnvironment(started.sessionId, input.threadId);
6234 rememberTerminalEnvironment(started.sessionId, yield* readMcpContext(input.threadId));
62336235 const capabilities = negotiatedCapabilities(flavor.capabilities, started);
62346236 const canLoadSession = started.initializeResult.agentCapabilities?.loadSession === true;
62356237 const canResumeSession =
......@@ -6250,8 +6252,9 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* (
62506252 if (initialFailure !== undefined) {
62516253 return yield* initialFailure;
62526254 }
6253 const activationOptions = acpMcpActivation(threadId, self);
6254 prepareTerminalEnvironment(threadId, sessionId);
6255 const mcpContext = yield* readMcpContext(threadId);
6256 const activationOptions = acpMcpActivation(mcpContext);
6257 prepareTerminalEnvironment(mcpContext, sessionId);
62556258 const activated = canLoadSession
62566259 ? yield* runtime.loadSession(sessionId, activationOptions)
62576260 : canResumeSession
......@@ -6260,7 +6263,7 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* (
62606263 driver,
62616264 detail: `ACP driver cannot load or resume session ${sessionId}`,
62626265 });
6263 rememberTerminalEnvironment(activated.sessionId, threadId);
6266 rememberTerminalEnvironment(activated.sessionId, yield* readMcpContext(threadId));
62646267 return activated;
62656268 });
62666269
......@@ -6771,7 +6774,7 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* (
67716774 const prompt: Array<EffectAcpSchema.ContentBlock> = [];
67726775 const instructionState = {
67736776 interactionMode: turnInput.runtimePolicy.interactionMode,
6774 hasT3Mcp: acpMcpServers(turnInput.threadId, self).length > 0,
6777 hasT3Mcp: (yield* readMcpContext(turnInput.threadId)).servers.length > 0,
67756778 } satisfies T3AcpInstructionState;
67766779 const previousInstructionState = (yield* Ref.get(promptInstructionStates)).get(sessionId);
67776780 const messageText = providerMessageTextWithAttachmentPaths({
......@@ -7820,14 +7823,17 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* (
78207823 loadingMessageId: null,
78217824 loadingIndex: 0,
78227825 });
7823 prepareTerminalEnvironment(snapshotInput.providerThread.appThreadId, sessionId);
7826 const mcpContext = yield* readMcpContext(
7827 snapshotInput.providerThread.appThreadId,
7828 );
7829 prepareTerminalEnvironment(mcpContext, sessionId);
78247830 const activated = yield* runtime.loadSession(
78257831 sessionId,
7826 acpMcpActivation(snapshotInput.providerThread.appThreadId, self),
7832 acpMcpActivation(mcpContext),
78277833 );
78287834 rememberTerminalEnvironment(
78297835 activated.sessionId,
7830 snapshotInput.providerThread.appThreadId,
7836 yield* readMcpContext(snapshotInput.providerThread.appThreadId),
78317837 );
78327838 yield* Ref.set(activeSessionId, activated.sessionId);
78337839 yield* Ref.set(activeSessionSetup, activated);
......@@ -7883,14 +7889,16 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* (
78837889 // loading any of the rolled-back conversation.
78847890 yield* awaitRuntimeTeardown();
78857891 itemIdentityVersion = 2;
7886 prepareTerminalEnvironment(rollbackInput.providerThread.appThreadId);
7892 prepareTerminalEnvironment(
7893 yield* readMcpContext(rollbackInput.providerThread.appThreadId),
7894 );
78877895 const replacement = yield* startReplacementAcpRuntime(
78887896 rollbackInput.providerThread.appThreadId,
78897897 (candidate) =>
78907898 Effect.gen(function* () {
78917899 rememberTerminalEnvironment(
78927900 candidate.sessionId,
7893 rollbackInput.providerThread.appThreadId,
7901 yield* readMcpContext(rollbackInput.providerThread.appThreadId),
78947902 );
78957903 yield* Ref.set(runtimeRestartRequired, false);
78967904 yield* Ref.set(activeSessionId, candidate.sessionId);
......@@ -7983,12 +7991,16 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* (
79837991 driver,
79847992 forkInput.sourceProviderThread,
79857993 );
7986 prepareTerminalEnvironment(forkInput.targetThreadId);
7994 const mcpContext = yield* readMcpContext(forkInput.targetThreadId);
7995 prepareTerminalEnvironment(mcpContext);
79877996 const forked = yield* runtime.forkSession(
79887997 sourceSessionId,
7989 acpMcpActivation(forkInput.targetThreadId, self),
7998 acpMcpActivation(mcpContext),
7999 );
8000 rememberTerminalEnvironment(
8001 forked.sessionId,
8002 yield* readMcpContext(forkInput.targetThreadId),
79908003 );
7991 rememberTerminalEnvironment(forked.sessionId, forkInput.targetThreadId);
79928004 yield* Ref.set(activeSessionId, forked.sessionId);
79938005 yield* Ref.set(activeSessionSetup, forked);
79948006 yield* Ref.set(activeSelection, null);
......@@ -8051,4 +8063,7 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* (
80518063 });
80528064});
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 @@
6363 "types": "./src/server/managedProvider.ts",
6464 "import": "./src/server/managedProvider.ts"
6565 },
66 "./server/McpProviderSessions": {
67 "types": "./src/server/McpProviderSessions.ts",
68 "import": "./src/server/McpProviderSessions.ts"
69 },
6670 "./server/mcpSession": {
6771 "types": "./src/server/mcpSession.ts",
6872 "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(
4040 ...(shimDir ? { PATH: basePath ? `${shimDir}${separator}${basePath}` : shimDir } : {}),
4141 };
4242}
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";
2323import * as Stream from "effect/Stream";
2424
2525import { layerTestProviderHost } from "@t3tools/provider-testing/host";
26import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession";
2726import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
27import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
2828import * as ProviderAdapter from "@t3tools/provider-core/server/ProviderAdapter";
2929import {
3030 cursorMcpServers,
......@@ -199,7 +199,12 @@ describe("CursorAdapterV2", () => {
199199 : "failed",
200200 );
201201 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 ),
203208 );
204209
205210 it.effect("fails standalone SDK transport diagnostics and sends compaction as /compress", () =>
......@@ -344,7 +349,12 @@ describe("CursorAdapterV2", () => {
344349 Stream.runHead,
345350 );
346351 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 ),
348358 );
349359
350360 it.effect("projects Cursor directory trees and lint diagnostics as file search results", () =>
......@@ -746,7 +756,12 @@ describe("CursorAdapterV2", () => {
746756 },
747757 ],
748758 );
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 ),
750765 );
751766
752767 it("maps Cursor auto and model parameters to SDK selections", () => {
......@@ -843,6 +858,7 @@ describe("CursorAdapterV2", () => {
843858 },
844859 runtimePolicy: { runtimeMode, interactionMode: "default", cwd: "/workspace" },
845860 threadId: ThreadId.make("thread-cursor-setting-sources"),
861 mcpSession: undefined,
846862 });
847863 assert.deepEqual(options.local?.settingSources, [
848864 "project",
......@@ -856,7 +872,7 @@ describe("CursorAdapterV2", () => {
856872
857873 it("injects thread-scoped MCP credentials without logging them", () => {
858874 const threadId = ThreadId.make("thread-cursor-mcp");
859 McpProviderSession.setMcpProviderSession({
875 const mcpSession = {
860876 environmentId: EnvironmentId.make("environment-cursor-mcp"),
861877 threadId,
862878 providerSessionId: "mcp-session-cursor",
......@@ -864,40 +880,37 @@ describe("CursorAdapterV2", () => {
864880 endpoint: "http://127.0.0.1:43123/mcp",
865881 authorizationHeader: "Bearer secret-cursor-mcp-token",
866882 browserToolsAvailable: true,
867 });
883 };
868884
869 try {
870 assert.deepEqual(cursorMcpServers(threadId), {
871 "t3-code": {
872 type: "http",
873 url: "http://127.0.0.1:43123/mcp",
874 headers: {
875 Authorization: "Bearer secret-cursor-mcp-token",
876 },
885 assert.deepEqual(cursorMcpServers(mcpSession), {
886 "t3-code": {
887 type: "http",
888 url: "http://127.0.0.1:43123/mcp",
889 headers: {
890 Authorization: "Bearer secret-cursor-mcp-token",
877891 },
878 });
892 },
893 });
879894
880 const options = makeCursorAgentOptions({
881 apiKey: "secret-cursor-api-key",
882 modelSelection: {
883 instanceId: ProviderInstanceId.make("cursor"),
884 model: "composer-2.5",
885 },
886 runtimePolicy: {
887 runtimeMode: "full-access",
888 interactionMode: "default",
889 cwd: "/workspace",
890 },
891 threadId,
892 });
893 assert.deepEqual(options.mcpServers, cursorMcpServers(threadId));
895 const options = makeCursorAgentOptions({
896 apiKey: "secret-cursor-api-key",
897 modelSelection: {
898 instanceId: ProviderInstanceId.make("cursor"),
899 model: "composer-2.5",
900 },
901 runtimePolicy: {
902 runtimeMode: "full-access",
903 interactionMode: "default",
904 cwd: "/workspace",
905 },
906 threadId,
907 mcpSession,
908 });
909 assert.deepEqual(options.mcpServers, cursorMcpServers(mcpSession));
894910
895 const logged = JSON.stringify(CursorAgentSdk.loggedCursorAgentOptions(options));
896 assert.notInclude(logged, "secret-cursor-api-key");
897 assert.notInclude(logged, "secret-cursor-mcp-token");
898 } finally {
899 McpProviderSession.clearMcpProviderSession(threadId);
900 }
911 const logged = JSON.stringify(CursorAgentSdk.loggedCursorAgentOptions(options));
912 assert.notInclude(logged, "secret-cursor-api-key");
913 assert.notInclude(logged, "secret-cursor-mcp-token");
901914 });
902915
903916 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";
4444
4545import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";
4646import { mcpToolPresentation } from "@t3tools/provider-core/server/mcpToolPresentation";
47import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
4748import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession";
4849import { CursorTransportFailure } from "./transportFailure.ts";
4950import { cursorSdkModelSelection } from "./sdkModel.ts";
......@@ -201,8 +202,9 @@ export function cursorRuntimeAgentPolicy(
201202 };
202203}
203204
204export function cursorMcpServers(threadId: ThreadId): Record<string, McpServerConfig> | undefined {
205 const session = McpProviderSession.readMcpProviderSession(threadId);
205export function cursorMcpServers(
206 session: McpProviderSession.McpProviderSessionConfig | undefined,
207): Record<string, McpServerConfig> | undefined {
206208 if (session === undefined) {
207209 return undefined;
208210 }
......@@ -306,9 +308,10 @@ export function makeCursorAgentOptions(input: {
306308 readonly modelSelection: ModelSelection;
307309 readonly runtimePolicy: ProviderAdapter.ProviderAdapterV2RuntimePolicy;
308310 readonly threadId: ThreadId;
311 readonly mcpSession: McpProviderSession.McpProviderSessionConfig | undefined;
309312}): AgentOptions {
310313 const policy = cursorRuntimeAgentPolicy(input.runtimePolicy);
311 const mcpServers = cursorMcpServers(input.threadId);
314 const mcpServers = cursorMcpServers(input.mcpSession);
312315 return {
313316 model: cursorSdkModelSelection(input.modelSelection),
314317 name: `T3 Code ${input.threadId}`,
......@@ -854,6 +857,7 @@ export const makeCursorAdapterV2 = Effect.fn("makeCursorAdapterV2")(function* (
854857 const idAllocator = yield* IdAllocator.IdAllocatorV2;
855858 const runner = yield* CursorAgentSdk.CursorAgentSdkRunner;
856859 const host = yield* ProviderHost.ProviderHost;
860 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
857861 const adapterOptions = options;
858862 const apiKey = adapterOptions.environment.CURSOR_API_KEY?.trim() || undefined;
859863
......@@ -2091,6 +2095,7 @@ export const makeCursorAdapterV2 = Effect.fn("makeCursorAdapterV2")(function* (
20912095 modelSelection: openInput.modelSelection,
20922096 runtimePolicy: openInput.runtimePolicy,
20932097 threadId: openInput.threadId,
2098 mcpSession: yield* mcpSessions.read(openInput.threadId),
20942099 }),
20952100 threadId: openInput.threadId,
20962101 providerSessionId: input.providerSessionId,
......@@ -2106,6 +2111,7 @@ export const makeCursorAdapterV2 = Effect.fn("makeCursorAdapterV2")(function* (
21062111 let cursorSkillNames: ReadonlySet<string> | undefined;
21072112 const resolveUserMessage = Effect.fnUntraced(function* (
21082113 turnInput: ProviderAdapter.ProviderAdapterV2TurnInput,
2114 mcpServers: Record<string, McpServerConfig> | undefined,
21092115 ) {
21102116 const rawText = turnInput.message.text;
21112117 if (rawText.trim() === "/compress" && turnInput.message.attachments.length === 0) {
......@@ -2135,7 +2141,7 @@ export const makeCursorAdapterV2 = Effect.fn("makeCursorAdapterV2")(function* (
21352141 resolveAttachmentPath: host.resolveAttachmentPath,
21362142 }),
21372143 runOrdinal: turnInput.runOrdinal,
2138 hasT3Mcp: cursorMcpServers(turnInput.threadId) !== undefined,
2144 hasT3Mcp: mcpServers !== undefined,
21392145 });
21402146 const images = yield* Effect.forEach(
21412147 turnInput.message.attachments.filter(isProviderNativeImageAttachment),
......@@ -2197,8 +2203,8 @@ export const makeCursorAdapterV2 = Effect.fn("makeCursorAdapterV2")(function* (
21972203 modelSelection: turnInput.modelSelection,
21982204 runtimePolicy: turnInput.runtimePolicy,
21992205 });
2200 const message = yield* resolveUserMessage(turnInput);
2201 const mcpServers = cursorMcpServers(turnInput.threadId);
2206 const mcpServers = cursorMcpServers(yield* mcpSessions.read(turnInput.threadId));
2207 const message = yield* resolveUserMessage(turnInput, mcpServers);
22022208 const pendingUpdates: Array<InteractionUpdate> = [];
22032209 let context: ActiveCursorTurn | null = null;
22042210 const sdkRun = yield* agent.session.send({
......@@ -2603,6 +2609,7 @@ export type CursorAdapterV2DriverEnv =
26032609 | FileSystem.FileSystem
26042610 | Path.Path
26052611 | IdAllocator.IdAllocatorV2
2612 | McpProviderSessions.McpProviderSessions
26062613 | ProviderHost.ProviderHost;
26072614
26082615export const CursorAdapterV2Driver: ProviderAdapterDriver<
......@@ -2646,6 +2653,7 @@ const layer: Layer.Layer<
26462653 | FileSystem.FileSystem
26472654 | Path.Path
26482655 | IdAllocator.IdAllocatorV2
2656 | McpProviderSessions.McpProviderSessions
26492657 | ProviderHost.ProviderHost
26502658> = Layer.effect(
26512659 ProviderAdapter.ProviderAdapterV2,
packages/provider-cursor/src/server/driver.test.ts+2
......@@ -15,12 +15,14 @@ import { layerTestProviderHost } from "@t3tools/provider-testing/host";
1515import { CursorDriver } from "./driver.ts";
1616import * as CursorAgentSdk from "./CursorAgentSdk.ts";
1717import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
18import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
1819import * as ProviderAdapter from "@t3tools/provider-core/server/ProviderAdapter";
1920import { Cursor } from "./sdk.ts";
2021
2122const layerTest = layerTestProviderHost({ runBackgroundWork: false }).pipe(
2223 Layer.provideMerge(NodeServices.layer),
2324 Layer.provideMerge(IdAllocator.layer),
25 Layer.provideMerge(McpProviderSessions.layer),
2426 Layer.provideMerge(
2527 Layer.mock(CursorAgentSdk.CursorAgentSdkRunner)({
2628 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";
2323import { ChildProcessSpawner } from "effect/process";
2424import * as EffectAcpErrors from "effect-acp/errors";
2525
26import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
2627import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";
2728import { makeAcpNativeLoggerFactory } from "@t3tools/provider-acp/server/nativeLogging";
2829import {
......@@ -352,6 +353,7 @@ export type GrokAdapterV2DriverEnv =
352353 | FileSystem.FileSystem
353354 | IdAllocator.IdAllocatorV2
354355 | Path.Path
356 | McpProviderSessions.McpProviderSessions
355357 | ProviderEventLoggers.ProviderEventLoggers
356358 | ProviderHost.ProviderHost;
357359
......@@ -405,6 +407,7 @@ const layer: Layer.Layer<
405407 | Crypto.Crypto
406408 | FileSystem.FileSystem
407409 | IdAllocator.IdAllocatorV2
410 | McpProviderSessions.McpProviderSessions
408411 | ProviderEventLoggers.ProviderEventLoggers
409412 | ProviderHost.ProviderHost
410413> = Layer.effect(
packages/provider-grok/src/server/driver.test.ts+2
......@@ -15,10 +15,12 @@ import { layerTestProviderHost } from "@t3tools/provider-testing/host";
1515import { GrokDriver } from "./driver.ts";
1616
1717import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
18import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
1819
1920const layerTest = layerTestProviderHost({ runBackgroundWork: false }).pipe(
2021 Layer.provideMerge(NodeServices.layer),
2122 Layer.provideMerge(IdAllocator.layer),
23 Layer.provideMerge(McpProviderSessions.layer),
2224 Layer.provideMerge(ProviderLatestVersions.layer),
2325 Layer.provideMerge(
2426 Layer.succeed(
packages/provider-muse/src/server/adapter.test.ts+4-9
......@@ -31,7 +31,7 @@ import * as Schema from "effect/Schema";
3131import * as Stream from "effect/Stream";
3232
3333import { layerTestProviderHost } from "@t3tools/provider-testing/host";
34import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession";
34import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
3535import type { MuseItem } from "./protocol.ts";
3636import type { MuseSdkHost } from "./sdk.ts";
3737import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
......@@ -42,6 +42,7 @@ import { makeMuseAdapterV2, type MuseAdapterV2Options } from "./adapter.ts";
4242const testLayer = Layer.mergeAll(
4343 NodeServices.layer,
4444 IdAllocator.layer,
45 McpProviderSessions.layer,
4546 layerTestProviderHost().pipe(Layer.provide(NodeServices.layer)),
4647);
4748const MUSE_PROVIDER = ProviderDriverKind.make("muse");
......@@ -351,10 +352,7 @@ const approval = (turnId: string) => ({
351352describe("MuseAdapterV2", () => {
352353 it.effect("connects the thread's MCP credential on native start and resume", () =>
353354 Effect.gen(function* () {
354 yield* Effect.addFinalizer(() =>
355 Effect.sync(() => McpProviderSession.clearMcpProviderSession(THREAD_ID)),
356 );
357 McpProviderSession.setMcpProviderSession({
355 yield* (yield* McpProviderSessions.McpProviderSessions).set({
358356 environmentId: EnvironmentId.make("test-environment"),
359357 threadId: THREAD_ID,
360358 providerSessionId: "test-session",
......@@ -384,10 +382,7 @@ describe("MuseAdapterV2", () => {
384382
385383 it.effect("rejects a host without session MCP support before opening a native session", () =>
386384 Effect.gen(function* () {
387 yield* Effect.addFinalizer(() =>
388 Effect.sync(() => McpProviderSession.clearMcpProviderSession(THREAD_ID)),
389 );
390 McpProviderSession.setMcpProviderSession({
385 yield* (yield* McpProviderSessions.McpProviderSessions).set({
391386 environmentId: EnvironmentId.make("test-environment"),
392387 threadId: THREAD_ID,
393388 providerSessionId: "test-session",
packages/provider-muse/src/server/adapter.ts+4-2
......@@ -37,6 +37,7 @@ import * as Stream from "effect/Stream";
3737
3838import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";
3939import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession";
40import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
4041import { buildRuntimeInstructions } from "@t3tools/provider-core/server/runtimeInstructions";
4142import { museModelCapabilities, resolveMuseReasoningEffort } from "./modelCatalog.ts";
4243import {
......@@ -248,6 +249,7 @@ export const makeMuseAdapterV2 = Effect.fn("makeMuseAdapterV2")(function* (
248249 const idAllocator = yield* IdAllocator.IdAllocatorV2;
249250 const providerHost = yield* ProviderHost.ProviderHost;
250251 const fileSystem = yield* FileSystem.FileSystem;
252 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
251253
252254 const protocolError = (detail: string, payload?: unknown) =>
253255 new ProviderAdapter.ProviderAdapterProtocolError({
......@@ -1364,7 +1366,7 @@ export const makeMuseAdapterV2 = Effect.fn("makeMuseAdapterV2")(function* (
13641366 );
13651367 const launchHost = Effect.fnUntraced(function* () {
13661368 const epoch = ++hostEpoch;
1367 const mcpSession = McpProviderSession.readMcpProviderSession(input.threadId);
1369 const mcpSession = yield* mcpSessions.read(input.threadId);
13681370 const created = yield* Effect.acquireRelease(
13691371 createMuseSdkHostEffect(
13701372 {
......@@ -1478,7 +1480,7 @@ export const makeMuseAdapterV2 = Effect.fn("makeMuseAdapterV2")(function* (
14781480 let missingNativeSession = false;
14791481 return yield* Effect.gen(function* () {
14801482 nativeSessionId = requestedId ?? host.connection.mintCommandId();
1481 const mcpSession = McpProviderSession.readMcpProviderSession(args.threadId);
1483 const mcpSession = yield* mcpSessions.read(args.threadId);
14821484 if (mcpSession && !host.initializeResult.grantedCapabilities.includes("sessionMcp"))
14831485 return yield* protocolError(
14841486 "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";
1010
1111import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";
1212import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";
13import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
1314import { expandHomePath } from "@t3tools/provider-core/server/pathExpansion";
1415import { makeMuseTextGeneration } from "./textGeneration.ts";
1516import { ProviderDriverError } from "@t3tools/provider-core/server/errors";
......@@ -43,6 +44,7 @@ const decodeMuseSettings = Schema.decodeSync(MuseSettings);
4344
4445export type MuseDriverEnv =
4546 | IdAllocator.IdAllocatorV2
47 | McpProviderSessions.McpProviderSessions
4648 | ProviderHost.ProviderHost
4749 | ChildProcessSpawner.ChildProcessSpawner
4850 | FileSystem.FileSystem
packages/provider-opencode/src/server/adapter.test.ts+91-23
......@@ -36,6 +36,7 @@ import { layerTestProviderHost } from "@t3tools/provider-testing/host";
3636import type * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";
3737import * as OpenCodeRuntime from "./OpenCodeRuntime.ts";
3838import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
39import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
3940
4041import {
4142 advanceOpenCodePromptAdmission,
......@@ -378,7 +379,10 @@ describe("OpenCodeAdapterV2", () => {
378379 hasSubagents: false,
379380 },
380381 );
381 }).pipe(Effect.provide(IdAllocator.layer), Effect.scoped),
382 }).pipe(
383 Effect.provide(Layer.merge(IdAllocator.layer, McpProviderSessions.layer)),
384 Effect.scoped,
385 ),
382386 );
383387
384388 it.effect.each(["permission", "question"] as const)(
......@@ -456,7 +460,10 @@ describe("OpenCodeAdapterV2", () => {
456460 deliver = true;
457461 // Failed delivery leaves the request available for an explicit retry.
458462 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 ),
460467 );
461468
462469 it.effect("aborts external root and descendants before closing the event stream", () =>
......@@ -494,7 +501,10 @@ describe("OpenCodeAdapterV2", () => {
494501 "children:child",
495502 "stream.close",
496503 ]);
497 }).pipe(Effect.provide(IdAllocator.layer), Effect.scoped),
504 }).pipe(
505 Effect.provide(Layer.merge(IdAllocator.layer, McpProviderSessions.layer)),
506 Effect.scoped,
507 ),
498508 );
499509
500510 it.effect.each(["enumeration", "abort", "not-found", "timeout"] as const)(
......@@ -548,7 +558,10 @@ describe("OpenCodeAdapterV2", () => {
548558 const result = yield* Fiber.join(stop);
549559 assert.equal(Exit.isSuccess(result), failure === "not-found");
550560 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 ),
552565 );
553566
554567 it.effect(
......@@ -712,7 +725,10 @@ describe("OpenCodeAdapterV2", () => {
712725 const assistant = items.filter((item) => item.type === "assistant_message");
713726 assert.equal(assistant.at(-1)?.text, "Tool results received");
714727 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 ),
716732 );
717733
718734 // Event order from a live OpenCode 1.18.32 run of a `task` call with
......@@ -807,7 +823,10 @@ describe("OpenCodeAdapterV2", () => {
807823 yield* push(status(child, "idle"));
808824 yield* push({ type: "session.idle", properties: { sessionID: child } });
809825 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 ),
811830 );
812831
813832 it.effect("titles OpenCode reads and searches from their input", () =>
......@@ -902,7 +921,10 @@ describe("OpenCodeAdapterV2", () => {
902921 );
903922 assert.ok(emptyWebSearch?.type === "web_search");
904923 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 ),
906928 );
907929
908930 it.effect("presents OpenCode MCP calls without treating remote tools as local edits", () =>
......@@ -1058,7 +1080,10 @@ describe("OpenCodeAdapterV2", () => {
10581080 assert.equal(items[3]?.toolName, "ambiguous_server_edit_document");
10591081 assert.isUndefined(items[3]?.toolSource);
10601082 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 ),
10621087 );
10631088
10641089 it.effect.each(["failure", "timeout"] as const)("retries MCP status after %s", (failure) =>
......@@ -1146,7 +1171,10 @@ describe("OpenCodeAdapterV2", () => {
11461171 kind: "integration",
11471172 });
11481173 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 ),
11501178 );
11511179
11521180 it.effect("admits a native command on its user receipt before generation completes", () =>
......@@ -1209,7 +1237,10 @@ describe("OpenCodeAdapterV2", () => {
12091237 yield* Fiber.join(start);
12101238 assert.equal(promptCalls, 0);
12111239 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 ),
12131244 );
12141245
12151246 it.effect("sends unadvertised slash commands as ordinary prompts", () =>
......@@ -1236,7 +1267,10 @@ describe("OpenCodeAdapterV2", () => {
12361267 });
12371268 yield* harness.startTurn("/unknown words");
12381269 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 ),
12401274 );
12411275
12421276 it.effect("compacts with the native summarize API and emits a completed compaction", () =>
......@@ -1291,7 +1325,10 @@ describe("OpenCodeAdapterV2", () => {
12911325 assert.isTrue(
12921326 events.some((event) => event.type === "turn.terminal" && event.status === "completed"),
12931327 );
1294 }).pipe(Effect.provide(IdAllocator.layer), Effect.scoped),
1328 }).pipe(
1329 Effect.provide(Layer.merge(IdAllocator.layer, McpProviderSessions.layer)),
1330 Effect.scoped,
1331 ),
12951332 );
12961333
12971334 it.effect("ends a turn on the provider thread it started on", () =>
......@@ -1330,7 +1367,10 @@ describe("OpenCodeAdapterV2", () => {
13301367 Stream.runHead,
13311368 );
13321369 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 ),
13341374 );
13351375
13361376 it.effect("does not restore messages beyond OpenCode's persisted revert boundary", () =>
......@@ -1415,7 +1455,10 @@ describe("OpenCodeAdapterV2", () => {
14151455 ["user-kept", "assistant-kept"],
14161456 );
14171457 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 ),
14191462 );
14201463
14211464 it.effect("keeps a newly admitted prompt alive across stale idle and delayed busy evidence", () =>
......@@ -1732,7 +1775,10 @@ describe("OpenCodeAdapterV2", () => {
17321775 );
17331776 const interrupted = yield* runtime.readThreadSnapshot({ providerThread });
17341777 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 ),
17361782 );
17371783
17381784 it.effect("interrupts an initial prompt while its SDK request is pending", () =>
......@@ -1897,7 +1943,10 @@ describe("OpenCodeAdapterV2", () => {
18971943 (event.type === "provider_session.updated" && event.providerSession.status === "error"),
18981944 ),
18991945 );
1900 }).pipe(Effect.provide(IdAllocator.layer), Effect.scoped),
1946 }).pipe(
1947 Effect.provide(Layer.merge(IdAllocator.layer, McpProviderSessions.layer)),
1948 Effect.scoped,
1949 ),
19011950 );
19021951
19031952 it.effect("fails an active turn when the OpenCode event stream ends cleanly", () =>
......@@ -1940,7 +1989,10 @@ describe("OpenCodeAdapterV2", () => {
19401989 assert.equal(terminal?.failure?.class, "transport_error");
19411990 assert.equal(terminal?.threadDisposition, "broken");
19421991 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 ),
19441996 );
19451997
19461998 it.effect("fails compaction when its response races stream termination", () =>
......@@ -2001,7 +2053,10 @@ describe("OpenCodeAdapterV2", () => {
20012053 event.turnItem.status === "completed",
20022054 ),
20032055 );
2004 }).pipe(Effect.provide(IdAllocator.layer), Effect.scoped),
2056 }).pipe(
2057 Effect.provide(Layer.merge(IdAllocator.layer, McpProviderSessions.layer)),
2058 Effect.scoped,
2059 ),
20052060 );
20062061
20072062 it.effect("does not register a turn after the OpenCode event stream ends", () =>
......@@ -2059,7 +2114,10 @@ describe("OpenCodeAdapterV2", () => {
20592114
20602115 assert.isTrue(Exit.isFailure(yield* Fiber.join(start)));
20612116 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 ),
20632121 );
20642122
20652123 it("holds stale idle through prompt admission until the new user message is observed", () => {
......@@ -2237,7 +2295,10 @@ describe("OpenCodeAdapterV2", () => {
22372295 assert.lengthOf(terminals, 1);
22382296 assert.equal(terminals[0]?.status, "completed");
22392297 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 ),
22412302 );
22422303
22432304 it.effect("does not let a queued status retry adopt a newer steer generation", () =>
......@@ -2340,7 +2401,10 @@ describe("OpenCodeAdapterV2", () => {
23402401 });
23412402 assert.equal(statusCallCount, 1);
23422403 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 ),
23442408 );
23452409
23462410 it.effect("ignores a delayed status reply after steering starts a newer admission", () =>
......@@ -2411,7 +2475,7 @@ describe("OpenCodeAdapterV2", () => {
24112475 assert.include(serialized, '"protocol":"opencode-sdk.sse"');
24122476 assert.include(serialized, '"method":"session.prompt"');
24132477 assert.include(serialized, '"fieldCount":2');
2414 }).pipe(Effect.provide(IdAllocator.layer)),
2478 }).pipe(Effect.provide(Layer.merge(IdAllocator.layer, McpProviderSessions.layer))),
24152479 );
24162480
24172481 it.effect("adopts the handed-over provider thread identity on session create", () =>
......@@ -2520,6 +2584,7 @@ describe("OpenCodeAdapterV2", () => {
25202584 Effect.provide(
25212585 Layer.mergeAll(
25222586 IdAllocator.layer,
2587 McpProviderSessions.layer,
25232588 layerTestProviderHost().pipe(Layer.provide(NodeServices.layer)),
25242589 ),
25252590 ),
......@@ -2718,5 +2783,8 @@ it.effect.each([false, true])(
27182783 assert.equal(result.messages.length, 0);
27192784 assert.deepEqual(calls, ["fork", "permissions"]);
27202785 }
2721 }).pipe(Effect.provide(IdAllocator.layer), Effect.scoped),
2786 }).pipe(
2787 Effect.provide(Layer.merge(IdAllocator.layer, McpProviderSessions.layer)),
2788 Effect.scoped,
2789 ),
27222790);
packages/provider-opencode/src/server/adapter.ts+4-2
......@@ -55,7 +55,7 @@ import * as Stream from "effect/Stream";
5555
5656import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";
5757import { 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";
5959import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";
6060import {
6161 structuralProtocolMethod,
......@@ -947,6 +947,7 @@ export const makeOpenCodeAdapterV2 = Effect.fn("makeOpenCodeAdapterV2")(function
947947 const runtime = yield* OpenCodeRuntime.OpenCodeRuntime;
948948 const idAllocator = yield* IdAllocator.IdAllocatorV2;
949949 const host = yield* ProviderHost.ProviderHost;
950 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
950951
951952 return ProviderAdapter.ProviderAdapterV2.of({
952953 instanceId: options.instanceId,
......@@ -971,7 +972,7 @@ export const makeOpenCodeAdapterV2 = Effect.fn("makeOpenCodeAdapterV2")(function
971972 : {}),
972973 });
973974
974 const mcpSession = McpProviderSession.readMcpProviderSession(input.threadId);
975 const mcpSession = yield* mcpSessions.read(input.threadId);
975976 const hasT3Mcp = mcpSession !== undefined && !connection.external;
976977 const orchestrationSystemPrompt = t3OrchestrationSystemPrompt(hasT3Mcp);
977978 if (hasT3Mcp) {
......@@ -3728,6 +3729,7 @@ export const makeOpenCodeAdapterV2 = Effect.fn("makeOpenCodeAdapterV2")(function
37283729export type OpenCodeAdapterV2DriverEnv =
37293730 | OpenCodeRuntime.OpenCodeRuntime
37303731 | IdAllocator.IdAllocatorV2
3732 | McpProviderSessions.McpProviderSessions
37313733 | ProviderEventLoggers.ProviderEventLoggers
37323734 | ProviderHost.ProviderHost;
37333735
packages/provider-opencode/src/server/driver.test.ts+3
......@@ -17,6 +17,7 @@ import { ChildProcessSpawner } from "effect/process";
1717
1818import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";
1919import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
20import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
2021import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";
2122import * as ProviderMaintenance from "@t3tools/provider-core/server/maintenanceResolver";
2223import * as OpenCodeRuntime from "./OpenCodeRuntime.ts";
......@@ -49,6 +50,7 @@ const openCode2Runtime = {
4950
5051const layer = Layer.mergeAll(
5152 IdAllocator.layer,
53 McpProviderSessions.layer,
5254 ProviderLatestVersions.layer,
5355 layerTestProviderHost(),
5456 Layer.succeed(
......@@ -212,6 +214,7 @@ const changingRuntime = {
212214} as unknown as OpenCodeRuntime.OpenCodeRuntimeShape;
213215const layerUpdate = Layer.mergeAll(
214216 IdAllocator.layer,
217 McpProviderSessions.layer,
215218 ProviderLatestVersions.layer,
216219 layerTestProviderHost(),
217220 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";
7474import * as OpenCode2Client from "./OpenCode2Client.ts";
7575import * as OpenCode2Server from "./OpenCode2Server.ts";
7676import * as OpenCodeRuntime from "../OpenCodeRuntime.ts";
77import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession";
77import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
7878import { buildRuntimeInstructions } from "@t3tools/provider-core/server/runtimeInstructions";
7979import { t3OrchestrationSystemPrompt } from "@t3tools/provider-core/server/orchestrationInstructions";
8080import { SKILL_MENTION_PATTERN } from "@t3tools/shared/composerInlineTokens";
......@@ -831,6 +831,7 @@ export const make = Effect.fn("OpenCode2Adapter.make")(function* (instanceId: Pr
831831 const server = yield* OpenCode2Server.OpenCode2Server;
832832 const idAllocator = yield* IdAllocator.IdAllocatorV2;
833833 const host = yield* ProviderHost.ProviderHost;
834 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
834835 const continuationRequests = yield* ProviderContinuationRequests.ProviderContinuationRequests;
835836 const crypto = yield* Crypto.Crypto;
836837 const driver = OPENCODE_PROVIDER;
......@@ -3251,7 +3252,7 @@ export const make = Effect.fn("OpenCode2Adapter.make")(function* (instanceId: Pr
32513252 state: ThreadState,
32523253 turnInput: ProviderAdapter.ProviderAdapterV2TurnInput,
32533254 ) {
3254 const mcpSession = McpProviderSession.readMcpProviderSession(turnInput.threadId);
3255 const mcpSession = yield* mcpSessions.read(turnInput.threadId);
32553256 const directory = turnInput.runtimePolicy.cwd ?? host.paths.cwd;
32563257 const name = yield* mcpServerNameFor(turnInput.threadId);
32573258 // 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";
3333
3434import { layerTestProviderHost } from "@t3tools/provider-testing/host";
3535import * 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";
3737import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
3838import * as ProviderAdapter from "@t3tools/provider-core/server/ProviderAdapter";
3939import { handoffBudget } from "@t3tools/provider-core/server/handoffBudget";
......@@ -49,6 +49,7 @@ import { makePiRpcConnection, type PiRpcRecord } from "./rpc.ts";
4949const layerTest = Layer.mergeAll(
5050 NodeServices.layer,
5151 IdAllocator.layer,
52 McpProviderSessions.layer,
5253 layerTestProviderHost().pipe(Layer.provide(NodeServices.layer)),
5354);
5455
......@@ -1285,7 +1286,7 @@ describe("PiAdapterV2", () => {
12851286
12861287 it.effect("injects the T3 MCP extension and bearer when a session exists", () =>
12871288 Effect.gen(function* () {
1288 McpProviderSession.setMcpProviderSession({
1289 yield* (yield* McpProviderSessions.McpProviderSessions).set({
12891290 environmentId: EnvironmentId.make("environment-pi-mcp"),
12901291 threadId: THREAD_ID,
12911292 providerSessionId: "mcp-session-pi",
......@@ -1306,11 +1307,7 @@ describe("PiAdapterV2", () => {
13061307 assert.equal(spawn.env.T3_MCP_URL, "http://127.0.0.1:43123/mcp");
13071308 assert.equal(spawn.env.T3_MCP_BEARER_TOKEN, "secret-pi-token");
13081309 assert.equal(spawn.env.T3_PI_RUNTIME_MODE, "full-access");
1309 }).pipe(
1310 Effect.ensuring(Effect.sync(() => McpProviderSession.clearMcpProviderSession(THREAD_ID))),
1311 Effect.scoped,
1312 Effect.provide(layerTest),
1313 ),
1310 }).pipe(Effect.scoped, Effect.provide(layerTest)),
13141311 );
13151312
13161313 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";
6262
6363import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";
6464import { 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";
6666import { parsePiCompactCommand, type PiCompactCommand } from "./commands.ts";
6767import { mergeProviderInstanceEnvironment } from "@t3tools/provider-core/server/instanceEnvironment";
6868import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
......@@ -390,6 +390,7 @@ export const makePiAdapterV2 = Effect.fn("makePiAdapterV2")(function* (
390390 const fileSystem = yield* FileSystem.FileSystem;
391391 const idAllocator = yield* IdAllocator.IdAllocatorV2;
392392 const host = yield* ProviderHost.ProviderHost;
393 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
393394 const { continuationRequests } = options;
394395
395396 const protocolError = (detail: string, payload?: unknown) =>
......@@ -409,7 +410,7 @@ export const makePiAdapterV2 = Effect.fn("makePiAdapterV2")(function* (
409410 ) {
410411 const scope = yield* Effect.scope;
411412 const cwd = input.runtimePolicy.cwd ?? host.paths.cwd;
412 const mcpSession = McpProviderSession.readMcpProviderSession(input.threadId);
413 const mcpSession = yield* mcpSessions.read(input.threadId);
413414 const provideCacheFs = <A, E>(effect: Effect.Effect<A, E, FileSystem.FileSystem>) =>
414415 effect.pipe(
415416 Effect.provideService(FileSystem.FileSystem, fileSystem),
......@@ -3261,6 +3262,7 @@ export type PiAdapterV2DriverEnv =
32613262 | ChildProcessSpawner.ChildProcessSpawner
32623263 | FileSystem.FileSystem
32633264 | IdAllocator.IdAllocatorV2
3265 | McpProviderSessions.McpProviderSessions
32643266 | ProviderHost.ProviderHost;
32653267
32663268export const PiAdapterV2Driver: ProviderAdapterDriver<PiSettings, PiAdapterV2DriverEnv> = {
packages/provider-pi/src/server/driver.test.ts+2
......@@ -15,6 +15,7 @@ import { ChildProcess, ChildProcessSpawner } from "effect/process";
1515
1616import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";
1717import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
18import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
1819import { layerTestProviderHost } from "@t3tools/provider-testing/host";
1920import type { PiSettings } from "../settings.ts";
2021import { PiDriver } from "./driver.ts";
......@@ -26,6 +27,7 @@ const layerTest = Layer.mergeAll(
2627 runBackgroundWork: false,
2728 }),
2829 IdAllocator.layer,
30 McpProviderSessions.layer,
2931 ProviderLatestVersions.layer,
3032 Layer.succeed(
3133 HttpClient.HttpClient,