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