| author | |
| committer | |
| log | f853d514cab475e73468f7cb7bab1aae48dbe549 |
| tree | 36873d827cc8e363ee25749de8a22c7e2b759167 |
| parent | 31b04e2ee929660d80d5931ed53fd17bf1d33ce6 |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>28 files changed, 436 insertions(+), 452 deletions(-)
apps/server/scripts/record-grok-acp-replay-fixture.ts-3| ... | @@ -21,7 +21,6 @@ import * as FileSystem from "effect/FileSystem"; | ... | @@ -21,7 +21,6 @@ import * as FileSystem from "effect/FileSystem"; |
| 21 | import * as Layer from "effect/Layer"; | 21 | import * as Layer from "effect/Layer"; |
| 22 | import * as Path from "effect/Path"; | 22 | import * as Path from "effect/Path"; |
| 23 | import * as Schema from "effect/Schema"; | 23 | import * as Schema from "effect/Schema"; |
| 24 | import { ChildProcessSpawner } from "effect/process"; | ||
| 25 | import type * as EffectAcpProtocol from "effect-acp/protocol"; | 24 | import type * as EffectAcpProtocol from "effect-acp/protocol"; |
| 26 | 25 | ||
| 27 | import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost"; | 26 | import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost"; |
| ... | @@ -443,7 +442,6 @@ const recordScenario = Effect.fn("recordGrokScenario")(function* (fixtureName: s | ... | @@ -443,7 +442,6 @@ const recordScenario = Effect.fn("recordGrokScenario")(function* (fixtureName: s |
| 443 | const settings = { ...DEFAULT_GROK_SETTINGS, binaryPath: process.env.T3_GROK_BIN ?? "grok" }; | 442 | const settings = { ...DEFAULT_GROK_SETTINGS, binaryPath: process.env.T3_GROK_BIN ?? "grok" }; |
| 444 | const layerRegistry = ProviderAdapterRegistry.layerFromAdaptersEffect( | 443 | const layerRegistry = ProviderAdapterRegistry.layerFromAdaptersEffect( |
| 445 | Effect.gen(function* () { | 444 | Effect.gen(function* () { |
| 446 | const childProcessSpawner = yield* ChildProcessSpawner.ChildProcessSpawner; | ||
| 447 | const environment = yield* HostProcessEnvironment; | 445 | const environment = yield* HostProcessEnvironment; |
| 448 | const adapter = yield* makeGrokAdapterV2({ | 446 | const adapter = yield* makeGrokAdapterV2({ |
| 449 | instanceId: GROK_DEFAULT_INSTANCE_ID, | 447 | instanceId: GROK_DEFAULT_INSTANCE_ID, |
| ... | @@ -460,7 +458,6 @@ const recordScenario = Effect.fn("recordGrokScenario")(function* (fixtureName: s | ... | @@ -460,7 +458,6 @@ const recordScenario = Effect.fn("recordGrokScenario")(function* (fixtureName: s |
| 460 | interruptPromptOnCancel: input.interruptPromptOnCancel ?? false, | 458 | interruptPromptOnCancel: input.interruptPromptOnCancel ?? false, |
| 461 | grokSettings: settings, | 459 | grokSettings: settings, |
| 462 | environment, | 460 | environment, |
| 463 | childProcessSpawner, | ||
| 464 | runtimeMode: grokLaunchRuntimeMode(runtimePolicy), | 461 | runtimeMode: grokLaunchRuntimeMode(runtimePolicy), |
| 465 | }), | 462 | }), |
| 466 | }); | 463 | }); |
apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.test.ts+23-15| ... | @@ -48,7 +48,7 @@ import * as Sink from "effect/Sink"; | ... | @@ -48,7 +48,7 @@ import * as Sink from "effect/Sink"; |
| 48 | import * as Stream from "effect/Stream"; | 48 | import * as Stream from "effect/Stream"; |
| 49 | import * as Scope from "effect/Scope"; | 49 | import * as Scope from "effect/Scope"; |
| 50 | import * as TestClock from "effect/testing/TestClock"; | 50 | import * as TestClock from "effect/testing/TestClock"; |
| 51 | import { ChildProcessSpawner } from "effect/process"; | 51 | import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner"; |
| 52 | import * as EffectAcpErrors from "effect-acp/errors"; | 52 | import * as EffectAcpErrors from "effect-acp/errors"; |
| 53 | import type * as EffectAcpProtocol from "effect-acp/protocol"; | 53 | import type * as EffectAcpProtocol from "effect-acp/protocol"; |
| 54 | import type * as EffectAcpSchema from "effect-acp/compat"; | 54 | import type * as EffectAcpSchema from "effect-acp/compat"; |
| ... | @@ -1150,7 +1150,7 @@ describe("AcpAdapterV2", () => { | ... | @@ -1150,7 +1150,7 @@ describe("AcpAdapterV2", () => { |
| 1150 | const adapter = yield* makeAcpAdapterV2({ | 1150 | const adapter = yield* makeAcpAdapterV2({ |
| 1151 | instanceId, | 1151 | instanceId, |
| 1152 | // Production Devin runs commands through client terminals. | 1152 | // Production Devin runs commands through client terminals. |
| 1153 | clientTerminals: { childProcessSpawner, shellCommands: true }, | 1153 | clientTerminals: { shellCommands: true }, |
| 1154 | selfInvocation: yield* resolveSelfInvocation(), | 1154 | selfInvocation: yield* resolveSelfInvocation(), |
| 1155 | flavor: { | 1155 | flavor: { |
| 1156 | driver: ACP_TEST_DRIVER, | 1156 | driver: ACP_TEST_DRIVER, |
| ... | @@ -2190,7 +2190,7 @@ describe("AcpAdapterV2", () => { | ... | @@ -2190,7 +2190,7 @@ describe("AcpAdapterV2", () => { |
| 2190 | makeRuntime, | 2190 | makeRuntime, |
| 2191 | }, | 2191 | }, |
| 2192 | selfInvocation, | 2192 | selfInvocation, |
| 2193 | clientTerminals: { childProcessSpawner }, | 2193 | clientTerminals: {}, |
| 2194 | }); | 2194 | }); |
| 2195 | const sourceThreadId = ThreadId.make("thread-acp-native-fork-source"); | 2195 | const sourceThreadId = ThreadId.make("thread-acp-native-fork-source"); |
| 2196 | const targetThreadId = ThreadId.make("thread-acp-native-fork-target"); | 2196 | const targetThreadId = ThreadId.make("thread-acp-native-fork-target"); |
| ... | @@ -2476,7 +2476,7 @@ describe("AcpAdapterV2", () => { | ... | @@ -2476,7 +2476,7 @@ describe("AcpAdapterV2", () => { |
| 2476 | }).pipe(Effect.andThen(makeRuntime(input))), | 2476 | }).pipe(Effect.andThen(makeRuntime(input))), |
| 2477 | }, | 2477 | }, |
| 2478 | selfInvocation, | 2478 | selfInvocation, |
| 2479 | clientTerminals: { childProcessSpawner }, | 2479 | clientTerminals: {}, |
| 2480 | }); | 2480 | }); |
| 2481 | const threadId = ThreadId.make("thread-acp-unknown-permission-grant"); | 2481 | const threadId = ThreadId.make("thread-acp-unknown-permission-grant"); |
| 2482 | const runtimePolicy = ProviderAdapter.ProviderAdapterV2RuntimePolicy.make({ | 2482 | const runtimePolicy = ProviderAdapter.ProviderAdapterV2RuntimePolicy.make({ |
| ... | @@ -12045,9 +12045,11 @@ describe("AcpAdapterV2", () => { | ... | @@ -12045,9 +12045,11 @@ describe("AcpAdapterV2", () => { |
| 12045 | Effect.gen(function* () { | 12045 | Effect.gen(function* () { |
| 12046 | const commands: Array<{ readonly command: string; readonly args: ReadonlyArray<string> }> = | 12046 | const commands: Array<{ readonly command: string; readonly args: ReadonlyArray<string> }> = |
| 12047 | []; | 12047 | []; |
| 12048 | yield* AcpSessionRuntime.terminateWindowsProcessTreeWithTaskkill( | 12048 | yield* AcpSessionRuntime.terminateWindowsProcessTreeWithTaskkill(4321).pipe( |
| 12049 | makeTaskkillSpawner({ exitCode: 0, commands }), | 12049 | Effect.provideService( |
| 12050 | 4321, | 12050 | ChildProcessSpawner.ChildProcessSpawner, |
| 12051 | makeTaskkillSpawner({ exitCode: 0, commands }), | ||
| 12052 | ), | ||
| 12051 | ); | 12053 | ); |
| 12052 | assert.deepEqual(commands, [{ command: "taskkill", args: ["/PID", "4321", "/T", "/F"] }]); | 12054 | assert.deepEqual(commands, [{ command: "taskkill", args: ["/PID", "4321", "/T", "/F"] }]); |
| 12053 | 12055 | ||
| ... | @@ -12058,10 +12060,13 @@ describe("AcpAdapterV2", () => { | ... | @@ -12058,10 +12060,13 @@ describe("AcpAdapterV2", () => { |
| 12058 | { exitCode: 128, output: "ERROR: Access is denied." }, | 12060 | { exitCode: 128, output: "ERROR: Access is denied." }, |
| 12059 | { exitCode: 1, output: "generic failure" }, | 12061 | { exitCode: 1, output: "generic failure" }, |
| 12060 | ]) { | 12062 | ]) { |
| 12061 | const failed = yield* AcpSessionRuntime.terminateWindowsProcessTreeWithTaskkill( | 12063 | const failed = yield* AcpSessionRuntime.terminateWindowsProcessTreeWithTaskkill(4321).pipe( |
| 12062 | makeTaskkillSpawner(fixture), | 12064 | Effect.provideService( |
| 12063 | 4321, | 12065 | ChildProcessSpawner.ChildProcessSpawner, |
| 12064 | ).pipe(Effect.exit); | 12066 | makeTaskkillSpawner(fixture), |
| 12067 | ), | ||
| 12068 | Effect.exit, | ||
| 12069 | ); | ||
| 12065 | if (Exit.isSuccess(failed)) assert.fail(`taskkill ${fixture.exitCode} must fail`); | 12070 | if (Exit.isSuccess(failed)) assert.fail(`taskkill ${fixture.exitCode} must fail`); |
| 12066 | const error = Cause.squash(failed.cause); | 12071 | const error = Cause.squash(failed.cause); |
| 12067 | assert.instanceOf(error, AcpSessionRuntime.AcpProcessGroupTerminationError); | 12072 | assert.instanceOf(error, AcpSessionRuntime.AcpProcessGroupTerminationError); |
| ... | @@ -12082,10 +12087,13 @@ describe("AcpAdapterV2", () => { | ... | @@ -12082,10 +12087,13 @@ describe("AcpAdapterV2", () => { |
| 12082 | { outputFailure: true }, | 12087 | { outputFailure: true }, |
| 12083 | { exitFailure: true }, | 12088 | { exitFailure: true }, |
| 12084 | ]) { | 12089 | ]) { |
| 12085 | const failed = yield* AcpSessionRuntime.terminateWindowsProcessTreeWithTaskkill( | 12090 | const failed = yield* AcpSessionRuntime.terminateWindowsProcessTreeWithTaskkill(4321).pipe( |
| 12086 | makeTaskkillSpawner(fixture), | 12091 | Effect.provideService( |
| 12087 | 4321, | 12092 | ChildProcessSpawner.ChildProcessSpawner, |
| 12088 | ).pipe(Effect.exit); | 12093 | makeTaskkillSpawner(fixture), |
| 12094 | ), | ||
| 12095 | Effect.exit, | ||
| 12096 | ); | ||
| 12089 | if (Exit.isSuccess(failed)) assert.fail("taskkill infrastructure failure must fail"); | 12097 | if (Exit.isSuccess(failed)) assert.fail("taskkill infrastructure failure must fail"); |
| 12090 | const error = Cause.squash(failed.cause); | 12098 | const error = Cause.squash(failed.cause); |
| 12091 | assert.instanceOf(error, AcpSessionRuntime.AcpProcessGroupTerminationError); | 12099 | assert.instanceOf(error, AcpSessionRuntime.AcpProcessGroupTerminationError); |
apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.testkit.ts+1-1| ... | @@ -10,7 +10,7 @@ import * as Layer from "effect/Layer"; | ... | @@ -10,7 +10,7 @@ import * as Layer from "effect/Layer"; |
| 10 | import * as Schema from "effect/Schema"; | 10 | import * as Schema from "effect/Schema"; |
| 11 | import type * as Scope from "effect/Scope"; | 11 | import type * as Scope from "effect/Scope"; |
| 12 | import * as Stream from "effect/Stream"; | 12 | import * as Stream from "effect/Stream"; |
| 13 | import { ChildProcessSpawner } from "effect/process"; | 13 | import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner"; |
| 14 | import * as EffectAcpErrors from "effect-acp/errors"; | 14 | import * as EffectAcpErrors from "effect-acp/errors"; |
| 15 | 15 | ||
| 16 | import * as AcpSessionRuntime from "@t3tools/provider-acp/server/AcpSessionRuntime"; | 16 | import * as AcpSessionRuntime from "@t3tools/provider-acp/server/AcpSessionRuntime"; |
apps/server/src/orchestration-v2/Adapters/AcpRegistryAdapterV2.test.ts+64-58| ... | @@ -9,8 +9,9 @@ import * as Layer from "effect/Layer"; | ... | @@ -9,8 +9,9 @@ import * as Layer from "effect/Layer"; |
| 9 | import * as Option from "effect/Option"; | 9 | import * as Option from "effect/Option"; |
| 10 | import * as Path from "effect/Path"; | 10 | import * as Path from "effect/Path"; |
| 11 | import * as Schema from "effect/Schema"; | 11 | import * as Schema from "effect/Schema"; |
| 12 | import { HttpClient, HttpClientResponse } from "effect/http"; | 12 | import * as HttpClient from "effect/http/HttpClient"; |
| 13 | import { ChildProcessSpawner } from "effect/process"; | 13 | import * as HttpClientResponse from "effect/http/HttpClientResponse"; |
| 14 | import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner"; | ||
| 14 | import * as EffectAcpErrors from "effect-acp/errors"; | 15 | import * as EffectAcpErrors from "effect-acp/errors"; |
| 15 | 16 | ||
| 16 | import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost"; | 17 | import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost"; |
| ... | @@ -22,6 +23,7 @@ import type { | ... | @@ -22,6 +23,7 @@ import type { |
| 22 | AcpRegistryLiveConfiguration, | 23 | AcpRegistryLiveConfiguration, |
| 23 | } from "@t3tools/provider-acp-registry/testing"; | 24 | } from "@t3tools/provider-acp-registry/testing"; |
| 24 | import * as AcpRegistrySupport from "@t3tools/provider-acp-registry/server/AcpRegistrySupport"; | 25 | import * as AcpRegistrySupport from "@t3tools/provider-acp-registry/server/AcpRegistrySupport"; |
| 26 | import * as AcpRegistryRuntimeCoordinator from "@t3tools/provider-acp-registry/server/AcpRegistryRuntimeCoordinator"; | ||
| 25 | import { ACP_SESSION_MODE_OPTION_ID } from "@t3tools/provider-acp/server/sessionConfig"; | 27 | import { ACP_SESSION_MODE_OPTION_ID } from "@t3tools/provider-acp/server/sessionConfig"; |
| 26 | import * as AcpSessionRuntime from "@t3tools/provider-acp/server/AcpSessionRuntime"; | 28 | import * as AcpSessionRuntime from "@t3tools/provider-acp/server/AcpSessionRuntime"; |
| 27 | import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator"; | 29 | import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator"; |
| ... | @@ -82,6 +84,11 @@ const layerRegistry = Layer.succeed( | ... | @@ -82,6 +84,11 @@ const layerRegistry = Layer.succeed( |
| 82 | ), | 84 | ), |
| 83 | ); | 85 | ); |
| 84 | 86 | ||
| 87 | // Adapters given an explicit runtime never resolve through the catalog. | ||
| 88 | const layerInjectedRuntimeCatalog = Layer.mock(AcpRegistrySupport.AcpRegistryCatalog)({ | ||
| 89 | resolve: () => Effect.die("the runtime is injected"), | ||
| 90 | }); | ||
| 91 | |||
| 85 | const layerTest = Layer.mergeAll( | 92 | const layerTest = Layer.mergeAll( |
| 86 | NodeServices.layer, | 93 | NodeServices.layer, |
| 87 | IdAllocator.layer, | 94 | IdAllocator.layer, |
| ... | @@ -197,8 +204,6 @@ describe("AcpRegistryAdapterV2", () => { | ... | @@ -197,8 +204,6 @@ describe("AcpRegistryAdapterV2", () => { |
| 197 | authMethodId: "test", | 204 | authMethodId: "test", |
| 198 | }), | 205 | }), |
| 199 | environment: {}, | 206 | environment: {}, |
| 200 | childProcessSpawner, | ||
| 201 | resolver: { resolve: () => Effect.die("the runtime is injected") }, | ||
| 202 | makeRuntime: makeAcpReplayRuntime({ | 207 | makeRuntime: makeAcpReplayRuntime({ |
| 203 | transcript, | 208 | transcript, |
| 204 | statusPath, | 209 | statusPath, |
| ... | @@ -208,7 +213,7 @@ describe("AcpRegistryAdapterV2", () => { | ... | @@ -208,7 +213,7 @@ describe("AcpRegistryAdapterV2", () => { |
| 208 | childProcessSpawner, | 213 | childProcessSpawner, |
| 209 | fileSystem, | 214 | fileSystem, |
| 210 | }), | 215 | }), |
| 211 | }); | 216 | }).pipe(Effect.provide(layerInjectedRuntimeCatalog)); |
| 212 | yield* adapter | 217 | yield* adapter |
| 213 | .openSession({ | 218 | .openSession({ |
| 214 | threadId: ThreadId.make("thread-acp-registry-mode-pick"), | 219 | threadId: ThreadId.make("thread-acp-registry-mode-pick"), |
| ... | @@ -279,8 +284,6 @@ describe("AcpRegistryAdapterV2", () => { | ... | @@ -279,8 +284,6 @@ describe("AcpRegistryAdapterV2", () => { |
| 279 | instanceId, | 284 | instanceId, |
| 280 | settings: yield* decodeAcpRegistryAdapterSettings({ agentId, authMethodId: "test" }), | 285 | settings: yield* decodeAcpRegistryAdapterSettings({ agentId, authMethodId: "test" }), |
| 281 | environment: {}, | 286 | environment: {}, |
| 282 | childProcessSpawner, | ||
| 283 | resolver: { resolve: () => Effect.die("the runtime is injected") }, | ||
| 284 | makeRuntime: (input) => | 287 | makeRuntime: (input) => |
| 285 | Effect.gen(function* () { | 288 | Effect.gen(function* () { |
| 286 | clientCapabilities = input.clientCapabilities; | 289 | clientCapabilities = input.clientCapabilities; |
| ... | @@ -305,7 +308,7 @@ describe("AcpRegistryAdapterV2", () => { | ... | @@ -305,7 +308,7 @@ describe("AcpRegistryAdapterV2", () => { |
| 305 | Effect.provide(context), | 308 | Effect.provide(context), |
| 306 | ); | 309 | ); |
| 307 | }), | 310 | }), |
| 308 | }); | 311 | }).pipe(Effect.provide(layerInjectedRuntimeCatalog)); |
| 309 | const runtimePolicy = ProviderAdapter.ProviderAdapterV2RuntimePolicy.make({ | 312 | const runtimePolicy = ProviderAdapter.ProviderAdapterV2RuntimePolicy.make({ |
| 310 | runtimeMode: "full-access", | 313 | runtimeMode: "full-access", |
| 311 | interactionMode: "default", | 314 | interactionMode: "default", |
| ... | @@ -333,7 +336,6 @@ describe("AcpRegistryAdapterV2", () => { | ... | @@ -333,7 +336,6 @@ describe("AcpRegistryAdapterV2", () => { |
| 333 | 336 | ||
| 334 | it.effect("opens a real ACP child process resolved from registry configuration", () => | 337 | it.effect("opens a real ACP child process resolved from registry configuration", () => |
| 335 | Effect.gen(function* () { | 338 | Effect.gen(function* () { |
| 336 | const childProcessSpawner = yield* ChildProcessSpawner.ChildProcessSpawner; | ||
| 337 | const path = yield* Path.Path; | 339 | const path = yield* Path.Path; |
| 338 | const host = yield* ProviderHost.ProviderHost; | 340 | const host = yield* ProviderHost.ProviderHost; |
| 339 | const mockAgentPath = yield* path.fromFileUrl( | 341 | const mockAgentPath = yield* path.fromFileUrl( |
| ... | @@ -365,55 +367,59 @@ describe("AcpRegistryAdapterV2", () => { | ... | @@ -365,55 +367,59 @@ describe("AcpRegistryAdapterV2", () => { |
| 365 | T3_ACP_SESSION_LIFECYCLE: "1", | 367 | T3_ACP_SESSION_LIFECYCLE: "1", |
| 366 | T3_ACP_COMMAND_ADVERTISEMENT_DELAY_MS: "750", | 368 | T3_ACP_COMMAND_ADVERTISEMENT_DELAY_MS: "750", |
| 367 | }, | 369 | }, |
| 368 | childProcessSpawner, | 370 | }).pipe( |
| 369 | runtimeCoordinator: { | 371 | Effect.provide( |
| 370 | withForegroundStartup: (agentId, effect) => | 372 | Layer.mergeAll( |
| 371 | Effect.acquireUseRelease( | 373 | Layer.mock(AcpRegistryRuntimeCoordinator.AcpRegistryRuntimeCoordinator)({ |
| 372 | Effect.sync(() => { | 374 | withForegroundStartup: (agentId, effect) => |
| 373 | assert.equal(agentId, "fixture-agent"); | 375 | Effect.acquireUseRelease( |
| 374 | startupActive = true; | 376 | Effect.sync(() => { |
| 375 | startupCount += 1; | 377 | assert.equal(agentId, "fixture-agent"); |
| 376 | }), | 378 | startupActive = true; |
| 377 | () => effect, | 379 | startupCount += 1; |
| 378 | () => | 380 | }), |
| 379 | Effect.sync(() => { | 381 | () => effect, |
| 380 | startupActive = false; | 382 | () => |
| 381 | }), | 383 | Effect.sync(() => { |
| 382 | ), | 384 | startupActive = false; |
| 383 | runBackgroundProbe: (_agentId, effect) => effect.pipe(Effect.map(Option.some)), | 385 | }), |
| 384 | withSessionMutation: (effect) => effect, | 386 | ), |
| 385 | clearAvailableCommands: () => Effect.void, | 387 | runBackgroundProbe: (_agentId, effect) => effect.pipe(Effect.map(Option.some)), |
| 386 | publishAvailableCommands: (publishedInstanceId, commands) => | 388 | withSessionMutation: (effect) => effect, |
| 387 | Deferred.succeed(commandsPublished, { | 389 | clearAvailableCommands: () => Effect.void, |
| 388 | instanceId: publishedInstanceId, | 390 | publishAvailableCommands: (publishedInstanceId, commands) => |
| 389 | commands, | 391 | Deferred.succeed(commandsPublished, { |
| 390 | }).pipe(Effect.asVoid), | 392 | instanceId: publishedInstanceId, |
| 391 | getAvailableCommands: () => Effect.succeed(Option.none()), | 393 | commands, |
| 392 | watchAvailableCommands: () => Effect.never, | 394 | }).pipe(Effect.asVoid), |
| 393 | clearLiveConfiguration: () => Effect.void, | 395 | getAvailableCommands: () => Effect.succeed(Option.none()), |
| 394 | publishLiveConfiguration: (_publishedInstanceId, configuration) => | 396 | watchAvailableCommands: () => Effect.never, |
| 395 | Deferred.succeed(configurationPublished, configuration).pipe(Effect.asVoid), | 397 | clearLiveConfiguration: () => Effect.void, |
| 396 | getLiveConfiguration: () => Effect.succeed(Option.none()), | 398 | publishLiveConfiguration: (_publishedInstanceId, configuration) => |
| 397 | watchLiveConfiguration: () => Effect.never, | 399 | Deferred.succeed(configurationPublished, configuration).pipe(Effect.asVoid), |
| 398 | requestUrlAuthentication: () => Effect.succeed(false), | 400 | getLiveConfiguration: () => Effect.succeed(Option.none()), |
| 399 | acceptUrlAuthentication: () => Effect.succeed(false), | 401 | watchLiveConfiguration: () => Effect.never, |
| 400 | getUrlAuthAction: () => Effect.succeed(Option.none()), | 402 | requestUrlAuthentication: () => Effect.succeed(false), |
| 401 | watchUrlAuthAction: () => Effect.never, | 403 | acceptUrlAuthentication: () => Effect.succeed(false), |
| 402 | }, | 404 | getUrlAuthAction: () => Effect.succeed(Option.none()), |
| 403 | resolver: { | 405 | watchUrlAuthAction: () => Effect.never, |
| 404 | resolve: (configuredSettings, cwd, environment) => | 406 | }), |
| 405 | Effect.sync(() => assert.isTrue(startupActive)).pipe( | 407 | Layer.mock(AcpRegistrySupport.AcpRegistryCatalog)({ |
| 406 | Effect.andThen(resolver.resolve(configuredSettings, cwd, environment)), | 408 | resolve: (configuredSettings, cwd, environment) => |
| 407 | Effect.map((resolved) => ({ | 409 | Effect.sync(() => assert.isTrue(startupActive)).pipe( |
| 408 | ...resolved, | 410 | Effect.andThen(resolver.resolve(configuredSettings, cwd, environment)), |
| 409 | spawn: { | 411 | Effect.map((resolved) => ({ |
| 410 | ...resolved.spawn, | 412 | ...resolved, |
| 411 | args: [mockAgentPath], | 413 | spawn: { |
| 412 | }, | 414 | ...resolved.spawn, |
| 413 | })), | 415 | args: [mockAgentPath], |
| 414 | ), | 416 | }, |
| 415 | }, | 417 | })), |
| 416 | }); | 418 | ), |
| 419 | }), | ||
| 420 | ), | ||
| 421 | ), | ||
| 422 | ); | ||
| 417 | const threadId = ThreadId.make("thread-acp-registry-fixture"); | 423 | const threadId = ThreadId.make("thread-acp-registry-fixture"); |
| 418 | const runtimePolicy = ProviderAdapter.ProviderAdapterV2RuntimePolicy.make({ | 424 | const runtimePolicy = ProviderAdapter.ProviderAdapterV2RuntimePolicy.make({ |
| 419 | runtimeMode: "full-access", | 425 | runtimeMode: "full-access", |
apps/server/src/orchestration-v2/Adapters/AcpRegistryAdapterV2.testkit.ts+12-6| ... | @@ -6,7 +6,7 @@ import * as FileSystem from "effect/FileSystem"; | ... | @@ -6,7 +6,7 @@ import * as FileSystem from "effect/FileSystem"; |
| 6 | import * as Layer from "effect/Layer"; | 6 | import * as Layer from "effect/Layer"; |
| 7 | import * as Path from "effect/Path"; | 7 | import * as Path from "effect/Path"; |
| 8 | import * as Schema from "effect/Schema"; | 8 | import * as Schema from "effect/Schema"; |
| 9 | import { ChildProcessSpawner } from "effect/process"; | 9 | import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner"; |
| 10 | 10 | ||
| 11 | import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost"; | 11 | import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost"; |
| 12 | import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator"; | 12 | import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator"; |
| ... | @@ -25,6 +25,7 @@ import { | ... | @@ -25,6 +25,7 @@ import { |
| 25 | ACP_REGISTRY_PROVIDER, | 25 | ACP_REGISTRY_PROVIDER, |
| 26 | makeAcpRegistryAdapterV2, | 26 | makeAcpRegistryAdapterV2, |
| 27 | } from "@t3tools/provider-acp-registry/testing"; | 27 | } from "@t3tools/provider-acp-registry/testing"; |
| 28 | import * as AcpRegistrySupport from "@t3tools/provider-acp-registry/server/AcpRegistrySupport"; | ||
| 28 | 29 | ||
| 29 | const REPLAY_SETTINGS = Schema.decodeUnknownSync(AcpRegistrySettings)({ | 30 | const REPLAY_SETTINGS = Schema.decodeUnknownSync(AcpRegistrySettings)({ |
| 30 | agentId: "replay-agent", | 31 | agentId: "replay-agent", |
| ... | @@ -56,10 +57,6 @@ function layerAcpRegistryProviderAdapterRegistryReplay( | ... | @@ -56,10 +57,6 @@ function layerAcpRegistryProviderAdapterRegistryReplay( |
| 56 | instanceId: ACP_REGISTRY_DEFAULT_INSTANCE_ID, | 57 | instanceId: ACP_REGISTRY_DEFAULT_INSTANCE_ID, |
| 57 | settings: REPLAY_SETTINGS, | 58 | settings: REPLAY_SETTINGS, |
| 58 | environment: {}, | 59 | environment: {}, |
| 59 | childProcessSpawner, | ||
| 60 | resolver: { | ||
| 61 | resolve: () => Effect.die("ACP registry resolver must not run during replay"), | ||
| 62 | }, | ||
| 63 | selfInvocation: yield* resolveSelfInvocation(), | 60 | selfInvocation: yield* resolveSelfInvocation(), |
| 64 | makeRuntime: makeAcpReplayRuntime({ | 61 | makeRuntime: makeAcpReplayRuntime({ |
| 65 | transcript, | 62 | transcript, |
| ... | @@ -74,7 +71,16 @@ function layerAcpRegistryProviderAdapterRegistryReplay( | ... | @@ -74,7 +71,16 @@ function layerAcpRegistryProviderAdapterRegistryReplay( |
| 74 | return [adapter]; | 71 | return [adapter]; |
| 75 | }), | 72 | }), |
| 76 | ).pipe( | 73 | ).pipe( |
| 77 | Layer.provide(Layer.mergeAll(layerHost, NodeServices.layer, IdAllocator.layer)), | 74 | Layer.provide( |
| 75 | Layer.mergeAll( | ||
| 76 | layerHost, | ||
| 77 | NodeServices.layer, | ||
| 78 | IdAllocator.layer, | ||
| 79 | Layer.mock(AcpRegistrySupport.AcpRegistryCatalog)({ | ||
| 80 | resolve: () => Effect.die("ACP registry resolver must not run during replay"), | ||
| 81 | }), | ||
| 82 | ), | ||
| 83 | ), | ||
| 78 | // Held inbound lines must not outlive the scenario and wedge teardown. | 84 | // Held inbound lines must not outlive the scenario and wedge teardown. |
| 79 | Layer.merge( | 85 | Layer.merge( |
| 80 | Layer.effectDiscard( | 86 | Layer.effectDiscard( |
apps/server/src/orchestration-v2/Adapters/GrokAdapterV2.test.ts+1-1| ... | @@ -17,7 +17,7 @@ import * as Effect from "effect/Effect"; | ... | @@ -17,7 +17,7 @@ import * as Effect from "effect/Effect"; |
| 17 | import * as Layer from "effect/Layer"; | 17 | import * as Layer from "effect/Layer"; |
| 18 | import * as PlatformError from "effect/PlatformError"; | 18 | import * as PlatformError from "effect/PlatformError"; |
| 19 | import * as Schema from "effect/Schema"; | 19 | import * as Schema from "effect/Schema"; |
| 20 | import { ChildProcessSpawner } from "effect/process"; | 20 | import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner"; |
| 21 | import type * as EffectAcpSchema from "effect-acp/compat"; | 21 | import type * as EffectAcpSchema from "effect-acp/compat"; |
| 22 | 22 | ||
| 23 | import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost"; | 23 | import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost"; |
apps/server/src/orchestration-v2/Adapters/GrokAdapterV2.testkit.ts+1-1| ... | @@ -5,7 +5,7 @@ import * as FileSystem from "effect/FileSystem"; | ... | @@ -5,7 +5,7 @@ import * as FileSystem from "effect/FileSystem"; |
| 5 | import * as Layer from "effect/Layer"; | 5 | import * as Layer from "effect/Layer"; |
| 6 | import * as Path from "effect/Path"; | 6 | import * as Path from "effect/Path"; |
| 7 | import * as Schema from "effect/Schema"; | 7 | import * as Schema from "effect/Schema"; |
| 8 | import { ChildProcessSpawner } from "effect/process"; | 8 | import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner"; |
| 9 | import { HostProcessPlatform } from "@t3tools/shared/hostProcess"; | 9 | import { HostProcessPlatform } from "@t3tools/shared/hostProcess"; |
| 10 | import { resolveSelfInvocation } from "@t3tools/shared/nodeRuntime"; | 10 | import { resolveSelfInvocation } from "@t3tools/shared/nodeRuntime"; |
| 11 | 11 |
apps/server/src/terminal/Manager.ts+4-3| ... | @@ -2253,13 +2253,14 @@ export const makeWithOptions = Effect.fn("TerminalManager.makeWithOptions")(func | ... | @@ -2253,13 +2253,14 @@ export const makeWithOptions = Effect.fn("TerminalManager.makeWithOptions")(func |
| 2253 | options.managedBinaryToolsDir !== undefined | 2253 | options.managedBinaryToolsDir !== undefined |
| 2254 | ) { | 2254 | ) { |
| 2255 | const managedDirectories = yield* acpRegistryManagedBinaryDirectories({ | 2255 | const managedDirectories = yield* acpRegistryManagedBinaryDirectories({ |
| 2256 | fileSystem, | ||
| 2257 | path, | ||
| 2258 | cacheDir: options.managedBinaryCacheDir, | 2256 | cacheDir: options.managedBinaryCacheDir, |
| 2259 | toolsDir: options.managedBinaryToolsDir, | 2257 | toolsDir: options.managedBinaryToolsDir, |
| 2260 | platform, | 2258 | platform, |
| 2261 | architecture, | 2259 | architecture, |
| 2262 | }); | 2260 | }).pipe( |
| 2261 | Effect.provideService(FileSystem.FileSystem, fileSystem), | ||
| 2262 | Effect.provideService(Path.Path, path), | ||
| 2263 | ); | ||
| 2263 | if (managedDirectories.length > 0) { | 2264 | if (managedDirectories.length > 0) { |
| 2264 | const delimiter = platform === "win32" ? ";" : ":"; | 2265 | const delimiter = platform === "win32" ? ";" : ":"; |
| 2265 | const pathKey = | 2266 | const pathKey = |
packages/provider-acp-registry/src/server/AcpRegistrySupport.test.ts+4-6| ... | @@ -19,9 +19,11 @@ import * as Path from "effect/Path"; | ... | @@ -19,9 +19,11 @@ import * as Path from "effect/Path"; |
| 19 | import * as Fiber from "effect/Fiber"; | 19 | import * as Fiber from "effect/Fiber"; |
| 20 | import * as Layer from "effect/Layer"; | 20 | import * as Layer from "effect/Layer"; |
| 21 | import * as Schema from "effect/Schema"; | 21 | import * as Schema from "effect/Schema"; |
| 22 | import { HttpClient, HttpClientResponse } from "effect/http"; | 22 | import * as HttpClient from "effect/http/HttpClient"; |
| 23 | import * as HttpClientResponse from "effect/http/HttpClientResponse"; | ||
| 23 | import * as TestClock from "effect/testing/TestClock"; | 24 | import * as TestClock from "effect/testing/TestClock"; |
| 24 | import { ChildProcess, ChildProcessSpawner } from "effect/process"; | 25 | import * as ChildProcess from "effect/process/ChildProcess"; |
| 26 | import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner"; | ||
| 25 | 27 | ||
| 26 | import * as AcpRegistrySupport from "./AcpRegistrySupport.ts"; | 28 | import * as AcpRegistrySupport from "./AcpRegistrySupport.ts"; |
| 27 | import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost"; | 29 | import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost"; |
| ... | @@ -1713,8 +1715,6 @@ describe("acpRegistryManagedBinaryDirectories", () => { | ... | @@ -1713,8 +1715,6 @@ describe("acpRegistryManagedBinaryDirectories", () => { |
| 1713 | ); | 1715 | ); |
| 1714 | 1716 | ||
| 1715 | const directories = yield* AcpRegistrySupport.acpRegistryManagedBinaryDirectories({ | 1717 | const directories = yield* AcpRegistrySupport.acpRegistryManagedBinaryDirectories({ |
| 1716 | fileSystem, | ||
| 1717 | path, | ||
| 1718 | cacheDir, | 1718 | cacheDir, |
| 1719 | toolsDir: path.join(cacheDir, "tools"), | 1719 | toolsDir: path.join(cacheDir, "tools"), |
| 1720 | platform: "linux", | 1720 | platform: "linux", |
| ... | @@ -1727,8 +1727,6 @@ describe("acpRegistryManagedBinaryDirectories", () => { | ... | @@ -1727,8 +1727,6 @@ describe("acpRegistryManagedBinaryDirectories", () => { |
| 1727 | ]); | 1727 | ]); |
| 1728 | 1728 | ||
| 1729 | const missing = yield* AcpRegistrySupport.acpRegistryManagedBinaryDirectories({ | 1729 | const missing = yield* AcpRegistrySupport.acpRegistryManagedBinaryDirectories({ |
| 1730 | fileSystem, | ||
| 1731 | path, | ||
| 1732 | cacheDir: path.join(cacheDir, "does-not-exist"), | 1730 | cacheDir: path.join(cacheDir, "does-not-exist"), |
| 1733 | toolsDir: path.join(cacheDir, "does-not-exist", "tools"), | 1731 | toolsDir: path.join(cacheDir, "does-not-exist", "tools"), |
| 1734 | platform: "linux", | 1732 | platform: "linux", |
packages/provider-acp-registry/src/server/AcpRegistrySupport.ts+31-27| ... | @@ -39,8 +39,11 @@ import * as Schema from "effect/Schema"; | ... | @@ -39,8 +39,11 @@ import * as Schema from "effect/Schema"; |
| 39 | import * as Semaphore from "effect/Semaphore"; | 39 | import * as Semaphore from "effect/Semaphore"; |
| 40 | import * as Stream from "effect/Stream"; | 40 | import * as Stream from "effect/Stream"; |
| 41 | import { sha256 } from "@noble/hashes/sha2"; | 41 | import { sha256 } from "@noble/hashes/sha2"; |
| 42 | import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/http"; | 42 | import * as HttpClient from "effect/http/HttpClient"; |
| 43 | import { ChildProcess, ChildProcessSpawner } from "effect/process"; | 43 | import * as HttpClientRequest from "effect/http/HttpClientRequest"; |
| 44 | import * as HttpClientResponse from "effect/http/HttpClientResponse"; | ||
| 45 | import * as ChildProcess from "effect/process/ChildProcess"; | ||
| 46 | import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner"; | ||
| 44 | 47 | ||
| 45 | import { collectUint8StreamText } from "@t3tools/provider-core/server/collectStreamText"; | 48 | import { collectUint8StreamText } from "@t3tools/provider-core/server/collectStreamText"; |
| 46 | import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost"; | 49 | import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost"; |
| ... | @@ -308,26 +311,26 @@ function withPreferredPath( | ... | @@ -308,26 +311,26 @@ function withPreferredPath( |
| 308 | * managed binary agents are both available by name. Best effort: unreadable | 311 | * managed binary agents are both available by name. Best effort: unreadable |
| 309 | * directories resolve to no entries. | 312 | * directories resolve to no entries. |
| 310 | */ | 313 | */ |
| 311 | export const acpRegistryManagedBinaryDirectories = (input: { | 314 | export const acpRegistryManagedBinaryDirectories = Effect.fn("acpRegistryManagedBinaryDirectories")( |
| 312 | readonly fileSystem: FileSystem.FileSystem; | 315 | function* (input: { |
| 313 | readonly path: Path.Path; | 316 | readonly cacheDir: string; |
| 314 | readonly cacheDir: string; | 317 | readonly toolsDir: string; |
| 315 | readonly toolsDir: string; | 318 | readonly platform: NodeJS.Platform; |
| 316 | readonly platform: NodeJS.Platform; | 319 | readonly architecture: NodeJS.Architecture; |
| 317 | readonly architecture: NodeJS.Architecture; | 320 | }): Effect.fn.Return<ReadonlyArray<string>, never, FileSystem.FileSystem | Path.Path> { |
| 318 | }): Effect.Effect<ReadonlyArray<string>> => | 321 | const fileSystem = yield* FileSystem.FileSystem; |
| 319 | Effect.gen(function* () { | 322 | const path = yield* Path.Path; |
| 320 | const target = resolveAcpRegistryPlatformTarget(input.platform, input.architecture); | 323 | const target = resolveAcpRegistryPlatformTarget(input.platform, input.architecture); |
| 321 | const registryDirectory = input.path.join(input.cacheDir, "acp-registry"); | 324 | const registryDirectory = path.join(input.cacheDir, "acp-registry"); |
| 322 | const installsDirectory = input.toolsDir; | 325 | const installsDirectory = input.toolsDir; |
| 323 | const packageReceiptsDirectory = input.path.join(registryDirectory, "package-installs"); | 326 | const packageReceiptsDirectory = path.join(registryDirectory, "package-installs"); |
| 324 | const listDirectories = (directory: string) => | 327 | const listDirectories = (directory: string) => |
| 325 | input.fileSystem.readDirectory(directory).pipe(Effect.orElseSucceed((): Array<string> => [])); | 328 | fileSystem.readDirectory(directory).pipe(Effect.orElseSucceed((): Array<string> => [])); |
| 326 | const directories: Array<string> = []; | 329 | const directories: Array<string> = []; |
| 327 | 330 | ||
| 328 | for (const receiptFile of (yield* listDirectories(packageReceiptsDirectory)).toSorted()) { | 331 | for (const receiptFile of (yield* listDirectories(packageReceiptsDirectory)).toSorted()) { |
| 329 | const receipt = yield* input.fileSystem | 332 | const receipt = yield* fileSystem |
| 330 | .readFileString(input.path.join(packageReceiptsDirectory, receiptFile)) | 333 | .readFileString(path.join(packageReceiptsDirectory, receiptFile)) |
| 331 | .pipe( | 334 | .pipe( |
| 332 | Effect.map(decodePackageInstallReceipt), | 335 | Effect.map(decodePackageInstallReceipt), |
| 333 | Effect.orElseSucceed(() => Option.none()), | 336 | Effect.orElseSucceed(() => Option.none()), |
| ... | @@ -335,15 +338,15 @@ export const acpRegistryManagedBinaryDirectories = (input: { | ... | @@ -335,15 +338,15 @@ export const acpRegistryManagedBinaryDirectories = (input: { |
| 335 | if ( | 338 | if ( |
| 336 | Option.isSome(receipt) && | 339 | Option.isSome(receipt) && |
| 337 | receipt.value.binDirectory === | 340 | receipt.value.binDirectory === |
| 338 | input.path.join( | 341 | path.join( |
| 339 | installsDirectory, | 342 | installsDirectory, |
| 340 | receipt.value.agentId, | 343 | receipt.value.agentId, |
| 341 | encodeURIComponent(receipt.value.agentVersion), | 344 | encodeURIComponent(receipt.value.agentVersion), |
| 342 | receipt.value.distribution === "npx" ? "npm" : "python", | 345 | receipt.value.distribution === "npx" ? "npm" : "python", |
| 343 | ...(receipt.value.distribution === "npx" && input.platform === "win32" ? [] : ["bin"]), | 346 | ...(receipt.value.distribution === "npx" && input.platform === "win32" ? [] : ["bin"]), |
| 344 | ) && | 347 | ) && |
| 345 | input.path.dirname(receipt.value.executablePath) === receipt.value.binDirectory && | 348 | path.dirname(receipt.value.executablePath) === receipt.value.binDirectory && |
| 346 | (yield* input.fileSystem | 349 | (yield* fileSystem |
| 347 | .exists(receipt.value.executablePath) | 350 | .exists(receipt.value.executablePath) |
| 348 | .pipe(Effect.orElseSucceed(() => false))) | 351 | .pipe(Effect.orElseSucceed(() => false))) |
| 349 | ) { | 352 | ) { |
| ... | @@ -352,8 +355,8 @@ export const acpRegistryManagedBinaryDirectories = (input: { | ... | @@ -352,8 +355,8 @@ export const acpRegistryManagedBinaryDirectories = (input: { |
| 352 | } | 355 | } |
| 353 | 356 | ||
| 354 | if (target === undefined) return Array.from(new Set(directories)); | 357 | if (target === undefined) return Array.from(new Set(directories)); |
| 355 | const cachedAgents = yield* input.fileSystem | 358 | const cachedAgents = yield* fileSystem |
| 356 | .readFileString(input.path.join(registryDirectory, "registry.json")) | 359 | .readFileString(path.join(registryDirectory, "registry.json")) |
| 357 | .pipe( | 360 | .pipe( |
| 358 | Effect.flatMap(decodeJson), | 361 | Effect.flatMap(decodeJson), |
| 359 | Effect.flatMap(decodeRegistryIndexEnvelope), | 362 | Effect.flatMap(decodeRegistryIndexEnvelope), |
| ... | @@ -372,12 +375,12 @@ export const acpRegistryManagedBinaryDirectories = (input: { | ... | @@ -372,12 +375,12 @@ export const acpRegistryManagedBinaryDirectories = (input: { |
| 372 | ), | 375 | ), |
| 373 | ); | 376 | ); |
| 374 | for (const agent of (yield* listDirectories(installsDirectory)).toSorted()) { | 377 | for (const agent of (yield* listDirectories(installsDirectory)).toSorted()) { |
| 375 | const versions = (yield* listDirectories(input.path.join(installsDirectory, agent))).toSorted( | 378 | const versions = (yield* listDirectories(path.join(installsDirectory, agent))).toSorted( |
| 376 | (left, right) => right.localeCompare(left, undefined, { numeric: true }), | 379 | (left, right) => right.localeCompare(left, undefined, { numeric: true }), |
| 377 | ); | 380 | ); |
| 378 | for (const version of versions) { | 381 | for (const version of versions) { |
| 379 | const installRoot = input.path.join(installsDirectory, agent, version, target); | 382 | const installRoot = path.join(installsDirectory, agent, version, target); |
| 380 | if (yield* input.fileSystem.exists(installRoot).pipe(Effect.orElseSucceed(() => false))) { | 383 | if (yield* fileSystem.exists(installRoot).pipe(Effect.orElseSucceed(() => false))) { |
| 381 | const binaryTarget = cachedAgentByInstall.get(`${agent}\0${version}`)?.distribution | 384 | const binaryTarget = cachedAgentByInstall.get(`${agent}\0${version}`)?.distribution |
| 382 | .binary?.[target]; | 385 | .binary?.[target]; |
| 383 | const commandSegments = | 386 | const commandSegments = |
| ... | @@ -385,15 +388,16 @@ export const acpRegistryManagedBinaryDirectories = (input: { | ... | @@ -385,15 +388,16 @@ export const acpRegistryManagedBinaryDirectories = (input: { |
| 385 | const directory = | 388 | const directory = |
| 386 | commandSegments === undefined | 389 | commandSegments === undefined |
| 387 | ? installRoot | 390 | ? installRoot |
| 388 | : input.path.join(installRoot, ...commandSegments.slice(0, -1)); | 391 | : path.join(installRoot, ...commandSegments.slice(0, -1)); |
| 389 | if (yield* input.fileSystem.exists(directory).pipe(Effect.orElseSucceed(() => false))) { | 392 | if (yield* fileSystem.exists(directory).pipe(Effect.orElseSucceed(() => false))) { |
| 390 | directories.push(directory); | 393 | directories.push(directory); |
| 391 | } | 394 | } |
| 392 | } | 395 | } |
| 393 | } | 396 | } |
| 394 | } | 397 | } |
| 395 | return Array.from(new Set(directories)); | 398 | return Array.from(new Set(directories)); |
| 396 | }); | 399 | }, |
| 400 | ); | ||
| 397 | 401 | ||
| 398 | export interface ResolvedAcpRegistryDistribution { | 402 | export interface ResolvedAcpRegistryDistribution { |
| 399 | readonly kind: AcpRegistryDistributionKind; | 403 | readonly kind: AcpRegistryDistributionKind; |
packages/provider-acp-registry/src/server/adapter.ts+14-26| ... | @@ -15,7 +15,7 @@ import * as Layer from "effect/Layer"; | ... | @@ -15,7 +15,7 @@ import * as Layer from "effect/Layer"; |
| 15 | import * as Option from "effect/Option"; | 15 | import * as Option from "effect/Option"; |
| 16 | import * as Schema from "effect/Schema"; | 16 | import * as Schema from "effect/Schema"; |
| 17 | import type * as Scope from "effect/Scope"; | 17 | import type * as Scope from "effect/Scope"; |
| 18 | import { ChildProcessSpawner } from "effect/process"; | 18 | import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner"; |
| 19 | import * as EffectAcpErrors from "effect-acp/errors"; | 19 | import * as EffectAcpErrors from "effect-acp/errors"; |
| 20 | 20 | ||
| 21 | import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions"; | 21 | import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions"; |
| ... | @@ -56,17 +56,14 @@ export interface AcpRegistryAdapterV2Options { | ... | @@ -56,17 +56,14 @@ export interface AcpRegistryAdapterV2Options { |
| 56 | readonly instanceId: Parameters<typeof makeAcpAdapterV2>[0]["instanceId"]; | 56 | readonly instanceId: Parameters<typeof makeAcpAdapterV2>[0]["instanceId"]; |
| 57 | readonly settings: AcpRegistrySettings; | 57 | readonly settings: AcpRegistrySettings; |
| 58 | readonly environment: NodeJS.ProcessEnv; | 58 | readonly environment: NodeJS.ProcessEnv; |
| 59 | readonly childProcessSpawner: ChildProcessSpawner.ChildProcessSpawner["Service"]; | ||
| 60 | readonly selfInvocation: SelfInvocation; | 59 | readonly selfInvocation: SelfInvocation; |
| 61 | readonly resolver: Pick<AcpRegistrySupport.AcpRegistryCatalog["Service"], "resolve">; | ||
| 62 | readonly runtimeCoordinator?: AcpRegistryRuntimeCoordinator.AcpRegistryRuntimeCoordinator["Service"]; | ||
| 63 | readonly nativeLogging?: Parameters<typeof makeAcpAdapterV2>[0]["nativeLogging"]; | 60 | readonly nativeLogging?: Parameters<typeof makeAcpAdapterV2>[0]["nativeLogging"]; |
| 64 | readonly makeRuntime?: ( | 61 | readonly makeRuntime?: ( |
| 65 | input: AcpAdapterV2RuntimeInput, | 62 | input: AcpAdapterV2RuntimeInput, |
| 66 | ) => Effect.Effect< | 63 | ) => Effect.Effect< |
| 67 | AcpSessionRuntime.AcpSessionRuntime["Service"], | 64 | AcpSessionRuntime.AcpSessionRuntime["Service"], |
| 68 | EffectAcpErrors.AcpError, | 65 | EffectAcpErrors.AcpError, |
| 69 | Crypto.Crypto | Scope.Scope | 66 | ChildProcessSpawner.ChildProcessSpawner | Crypto.Crypto | Scope.Scope |
| 70 | >; | 67 | >; |
| 71 | readonly assertComplete?: Effect.Effect<void, EffectAcpErrors.AcpError>; | 68 | readonly assertComplete?: Effect.Effect<void, EffectAcpErrors.AcpError>; |
| 72 | } | 69 | } |
| ... | @@ -131,17 +128,20 @@ export function acpRegistryPromptFailure(agentId: string, cause: unknown) { | ... | @@ -131,17 +128,20 @@ export function acpRegistryPromptFailure(agentId: string, cause: unknown) { |
| 131 | }); | 128 | }); |
| 132 | } | 129 | } |
| 133 | 130 | ||
| 134 | function makeAcpRegistryRuntime(options: AcpRegistryAdapterV2Options) { | 131 | function makeAcpRegistryRuntime( |
| 132 | options: AcpRegistryAdapterV2Options, | ||
| 133 | catalog: AcpRegistrySupport.AcpRegistryCatalog["Service"], | ||
| 134 | ) { | ||
| 135 | return ( | 135 | return ( |
| 136 | input: AcpAdapterV2RuntimeInput, | 136 | input: AcpAdapterV2RuntimeInput, |
| 137 | ): Effect.Effect< | 137 | ): Effect.Effect< |
| 138 | AcpSessionRuntime.AcpSessionRuntime["Service"], | 138 | AcpSessionRuntime.AcpSessionRuntime["Service"], |
| 139 | EffectAcpErrors.AcpError, | 139 | EffectAcpErrors.AcpError, |
| 140 | Crypto.Crypto | Scope.Scope | 140 | ChildProcessSpawner.ChildProcessSpawner | Crypto.Crypto | Scope.Scope |
| 141 | > => | 141 | > => |
| 142 | Effect.gen(function* () { | 142 | Effect.gen(function* () { |
| 143 | const { processEnvironment, ...runtimeInput } = input; | 143 | const { processEnvironment, ...runtimeInput } = input; |
| 144 | const resolved = yield* options.resolver | 144 | const resolved = yield* catalog |
| 145 | .resolve(options.settings, input.cwd, options.environment) | 145 | .resolve(options.settings, input.cwd, options.environment) |
| 146 | .pipe( | 146 | .pipe( |
| 147 | Effect.mapError( | 147 | Effect.mapError( |
| ... | @@ -163,11 +163,7 @@ function makeAcpRegistryRuntime(options: AcpRegistryAdapterV2Options) { | ... | @@ -163,11 +163,7 @@ function makeAcpRegistryRuntime(options: AcpRegistryAdapterV2Options) { |
| 163 | env: { ...resolved.spawn.env, ...processEnvironment }, | 163 | env: { ...resolved.spawn.env, ...processEnvironment }, |
| 164 | }, | 164 | }, |
| 165 | ...(options.settings.authMethodId ? { authMethodId: options.settings.authMethodId } : {}), | 165 | ...(options.settings.authMethodId ? { authMethodId: options.settings.authMethodId } : {}), |
| 166 | }).pipe( | 166 | }), |
| 167 | Layer.provide( | ||
| 168 | Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, options.childProcessSpawner), | ||
| 169 | ), | ||
| 170 | ), | ||
| 171 | ); | 167 | ); |
| 172 | return yield* Effect.service(AcpSessionRuntime.AcpSessionRuntime).pipe( | 168 | return yield* Effect.service(AcpSessionRuntime.AcpSessionRuntime).pipe( |
| 173 | Effect.provide(context), | 169 | Effect.provide(context), |
| ... | @@ -178,7 +174,10 @@ function makeAcpRegistryRuntime(options: AcpRegistryAdapterV2Options) { | ... | @@ -178,7 +174,10 @@ function makeAcpRegistryRuntime(options: AcpRegistryAdapterV2Options) { |
| 178 | export const makeAcpRegistryAdapterV2 = Effect.fn("makeAcpRegistryAdapterV2")(function* ( | 174 | export const makeAcpRegistryAdapterV2 = Effect.fn("makeAcpRegistryAdapterV2")(function* ( |
| 179 | options: AcpRegistryAdapterV2Options, | 175 | options: AcpRegistryAdapterV2Options, |
| 180 | ) { | 176 | ) { |
| 181 | const runtimeCoordinator = options.runtimeCoordinator; | 177 | const catalog = yield* AcpRegistrySupport.AcpRegistryCatalog; |
| 178 | const runtimeCoordinator = Option.getOrUndefined( | ||
| 179 | yield* Effect.serviceOption(AcpRegistryRuntimeCoordinator.AcpRegistryRuntimeCoordinator), | ||
| 180 | ); | ||
| 182 | const registryAgentId = options.settings.source === "local" ? "" : options.settings.agentId; | 181 | const registryAgentId = options.settings.source === "local" ? "" : options.settings.agentId; |
| 183 | const startupKey = | 182 | const startupKey = |
| 184 | options.settings.source === "local" ? `local:${options.instanceId}` : registryAgentId; | 183 | options.settings.source === "local" ? `local:${options.instanceId}` : registryAgentId; |
| ... | @@ -203,7 +202,7 @@ export const makeAcpRegistryAdapterV2 = Effect.fn("makeAcpRegistryAdapterV2")(fu | ... | @@ -203,7 +202,7 @@ export const makeAcpRegistryAdapterV2 = Effect.fn("makeAcpRegistryAdapterV2")(fu |
| 203 | extractSubagentUpdate: extractDevinSubagentUpdate, | 202 | extractSubagentUpdate: extractDevinSubagentUpdate, |
| 204 | } | 203 | } |
| 205 | : {}), | 204 | : {}), |
| 206 | makeRuntime: options.makeRuntime ?? makeAcpRegistryRuntime(options), | 205 | makeRuntime: options.makeRuntime ?? makeAcpRegistryRuntime(options, catalog), |
| 207 | ...(runtimeCoordinator === undefined | 206 | ...(runtimeCoordinator === undefined |
| 208 | ? {} | 207 | ? {} |
| 209 | : { | 208 | : { |
| ... | @@ -243,7 +242,6 @@ export const makeAcpRegistryAdapterV2 = Effect.fn("makeAcpRegistryAdapterV2")(fu | ... | @@ -243,7 +242,6 @@ export const makeAcpRegistryAdapterV2 = Effect.fn("makeAcpRegistryAdapterV2")(fu |
| 243 | ...(isDevin | 242 | ...(isDevin |
| 244 | ? { | 243 | ? { |
| 245 | clientTerminals: { | 244 | clientTerminals: { |
| 246 | childProcessSpawner: options.childProcessSpawner, | ||
| 247 | environment: options.environment, | 245 | environment: options.environment, |
| 248 | shellCommands: true, | 246 | shellCommands: true, |
| 249 | }, | 247 | }, |
| ... | @@ -275,22 +273,12 @@ export const AcpRegistryAdapterV2Driver: ProviderAdapterDriver< | ... | @@ -275,22 +273,12 @@ export const AcpRegistryAdapterV2Driver: ProviderAdapterDriver< |
| 275 | function* (input: ProviderAdapterDriverCreateInput<AcpRegistrySettings>) { | 273 | function* (input: ProviderAdapterDriverCreateInput<AcpRegistrySettings>) { |
| 276 | const hostEnvironment = yield* HostProcessEnvironment; | 274 | const hostEnvironment = yield* HostProcessEnvironment; |
| 277 | const selfInvocation = yield* resolveSelfInvocation(); | 275 | const selfInvocation = yield* resolveSelfInvocation(); |
| 278 | const childProcessSpawner = yield* ChildProcessSpawner.ChildProcessSpawner; | ||
| 279 | const providerEventLoggers = yield* ProviderEventLoggers.ProviderEventLoggers; | 276 | const providerEventLoggers = yield* ProviderEventLoggers.ProviderEventLoggers; |
| 280 | const makeNativeLogger = yield* makeAcpNativeLoggerFactory(); | 277 | const makeNativeLogger = yield* makeAcpNativeLoggerFactory(); |
| 281 | const resolver = yield* AcpRegistrySupport.AcpRegistryCatalog; | ||
| 282 | const runtimeCoordinator = yield* Effect.serviceOption( | ||
| 283 | AcpRegistryRuntimeCoordinator.AcpRegistryRuntimeCoordinator, | ||
| 284 | ); | ||
| 285 | return yield* makeAcpRegistryAdapterV2({ | 278 | return yield* makeAcpRegistryAdapterV2({ |
| 286 | instanceId: input.instanceId, | 279 | instanceId: input.instanceId, |
| 287 | settings: { ...input.config, enabled: input.enabled }, | 280 | settings: { ...input.config, enabled: input.enabled }, |
| 288 | environment: mergeProviderInstanceEnvironment(input.environment, hostEnvironment), | 281 | environment: mergeProviderInstanceEnvironment(input.environment, hostEnvironment), |
| 289 | childProcessSpawner, | ||
| 290 | resolver, | ||
| 291 | ...(Option.isSome(runtimeCoordinator) | ||
| 292 | ? { runtimeCoordinator: runtimeCoordinator.value } | ||
| 293 | : {}), | ||
| 294 | selfInvocation, | 282 | selfInvocation, |
| 295 | nativeLogging: (threadId) => | 283 | nativeLogging: (threadId) => |
| 296 | makeNativeLogger({ | 284 | makeNativeLogger({ |
packages/provider-acp-registry/src/server/auth.ts+1-1| ... | @@ -12,7 +12,7 @@ import * as Layer from "effect/Layer"; | ... | @@ -12,7 +12,7 @@ import * as Layer from "effect/Layer"; |
| 12 | import * as Option from "effect/Option"; | 12 | import * as Option from "effect/Option"; |
| 13 | import * as Queue from "effect/Queue"; | 13 | import * as Queue from "effect/Queue"; |
| 14 | import * as Scope from "effect/Scope"; | 14 | import * as Scope from "effect/Scope"; |
| 15 | import { ChildProcessSpawner } from "effect/process"; | 15 | import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner"; |
| 16 | import type * as AcpSchema from "effect-acp/compat"; | 16 | import type * as AcpSchema from "effect-acp/compat"; |
| 17 | 17 | ||
| 18 | import * as PtyAdapter from "@t3tools/shared/PtyAdapter"; | 18 | import * as PtyAdapter from "@t3tools/shared/PtyAdapter"; |
packages/provider-acp-registry/src/server/driver.ts+1-1| ... | @@ -19,7 +19,7 @@ import * as Ref from "effect/Ref"; | ... | @@ -19,7 +19,7 @@ import * as Ref from "effect/Ref"; |
| 19 | import * as Result from "effect/Result"; | 19 | import * as Result from "effect/Result"; |
| 20 | import * as Schema from "effect/Schema"; | 20 | import * as Schema from "effect/Schema"; |
| 21 | import * as Semaphore from "effect/Semaphore"; | 21 | import * as Semaphore from "effect/Semaphore"; |
| 22 | import { ChildProcessSpawner } from "effect/process"; | 22 | import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner"; |
| 23 | 23 | ||
| 24 | import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost"; | 24 | import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost"; |
| 25 | import { AcpRegistryAdapterV2Driver, type AcpRegistryAdapterV2DriverEnv } from "./adapter.ts"; | 25 | import { AcpRegistryAdapterV2Driver, type AcpRegistryAdapterV2DriverEnv } from "./adapter.ts"; |
packages/provider-acp-registry/src/server/probe.ts+1-1| ... | @@ -19,7 +19,7 @@ import * as Layer from "effect/Layer"; | ... | @@ -19,7 +19,7 @@ import * as Layer from "effect/Layer"; |
| 19 | import * as Option from "effect/Option"; | 19 | import * as Option from "effect/Option"; |
| 20 | import * as Ref from "effect/Ref"; | 20 | import * as Ref from "effect/Ref"; |
| 21 | import * as Result from "effect/Result"; | 21 | import * as Result from "effect/Result"; |
| 22 | import { ChildProcessSpawner } from "effect/process"; | 22 | import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner"; |
| 23 | import type * as EffectAcpErrors from "effect-acp/errors"; | 23 | import type * as EffectAcpErrors from "effect-acp/errors"; |
| 24 | import type * as EffectAcpSchema from "effect-acp/compat"; | 24 | import type * as EffectAcpSchema from "effect-acp/compat"; |
| 25 | 25 |
packages/provider-acp/src/server/AcpSessionRuntime.ts+9-4| ... | @@ -1055,11 +1055,11 @@ export function windowsTaskkillResultIsSuccess(exitCode: number, _output?: strin | ... | @@ -1055,11 +1055,11 @@ export function windowsTaskkillResultIsSuccess(exitCode: number, _output?: strin |
| 1055 | } | 1055 | } |
| 1056 | 1056 | ||
| 1057 | export const terminateWindowsProcessTreeWithTaskkill = ( | 1057 | export const terminateWindowsProcessTreeWithTaskkill = ( |
| 1058 | spawner: ChildProcessSpawner.ChildProcessSpawner["Service"], | ||
| 1059 | pid: number, | 1058 | pid: number, |
| 1060 | ): Effect.Effect<void, AcpProcessGroupTerminationError> => | 1059 | ): Effect.Effect<void, AcpProcessGroupTerminationError, ChildProcessSpawner.ChildProcessSpawner> => |
| 1061 | Effect.scoped( | 1060 | Effect.scoped( |
| 1062 | Effect.gen(function* () { | 1061 | Effect.gen(function* () { |
| 1062 | const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; | ||
| 1063 | const taskkill = yield* spawner.spawn( | 1063 | const taskkill = yield* spawner.spawn( |
| 1064 | ChildProcess.make("taskkill", ["/PID", String(pid), "/T", "/F"]), | 1064 | ChildProcess.make("taskkill", ["/PID", String(pid), "/T", "/F"]), |
| 1065 | ); | 1065 | ); |
| ... | @@ -1659,7 +1659,10 @@ export const make = ( | ... | @@ -1659,7 +1659,10 @@ export const make = ( |
| 1659 | ); | 1659 | ); |
| 1660 | const terminateWindowsProcessTree = | 1660 | const terminateWindowsProcessTree = |
| 1661 | options.windowsProcessTreeTerminator ?? | 1661 | options.windowsProcessTreeTerminator ?? |
| 1662 | ((pid: number) => terminateWindowsProcessTreeWithTaskkill(spawner, pid)); | 1662 | ((pid: number) => |
| 1663 | terminateWindowsProcessTreeWithTaskkill(pid).pipe( | ||
| 1664 | Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner), | ||
| 1665 | )); | ||
| 1663 | const terminatePosixProcessTree = (grace: Duration.Input, platformOverride?: NodeJS.Platform) => | 1666 | const terminatePosixProcessTree = (grace: Duration.Input, platformOverride?: NodeJS.Platform) => |
| 1664 | Effect.gen(function* () { | 1667 | Effect.gen(function* () { |
| 1665 | const platform = platformOverride ?? options.processGroupPlatform; | 1668 | const platform = platformOverride ?? options.processGroupPlatform; |
| ... | @@ -1728,7 +1731,9 @@ export const make = ( | ... | @@ -1728,7 +1731,9 @@ export const make = ( |
| 1728 | const hostPlatform = yield* HostProcessPlatform; | 1731 | const hostPlatform = yield* HostProcessPlatform; |
| 1729 | const forceTerminateOwnedProcessGroup = | 1732 | const forceTerminateOwnedProcessGroup = |
| 1730 | hostPlatform === "win32" | 1733 | hostPlatform === "win32" |
| 1731 | ? terminateWindowsProcessTreeWithTaskkill(spawner, Number(child.pid)) | 1734 | ? terminateWindowsProcessTreeWithTaskkill(Number(child.pid)).pipe( |
| 1735 | Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner), | ||
| 1736 | ) | ||
| 1732 | : linuxCgroupLease !== undefined | 1737 | : linuxCgroupLease !== undefined |
| 1733 | ? terminateLinuxCgroupLease(linuxCgroupLease) | 1738 | ? terminateLinuxCgroupLease(linuxCgroupLease) |
| 1734 | : options.ownDescendantProcessGroups === true | 1739 | : options.ownDescendantProcessGroups === true |
packages/provider-acp/src/server/adapter.ts+10-7| ... | @@ -50,7 +50,7 @@ import * as Result from "effect/Result"; | ... | @@ -50,7 +50,7 @@ import * as Result from "effect/Result"; |
| 50 | import * as Scope from "effect/Scope"; | 50 | import * as Scope from "effect/Scope"; |
| 51 | import * as Semaphore from "effect/Semaphore"; | 51 | import * as Semaphore from "effect/Semaphore"; |
| 52 | import * as Stream from "effect/Stream"; | 52 | import * as Stream from "effect/Stream"; |
| 53 | import type { ChildProcessSpawner } from "effect/process"; | 53 | import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner"; |
| 54 | import * as EffectAcpErrors from "effect-acp/errors"; | 54 | import * as EffectAcpErrors from "effect-acp/errors"; |
| 55 | import type * as EffectAcpProtocol from "effect-acp/protocol"; | 55 | import type * as EffectAcpProtocol from "effect-acp/protocol"; |
| 56 | import type * as EffectAcpSchema from "effect-acp/compat"; | 56 | import type * as EffectAcpSchema from "effect-acp/compat"; |
| ... | @@ -239,7 +239,7 @@ export interface AcpAdapterV2Flavor { | ... | @@ -239,7 +239,7 @@ export interface AcpAdapterV2Flavor { |
| 239 | ) => Effect.Effect< | 239 | ) => Effect.Effect< |
| 240 | AcpSessionRuntime.AcpSessionRuntime["Service"], | 240 | AcpSessionRuntime.AcpSessionRuntime["Service"], |
| 241 | EffectAcpErrors.AcpError, | 241 | EffectAcpErrors.AcpError, |
| 242 | Crypto.Crypto | Scope.Scope | 242 | ChildProcessSpawner.ChildProcessSpawner | Crypto.Crypto | Scope.Scope |
| 243 | >; | 243 | >; |
| 244 | readonly resolveModelId?: (selection: ModelSelection) => string | undefined; | 244 | readonly resolveModelId?: (selection: ModelSelection) => string | undefined; |
| 245 | /** | 245 | /** |
| ... | @@ -485,11 +485,10 @@ export interface AcpAdapterV2Options { | ... | @@ -485,11 +485,10 @@ export interface AcpAdapterV2Options { |
| 485 | /** | 485 | /** |
| 486 | * Opts the session into the ACP client `terminal` capability. Agents run | 486 | * Opts the session into the ACP client `terminal` capability. Agents run |
| 487 | * commands themselves unless an adapter sets this; with it, sessions | 487 | * commands themselves unless an adapter sets this; with it, sessions |
| 488 | * advertise `terminal: true` and run agent-created terminals through this | 488 | * advertise `terminal: true` and run agent-created terminals through the |
| 489 | * spawner with the provider instance's environment. Devin sets it. | 489 | * adapter's spawner with the provider instance's environment. Devin sets it. |
| 490 | */ | 490 | */ |
| 491 | readonly clientTerminals?: { | 491 | readonly clientTerminals?: { |
| 492 | readonly childProcessSpawner: ChildProcessSpawner.ChildProcessSpawner["Service"]; | ||
| 493 | readonly environment?: NodeJS.ProcessEnv; | 492 | readonly environment?: NodeJS.ProcessEnv; |
| 494 | readonly shellCommands?: boolean; | 493 | readonly shellCommands?: boolean; |
| 495 | }; | 494 | }; |
| ... | @@ -1486,6 +1485,7 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( | ... | @@ -1486,6 +1485,7 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( |
| 1486 | ) { | 1485 | ) { |
| 1487 | const crypto = yield* Crypto.Crypto; | 1486 | const crypto = yield* Crypto.Crypto; |
| 1488 | const fileSystem = yield* FileSystem.FileSystem; | 1487 | const fileSystem = yield* FileSystem.FileSystem; |
| 1488 | const childProcessSpawner = yield* ChildProcessSpawner.ChildProcessSpawner; | ||
| 1489 | const idAllocator = yield* IdAllocator.IdAllocatorV2; | 1489 | const idAllocator = yield* IdAllocator.IdAllocatorV2; |
| 1490 | const host = yield* ProviderHost.ProviderHost; | 1490 | const host = yield* ProviderHost.ProviderHost; |
| 1491 | const mcpSessions = yield* McpProviderSessions.McpProviderSessions; | 1491 | const mcpSessions = yield* McpProviderSessions.McpProviderSessions; |
| ... | @@ -1586,7 +1586,6 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( | ... | @@ -1586,7 +1586,6 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( |
| 1586 | options.clientTerminals === undefined | 1586 | options.clientTerminals === undefined |
| 1587 | ? undefined | 1587 | ? undefined |
| 1588 | : yield* makeAcpClientTerminals({ | 1588 | : yield* makeAcpClientTerminals({ |
| 1589 | spawner: options.clientTerminals.childProcessSpawner, | ||
| 1590 | defaultCwd: input.runtimePolicy.cwd ?? process.cwd(), | 1589 | defaultCwd: input.runtimePolicy.cwd ?? process.cwd(), |
| 1591 | environment: options.clientTerminals.environment, | 1590 | environment: options.clientTerminals.environment, |
| 1592 | shellCommands: options.clientTerminals.shellCommands, | 1591 | shellCommands: options.clientTerminals.shellCommands, |
| ... | @@ -1607,7 +1606,9 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( | ... | @@ -1607,7 +1606,9 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( |
| 1607 | ? pendingTerminalEnvironment.environment | 1606 | ? pendingTerminalEnvironment.environment |
| 1608 | : undefined; | 1607 | : undefined; |
| 1609 | }, | 1608 | }, |
| 1610 | }); | 1609 | }).pipe( |
| 1610 | Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, childProcessSpawner), | ||
| 1611 | ); | ||
| 1611 | if (clientTerminals !== undefined) { | 1612 | if (clientTerminals !== undefined) { |
| 1612 | yield* Scope.addFinalizer(sessionScope, clientTerminals.disposeAll); | 1613 | yield* Scope.addFinalizer(sessionScope, clientTerminals.disposeAll); |
| 1613 | } | 1614 | } |
| ... | @@ -6018,6 +6019,7 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( | ... | @@ -6018,6 +6019,7 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( |
| 6018 | .pipe( | 6019 | .pipe( |
| 6019 | Effect.provideService(Scope.Scope, runtimeScope), | 6020 | Effect.provideService(Scope.Scope, runtimeScope), |
| 6020 | Effect.provideService(Crypto.Crypto, crypto), | 6021 | Effect.provideService(Crypto.Crypto, crypto), |
| 6022 | Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, childProcessSpawner), | ||
| 6021 | ); | 6023 | ); |
| 6022 | }); | 6024 | }); |
| 6023 | 6025 | ||
| ... | @@ -6096,6 +6098,7 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( | ... | @@ -6096,6 +6098,7 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( |
| 6096 | .pipe( | 6098 | .pipe( |
| 6097 | Effect.provideService(Scope.Scope, replacementScope), | 6099 | Effect.provideService(Scope.Scope, replacementScope), |
| 6098 | Effect.provideService(Crypto.Crypto, crypto), | 6100 | Effect.provideService(Crypto.Crypto, crypto), |
| 6101 | Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, childProcessSpawner), | ||
| 6099 | ); | 6102 | ); |
| 6100 | // Session setup may publish commands before it returns. Buffer those | 6103 | // Session setup may publish commands before it returns. Buffer those |
| 6101 | // notifications, but do not expose request or extension handlers | 6104 | // notifications, but do not expose request or extension handlers |
packages/provider-acp/src/server/clientTerminals.test.ts-6| ... | @@ -2,7 +2,6 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; | ... | @@ -2,7 +2,6 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 2 | import { describe, expect, it } from "@effect/vitest"; | 2 | import { describe, expect, it } from "@effect/vitest"; |
| 3 | import * as Effect from "effect/Effect"; | 3 | import * as Effect from "effect/Effect"; |
| 4 | import * as Result from "effect/Result"; | 4 | import * as Result from "effect/Result"; |
| 5 | import { ChildProcessSpawner } from "effect/process"; | ||
| 6 | 5 | ||
| 7 | import { | 6 | import { |
| 8 | makeAcpClientTerminals, | 7 | makeAcpClientTerminals, |
| ... | @@ -12,9 +11,7 @@ import { | ... | @@ -12,9 +11,7 @@ import { |
| 12 | 11 | ||
| 13 | const withTerminals = <A, E>(use: (terminals: AcpClientTerminals) => Effect.Effect<A, E>) => | 12 | const withTerminals = <A, E>(use: (terminals: AcpClientTerminals) => Effect.Effect<A, E>) => |
| 14 | Effect.gen(function* () { | 13 | Effect.gen(function* () { |
| 15 | const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; | ||
| 16 | const terminals = yield* makeAcpClientTerminals({ | 14 | const terminals = yield* makeAcpClientTerminals({ |
| 17 | spawner, | ||
| 18 | defaultCwd: process.cwd(), | 15 | defaultCwd: process.cwd(), |
| 19 | }); | 16 | }); |
| 20 | return yield* use(terminals).pipe(Effect.ensuring(terminals.disposeAll)); | 17 | return yield* use(terminals).pipe(Effect.ensuring(terminals.disposeAll)); |
| ... | @@ -49,9 +46,7 @@ describe("AcpClientTerminals", () => { | ... | @@ -49,9 +46,7 @@ describe("AcpClientTerminals", () => { |
| 49 | 46 | ||
| 50 | it.effect("resolves terminal environment from the requesting ACP session", () => | 47 | it.effect("resolves terminal environment from the requesting ACP session", () => |
| 51 | Effect.gen(function* () { | 48 | Effect.gen(function* () { |
| 52 | const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; | ||
| 53 | const terminals = yield* makeAcpClientTerminals({ | 49 | const terminals = yield* makeAcpClientTerminals({ |
| 54 | spawner, | ||
| 55 | defaultCwd: process.cwd(), | 50 | defaultCwd: process.cwd(), |
| 56 | environmentForSession: (sessionId) => ({ | 51 | environmentForSession: (sessionId) => ({ |
| 57 | T3_TEST_ACP_SESSION_TOKEN: sessionId === "session-a" ? "token-a" : "token-b", | 52 | T3_TEST_ACP_SESSION_TOKEN: sessionId === "session-a" ? "token-a" : "token-b", |
| ... | @@ -78,7 +73,6 @@ describe("AcpClientTerminals", () => { | ... | @@ -78,7 +73,6 @@ describe("AcpClientTerminals", () => { |
| 78 | it.effect("runs Devin shell source with arguments, pipes, and session fallback variables", () => | 73 | it.effect("runs Devin shell source with arguments, pipes, and session fallback variables", () => |
| 79 | Effect.gen(function* () { | 74 | Effect.gen(function* () { |
| 80 | const terminals = yield* makeAcpClientTerminals({ | 75 | const terminals = yield* makeAcpClientTerminals({ |
| 81 | spawner: yield* ChildProcessSpawner.ChildProcessSpawner, | ||
| 82 | defaultCwd: process.cwd(), | 76 | defaultCwd: process.cwd(), |
| 83 | shellCommands: true, | 77 | shellCommands: true, |
| 84 | environmentForSession: () => ({ T3_ACP_MCP_NODE: "mailbox-probe" }), | 78 | environmentForSession: () => ({ T3_ACP_MCP_NODE: "mailbox-probe" }), |
packages/provider-acp/src/server/clientTerminals.ts+229-240| ... | @@ -6,7 +6,8 @@ import * as Fiber from "effect/Fiber"; | ... | @@ -6,7 +6,8 @@ import * as Fiber from "effect/Fiber"; |
| 6 | import * as Scope from "effect/Scope"; | 6 | import * as Scope from "effect/Scope"; |
| 7 | import * as Semaphore from "effect/Semaphore"; | 7 | import * as Semaphore from "effect/Semaphore"; |
| 8 | import * as Stream from "effect/Stream"; | 8 | import * as Stream from "effect/Stream"; |
| 9 | import { ChildProcess, ChildProcessSpawner } from "effect/process"; | 9 | import * as ChildProcess from "effect/process/ChildProcess"; |
| 10 | import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner"; | ||
| 10 | import * as EffectAcpErrors from "effect-acp/errors"; | 11 | import * as EffectAcpErrors from "effect-acp/errors"; |
| 11 | import type * as EffectAcpSchema from "effect-acp/compat"; | 12 | import type * as EffectAcpSchema from "effect-acp/compat"; |
| 12 | import * as NodeBuffer from "node:buffer"; | 13 | import * as NodeBuffer from "node:buffer"; |
| ... | @@ -137,7 +138,6 @@ export interface AcpClientTerminals { | ... | @@ -137,7 +138,6 @@ export interface AcpClientTerminals { |
| 137 | export interface AcpClientTerminalsOptions { | 138 | export interface AcpClientTerminalsOptions { |
| 138 | /** Devin sends shell source in command, rather than an executable plus args. */ | 139 | /** Devin sends shell source in command, rather than an executable plus args. */ |
| 139 | readonly shellCommands?: boolean | undefined; | 140 | readonly shellCommands?: boolean | undefined; |
| 140 | readonly spawner: ChildProcessSpawner.ChildProcessSpawner["Service"]; | ||
| 141 | readonly defaultCwd: string; | 141 | readonly defaultCwd: string; |
| 142 | readonly environment?: NodeJS.ProcessEnv | undefined; | 142 | readonly environment?: NodeJS.ProcessEnv | undefined; |
| 143 | readonly environmentForSession?: | 143 | readonly environmentForSession?: |
| ... | @@ -161,272 +161,261 @@ function acpTerminalCommand(input: { | ... | @@ -161,272 +161,261 @@ function acpTerminalCommand(input: { |
| 161 | }); | 161 | }); |
| 162 | } | 162 | } |
| 163 | 163 | ||
| 164 | export const makeAcpClientTerminals = ( | 164 | export const makeAcpClientTerminals = Effect.fn("makeAcpClientTerminals")(function* ( |
| 165 | options: AcpClientTerminalsOptions, | 165 | options: AcpClientTerminalsOptions, |
| 166 | ): Effect.Effect<AcpClientTerminals> => | 166 | ): Effect.fn.Return<AcpClientTerminals, never, ChildProcessSpawner.ChildProcessSpawner> { |
| 167 | Effect.gen(function* () { | 167 | const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; |
| 168 | const creationPermit = yield* Semaphore.make(1); | 168 | const creationPermit = yield* Semaphore.make(1); |
| 169 | const terminals = new Map<string, ManagedAcpTerminal>(); | 169 | const terminals = new Map<string, ManagedAcpTerminal>(); |
| 170 | let nextTerminalNumber = 1; | 170 | let nextTerminalNumber = 1; |
| 171 | 171 | ||
| 172 | const enforceTotalOutputLimit = (): void => { | 172 | const enforceTotalOutputLimit = (): void => { |
| 173 | let excess = | 173 | let excess = |
| 174 | Array.from(terminals.values()).reduce( | 174 | Array.from(terminals.values()).reduce((total, terminal) => total + terminal.buffer.bytes, 0) - |
| 175 | (total, terminal) => total + terminal.buffer.bytes, | 175 | MAX_RETAINED_OUTPUT_BYTES; |
| 176 | 0, | 176 | if (excess <= 0) return; |
| 177 | ) - MAX_RETAINED_OUTPUT_BYTES; | 177 | for (const terminal of terminals.values()) { |
| 178 | const before = terminal.buffer.bytes; | ||
| 179 | trimOutputStart(terminal.buffer, excess); | ||
| 180 | excess -= before - terminal.buffer.bytes; | ||
| 178 | if (excess <= 0) return; | 181 | if (excess <= 0) return; |
| 179 | for (const terminal of terminals.values()) { | 182 | } |
| 180 | const before = terminal.buffer.bytes; | 183 | }; |
| 181 | trimOutputStart(terminal.buffer, excess); | ||
| 182 | excess -= before - terminal.buffer.bytes; | ||
| 183 | if (excess <= 0) return; | ||
| 184 | } | ||
| 185 | }; | ||
| 186 | 184 | ||
| 187 | const pruneReleasedSnapshots = (): void => { | 185 | const pruneReleasedSnapshots = (): void => { |
| 188 | const released = Array.from(terminals.values()).filter((terminal) => terminal.released); | 186 | const released = Array.from(terminals.values()).filter((terminal) => terminal.released); |
| 189 | let retainedBytes = released.reduce((total, terminal) => total + terminal.buffer.bytes, 0); | 187 | let retainedBytes = released.reduce((total, terminal) => total + terminal.buffer.bytes, 0); |
| 190 | while ( | 188 | while (released.length > MAX_RETAINED_TERMINALS || retainedBytes > MAX_RETAINED_OUTPUT_BYTES) { |
| 191 | released.length > MAX_RETAINED_TERMINALS || | 189 | const terminal = released.shift(); |
| 192 | retainedBytes > MAX_RETAINED_OUTPUT_BYTES | 190 | if (terminal === undefined) break; |
| 193 | ) { | 191 | retainedBytes -= terminal.buffer.bytes; |
| 194 | const terminal = released.shift(); | 192 | terminals.delete(terminal.terminalId); |
| 195 | if (terminal === undefined) break; | 193 | } |
| 196 | retainedBytes -= terminal.buffer.bytes; | 194 | }; |
| 197 | terminals.delete(terminal.terminalId); | ||
| 198 | } | ||
| 199 | }; | ||
| 200 | 195 | ||
| 201 | const requireTerminal = ( | 196 | const requireTerminal = ( |
| 202 | sessionId: string, | 197 | sessionId: string, |
| 203 | terminalId: string, | 198 | terminalId: string, |
| 204 | operation: string, | 199 | operation: string, |
| 205 | ): Effect.Effect<ManagedAcpTerminal, EffectAcpErrors.AcpRequestError> => { | 200 | ): Effect.Effect<ManagedAcpTerminal, EffectAcpErrors.AcpRequestError> => { |
| 206 | const terminal = terminals.get(terminalId); | 201 | const terminal = terminals.get(terminalId); |
| 207 | if (terminal === undefined || terminal.sessionId !== sessionId || terminal.released) { | 202 | if (terminal === undefined || terminal.sessionId !== sessionId || terminal.released) { |
| 208 | return Effect.fail( | 203 | return Effect.fail( |
| 209 | terminalRequestError(`ACP ${operation} received an unknown terminal ID '${terminalId}'.`), | 204 | terminalRequestError(`ACP ${operation} received an unknown terminal ID '${terminalId}'.`), |
| 210 | ); | 205 | ); |
| 211 | } | 206 | } |
| 212 | return Effect.succeed(terminal); | 207 | return Effect.succeed(terminal); |
| 213 | }; | 208 | }; |
| 214 | 209 | ||
| 215 | const create: AcpClientTerminals["create"] = (request) => | 210 | const create: AcpClientTerminals["create"] = (request) => |
| 216 | creationPermit.withPermit( | 211 | creationPermit.withPermit( |
| 217 | Effect.uninterruptibleMask((restore) => | 212 | Effect.uninterruptibleMask((restore) => |
| 218 | Effect.gen(function* () { | 213 | Effect.gen(function* () { |
| 219 | const liveCount = Array.from(terminals.values()).filter( | 214 | const liveCount = Array.from(terminals.values()).filter( |
| 220 | (terminal) => !terminal.released && terminal.exitStatus === undefined, | 215 | (terminal) => !terminal.released && terminal.exitStatus === undefined, |
| 221 | ).length; | 216 | ).length; |
| 222 | const unreleasedCount = Array.from(terminals.values()).filter( | 217 | const unreleasedCount = Array.from(terminals.values()).filter( |
| 223 | (terminal) => !terminal.released, | 218 | (terminal) => !terminal.released, |
| 224 | ).length; | 219 | ).length; |
| 225 | if (unreleasedCount >= MAX_UNRELEASED_TERMINALS) { | 220 | if (unreleasedCount >= MAX_UNRELEASED_TERMINALS) { |
| 226 | return yield* terminalRequestError( | 221 | return yield* terminalRequestError( |
| 227 | `ACP terminal/create exceeded the limit of ${MAX_UNRELEASED_TERMINALS} unreleased terminals.`, | 222 | `ACP terminal/create exceeded the limit of ${MAX_UNRELEASED_TERMINALS} unreleased terminals.`, |
| 228 | ); | 223 | ); |
| 229 | } | 224 | } |
| 230 | if (liveCount >= MAX_LIVE_TERMINALS) { | 225 | if (liveCount >= MAX_LIVE_TERMINALS) { |
| 231 | return yield* terminalRequestError( | 226 | return yield* terminalRequestError( |
| 232 | `ACP terminal/create exceeded the limit of ${MAX_LIVE_TERMINALS} concurrent terminals.`, | 227 | `ACP terminal/create exceeded the limit of ${MAX_LIVE_TERMINALS} concurrent terminals.`, |
| 233 | ); | ||
| 234 | } | ||
| 235 | const requestEnvironment = Object.fromEntries( | ||
| 236 | (request.env ?? []).map((variable) => [variable.name, variable.value] as const), | ||
| 237 | ); | 228 | ); |
| 238 | const sessionEnvironment = options.environmentForSession?.(request.sessionId); | 229 | } |
| 239 | const environment = | 230 | const requestEnvironment = Object.fromEntries( |
| 240 | options.environment === undefined && | 231 | (request.env ?? []).map((variable) => [variable.name, variable.value] as const), |
| 241 | sessionEnvironment === undefined && | 232 | ); |
| 242 | (request.env?.length ?? 0) === 0 | 233 | const sessionEnvironment = options.environmentForSession?.(request.sessionId); |
| 243 | ? undefined | 234 | const environment = |
| 244 | : { ...options.environment, ...sessionEnvironment, ...requestEnvironment }; | 235 | options.environment === undefined && |
| 245 | // Each terminal owns a scope so the spawned process is reliably reaped: | 236 | sessionEnvironment === undefined && |
| 246 | // closing the scope kills a still-running command and frees the handle. | 237 | (request.env?.length ?? 0) === 0 |
| 247 | const terminalScope = yield* Scope.make(); | 238 | ? undefined |
| 248 | const child = yield* restore( | 239 | : { ...options.environment, ...sessionEnvironment, ...requestEnvironment }; |
| 249 | options.spawner | 240 | // Each terminal owns a scope so the spawned process is reliably reaped: |
| 250 | .spawn( | 241 | // closing the scope kills a still-running command and frees the handle. |
| 251 | acpTerminalCommand({ | 242 | const terminalScope = yield* Scope.make(); |
| 252 | request, | 243 | const child = yield* restore( |
| 253 | shellCommands: options.shellCommands, | 244 | spawner |
| 254 | defaultCwd: options.defaultCwd, | 245 | .spawn( |
| 255 | environment, | 246 | acpTerminalCommand({ |
| 256 | forceKillAfter: options.forceKillAfter, | 247 | request, |
| 257 | }), | 248 | shellCommands: options.shellCommands, |
| 258 | ) | 249 | defaultCwd: options.defaultCwd, |
| 259 | .pipe( | 250 | environment, |
| 260 | Effect.provideService(Scope.Scope, terminalScope), | 251 | forceKillAfter: options.forceKillAfter, |
| 261 | Effect.mapError((cause) => | 252 | }), |
| 262 | terminalRequestError( | 253 | ) |
| 263 | `Could not start terminal command '${request.command}'.`, | 254 | .pipe( |
| 264 | cause, | 255 | Effect.provideService(Scope.Scope, terminalScope), |
| 265 | ), | 256 | Effect.mapError((cause) => |
| 257 | terminalRequestError( | ||
| 258 | `Could not start terminal command '${request.command}'.`, | ||
| 259 | cause, | ||
| 266 | ), | 260 | ), |
| 267 | ), | 261 | ), |
| 268 | ).pipe( | ||
| 269 | Effect.onExit((exit) => | ||
| 270 | Exit.isFailure(exit) ? Scope.close(terminalScope, Exit.void) : Effect.void, | ||
| 271 | ), | 262 | ), |
| 272 | ); | 263 | ).pipe( |
| 264 | Effect.onExit((exit) => | ||
| 265 | Exit.isFailure(exit) ? Scope.close(terminalScope, Exit.void) : Effect.void, | ||
| 266 | ), | ||
| 267 | ); | ||
| 273 | 268 | ||
| 274 | const terminalId = `t3-term-${nextTerminalNumber}`; | 269 | const terminalId = `t3-term-${nextTerminalNumber}`; |
| 275 | nextTerminalNumber += 1; | 270 | nextTerminalNumber += 1; |
| 276 | const record: ManagedAcpTerminal = { | 271 | const record: ManagedAcpTerminal = { |
| 277 | terminalId, | 272 | terminalId, |
| 278 | sessionId: request.sessionId, | 273 | sessionId: request.sessionId, |
| 279 | commandLine: [request.command, ...(request.args ?? [])].join(" "), | 274 | commandLine: [request.command, ...(request.args ?? [])].join(" "), |
| 280 | buffer: { | 275 | buffer: { |
| 281 | chunks: [], | 276 | chunks: [], |
| 282 | bytes: 0, | 277 | bytes: 0, |
| 283 | truncated: false, | 278 | truncated: false, |
| 284 | limit: Math.min( | 279 | limit: Math.min( |
| 285 | request.outputByteLimit ?? DEFAULT_OUTPUT_BYTE_LIMIT, | 280 | request.outputByteLimit ?? DEFAULT_OUTPUT_BYTE_LIMIT, |
| 286 | MAX_OUTPUT_BYTE_LIMIT, | 281 | MAX_OUTPUT_BYTE_LIMIT, |
| 287 | ), | 282 | ), |
| 288 | }, | 283 | }, |
| 289 | exit: yield* Deferred.make<EffectAcpSchema.WaitForTerminalExitResponse>(), | 284 | exit: yield* Deferred.make<EffectAcpSchema.WaitForTerminalExitResponse>(), |
| 290 | kill: child.kill().pipe(Effect.ignore), | 285 | kill: child.kill().pipe(Effect.ignore), |
| 291 | dispose: Scope.close(terminalScope, Exit.void).pipe(Effect.ignore), | 286 | dispose: Scope.close(terminalScope, Exit.void).pipe(Effect.ignore), |
| 292 | exitStatus: undefined, | 287 | exitStatus: undefined, |
| 293 | released: false, | 288 | released: false, |
| 294 | }; | 289 | }; |
| 295 | terminals.set(terminalId, record); | 290 | terminals.set(terminalId, record); |
| 296 | 291 | ||
| 297 | const pump = (stream: typeof child.stdout) => | 292 | const pump = (stream: typeof child.stdout) => |
| 298 | stream.pipe( | 293 | stream.pipe( |
| 299 | Stream.runForEach((chunk) => | 294 | Stream.runForEach((chunk) => |
| 300 | Effect.sync(() => { | 295 | Effect.sync(() => { |
| 301 | appendOutput(record.buffer, chunk); | 296 | appendOutput(record.buffer, chunk); |
| 302 | enforceTotalOutputLimit(); | 297 | enforceTotalOutputLimit(); |
| 303 | }), | ||
| 304 | ), | ||
| 305 | Effect.ignore, | ||
| 306 | ); | ||
| 307 | const stdoutPump = yield* Effect.forkDetach(pump(child.stdout)); | ||
| 308 | const stderrPump = yield* Effect.forkDetach(pump(child.stderr)); | ||
| 309 | yield* Effect.forkDetach( | ||
| 310 | child.exitCode.pipe( | ||
| 311 | Effect.match({ | ||
| 312 | onFailure: (error) => ({ exitCode: null, signal: terminalExitSignal(error) }), | ||
| 313 | onSuccess: (code) => ({ exitCode: Number(code), signal: null }), | ||
| 314 | }), | 298 | }), |
| 315 | Effect.flatMap((status) => | 299 | ), |
| 316 | Effect.all([Fiber.join(stdoutPump), Fiber.join(stderrPump)], { | 300 | Effect.ignore, |
| 317 | discard: true, | 301 | ); |
| 318 | }).pipe( | 302 | const stdoutPump = yield* Effect.forkDetach(pump(child.stdout)); |
| 319 | Effect.andThen( | 303 | const stderrPump = yield* Effect.forkDetach(pump(child.stderr)); |
| 320 | Effect.sync(() => { | 304 | yield* Effect.forkDetach( |
| 321 | record.exitStatus = status; | 305 | child.exitCode.pipe( |
| 322 | }), | 306 | Effect.match({ |
| 323 | ), | 307 | onFailure: (error) => ({ exitCode: null, signal: terminalExitSignal(error) }), |
| 324 | Effect.andThen(Deferred.succeed(record.exit, status)), | 308 | onSuccess: (code) => ({ exitCode: Number(code), signal: null }), |
| 309 | }), | ||
| 310 | Effect.flatMap((status) => | ||
| 311 | Effect.all([Fiber.join(stdoutPump), Fiber.join(stderrPump)], { | ||
| 312 | discard: true, | ||
| 313 | }).pipe( | ||
| 314 | Effect.andThen( | ||
| 315 | Effect.sync(() => { | ||
| 316 | record.exitStatus = status; | ||
| 317 | }), | ||
| 325 | ), | 318 | ), |
| 319 | Effect.andThen(Deferred.succeed(record.exit, status)), | ||
| 326 | ), | 320 | ), |
| 327 | ), | 321 | ), |
| 328 | ); | ||
| 329 | return { terminalId }; | ||
| 330 | }), | ||
| 331 | ), | ||
| 332 | ); | ||
| 333 | |||
| 334 | const output: AcpClientTerminals["output"] = (request) => | ||
| 335 | requireTerminal(request.sessionId, request.terminalId, "terminal/output").pipe( | ||
| 336 | Effect.map((terminal) => ({ | ||
| 337 | output: readOutput(terminal.buffer), | ||
| 338 | truncated: terminal.buffer.truncated, | ||
| 339 | ...(terminal.exitStatus === undefined ? {} : { exitStatus: terminal.exitStatus }), | ||
| 340 | })), | ||
| 341 | ); | ||
| 342 | |||
| 343 | const waitForExit: AcpClientTerminals["waitForExit"] = (request) => | ||
| 344 | requireTerminal(request.sessionId, request.terminalId, "terminal/wait_for_exit").pipe( | ||
| 345 | Effect.flatMap((terminal) => Deferred.await(terminal.exit)), | ||
| 346 | ); | ||
| 347 | |||
| 348 | const kill: AcpClientTerminals["kill"] = (request) => | ||
| 349 | requireTerminal(request.sessionId, request.terminalId, "terminal/kill").pipe( | ||
| 350 | Effect.flatMap((terminal) => terminal.kill), | ||
| 351 | Effect.as({}), | ||
| 352 | ); | ||
| 353 | |||
| 354 | const release: AcpClientTerminals["release"] = (request) => | ||
| 355 | Effect.uninterruptible( | ||
| 356 | requireTerminal(request.sessionId, request.terminalId, "terminal/release").pipe( | ||
| 357 | Effect.flatMap((terminal) => | ||
| 358 | Effect.sync(() => { | ||
| 359 | terminal.released = true; | ||
| 360 | }).pipe( | ||
| 361 | Effect.andThen(terminal.dispose), | ||
| 362 | Effect.andThen(Deferred.await(terminal.exit)), | ||
| 363 | Effect.andThen(Effect.sync(pruneReleasedSnapshots)), | ||
| 364 | ), | 322 | ), |
| 365 | ), | 323 | ); |
| 366 | Effect.as({}), | 324 | return { terminalId }; |
| 367 | ), | 325 | }), |
| 368 | ); | 326 | ), |
| 327 | ); | ||
| 369 | 328 | ||
| 370 | const readOutputSnapshot: AcpClientTerminals["readOutputSnapshot"] = ( | 329 | const output: AcpClientTerminals["output"] = (request) => |
| 371 | sessionId, | 330 | requireTerminal(request.sessionId, request.terminalId, "terminal/output").pipe( |
| 372 | terminalId, | 331 | Effect.map((terminal) => ({ |
| 373 | ) => { | ||
| 374 | const terminal = terminals.get(terminalId); | ||
| 375 | if (terminal === undefined || terminal.sessionId !== sessionId) return undefined; | ||
| 376 | return { | ||
| 377 | output: readOutput(terminal.buffer), | 332 | output: readOutput(terminal.buffer), |
| 378 | truncated: terminal.buffer.truncated, | 333 | truncated: terminal.buffer.truncated, |
| 379 | exitStatus: terminal.exitStatus, | 334 | ...(terminal.exitStatus === undefined ? {} : { exitStatus: terminal.exitStatus }), |
| 380 | }; | 335 | })), |
| 381 | }; | 336 | ); |
| 382 | 337 | ||
| 383 | const readCommandLine: AcpClientTerminals["readCommandLine"] = (terminalId) => | 338 | const waitForExit: AcpClientTerminals["waitForExit"] = (request) => |
| 384 | terminals.get(terminalId)?.commandLine; | 339 | requireTerminal(request.sessionId, request.terminalId, "terminal/wait_for_exit").pipe( |
| 340 | Effect.flatMap((terminal) => Deferred.await(terminal.exit)), | ||
| 341 | ); | ||
| 385 | 342 | ||
| 386 | const disposeAll: AcpClientTerminals["disposeAll"] = Effect.uninterruptible( | 343 | const kill: AcpClientTerminals["kill"] = (request) => |
| 387 | Effect.suspend(() => | 344 | requireTerminal(request.sessionId, request.terminalId, "terminal/kill").pipe( |
| 388 | Effect.forEach( | 345 | Effect.flatMap((terminal) => terminal.kill), |
| 389 | Array.from(terminals.values()), | 346 | Effect.as({}), |
| 390 | (terminal) => | ||
| 391 | Effect.sync(() => { | ||
| 392 | terminal.released = true; | ||
| 393 | }).pipe( | ||
| 394 | Effect.andThen(terminal.dispose), | ||
| 395 | Effect.andThen(Deferred.await(terminal.exit)), | ||
| 396 | ), | ||
| 397 | { concurrency: "unbounded", discard: true }, | ||
| 398 | ).pipe(Effect.andThen(Effect.sync(pruneReleasedSnapshots))), | ||
| 399 | ), | ||
| 400 | ); | 347 | ); |
| 401 | 348 | ||
| 402 | // Terminal failures surface to the agent as JSON-RPC errors and to the | 349 | const release: AcpClientTerminals["release"] = (request) => |
| 403 | // user only as a failed tool item, so log them for server-side diagnosis. | 350 | Effect.uninterruptible( |
| 404 | const logged = | 351 | requireTerminal(request.sessionId, request.terminalId, "terminal/release").pipe( |
| 405 | <Request, Response>( | 352 | Effect.flatMap((terminal) => |
| 406 | operation: string, | 353 | Effect.sync(() => { |
| 407 | handler: (request: Request) => Effect.Effect<Response, EffectAcpErrors.AcpRequestError>, | 354 | terminal.released = true; |
| 408 | ) => | 355 | }).pipe( |
| 409 | (request: Request) => | 356 | Effect.andThen(terminal.dispose), |
| 410 | handler(request).pipe( | 357 | Effect.andThen(Deferred.await(terminal.exit)), |
| 411 | Effect.tapError((error) => | 358 | Effect.andThen(Effect.sync(pruneReleasedSnapshots)), |
| 412 | Effect.logWarning("ACP client terminal operation failed", { | ||
| 413 | operation, | ||
| 414 | detail: error.message, | ||
| 415 | }), | ||
| 416 | ), | 359 | ), |
| 417 | ); | 360 | ), |
| 361 | Effect.as({}), | ||
| 362 | ), | ||
| 363 | ); | ||
| 418 | 364 | ||
| 365 | const readOutputSnapshot: AcpClientTerminals["readOutputSnapshot"] = (sessionId, terminalId) => { | ||
| 366 | const terminal = terminals.get(terminalId); | ||
| 367 | if (terminal === undefined || terminal.sessionId !== sessionId) return undefined; | ||
| 419 | return { | 368 | return { |
| 420 | create: logged("terminal/create", create), | 369 | output: readOutput(terminal.buffer), |
| 421 | output: logged("terminal/output", output), | 370 | truncated: terminal.buffer.truncated, |
| 422 | waitForExit: logged("terminal/wait_for_exit", waitForExit), | 371 | exitStatus: terminal.exitStatus, |
| 423 | kill: logged("terminal/kill", kill), | ||
| 424 | release: logged("terminal/release", release), | ||
| 425 | readOutputSnapshot, | ||
| 426 | readCommandLine, | ||
| 427 | disposeAll, | ||
| 428 | }; | 372 | }; |
| 429 | }); | 373 | }; |
| 374 | |||
| 375 | const readCommandLine: AcpClientTerminals["readCommandLine"] = (terminalId) => | ||
| 376 | terminals.get(terminalId)?.commandLine; | ||
| 377 | |||
| 378 | const disposeAll: AcpClientTerminals["disposeAll"] = Effect.uninterruptible( | ||
| 379 | Effect.suspend(() => | ||
| 380 | Effect.forEach( | ||
| 381 | Array.from(terminals.values()), | ||
| 382 | (terminal) => | ||
| 383 | Effect.sync(() => { | ||
| 384 | terminal.released = true; | ||
| 385 | }).pipe(Effect.andThen(terminal.dispose), Effect.andThen(Deferred.await(terminal.exit))), | ||
| 386 | { concurrency: "unbounded", discard: true }, | ||
| 387 | ).pipe(Effect.andThen(Effect.sync(pruneReleasedSnapshots))), | ||
| 388 | ), | ||
| 389 | ); | ||
| 390 | |||
| 391 | // Terminal failures surface to the agent as JSON-RPC errors and to the | ||
| 392 | // user only as a failed tool item, so log them for server-side diagnosis. | ||
| 393 | const logged = | ||
| 394 | <Request, Response>( | ||
| 395 | operation: string, | ||
| 396 | handler: (request: Request) => Effect.Effect<Response, EffectAcpErrors.AcpRequestError>, | ||
| 397 | ) => | ||
| 398 | (request: Request) => | ||
| 399 | handler(request).pipe( | ||
| 400 | Effect.tapError((error) => | ||
| 401 | Effect.logWarning("ACP client terminal operation failed", { | ||
| 402 | operation, | ||
| 403 | detail: error.message, | ||
| 404 | }), | ||
| 405 | ), | ||
| 406 | ); | ||
| 407 | |||
| 408 | return { | ||
| 409 | create: logged("terminal/create", create), | ||
| 410 | output: logged("terminal/output", output), | ||
| 411 | waitForExit: logged("terminal/wait_for_exit", waitForExit), | ||
| 412 | kill: logged("terminal/kill", kill), | ||
| 413 | release: logged("terminal/release", release), | ||
| 414 | readOutputSnapshot, | ||
| 415 | readCommandLine, | ||
| 416 | disposeAll, | ||
| 417 | }; | ||
| 418 | }); | ||
| 430 | 419 | ||
| 431 | /** | 420 | /** |
| 432 | * Rewrites embedded `terminal` tool-call content into plain text content so | 421 | * Rewrites embedded `terminal` tool-call content into plain text content so |
packages/provider-grok/src/server/acpCliProbe.test.ts+5-12| ... | @@ -15,20 +15,15 @@ import * as Deferred from "effect/Deferred"; | ... | @@ -15,20 +15,15 @@ import * as Deferred from "effect/Deferred"; |
| 15 | import * as Fiber from "effect/Fiber"; | 15 | import * as Fiber from "effect/Fiber"; |
| 16 | import * as FileSystem from "effect/FileSystem"; | 16 | import * as FileSystem from "effect/FileSystem"; |
| 17 | import * as Stream from "effect/Stream"; | 17 | import * as Stream from "effect/Stream"; |
| 18 | import { ChildProcessSpawner } from "effect/process"; | ||
| 19 | import { describe, expect } from "vite-plus/test"; | 18 | import { describe, expect } from "vite-plus/test"; |
| 20 | 19 | ||
| 21 | import { makeGrokAcpRuntime } from "./acpSupport.ts"; | 20 | import { makeGrokAcpRuntime } from "./acpSupport.ts"; |
| 22 | 21 | ||
| 23 | const makeProbeRuntime = Effect.gen(function* () { | 22 | const makeProbeRuntime = makeGrokAcpRuntime({ |
| 24 | const childProcessSpawner = yield* ChildProcessSpawner.ChildProcessSpawner; | 23 | grokSettings: { binaryPath: "grok" }, |
| 25 | return yield* makeGrokAcpRuntime({ | 24 | environment: process.env, |
| 26 | grokSettings: { binaryPath: "grok" }, | 25 | cwd: process.cwd(), |
| 27 | environment: process.env, | 26 | clientInfo: { name: "t3-grok-probe", version: "0.0.0" }, |
| 28 | childProcessSpawner, | ||
| 29 | cwd: process.cwd(), | ||
| 30 | clientInfo: { name: "t3-grok-probe", version: "0.0.0" }, | ||
| 31 | }); | ||
| 32 | }); | 27 | }); |
| 33 | 28 | ||
| 34 | describe.runIf(process.env.T3_GROK_ACP_PROBE === "1")("Grok ACP CLI probe", () => { | 29 | describe.runIf(process.env.T3_GROK_ACP_PROBE === "1")("Grok ACP CLI probe", () => { |
| ... | @@ -78,11 +73,9 @@ describe.runIf(process.env.T3_GROK_ACP_PROBE === "1")("Grok ACP CLI probe", () = | ... | @@ -78,11 +73,9 @@ describe.runIf(process.env.T3_GROK_ACP_PROBE === "1")("Grok ACP CLI probe", () = |
| 78 | Effect.gen(function* () { | 73 | Effect.gen(function* () { |
| 79 | const fileSystem = yield* FileSystem.FileSystem; | 74 | const fileSystem = yield* FileSystem.FileSystem; |
| 80 | const cwd = yield* fileSystem.makeTempDirectoryScoped(); | 75 | const cwd = yield* fileSystem.makeTempDirectoryScoped(); |
| 81 | const childProcessSpawner = yield* ChildProcessSpawner.ChildProcessSpawner; | ||
| 82 | const runtime = yield* makeGrokAcpRuntime({ | 76 | const runtime = yield* makeGrokAcpRuntime({ |
| 83 | grokSettings: { binaryPath: "grok" }, | 77 | grokSettings: { binaryPath: "grok" }, |
| 84 | environment: process.env, | 78 | environment: process.env, |
| 85 | childProcessSpawner, | ||
| 86 | cwd, | 79 | cwd, |
| 87 | runtimeMode: "approval-required", | 80 | runtimeMode: "approval-required", |
| 88 | clientInfo: { name: "t3-grok-probe", version: "0.0.0" }, | 81 | clientInfo: { name: "t3-grok-probe", version: "0.0.0" }, |
packages/provider-grok/src/server/acpSupport.ts+2-7| ... | @@ -31,7 +31,6 @@ interface GrokAcpRuntimeInput extends Omit< | ... | @@ -31,7 +31,6 @@ interface GrokAcpRuntimeInput extends Omit< |
| 31 | AcpSessionRuntime.AcpSessionRuntimeOptions, | 31 | AcpSessionRuntime.AcpSessionRuntimeOptions, |
| 32 | "authMethodId" | "clientCapabilities" | "spawn" | 32 | "authMethodId" | "clientCapabilities" | "spawn" |
| 33 | > { | 33 | > { |
| 34 | readonly childProcessSpawner: ChildProcessSpawner.ChildProcessSpawner["Service"]; | ||
| 35 | readonly grokSettings: GrokAcpRuntimeGrokSettings | null | undefined; | 34 | readonly grokSettings: GrokAcpRuntimeGrokSettings | null | undefined; |
| 36 | readonly environment?: NodeJS.ProcessEnv; | 35 | readonly environment?: NodeJS.ProcessEnv; |
| 37 | readonly runtimeMode?: RuntimeMode; | 36 | readonly runtimeMode?: RuntimeMode; |
| ... | @@ -157,7 +156,7 @@ export const makeGrokAcpRuntime = ( | ... | @@ -157,7 +156,7 @@ export const makeGrokAcpRuntime = ( |
| 157 | ): Effect.Effect< | 156 | ): Effect.Effect< |
| 158 | AcpSessionRuntime.AcpSessionRuntime["Service"], | 157 | AcpSessionRuntime.AcpSessionRuntime["Service"], |
| 159 | EffectAcpErrors.AcpError, | 158 | EffectAcpErrors.AcpError, |
| 160 | Crypto.Crypto | Scope.Scope | 159 | ChildProcessSpawner.ChildProcessSpawner | Crypto.Crypto | Scope.Scope |
| 161 | > => | 160 | > => |
| 162 | Effect.gen(function* () { | 161 | Effect.gen(function* () { |
| 163 | const processGroupPlatform = yield* HostProcessPlatform.pipe( | 162 | const processGroupPlatform = yield* HostProcessPlatform.pipe( |
| ... | @@ -176,11 +175,7 @@ export const makeGrokAcpRuntime = ( | ... | @@ -176,11 +175,7 @@ export const makeGrokAcpRuntime = ( |
| 176 | cancelMeta: { ...input.cancelMeta, ...GROK_ACP_CANCEL_META }, | 175 | cancelMeta: { ...input.cancelMeta, ...GROK_ACP_CANCEL_META }, |
| 177 | initializeMeta: GROK_ACP_INITIALIZE_META, | 176 | initializeMeta: GROK_ACP_INITIALIZE_META, |
| 178 | ...grokAcpRuntimeProcessOwnership(processGroupPlatform), | 177 | ...grokAcpRuntimeProcessOwnership(processGroupPlatform), |
| 179 | }).pipe( | 178 | }), |
| 180 | Layer.provide( | ||
| 181 | Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, input.childProcessSpawner), | ||
| 182 | ), | ||
| 183 | ), | ||
| 184 | ); | 179 | ); |
| 185 | const runtime = yield* Effect.service(AcpSessionRuntime.AcpSessionRuntime).pipe( | 180 | const runtime = yield* Effect.service(AcpSessionRuntime.AcpSessionRuntime).pipe( |
| 186 | Effect.provide(acpContext), | 181 | Effect.provide(acpContext), |
packages/provider-grok/src/server/adapter.ts+6-12| ... | @@ -20,7 +20,7 @@ import * as Path from "effect/Path"; | ... | @@ -20,7 +20,7 @@ import * as Path from "effect/Path"; |
| 20 | import * as Layer from "effect/Layer"; | 20 | import * as Layer from "effect/Layer"; |
| 21 | import * as Schema from "effect/Schema"; | 21 | import * as Schema from "effect/Schema"; |
| 22 | import type * as Scope from "effect/Scope"; | 22 | import type * as Scope from "effect/Scope"; |
| 23 | import { ChildProcessSpawner } from "effect/process"; | 23 | import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner"; |
| 24 | import * as EffectAcpErrors from "effect-acp/errors"; | 24 | import * as EffectAcpErrors from "effect-acp/errors"; |
| 25 | 25 | ||
| 26 | import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions"; | 26 | import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions"; |
| ... | @@ -78,6 +78,7 @@ export const GROK_PROVIDER = ProviderDriverKind.make("grok"); | ... | @@ -78,6 +78,7 @@ export const GROK_PROVIDER = ProviderDriverKind.make("grok"); |
| 78 | const GROK_DRIVER_KIND = GROK_PROVIDER; | 78 | const GROK_DRIVER_KIND = GROK_PROVIDER; |
| 79 | export const GROK_DEFAULT_INSTANCE_ID = defaultInstanceIdForDriver(GROK_DRIVER_KIND); | 79 | export const GROK_DEFAULT_INSTANCE_ID = defaultInstanceIdForDriver(GROK_DRIVER_KIND); |
| 80 | const DEFAULT_GROK_SETTINGS = Schema.decodeSync(GrokSettings)({}); | 80 | const DEFAULT_GROK_SETTINGS = Schema.decodeSync(GrokSettings)({}); |
| 81 | const isAcpRequestError = Schema.is(EffectAcpErrors.AcpRequestError); | ||
| 81 | 82 | ||
| 82 | export const GrokProviderCapabilitiesV2 = { | 83 | export const GrokProviderCapabilitiesV2 = { |
| 83 | ...AcpProviderCapabilitiesV2, | 84 | ...AcpProviderCapabilitiesV2, |
| ... | @@ -122,7 +123,7 @@ export interface GrokAdapterV2Options { | ... | @@ -122,7 +123,7 @@ export interface GrokAdapterV2Options { |
| 122 | ) => Effect.Effect< | 123 | ) => Effect.Effect< |
| 123 | AcpSessionRuntime.AcpSessionRuntime["Service"], | 124 | AcpSessionRuntime.AcpSessionRuntime["Service"], |
| 124 | EffectAcpErrors.AcpError, | 125 | EffectAcpErrors.AcpError, |
| 125 | Crypto.Crypto | Scope.Scope | 126 | ChildProcessSpawner.ChildProcessSpawner | Crypto.Crypto | Scope.Scope |
| 126 | >; | 127 | >; |
| 127 | readonly assertComplete?: Effect.Effect<void, EffectAcpErrors.AcpError>; | 128 | readonly assertComplete?: Effect.Effect<void, EffectAcpErrors.AcpError>; |
| 128 | } | 129 | } |
| ... | @@ -226,11 +227,7 @@ export function grokLaunchRuntimeMode( | ... | @@ -226,11 +227,7 @@ export function grokLaunchRuntimeMode( |
| 226 | } | 227 | } |
| 227 | 228 | ||
| 228 | /** The flavor runs Grok's launcher through the adapter's spawner. */ | 229 | /** The flavor runs Grok's launcher through the adapter's spawner. */ |
| 229 | export function makeGrokAcpAdapterFlavor( | 230 | export function makeGrokAcpAdapterFlavor(options: GrokAdapterV2Options): AcpAdapterV2Flavor { |
| 230 | options: GrokAdapterV2Options & { | ||
| 231 | readonly childProcessSpawner: ChildProcessSpawner.ChildProcessSpawner["Service"]; | ||
| 232 | }, | ||
| 233 | ): AcpAdapterV2Flavor { | ||
| 234 | return { | 231 | return { |
| 235 | driver: GROK_PROVIDER, | 232 | driver: GROK_PROVIDER, |
| 236 | runtimeHarness: "Grok", | 233 | runtimeHarness: "Grok", |
| ... | @@ -281,7 +278,6 @@ export function makeGrokAcpAdapterFlavor( | ... | @@ -281,7 +278,6 @@ export function makeGrokAcpAdapterFlavor( |
| 281 | interruptPromptOnCancel: input.interruptPromptOnCancel ?? false, | 278 | interruptPromptOnCancel: input.interruptPromptOnCancel ?? false, |
| 282 | grokSettings: options.settings, | 279 | grokSettings: options.settings, |
| 283 | environment: options.environment, | 280 | environment: options.environment, |
| 284 | childProcessSpawner: options.childProcessSpawner, | ||
| 285 | runtimeMode: grokLaunchRuntimeMode(runtimePolicy), | 281 | runtimeMode: grokLaunchRuntimeMode(runtimePolicy), |
| 286 | })), | 282 | })), |
| 287 | // In its Auto mode Grok decides routine actions itself and only asks about | 283 | // In its Auto mode Grok decides routine actions itself and only asks about |
| ... | @@ -292,7 +288,7 @@ export function makeGrokAcpAdapterFlavor( | ... | @@ -292,7 +288,7 @@ export function makeGrokAcpAdapterFlavor( |
| 292 | promptFailure: (cause) => | 288 | promptFailure: (cause) => |
| 293 | makeProviderFailure({ | 289 | makeProviderFailure({ |
| 294 | cause, | 290 | cause, |
| 295 | ...(Schema.is(EffectAcpErrors.AcpRequestError)(cause) | 291 | ...(isAcpRequestError(cause) |
| 296 | ? { | 292 | ? { |
| 297 | // Grok's own failure text rides on the cause; makeProviderFailure | 293 | // Grok's own failure text rides on the cause; makeProviderFailure |
| 298 | // redacts and bounds it before it reaches the user. | 294 | // redacts and bounds it before it reaches the user. |
| ... | @@ -333,11 +329,9 @@ export function makeGrokAcpAdapterFlavor( | ... | @@ -333,11 +329,9 @@ export function makeGrokAcpAdapterFlavor( |
| 333 | export const makeGrokAdapterV2 = Effect.fn("makeGrokAdapterV2")(function* ( | 329 | export const makeGrokAdapterV2 = Effect.fn("makeGrokAdapterV2")(function* ( |
| 334 | options: GrokAdapterV2Options, | 330 | options: GrokAdapterV2Options, |
| 335 | ) { | 331 | ) { |
| 336 | const childProcessSpawner = yield* ChildProcessSpawner.ChildProcessSpawner; | ||
| 337 | const flavor = makeGrokAcpAdapterFlavor({ ...options, childProcessSpawner }); | ||
| 338 | return yield* makeAcpAdapterV2({ | 332 | return yield* makeAcpAdapterV2({ |
| 339 | instanceId: options.instanceId, | 333 | instanceId: options.instanceId, |
| 340 | flavor, | 334 | flavor: makeGrokAcpAdapterFlavor(options), |
| 341 | selfInvocation: options.selfInvocation, | 335 | selfInvocation: options.selfInvocation, |
| 342 | ...(options.nativeLogging === undefined ? {} : { nativeLogging: options.nativeLogging }), | 336 | ...(options.nativeLogging === undefined ? {} : { nativeLogging: options.nativeLogging }), |
| 343 | ...(options.continuationRequests === undefined | 337 | ...(options.continuationRequests === undefined |
packages/provider-grok/src/server/driver.test.ts+1-1| ... | @@ -6,7 +6,7 @@ import * as Effect from "effect/Effect"; | ... | @@ -6,7 +6,7 @@ import * as Effect from "effect/Effect"; |
| 6 | import * as FileSystem from "effect/FileSystem"; | 6 | import * as FileSystem from "effect/FileSystem"; |
| 7 | import * as Layer from "effect/Layer"; | 7 | import * as Layer from "effect/Layer"; |
| 8 | import * as Path from "effect/Path"; | 8 | import * as Path from "effect/Path"; |
| 9 | import { HttpClient } from "effect/http"; | 9 | import * as HttpClient from "effect/http/HttpClient"; |
| 10 | import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner"; | 10 | import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner"; |
| 11 | 11 | ||
| 12 | import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions"; | 12 | import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions"; |
packages/provider-grok/src/server/driver.ts+3-3| ... | @@ -5,8 +5,8 @@ import * as Effect from "effect/Effect"; | ... | @@ -5,8 +5,8 @@ import * as Effect from "effect/Effect"; |
| 5 | import * as FileSystem from "effect/FileSystem"; | 5 | import * as FileSystem from "effect/FileSystem"; |
| 6 | import * as Path from "effect/Path"; | 6 | import * as Path from "effect/Path"; |
| 7 | import * as Schema from "effect/Schema"; | 7 | import * as Schema from "effect/Schema"; |
| 8 | import { HttpClient } from "effect/http"; | 8 | import * as HttpClient from "effect/http/HttpClient"; |
| 9 | import { ChildProcessSpawner } from "effect/process"; | 9 | import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner"; |
| 10 | 10 | ||
| 11 | import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions"; | 11 | import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions"; |
| 12 | import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost"; | 12 | import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost"; |
| ... | @@ -191,7 +191,7 @@ export const GrokDriver: ProviderDriver<GrokSettings, GrokDriverEnv> = { | ... | @@ -191,7 +191,7 @@ export const GrokDriver: ProviderDriver<GrokSettings, GrokDriverEnv> = { |
| 191 | new ProviderDriverError({ | 191 | new ProviderDriverError({ |
| 192 | driver: DRIVER_KIND, | 192 | driver: DRIVER_KIND, |
| 193 | instanceId, | 193 | instanceId, |
| 194 | detail: `Failed to build Grok snapshot: ${cause.message ?? String(cause)}`, | 194 | detail: "Failed to build Grok snapshot.", |
| 195 | cause, | 195 | cause, |
| 196 | }), | 196 | }), |
| 197 | ), | 197 | ), |
packages/provider-grok/src/server/skills.test.ts+1-1| ... | @@ -2,7 +2,7 @@ import { describe, expect, it } from "@effect/vitest"; | ... | @@ -2,7 +2,7 @@ import { describe, expect, it } from "@effect/vitest"; |
| 2 | import * as Effect from "effect/Effect"; | 2 | import * as Effect from "effect/Effect"; |
| 3 | import * as Sink from "effect/Sink"; | 3 | import * as Sink from "effect/Sink"; |
| 4 | import * as Stream from "effect/Stream"; | 4 | import * as Stream from "effect/Stream"; |
| 5 | import { ChildProcessSpawner } from "effect/process"; | 5 | import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner"; |
| 6 | 6 | ||
| 7 | import { discoverGrokSkills } from "./skills.ts"; | 7 | import { discoverGrokSkills } from "./skills.ts"; |
| 8 | 8 |
packages/provider-grok/src/server/skills.ts+1-1| ... | @@ -20,7 +20,7 @@ import type { GrokSettings } from "../settings.ts"; | ... | @@ -20,7 +20,7 @@ import type { GrokSettings } from "../settings.ts"; |
| 20 | import * as Effect from "effect/Effect"; | 20 | import * as Effect from "effect/Effect"; |
| 21 | import * as Option from "effect/Option"; | 21 | import * as Option from "effect/Option"; |
| 22 | import * as Schema from "effect/Schema"; | 22 | import * as Schema from "effect/Schema"; |
| 23 | import { ChildProcess } from "effect/process"; | 23 | import * as ChildProcess from "effect/process/ChildProcess"; |
| 24 | import { resolveSpawnCommand } from "@t3tools/shared/shell"; | 24 | import { resolveSpawnCommand } from "@t3tools/shared/shell"; |
| 25 | 25 | ||
| 26 | import { spawnAndCollect } from "@t3tools/provider-core/server/snapshotProbe"; | 26 | import { spawnAndCollect } from "@t3tools/provider-core/server/snapshotProbe"; |
packages/provider-grok/src/server/status.ts+3-4| ... | @@ -16,8 +16,9 @@ import * as Exit from "effect/Exit"; | ... | @@ -16,8 +16,9 @@ import * as Exit from "effect/Exit"; |
| 16 | import * as Option from "effect/Option"; | 16 | import * as Option from "effect/Option"; |
| 17 | import * as Result from "effect/Result"; | 17 | import * as Result from "effect/Result"; |
| 18 | import * as Schema from "effect/Schema"; | 18 | import * as Schema from "effect/Schema"; |
| 19 | import { HttpClient } from "effect/http"; | 19 | import * as HttpClient from "effect/http/HttpClient"; |
| 20 | import { ChildProcess, ChildProcessSpawner } from "effect/process"; | 20 | import * as ChildProcess from "effect/process/ChildProcess"; |
| 21 | import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner"; | ||
| 21 | import { createModelCapabilities } from "@t3tools/shared/model"; | 22 | import { createModelCapabilities } from "@t3tools/shared/model"; |
| 22 | import { resolveSpawnCommand } from "@t3tools/shared/shell"; | 23 | import { resolveSpawnCommand } from "@t3tools/shared/shell"; |
| 23 | 24 | ||
| ... | @@ -356,11 +357,9 @@ const discoverGrokMetadataViaAcpInitialize = ( | ... | @@ -356,11 +357,9 @@ const discoverGrokMetadataViaAcpInitialize = ( |
| 356 | environment: NodeJS.ProcessEnv, | 357 | environment: NodeJS.ProcessEnv, |
| 357 | ) => | 358 | ) => |
| 358 | Effect.gen(function* () { | 359 | Effect.gen(function* () { |
| 359 | const childProcessSpawner = yield* ChildProcessSpawner.ChildProcessSpawner; | ||
| 360 | const acp = yield* makeGrokAcpRuntime({ | 360 | const acp = yield* makeGrokAcpRuntime({ |
| 361 | grokSettings, | 361 | grokSettings, |
| 362 | environment, | 362 | environment, |
| 363 | childProcessSpawner, | ||
| 364 | cwd: process.cwd(), | 363 | cwd: process.cwd(), |
| 365 | clientInfo: { name: "t3-code-provider-probe", version: "0.0.0" }, | 364 | clientInfo: { name: "t3-code-provider-probe", version: "0.0.0" }, |
| 366 | }); | 365 | }); |
packages/provider-grok/src/server/textGeneration.ts+5-3| ... | @@ -3,7 +3,7 @@ import * as Effect from "effect/Effect"; | ... | @@ -3,7 +3,7 @@ import * as Effect from "effect/Effect"; |
| 3 | import * as Option from "effect/Option"; | 3 | import * as Option from "effect/Option"; |
| 4 | import * as Ref from "effect/Ref"; | 4 | import * as Ref from "effect/Ref"; |
| 5 | import * as Schema from "effect/Schema"; | 5 | import * as Schema from "effect/Schema"; |
| 6 | import { ChildProcessSpawner } from "effect/process"; | 6 | import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner"; |
| 7 | import type * as EffectAcpErrors from "effect-acp/errors"; | 7 | import type * as EffectAcpErrors from "effect-acp/errors"; |
| 8 | 8 | ||
| 9 | import { TextGenerationError } from "@t3tools/contracts"; | 9 | import { TextGenerationError } from "@t3tools/contracts"; |
| ... | @@ -37,10 +37,12 @@ export const makeGrokTextGeneration = Effect.fn("makeGrokTextGeneration")(functi | ... | @@ -37,10 +37,12 @@ export const makeGrokTextGeneration = Effect.fn("makeGrokTextGeneration")(functi |
| 37 | const runtime = yield* makeGrokAcpRuntime({ | 37 | const runtime = yield* makeGrokAcpRuntime({ |
| 38 | grokSettings, | 38 | grokSettings, |
| 39 | environment, | 39 | environment, |
| 40 | childProcessSpawner: commandSpawner, | ||
| 41 | cwd, | 40 | cwd, |
| 42 | clientInfo: { name: "t3-code-git-text", version: "0.0.0" }, | 41 | clientInfo: { name: "t3-code-git-text", version: "0.0.0" }, |
| 43 | }).pipe(Effect.provideService(Crypto.Crypto, crypto)); | 42 | }).pipe( |
| 43 | Effect.provideService(Crypto.Crypto, crypto), | ||
| 44 | Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, commandSpawner), | ||
| 45 | ); | ||
| 44 | 46 | ||
| 45 | yield* runtime.handleSessionUpdate((notification) => { | 47 | yield* runtime.handleSessionUpdate((notification) => { |
| 46 | const update = notification.update; | 48 | const update = notification.update; |
packages/provider-grok/src/server/usageLimits.ts+3-1| ... | @@ -6,7 +6,9 @@ import * as FileSystem from "effect/FileSystem"; | ... | @@ -6,7 +6,9 @@ import * as FileSystem from "effect/FileSystem"; |
| 6 | import * as Option from "effect/Option"; | 6 | import * as Option from "effect/Option"; |
| 7 | import * as Path from "effect/Path"; | 7 | import * as Path from "effect/Path"; |
| 8 | import * as Schema from "effect/Schema"; | 8 | import * as Schema from "effect/Schema"; |
| 9 | import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/http"; | 9 | import * as HttpClient from "effect/http/HttpClient"; |
| 10 | import * as HttpClientRequest from "effect/http/HttpClientRequest"; | ||
| 11 | import * as HttpClientResponse from "effect/http/HttpClientResponse"; | ||
| 10 | import { | 12 | import { |
| 11 | clampPercent, | 13 | clampPercent, |
| 12 | makeUnavailableUsageLimits, | 14 | makeUnavailableUsageLimits, |