Unified · Split
| @@ -33,17 +33,13 @@ import { makeSqlitePersistenceLive } from "../src/persistence/Layers/Sqlite.ts"; · 32 unmodified lines | ||
| 33 | 33 | import { ProjectionCheckpointRepository } from "../src/persistence/Services/ProjectionCheckpoints.ts"; |
| 34 | 34 | import { ProjectionPendingApprovalRepository } from "../src/persistence/Services/ProjectionPendingApprovals.ts"; |
| 35 | 35 | import { makeAdapterRegistryMock } from "../src/provider/testUtils/providerAdapterRegistryMock.ts"; |
| 36 | −import { ProviderAdapterRegistry } from "../src/provider/Services/ProviderAdapterRegistry.ts"; | |
| 36 | +import * as ProviderAdapterRegistry from "../src/provider/ProviderAdapterRegistry.ts"; | |
| 37 | 37 | import { makeProviderRegistryLayer } from "../src/provider/testUtils/providerRegistryMock.ts"; |
| 38 | −import { ProviderSessionDirectoryLive } from "../src/provider/Layers/ProviderSessionDirectory.ts"; | |
| 38 | +import * as ProviderSessionDirectory from "../src/provider/ProviderSessionDirectory.ts"; | |
| 39 | 39 | import { ServerSettingsService } from "../src/serverSettings.ts"; |
| 40 | −import { makeProviderServiceLive } from "../src/provider/Layers/ProviderService.ts"; | |
| 40 | +import * as ProviderService from "../src/provider/ProviderService.ts"; | |
| 41 | 41 | import { makeCodexAdapter } from "../src/provider/Layers/CodexAdapter.ts"; |
| 42 | −import { | |
| 43 | − NoOpProviderEventLoggers, | |
| 44 | − ProviderEventLoggers, | |
| 45 | −} from "../src/provider/Layers/ProviderEventLoggers.ts"; | |
| 46 | −import { ProviderService } from "../src/provider/Services/ProviderService.ts"; | |
| 42 | +import * as ProviderEventLoggers from "../src/provider/ProviderEventLoggers.ts"; | |
| 47 | 43 | import { AnalyticsService } from "../src/telemetry/Services/AnalyticsService.ts"; |
| 48 | 44 | import { CheckpointReactorLive } from "../src/orchestration/Layers/CheckpointReactor.ts"; |
| 49 | 45 | import * as RepositoryIdentityResolver from "../src/project/RepositoryIdentityResolver.ts"; |
| @@ -178,7 +174,7 @@ export interface OrchestrationIntegrationHarness { · 128 unmodified lines | ||
| 178 | 174 | readonly adapterHarness: TestProviderAdapterHarness | null; |
| 179 | 175 | readonly engine: OrchestrationEngineShape; |
| 180 | 176 | readonly snapshotQuery: ProjectionSnapshotQuery["Service"]; |
| 181 | − readonly providerService: ProviderService["Service"]; | |
| 177 | + readonly providerService: ProviderService.ProviderService["Service"]; | |
| 182 | 178 | readonly checkpointStore: CheckpointStore.CheckpointStore["Service"]; |
| 183 | 179 | readonly checkpointRepository: ProjectionCheckpointRepository["Service"]; |
| 184 | 180 | readonly pendingApprovalRepository: ProjectionPendingApprovalRepository["Service"]; |
| @@ -241,7 +237,7 @@ export const makeOrchestrationIntegrationHarness = ( · 56 unmodified lines | ||
| 241 | 237 | }); |
| 242 | 238 | const fakeRegistry = adapterHarness |
| 243 | 239 | ? Layer.succeed( |
| 244 | − ProviderAdapterRegistry, | |
| 240 | + ProviderAdapterRegistry.ProviderAdapterRegistry, | |
| 245 | 241 | makeAdapterRegistryMock({ [adapterHarness.provider]: adapterHarness.adapter }), |
| 246 | 242 | ) |
| 247 | 243 | : null; |
| @@ -262,11 +258,11 @@ export const makeOrchestrationIntegrationHarness = ( · 14 unmodified lines | ||
| 262 | 258 | Layer.provide(OrchestrationEventStoreLive), |
| 263 | 259 | Layer.provide(OrchestrationCommandReceiptRepositoryLive), |
| 264 | 260 | ); |
| 265 | − const providerSessionDirectoryLayer = ProviderSessionDirectoryLive.pipe( | |
| 261 | + const providerSessionDirectoryLayer = ProviderSessionDirectory.layer.pipe( | |
| 266 | 262 | Layer.provide(ProviderSessionRuntimeRepositoryLive), |
| 267 | 263 | ); |
| 268 | 264 | const realCodexRegistry = Layer.effect( |
| 269 | − ProviderAdapterRegistry, | |
| 265 | + ProviderAdapterRegistry.ProviderAdapterRegistry, | |
| 270 | 266 | Effect.gen(function* () { |
| 271 | 267 | const codexSettings = yield* decodeCodexSettings({}); |
| 272 | 268 | const codexAdapter = yield* makeCodexAdapter(codexSettings); |
| @@ -279,15 +275,18 @@ export const makeOrchestrationIntegrationHarness = ( · 6 unmodified lines | ||
| 279 | 275 | Layer.provideMerge(NodeServices.layer), |
| 280 | 276 | Layer.provideMerge(providerSessionDirectoryLayer), |
| 281 | 277 | ); |
| 282 | − const providerEventLoggersLayer = Layer.succeed(ProviderEventLoggers, NoOpProviderEventLoggers); | |
| 278 | + const providerEventLoggersLayer = Layer.succeed( | |
| 279 | + ProviderEventLoggers.ProviderEventLoggers, | |
| 280 | + ProviderEventLoggers.NoOpProviderEventLoggers, | |
| 281 | + ); | |
| 283 | 282 | const providerLayer = useRealCodex |
| 284 | − ? makeProviderServiceLive().pipe( | |
| 283 | + ? ProviderService.layer.pipe( | |
| 285 | 284 | Layer.provide(providerSessionDirectoryLayer), |
| 286 | 285 | Layer.provide(realCodexRegistry), |
| 287 | 286 | Layer.provide(AnalyticsService.layerTest), |
| 288 | 287 | Layer.provide(providerEventLoggersLayer), |
| 289 | 288 | ) |
| 290 | − : makeProviderServiceLive().pipe( | |
| 289 | + : ProviderService.layer.pipe( | |
| 291 | 290 | Layer.provide(providerSessionDirectoryLayer), |
| 292 | 291 | Layer.provide(fakeRegistry!), |
| 293 | 292 | Layer.provide(AnalyticsService.layerTest), |
| @@ -395,7 +394,7 @@ export const makeOrchestrationIntegrationHarness = ( · 101 unmodified lines | ||
| 395 | 394 | runtime.runPromise(Effect.service(ProjectionSnapshotQuery)), |
| 396 | 395 | ).pipe(Effect.orDie); |
| 397 | 396 | const providerService = yield* tryRuntimePromise("load ProviderService service", () => |
| 398 | − runtime.runPromise(Effect.service(ProviderService)), | |
| 397 | + runtime.runPromise(Effect.service(ProviderService.ProviderService)), | |
| 399 | 398 | ).pipe(Effect.orDie); |
| 400 | 399 | const checkpointStore = yield* tryRuntimePromise("load CheckpointStore service", () => |
| 401 | 400 | runtime.runPromise(Effect.service(CheckpointStore.CheckpointStore)), |
| @@ -70,9 +70,7 @@ const makeIntegrationFixture = Effect.gen(function* () { · 69 unmodified lines | ||
| 70 | 70 | ), |
| 71 | 71 | ).pipe(Layer.provide(SqlitePersistenceMemory)); |
| 72 | 72 | |
| 73 | − const layer = Layer.effect(ProviderService.ProviderService, ProviderService.make()).pipe( | |
| 74 | − Layer.provide(shared), | |
| 75 | − ); | |
| 73 | + const layer = ProviderService.layer.pipe(Layer.provide(shared)); | |
| 76 | 74 | |
| 77 | 75 | return { |
| 78 | 76 | cwd, |
| @@ -49,10 +49,7 @@ import { · 48 unmodified lines | ||
| 49 | 49 | } from "../Services/OrchestrationEngine.ts"; |
| 50 | 50 | import { CheckpointReactor } from "../Services/CheckpointReactor.ts"; |
| 51 | 51 | import { ProjectionSnapshotQuery } from "../Services/ProjectionSnapshotQuery.ts"; |
| 52 | −import { | |
| 53 | − ProviderService, | |
| 54 | − type ProviderServiceShape, | |
| 55 | −} from "../../provider/Services/ProviderService.ts"; | |
| 52 | +import * as ProviderService from "../../provider/ProviderService.ts"; | |
| 56 | 53 | import { checkpointRefForThreadTurn } from "../../checkpointing/Utils.ts"; |
| 57 | 54 | import { ServerConfig } from "../../config.ts"; |
| 58 | 55 | import * as WorkspaceEntries from "../../workspace/WorkspaceEntries.ts"; |
| @@ -102,7 +99,7 @@ function createProviderServiceHarness( · 43 unmodified lines | ||
| 102 | 99 | }, |
| 103 | 100 | ] satisfies ReadonlyArray<ProviderSession>) |
| 104 | 101 | : Effect.succeed([] as ReadonlyArray<ProviderSession>); |
| 105 | − const service: ProviderServiceShape = { | |
| 102 | + const service: ProviderService.ProviderService["Service"] = { | |
| 106 | 103 | startSession: () => unsupported(), |
| 107 | 104 | sendTurn: () => unsupported(), |
| 108 | 105 | interruptTurn: () => unsupported(), |
| @@ -328,7 +325,7 @@ describe("CheckpointReactor", () => { · 219 unmodified lines | ||
| 328 | 325 | Layer.provideMerge(orchestrationLayer), |
| 329 | 326 | Layer.provideMerge(projectionSnapshotLayer), |
| 330 | 327 | Layer.provideMerge(RuntimeReceiptBusLive), |
| 331 | − Layer.provideMerge(Layer.succeed(ProviderService, provider.service)), | |
| 328 | + Layer.provideMerge(Layer.succeed(ProviderService.ProviderService, provider.service)), | |
| 332 | 329 | Layer.provideMerge(vcsStatusBroadcasterLayer), |
| 333 | 330 | Layer.provideMerge(CheckpointStore.layer.pipe(Layer.provide(VcsDriverRegistry.layer))), |
| 334 | 331 | Layer.provideMerge( |
| @@ -25,7 +25,7 @@ import { · 24 unmodified lines | ||
| 25 | 25 | resolveThreadWorkspaceCwd, |
| 26 | 26 | } from "../../checkpointing/Utils.ts"; |
| 27 | 27 | import * as CheckpointStore from "../../checkpointing/CheckpointStore.ts"; |
| 28 | −import { ProviderService } from "../../provider/Services/ProviderService.ts"; | |
| 28 | +import * as ProviderService from "../../provider/ProviderService.ts"; | |
| 29 | 29 | import { CheckpointReactor, type CheckpointReactorShape } from "../Services/CheckpointReactor.ts"; |
| 30 | 30 | import { OrchestrationEngineService } from "../Services/OrchestrationEngine.ts"; |
| 31 | 31 | import { ProjectionSnapshotQuery } from "../Services/ProjectionSnapshotQuery.ts"; |
| @@ -80,7 +80,7 @@ const make = Effect.gen(function* () { · 48 unmodified lines | ||
| 80 | 80 | randomUUID.pipe(Effect.map((uuid) => CommandId.make(`server:${tag}:${uuid}`))); |
| 81 | 81 | const orchestrationEngine = yield* OrchestrationEngineService; |
| 82 | 82 | const projectionSnapshotQuery = yield* ProjectionSnapshotQuery; |
| 83 | − const providerService = yield* ProviderService; | |
| 83 | + const providerService = yield* ProviderService.ProviderService; | |
| 84 | 84 | const checkpointStore = yield* CheckpointStore.CheckpointStore; |
| 85 | 85 | const receiptBus = yield* RuntimeReceiptBus; |
| 86 | 86 | const workspaceEntries = yield* WorkspaceEntries.WorkspaceEntries; |
| @@ -37,10 +37,7 @@ import { ProviderAdapterRequestError } from "../../provider/Errors.ts"; · 36 unmodified lines | ||
| 37 | 37 | import { OrchestrationEventStoreLive } from "../../persistence/Layers/OrchestrationEventStore.ts"; |
| 38 | 38 | import { OrchestrationCommandReceiptRepositoryLive } from "../../persistence/Layers/OrchestrationCommandReceipts.ts"; |
| 39 | 39 | import { SqlitePersistenceMemory } from "../../persistence/Layers/Sqlite.ts"; |
| 40 | −import { | |
| 41 | − ProviderService, | |
| 42 | − type ProviderServiceShape, | |
| 43 | −} from "../../provider/Services/ProviderService.ts"; | |
| 40 | +import * as ProviderService from "../../provider/ProviderService.ts"; | |
| 44 | 41 | import { makeProviderRegistryLayer } from "../../provider/testUtils/providerRegistryMock.ts"; |
| 45 | 42 | import { TextGeneration, type TextGenerationShape } from "../../textGeneration/TextGeneration.ts"; |
| 46 | 43 | import * as RepositoryIdentityResolver from "../../project/RepositoryIdentityResolver.ts"; |
| @@ -222,8 +219,12 @@ describe("ProviderCommandReactor", () => { · 175 unmodified lines | ||
| 222 | 219 | }), |
| 223 | 220 | ); |
| 224 | 221 | const interruptTurn = vi.fn((_: unknown) => Effect.void); |
| 225 | − const respondToRequest = vi.fn<ProviderServiceShape["respondToRequest"]>(() => Effect.void); | |
| 226 | − const respondToUserInput = vi.fn<ProviderServiceShape["respondToUserInput"]>(() => Effect.void); | |
| 222 | + const respondToRequest = vi.fn<ProviderService.ProviderService["Service"]["respondToRequest"]>( | |
| 223 | + () => Effect.void, | |
| 224 | + ); | |
| 225 | + const respondToUserInput = vi.fn< | |
| 226 | + ProviderService.ProviderService["Service"]["respondToUserInput"] | |
| 227 | + >(() => Effect.void); | |
| 227 | 228 | const stopSession = vi.fn((input: unknown) => |
| 228 | 229 | Effect.sync(() => { |
| 229 | 230 | const threadId = |
| @@ -294,13 +295,15 @@ describe("ProviderCommandReactor", () => { · 64 unmodified lines | ||
| 294 | 295 | ]; |
| 295 | 296 | |
| 296 | 297 | const unsupported = () => Effect.die(new Error("Unsupported provider call in test")) as never; |
| 297 | − const service: ProviderServiceShape = { | |
| 298 | − startSession: startSession as ProviderServiceShape["startSession"], | |
| 299 | − sendTurn: sendTurn as ProviderServiceShape["sendTurn"], | |
| 300 | − interruptTurn: interruptTurn as ProviderServiceShape["interruptTurn"], | |
| 301 | − respondToRequest: respondToRequest as ProviderServiceShape["respondToRequest"], | |
| 302 | − respondToUserInput: respondToUserInput as ProviderServiceShape["respondToUserInput"], | |
| 303 | − stopSession: stopSession as ProviderServiceShape["stopSession"], | |
| 298 | + const service: ProviderService.ProviderService["Service"] = { | |
| 299 | + startSession: startSession as ProviderService.ProviderService["Service"]["startSession"], | |
| 300 | + sendTurn: sendTurn as ProviderService.ProviderService["Service"]["sendTurn"], | |
| 301 | + interruptTurn: interruptTurn as ProviderService.ProviderService["Service"]["interruptTurn"], | |
| 302 | + respondToRequest: | |
| 303 | + respondToRequest as ProviderService.ProviderService["Service"]["respondToRequest"], | |
| 304 | + respondToUserInput: | |
| 305 | + respondToUserInput as ProviderService.ProviderService["Service"]["respondToUserInput"], | |
| 306 | + stopSession: stopSession as ProviderService.ProviderService["Service"]["stopSession"], | |
| 304 | 307 | listSessions: () => Effect.succeed(runtimeSessions), |
| 305 | 308 | getCapabilities: (_provider) => |
| 306 | 309 | Effect.succeed({ |
| @@ -346,7 +349,7 @@ describe("ProviderCommandReactor", () => { · 39 unmodified lines | ||
| 346 | 349 | const layer = ProviderCommandReactorLive.pipe( |
| 347 | 350 | Layer.provideMerge(orchestrationLayer), |
| 348 | 351 | Layer.provideMerge(projectionSnapshotLayer), |
| 349 | − Layer.provideMerge(Layer.succeed(ProviderService, service)), | |
| 352 | + Layer.provideMerge(Layer.succeed(ProviderService.ProviderService, service)), | |
| 350 | 353 | Layer.provideMerge(makeProviderRegistryLayer(providerSnapshots as never)), |
| 351 | 354 | Layer.provideMerge( |
| 352 | 355 | Layer.mock(GitWorkflowService.GitWorkflowService)({ |
| @@ -30,8 +30,8 @@ import { increment, orchestrationEventsProcessedTotal } from "../../observabilit · 29 unmodified lines | ||
| 30 | 30 | import { ProviderAdapterRequestError } from "../../provider/Errors.ts"; |
| 31 | 31 | import type { ProviderServiceError } from "../../provider/Errors.ts"; |
| 32 | 32 | import { TextGeneration } from "../../textGeneration/TextGeneration.ts"; |
| 33 | −import { ProviderService } from "../../provider/Services/ProviderService.ts"; | |
| 34 | −import { ProviderRegistry } from "../../provider/Services/ProviderRegistry.ts"; | |
| 33 | +import * as ProviderService from "../../provider/ProviderService.ts"; | |
| 34 | +import * as ProviderRegistry from "../../provider/ProviderRegistry.ts"; | |
| 35 | 35 | import { OrchestrationEngineService } from "../Services/OrchestrationEngine.ts"; |
| 36 | 36 | import { ProjectionSnapshotQuery } from "../Services/ProjectionSnapshotQuery.ts"; |
| 37 | 37 | import { |
| @@ -190,8 +190,8 @@ const make = Effect.gen(function* () { · 152 unmodified lines | ||
| 190 | 190 | const crypto = yield* Crypto.Crypto; |
| 191 | 191 | const orchestrationEngine = yield* OrchestrationEngineService; |
| 192 | 192 | const projectionSnapshotQuery = yield* ProjectionSnapshotQuery; |
| 193 | − const providerService = yield* ProviderService; | |
| 194 | − const providerRegistry = yield* ProviderRegistry; | |
| 193 | + const providerService = yield* ProviderService.ProviderService; | |
| 194 | + const providerRegistry = yield* ProviderRegistry.ProviderRegistry; | |
| 195 | 195 | const gitWorkflow = yield* GitWorkflowService; |
| 196 | 196 | const vcsStatusBroadcaster = yield* VcsStatusBroadcaster; |
| 197 | 197 | const textGeneration = yield* TextGeneration; |
| @@ -35,10 +35,7 @@ import { afterEach, describe, expect, it } from "vite-plus/test"; · 34 unmodified lines | ||
| 35 | 35 | import { OrchestrationEventStoreLive } from "../../persistence/Layers/OrchestrationEventStore.ts"; |
| 36 | 36 | import { OrchestrationCommandReceiptRepositoryLive } from "../../persistence/Layers/OrchestrationCommandReceipts.ts"; |
| 37 | 37 | import { SqlitePersistenceMemory } from "../../persistence/Layers/Sqlite.ts"; |
| 38 | −import { | |
| 39 | − ProviderService, | |
| 40 | − type ProviderServiceShape, | |
| 41 | −} from "../../provider/Services/ProviderService.ts"; | |
| 38 | +import * as ProviderService from "../../provider/ProviderService.ts"; | |
| 42 | 39 | import * as RepositoryIdentityResolver from "../../project/RepositoryIdentityResolver.ts"; |
| 43 | 40 | import { OrchestrationEngineLive } from "./OrchestrationEngine.ts"; |
| 44 | 41 | import { OrchestrationProjectionPipelineLive } from "./ProjectionPipeline.ts"; |
| @@ -97,7 +94,7 @@ function createProviderServiceHarness() { · 52 unmodified lines | ||
| 97 | 94 | const runtimeSessions: ProviderSession[] = []; |
| 98 | 95 | |
| 99 | 96 | const unsupported = () => Effect.die(new Error("Unsupported provider call in test")) as never; |
| 100 | − const service: ProviderServiceShape = { | |
| 97 | + const service: ProviderService.ProviderService["Service"] = { | |
| 101 | 98 | startSession: () => unsupported(), |
| 102 | 99 | sendTurn: () => unsupported(), |
| 103 | 100 | interruptTurn: () => unsupported(), |
| @@ -237,7 +234,7 @@ describe("ProviderRuntimeIngestion", () => { · 133 unmodified lines | ||
| 237 | 234 | Layer.provideMerge(orchestrationLayer), |
| 238 | 235 | Layer.provideMerge(projectionSnapshotLayer), |
| 239 | 236 | Layer.provideMerge(SqlitePersistenceMemory), |
| 240 | − Layer.provideMerge(Layer.succeed(ProviderService, provider.service)), | |
| 237 | + Layer.provideMerge(Layer.succeed(ProviderService.ProviderService, provider.service)), | |
| 241 | 238 | Layer.provideMerge(makeTestServerSettingsLayer(options?.serverSettings)), |
| 242 | 239 | Layer.provideMerge(ServerConfig.layerTest(process.cwd(), process.cwd())), |
| 243 | 240 | Layer.provideMerge(NodeServices.layer), |
| @@ -27,7 +27,7 @@ import * as Option from "effect/Option"; · 26 unmodified lines | ||
| 27 | 27 | import * as Stream from "effect/Stream"; |
| 28 | 28 | import { makeDrainableWorker } from "@t3tools/shared/DrainableWorker"; |
| 29 | 29 | |
| 30 | −import { ProviderService } from "../../provider/Services/ProviderService.ts"; | |
| 30 | +import * as ProviderService from "../../provider/ProviderService.ts"; | |
| 31 | 31 | import { ProjectionTurnRepository } from "../../persistence/Services/ProjectionTurns.ts"; |
| 32 | 32 | import { ProjectionTurnRepositoryLive } from "../../persistence/Layers/ProjectionTurns.ts"; |
| 33 | 33 | import { isGitRepository } from "../../git/Utils.ts"; |
| @@ -631,7 +631,7 @@ const make = Effect.gen(function* () { · 597 unmodified lines | ||
| 631 | 631 | const crypto = yield* Crypto.Crypto; |
| 632 | 632 | const orchestrationEngine = yield* OrchestrationEngineService; |
| 633 | 633 | const projectionSnapshotQuery = yield* ProjectionSnapshotQuery; |
| 634 | − const providerService = yield* ProviderService; | |
| 634 | + const providerService = yield* ProviderService.ProviderService; | |
| 635 | 635 | const projectionTurnRepository = yield* ProjectionTurnRepository; |
| 636 | 636 | const serverSettingsService = yield* ServerSettingsService; |
| 637 | 637 | const providerCommandId = (event: ProviderRuntimeEvent, tag: string) => |
| @@ -5,7 +5,7 @@ import * as Effect from "effect/Effect"; · 4 unmodified lines | ||
| 5 | 5 | import * as Layer from "effect/Layer"; |
| 6 | 6 | import * as Stream from "effect/Stream"; |
| 7 | 7 | |
| 8 | −import { ProviderService } from "../../provider/Services/ProviderService.ts"; | |
| 8 | +import * as ProviderService from "../../provider/ProviderService.ts"; | |
| 9 | 9 | import * as TerminalManager from "../../terminal/Manager.ts"; |
| 10 | 10 | import { OrchestrationEngineService } from "../Services/OrchestrationEngine.ts"; |
| 11 | 11 | import { |
| @@ -38,7 +38,7 @@ export const logCleanupCauseUnlessInterrupted = <R, E>({ · 26 unmodified lines | ||
| 38 | 38 | |
| 39 | 39 | const make = Effect.gen(function* () { |
| 40 | 40 | const orchestrationEngine = yield* OrchestrationEngineService; |
| 41 | − const providerService = yield* ProviderService; | |
| 41 | + const providerService = yield* ProviderService.ProviderService; | |
| 42 | 42 | const terminalManager = yield* TerminalManager.TerminalManager; |
| 43 | 43 | |
| 44 | 44 | const stopProviderSession = (threadId: ThreadDeletedEvent["payload"]["threadId"]) => |
| @@ -16,6 +16,31 @@ const decodeOpenCodeSettings = Schema.decodeSync(OpenCodeSettings); · 15 unmodified lines | ||
| 16 | 16 | |
| 17 | 17 | const DEFAULT_VERSION_STDOUT = "opencode 1.14.19\n"; |
| 18 | 18 | |
| 19 | +it("keeps OpenCode runtime causes separate from stable messages", () => { | |
| 20 | + const cause = new Error("sdk response included sensitive diagnostics"); | |
| 21 | + const error = new OpenCodeRuntime.OpenCodeRuntimeError({ | |
| 22 | + operation: "provider.list", | |
| 23 | + detail: cause.message, | |
| 24 | + cause, | |
| 25 | + }); | |
| 26 | + | |
| 27 | + assert.equal(error.cause, cause); | |
| 28 | + assert.equal(error.message, "OpenCode runtime operation provider.list failed."); | |
| 29 | + assert.doesNotMatch(error.message, /sensitive diagnostics/u); | |
| 30 | + | |
| 31 | + const processExit = new OpenCodeRuntime.OpenCodeRuntimeError({ | |
| 32 | + operation: "startOpenCodeServerProcess", | |
| 33 | + detail: "OpenCode server exited before startup completed.", | |
| 34 | + exitCode: 1, | |
| 35 | + stdout: "startup output", | |
| 36 | + stderr: "startup failure", | |
| 37 | + }); | |
| 38 | + assert.equal(processExit.cause, undefined); | |
| 39 | + assert.equal(processExit.exitCode, 1); | |
| 40 | + assert.equal(processExit.stdout, "startup output"); | |
| 41 | + assert.equal(processExit.stderr, "startup failure"); | |
| 42 | +}); | |
| 43 | + | |
| 19 | 44 | /** |
| 20 | 45 | * The legacy `OpenCodeProviderLive` Layer + `OpenCodeProvider` service tag |
| 21 | 46 | * are deleted. The snapshot-producing logic they wrapped now lives in the |
| @@ -1,6 +0,0 @@ | ||
| 1 | −// Compatibility shim for the intentionally excluded orchestration harness. | |
| 2 | −import * as Canonical from "../ProviderEventLoggers.ts"; | |
| 3 | − | |
| 4 | −export const ProviderEventLoggersLive = Canonical.layer; | |
| 5 | −export const ProviderEventLoggers = Canonical.ProviderEventLoggers; | |
| 6 | −export const NoOpProviderEventLoggers = Canonical.NoOpProviderEventLoggers; | |
| @@ -1,11 +0,0 @@ | ||
| 1 | −// Compatibility shim for the intentionally excluded orchestration harness. | |
| 2 | −import * as Layer from "effect/Layer"; | |
| 3 | − | |
| 4 | −import * as ProviderService from "../ProviderService.ts"; | |
| 5 | − | |
| 6 | −export type ProviderServiceLiveOptions = ProviderService.ProviderServiceOptions; | |
| 7 | − | |
| 8 | −export const ProviderServiceLive = ProviderService.layer; | |
| 9 | − | |
| 10 | −export const makeProviderServiceLive = (options?: ProviderService.ProviderServiceOptions) => | |
| 11 | − Layer.effect(ProviderService.ProviderService, ProviderService.make(options)); | |
| @@ -1,6 +0,0 @@ | ||
| 1 | −// Compatibility shim for the intentionally excluded orchestration harness. | |
| 2 | −import * as ProviderSessionDirectory from "../ProviderSessionDirectory.ts"; | |
| 3 | − | |
| 4 | −export const ProviderSessionDirectoryLive = ProviderSessionDirectory.layer; | |
| 5 | − | |
| 6 | −export const makeProviderSessionDirectoryLive = () => ProviderSessionDirectory.layer; | |
| @@ -12,11 +12,16 @@ · 11 unmodified lines | ||
| 12 | 12 | * - `getByInstance` / `listInstances` — new per-instance routing. Callers |
| 13 | 13 | * that already know an `instanceId` (threads, sessions, events) |
| 14 | 14 | * should prefer these. |
| 15 | + * - `listProviders` — legacy kind-keyed routing for default instances | |
| 15 | 16 | * (`defaultInstanceIdForDriver(kind) === kind`), matching the pre-Slice-D |
| 16 | 17 | * behaviour. New code should not grow additional callers of the kind-keyed |
| 17 | 18 | * methods; they exist so the settings UI, WS refresh RPC, and a handful |
| 18 | 19 | * of legacy persisted rows can still be routed during the rollout. |
| 19 | 20 | * |
| 21 | + * Adapter lookups are resolved dynamically through `ProviderInstanceRegistry`. | |
| 22 | + * Settings-driven hot reload is therefore visible immediately without | |
| 23 | + * rebuilding this facade. | |
| 24 | + * | |
| 20 | 25 | * @module ProviderAdapterRegistry |
| 21 | 26 | */ |
| 22 | 27 | import { |
| @@ -50,7 +55,10 @@ export class ProviderAdapterRegistry extends Context.Service< · 27 unmodified lines | ||
| 50 | 55 | { |
| 51 | 56 | /** |
| 52 | 57 | * Resolve the adapter for a specific instance id. Returns |
| 53 | − * `ProviderUnsupportedError` if no such live instance is registered. | |
| 58 | + * `ProviderUnsupportedError` if no such instance is currently registered | |
| 59 | + * (which covers "never configured" and "configured but the driver is | |
| 60 | + * unavailable in this build"; both surface the same failure to callers | |
| 61 | + * that expect a working adapter). | |
| 54 | 62 | */ |
| 55 | 63 | readonly getByInstance: ( |
| 56 | 64 | instanceId: ProviderInstanceId, |
| @@ -62,28 +70,33 @@ export class ProviderAdapterRegistry extends Context.Service< · 5 unmodified lines | ||
| 62 | 70 | ) => Effect.Effect<ProviderInstanceRoutingInfo, ProviderUnsupportedError>; |
| 63 | 71 | |
| 64 | 72 | /** |
| 65 | − * List every live instance id. Unavailable shadow instances are excluded | |
| 66 | − * because callers use these ids with `getByInstance`. | |
| 73 | + * List all live instance ids. Excludes unavailable/shadow instances because | |
| 74 | + * callers of this method want something they can pass to `getByInstance`. | |
| 67 | 75 | */ |
| 68 | 76 | readonly listInstances: () => Effect.Effect<ReadonlyArray<ProviderInstanceId>>; |
| 69 | 77 | |
| 70 | 78 | /** |
| 71 | 79 | * List provider kinds whose default instance is currently registered. |
| 72 | 80 | * |
| 73 | − * @deprecated Prefer `listInstances`; this remains for migration-era | |
| 74 | − * callers that still address providers by driver kind. | |
| 81 | + * @deprecated Prefer `listInstances`. Retained for migration-era call sites | |
| 82 | + * that iterate providers to build UI or metrics. | |
| 75 | 83 | */ |
| 76 | 84 | readonly listProviders: () => Effect.Effect<ReadonlyArray<ProviderDriverKind>>; |
| 77 | 85 | |
| 78 | 86 | /** |
| 79 | − * Emits whenever the live instance set changes. Consumers should re-read | |
| 80 | − * `listInstances` and reconcile their per-instance subscriptions. | |
| 87 | + * Change notification stream mirroring | |
| 88 | + * `ProviderInstanceRegistry.streamChanges`. Emits one `void` tick whenever | |
| 89 | + * the set of live instances changes. Consumers that fan out | |
| 90 | + * `adapter.streamEvents` per instance re-pull `listInstances` on each tick | |
| 91 | + * and fork subscriptions for instances they have not seen yet. | |
| 81 | 92 | */ |
| 82 | 93 | readonly streamChanges: Stream.Stream<void>; |
| 83 | 94 | |
| 84 | 95 | /** |
| 85 | − * Acquire the change subscription synchronously in the caller's scope, | |
| 86 | − * avoiding the publish race inherent in forking `Stream.fromPubSub`. | |
| 96 | + * Acquire a change subscription synchronously in the caller's current | |
| 97 | + * fiber. Consumers that must avoid missing a publish between initial | |
| 98 | + * reconciliation and watcher startup should use this, then fork | |
| 99 | + * `Stream.fromSubscription`. | |
| 87 | 100 | */ |
| 88 | 101 | readonly subscribeChanges: Effect.Effect<PubSub.Subscription<void>, never, Scope.Scope>; |
| 89 | 102 | } |
| @@ -130,6 +143,8 @@ export const make = Effect.gen(function* () { · 40 unmodified lines | ||
| 130 | 143 | const kinds = new Set<ProviderDriverKind>(); |
| 131 | 144 | for (const instance of instances) { |
| 132 | 145 | if (instance.instanceId === defaultInstanceIdForDriver(instance.driverKind)) { |
| 146 | + // Only default instances appear through this legacy view; custom | |
| 147 | + // instances such as `codex_personal` have no driver-kind identity. | |
| 133 | 148 | kinds.add(instance.driverKind); |
| 134 | 149 | } |
| 135 | 150 | } |
| @@ -142,6 +157,8 @@ export const make = Effect.gen(function* () { · 6 unmodified lines | ||
| 142 | 157 | getInstanceInfo, |
| 143 | 158 | listInstances, |
| 144 | 159 | listProviders, |
| 160 | + // The facade owns no state; the instance registry already coalesces | |
| 161 | + // add, remove, and rebuild notifications into a single change channel. | |
| 145 | 162 | streamChanges: registry.streamChanges, |
| 146 | 163 | subscribeChanges: registry.subscribeChanges, |
| 147 | 164 | }); |
| @@ -58,38 +58,46 @@ export class ProviderInstanceRegistry extends Context.Service< · 57 unmodified lines | ||
| 58 | 58 | ProviderInstanceRegistry, |
| 59 | 59 | { |
| 60 | 60 | /** |
| 61 | − * Look up one instance by id. Unknown ids return `undefined`; callers map | |
| 62 | − * that absence to the appropriate domain error. | |
| 61 | + * Look up one instance by id. Returns `undefined` (not `Option`) when the | |
| 62 | + * id is unknown; callers branch on absence and emit the appropriate domain | |
| 63 | + * error. | |
| 63 | 64 | */ |
| 64 | 65 | readonly getInstance: ( |
| 65 | 66 | instanceId: ProviderInstanceId, |
| 66 | 67 | ) => Effect.Effect<ProviderInstance | undefined>; |
| 67 | 68 | |
| 68 | 69 | /** |
| 69 | − * Every successfully materialized instance, in stable settings-author | |
| 70 | − * order. | |
| 70 | + * Every available (driver-registered, successfully created) instance, in | |
| 71 | + * stable settings-author order. | |
| 71 | 72 | */ |
| 72 | 73 | readonly listInstances: Effect.Effect<ReadonlyArray<ProviderInstance>>; |
| 73 | 74 | |
| 74 | 75 | /** |
| 75 | − * Shadow snapshots for unknown drivers or invalid configurations, ready | |
| 76 | − * to merge into `ProviderRegistry` output. | |
| 76 | + * Wire-shape shadow snapshots for instances whose driver is unknown to this | |
| 77 | + * build or whose config failed to decode. Suitable for merging directly | |
| 78 | + * into `ProviderRegistry` output. | |
| 77 | 79 | */ |
| 78 | 80 | readonly listUnavailable: Effect.Effect<ReadonlyArray<ServerProvider>>; |
| 79 | 81 | |
| 80 | 82 | /** |
| 81 | − * Emits after the registry adds, removes, or rebuilds instances. The | |
| 82 | − * payload is `void` because consumers re-read both registry lists. | |
| 83 | + * Push notification stream emitted whenever the registry's contents change. | |
| 84 | + * The payload is `void` because consumers always re-pull `listInstances` | |
| 85 | + * and `listUnavailable` together. | |
| 83 | 86 | * |
| 84 | − * `Stream.fromPubSub` subscribes only when execution begins, so a newly | |
| 85 | − * forked consumer can miss a publish. Consumers that cannot tolerate that | |
| 86 | − * gap should acquire `subscribeChanges` first. | |
| 87 | + * `Stream.fromPubSub` defers `PubSub.subscribe` until the stream starts | |
| 88 | + * running, so forking a consumer races the next publish. Hot-reload | |
| 89 | + * consumers that cannot miss a publish should use `subscribeChanges` | |
| 90 | + * instead, which acquires the subscription synchronously before the | |
| 91 | + * consumer loop is forked. | |
| 87 | 92 | */ |
| 88 | 93 | readonly streamChanges: Stream.Stream<void>; |
| 89 | 94 | |
| 90 | 95 | /** |
| 91 | − * Acquire a scoped PubSub subscription synchronously before forking the | |
| 92 | − * consumer loop, ensuring subsequent publishes cannot land in a gap. | |
| 96 | + * Acquire a scoped subscription to the change channel synchronously in the | |
| 97 | + * caller's fiber. Consumers typically `yield*` this in the same fiber that | |
| 98 | + * forks their consumer loop, then drain it with `PubSub.take` or | |
| 99 | + * `Stream.fromSubscription`. Because the subscription is registered before | |
| 100 | + * this effect returns, no subsequent publish can land in a gap. | |
| 93 | 101 | */ |
| 94 | 102 | readonly subscribeChanges: Effect.Effect<PubSub.Subscription<void>, never, Scope.Scope>; |
| 95 | 103 | } |
| @@ -107,9 +115,14 @@ export class ProviderInstanceRegistryMutator extends Context.Service< · 11 unmodified lines | ||
| 107 | 115 | ProviderInstanceRegistryMutator, |
| 108 | 116 | { |
| 109 | 117 | /** |
| 110 | − * Reconcile the live registry with a new configuration map. Individual | |
| 111 | − * driver creation failures become unavailable shadow snapshots so the | |
| 112 | − * settings watcher itself never fails. | |
| 118 | + * Bring the live registry in line with the supplied config map. The | |
| 119 | + * operation closes removed or replaced scopes before creating replacements | |
| 120 | + * and publishes a single change tick after the batch. Reapplying an | |
| 121 | + * unchanged map is a no-op. | |
| 122 | + * | |
| 123 | + * The effect never fails: individual driver creation failures become | |
| 124 | + * unavailable shadow snapshots, keeping settings-watcher loops alive when | |
| 125 | + * one entry is invalid. | |
| 113 | 126 | */ |
| 114 | 127 | readonly reconcile: (configMap: ProviderInstanceConfigMap) => Effect.Effect<void>; |
| 115 | 128 | } |
| @@ -64,13 +64,18 @@ export class ProviderRegistry extends Context.Service< · 63 unmodified lines | ||
| 64 | 64 | ProviderRegistry, |
| 65 | 65 | { |
| 66 | 66 | /** |
| 67 | − * Read the latest snapshot for every configured instance. Multiple | |
| 68 | − * snapshots can share a driver kind and are distinguished by instance id. | |
| 67 | + * Read the latest provider snapshots for every configured instance. | |
| 68 | + * Multiple snapshots may share the same driver kind and disambiguate via | |
| 69 | + * `instanceId`. | |
| 69 | 70 | */ |
| 70 | 71 | readonly getProviders: Effect.Effect<ReadonlyArray<ServerProvider>>; |
| 71 | 72 | |
| 72 | 73 | /** |
| 73 | − * Refresh every provider, or the default instance for one driver kind. | |
| 74 | + * Refresh all providers, or the default instance of the specified kind | |
| 75 | + * when supplied. | |
| 76 | + * | |
| 77 | + * Retained for compatibility with legacy call sites such as the WS refresh | |
| 78 | + * RPC and orchestration metrics. | |
| 74 | 79 | * |
| 75 | 80 | * @deprecated Prefer `refreshInstance` for new call sites. |
| 76 | 81 | */ |
| @@ -80,15 +85,16 @@ export class ProviderRegistry extends Context.Service< · 3 unmodified lines | ||
| 80 | 85 | |
| 81 | 86 | /** |
| 82 | 87 | * Refresh one configured instance. Unknown ids resolve to the current |
| 83 | − * cached list to preserve the legacy transport behavior. | |
| 88 | + * cached list, matching the legacy `refresh` behavior so transport layers | |
| 89 | + * do not have to special-case unknown ids. | |
| 84 | 90 | */ |
| 85 | 91 | readonly refreshInstance: ( |
| 86 | 92 | instanceId: ProviderInstanceId, |
| 87 | 93 | ) => Effect.Effect<ReadonlyArray<ServerProvider>>; |
| 88 | 94 | |
| 89 | 95 | /** |
| 90 | − * Resolve maintenance capabilities for a live instance, falling back to | |
| 91 | − * manual-only capabilities when that instance is unavailable. | |
| 96 | + * Resolve maintenance capabilities for one live provider instance, | |
| 97 | + * falling back to manual-only capabilities when it is unavailable. | |
| 92 | 98 | */ |
| 93 | 99 | readonly getProviderMaintenanceCapabilitiesForInstance: ( |
| 94 | 100 | instanceId: ProviderInstanceId, |
| @@ -96,8 +102,10 @@ export class ProviderRegistry extends Context.Service< · 1 unmodified line | ||
| 96 | 102 | ) => Effect.Effect<ProviderMaintenanceCapabilities>; |
| 97 | 103 | |
| 98 | 104 | /** |
| 99 | − * Apply volatile maintenance state for one instance. This state is not | |
| 100 | − * persisted; update actions are projected onto `ServerProvider.updateState`. | |
| 105 | + * Apply volatile maintenance-action state to one configured instance. This | |
| 106 | + * state is never persisted. Today only update actions are projected onto | |
| 107 | + * `ServerProvider.updateState`; install/auth actions can extend this map | |
| 108 | + * without adding driver-scoped APIs. | |
| 101 | 109 | */ |
| 102 | 110 | readonly setProviderMaintenanceActionState: (input: { |
| 103 | 111 | readonly instanceId: ProviderInstanceId; |
| @@ -105,7 +113,10 @@ export class ProviderRegistry extends Context.Service< · 1 unmodified line | ||
| 105 | 113 | readonly state: ServerProviderUpdateState | null; |
| 106 | 114 | }) => Effect.Effect<ReadonlyArray<ServerProvider>>; |
| 107 | 115 | |
| 108 | − /** Emits the full materialized provider list after each aggregated change. */ | |
| 116 | + /** | |
| 117 | + * Stream of provider snapshot updates, one emission per aggregated change. | |
| 118 | + * The array contains the full current state. | |
| 119 | + */ | |
| 109 | 120 | readonly streamChanges: Stream.Stream<ReadonlyArray<ServerProvider>>; |
| 110 | 121 | } |
| 111 | 122 | >()("t3/provider/ProviderRegistry") {} |
| @@ -671,19 +682,6 @@ export const make = Effect.gen(function* () { · 559 unmodified lines | ||
| 671 | 682 | // and pending fallbacks fill the gaps. |
| 672 | 683 | yield* upsertProviders(fallbackProviders, { publish: false }); |
| 673 | 684 | // Subscribe to registry mutations BEFORE running the initial sync. |
| 674 | − // `subscribeChanges` acquires the dequeue synchronously in this | |
| 675 | − // fibre; the subscription is active the instant this `yield*` | |
| 676 | − // returns. Forking the consumer loop later cannot lose a publish | |
| 677 | − // because no publish can reach a not-yet-subscribed dequeue. | |
| 678 | − // | |
| 679 | − // (Contrast with the pre-fix code that did | |
| 680 | − // `Stream.runForEach(instanceRegistry.streamChanges, …).pipe(Effect.forkScoped)`. | |
| 681 | − // `Stream.fromPubSub` defers `PubSub.subscribe` to stream start, | |
| 682 | − // and `forkScoped` only schedules the fibre — so a reconcile that | |
| 683 | − // published between "fibre scheduled" and "fibre starts running" | |
| 684 | − // was dropped, which made any settings change that replaced an | |
| 685 | − // instance never propagate to the aggregator's `providersRef`.) | |
| 686 | − // Subscribe to registry mutations BEFORE running the initial sync. | |
| 687 | 685 | // `subscribeChanges` acquires the `PubSub.Subscription` synchronously |
| 688 | 686 | // in this fibre; the subscription is registered with the PubSub the |
| 689 | 687 | // instant this `yield*` returns, so any subsequent publish is |
| @@ -55,10 +55,13 @@ import { · 54 unmodified lines | ||
| 55 | 55 | } from "../persistence/Layers/Sqlite.ts"; |
| 56 | 56 | import * as ServerSettings from "../serverSettings.ts"; |
| 57 | 57 | import * as AnalyticsService from "../telemetry/AnalyticsService.ts"; |
| 58 | +import { makeAdapterRegistryMock } from "./testUtils/providerAdapterRegistryMock.ts"; | |
| 58 | 59 | |
| 59 | −const makeProviderServiceLive = (options?: ProviderService.ProviderServiceOptions) => | |
| 60 | +// Several tests build two ProviderService instances in one scope to simulate a | |
| 61 | +// process restart. Construct a fresh Layer for each build so Layer memoization | |
| 62 | +// cannot reuse the first service instance. | |
| 63 | +const makeFreshProviderServiceLayer = (options?: ProviderService.ProviderServiceOptions) => | |
| 60 | 64 | Layer.effect(ProviderService.ProviderService, ProviderService.make(options)); |
| 61 | −import { makeAdapterRegistryMock } from "./testUtils/providerAdapterRegistryMock.ts"; | |
| 62 | 65 | |
| 63 | 66 | const defaultServerSettingsLayer = ServerSettings.ServerSettingsService.layerTest(); |
| 64 | 67 | |
| @@ -289,7 +292,7 @@ function makeProviderServiceLayer() { · 224 unmodified lines | ||
| 289 | 292 | |
| 290 | 293 | const layer = it.layer( |
| 291 | 294 | Layer.mergeAll( |
| 292 | − makeProviderServiceLive().pipe( | |
| 295 | + makeFreshProviderServiceLayer().pipe( | |
| 293 | 296 | Layer.provide(providerAdapterLayer), |
| 294 | 297 | Layer.provide(directoryLayer), |
| 295 | 298 | Layer.provide(defaultServerSettingsLayer), |
| @@ -342,7 +345,7 @@ it.effect("ProviderServiceLive catches stopAll failures during shutdown", () => · 46 unmodified lines | ||
| 342 | 345 | Layer.provide(runtimeRepositoryLayer), |
| 343 | 346 | ); |
| 344 | 347 | const providerLayer = Layer.mergeAll( |
| 345 | − makeProviderServiceLive().pipe( | |
| 348 | + makeFreshProviderServiceLayer().pipe( | |
| 346 | 349 | Layer.provide(providerAdapterLayer), |
| 347 | 350 | Layer.provide(directoryLayer), |
| 348 | 351 | Layer.provide(defaultServerSettingsLayer), |
| @@ -403,7 +406,7 @@ it.effect("ProviderServiceLive rejects new sessions for disabled providers", () · 54 unmodified lines | ||
| 403 | 406 | const directoryLayer = ProviderSessionDirectory.layer.pipe( |
| 404 | 407 | Layer.provide(runtimeRepositoryLayer), |
| 405 | 408 | ); |
| 406 | − const providerLayer = makeProviderServiceLive().pipe( | |
| 409 | + const providerLayer = makeFreshProviderServiceLayer().pipe( | |
| 407 | 410 | Layer.provide(providerAdapterLayer), |
| 408 | 411 | Layer.provide(directoryLayer), |
| 409 | 412 | Layer.provide(defaultServerSettingsLayer), |
| @@ -487,7 +490,7 @@ it.effect( · 77 unmodified lines | ||
| 487 | 490 | const directoryLayer = ProviderSessionDirectory.layer.pipe( |
| 488 | 491 | Layer.provide(runtimeRepositoryLayer), |
| 489 | 492 | ); |
| 490 | − const providerLayer = makeProviderServiceLive().pipe( | |
| 493 | + const providerLayer = makeFreshProviderServiceLayer().pipe( | |
| 491 | 494 | Layer.provide(providerAdapterLayer), |
| 492 | 495 | Layer.provide(directoryLayer), |
| 493 | 496 | Layer.provide(serverSettingsLayer), |
| @@ -559,7 +562,7 @@ it.effect("ProviderServiceLive rejects new sessions for disabled custom instance · 65 unmodified lines | ||
| 559 | 562 | const directoryLayer = ProviderSessionDirectory.layer.pipe( |
| 560 | 563 | Layer.provide(runtimeRepositoryLayer), |
| 561 | 564 | ); |
| 562 | − const providerLayer = makeProviderServiceLive().pipe( | |
| 565 | + const providerLayer = makeFreshProviderServiceLayer().pipe( | |
| 563 | 566 | Layer.provide(providerAdapterLayer), |
| 564 | 567 | Layer.provide(directoryLayer), |
| 565 | 568 | Layer.provide(defaultServerSettingsLayer), |
| @@ -606,7 +609,7 @@ it.effect("ProviderServiceLive writes canonical events to the emitting thread se · 40 unmodified lines | ||
| 606 | 609 | const directoryLayer = ProviderSessionDirectory.layer.pipe( |
| 607 | 610 | Layer.provide(runtimeRepositoryLayer), |
| 608 | 611 | ); |
| 609 | − const providerLayer = makeProviderServiceLive({ | |
| 612 | + const providerLayer = makeFreshProviderServiceLayer({ | |
| 610 | 613 | canonicalEventLogger: { |
| 611 | 614 | filePath: "memory://provider-canonical-events", |
| 612 | 615 | write: (event, threadId) => { |
| @@ -678,7 +681,7 @@ it.effect("ProviderServiceLive keeps persisted resumable sessions on startup", ( · 65 unmodified lines | ||
| 678 | 681 | }); |
| 679 | 682 | }).pipe(Effect.provide(directoryLayer)); |
| 680 | 683 | |
| 681 | − const providerLayer = makeProviderServiceLive().pipe( | |
| 684 | + const providerLayer = makeFreshProviderServiceLayer().pipe( | |
| 682 | 685 | Layer.provide(Layer.succeed(ProviderAdapterRegistry.ProviderAdapterRegistry, registry)), |
| 683 | 686 | Layer.provide(directoryLayer), |
| 684 | 687 | Layer.provide(defaultServerSettingsLayer), |
| @@ -742,7 +745,7 @@ it.effect( · 57 unmodified lines | ||
| 742 | 745 | const firstDirectoryLayer = ProviderSessionDirectory.layer.pipe( |
| 743 | 746 | Layer.provide(runtimeRepositoryLayer), |
| 744 | 747 | ); |
| 745 | − const firstProviderLayer = makeProviderServiceLive().pipe( | |
| 748 | + const firstProviderLayer = makeFreshProviderServiceLayer().pipe( | |
| 746 | 749 | Layer.provide( |
| 747 | 750 | Layer.succeed(ProviderAdapterRegistry.ProviderAdapterRegistry, firstRegistry), |
| 748 | 751 | ), |
| @@ -801,7 +804,7 @@ it.effect( · 52 unmodified lines | ||
| 801 | 804 | const secondDirectoryLayer = ProviderSessionDirectory.layer.pipe( |
| 802 | 805 | Layer.provide(runtimeRepositoryLayer), |
| 803 | 806 | ); |
| 804 | − const secondProviderLayer = makeProviderServiceLive().pipe( | |
| 807 | + const secondProviderLayer = makeFreshProviderServiceLayer().pipe( | |
| 805 | 808 | Layer.provide( |
| 806 | 809 | Layer.succeed(ProviderAdapterRegistry.ProviderAdapterRegistry, secondRegistry), |
| 807 | 810 | ), |
| @@ -1312,7 +1315,7 @@ routing.layer("ProviderServiceLive routing", (it) => { · 504 unmodified lines | ||
| 1312 | 1315 | const firstDirectoryLayer = ProviderSessionDirectory.layer.pipe( |
| 1313 | 1316 | Layer.provide(runtimeRepositoryLayer), |
| 1314 | 1317 | ); |
| 1315 | − const firstProviderLayer = makeProviderServiceLive().pipe( | |
| 1318 | + const firstProviderLayer = makeFreshProviderServiceLayer().pipe( | |
| 1316 | 1319 | Layer.provide( |
| 1317 | 1320 | Layer.succeed(ProviderAdapterRegistry.ProviderAdapterRegistry, firstRegistry), |
| 1318 | 1321 | ), |
| @@ -1350,7 +1353,7 @@ routing.layer("ProviderServiceLive routing", (it) => { · 31 unmodified lines | ||
| 1350 | 1353 | const secondDirectoryLayer = ProviderSessionDirectory.layer.pipe( |
| 1351 | 1354 | Layer.provide(runtimeRepositoryLayer), |
| 1352 | 1355 | ); |
| 1353 | − const secondProviderLayer = makeProviderServiceLive().pipe( | |
| 1356 | + const secondProviderLayer = makeFreshProviderServiceLayer().pipe( | |
| 1354 | 1357 | Layer.provide( |
| 1355 | 1358 | Layer.succeed(ProviderAdapterRegistry.ProviderAdapterRegistry, secondRegistry), |
| 1356 | 1359 | ), |
| @@ -1418,7 +1421,7 @@ routing.layer("ProviderServiceLive routing", (it) => { · 61 unmodified lines | ||
| 1418 | 1421 | const firstDirectoryLayer = ProviderSessionDirectory.layer.pipe( |
| 1419 | 1422 | Layer.provide(runtimeRepositoryLayer), |
| 1420 | 1423 | ); |
| 1421 | − const firstProviderLayer = makeProviderServiceLive().pipe( | |
| 1424 | + const firstProviderLayer = makeFreshProviderServiceLayer().pipe( | |
| 1422 | 1425 | Layer.provide( |
| 1423 | 1426 | Layer.succeed(ProviderAdapterRegistry.ProviderAdapterRegistry, firstRegistry), |
| 1424 | 1427 | ), |
| @@ -1451,7 +1454,7 @@ routing.layer("ProviderServiceLive routing", (it) => { · 26 unmodified lines | ||
| 1451 | 1454 | const secondDirectoryLayer = ProviderSessionDirectory.layer.pipe( |
| 1452 | 1455 | Layer.provide(runtimeRepositoryLayer), |
| 1453 | 1456 | ); |
| 1454 | − const secondProviderLayer = makeProviderServiceLive().pipe( | |
| 1457 | + const secondProviderLayer = makeFreshProviderServiceLayer().pipe( | |
| 1455 | 1458 | Layer.provide( |
| 1456 | 1459 | Layer.succeed(ProviderAdapterRegistry.ProviderAdapterRegistry, secondRegistry), |
| 1457 | 1460 | ), |
| @@ -6,6 +6,8 @@ · 5 unmodified lines | ||
| 6 | 6 | * unified provider event stream for subscribers. |
| 7 | 7 | * |
| 8 | 8 | * It does not implement provider protocol details (adapter concern). |
| 9 | + * Uses Effect `Context.Service` for dependency injection and returns typed | |
| 10 | + * domain errors for validation, session, codex, and checkpoint workflows. | |
| 9 | 11 | * |
| 10 | 12 | * @module ProviderService |
| 11 | 13 | */ |
| @@ -61,14 +63,22 @@ import * as ProviderSessionDirectory from "./ProviderSessionDirectory.ts"; · 49 unmodified lines | ||
| 61 | 63 | import { type EventNdjsonLogger } from "./Layers/EventNdjsonLogger.ts"; |
| 62 | 64 | import * as ProviderEventLoggers from "./ProviderEventLoggers.ts"; |
| 63 | 65 | import * as AnalyticsService from "../telemetry/AnalyticsService.ts"; |
| 64 | −import * as McpProviderSession from "../mcp/McpProviderSession.ts"; | |
| 65 | −import * as McpSessionRegistry from "../mcp/McpSessionRegistry.ts"; | |
| 66 | +import { | |
| 67 | + clearAllMcpProviderSessions, | |
| 68 | + clearMcpProviderSession, | |
| 69 | + setMcpProviderSession, | |
| 70 | +} from "../mcp/McpProviderSession.ts"; | |
| 71 | +import { | |
| 72 | + issueActiveMcpCredential, | |
| 73 | + revokeActiveMcpThread, | |
| 74 | + revokeAllActiveMcpCredentials, | |
| 75 | +} from "../mcp/McpSessionRegistry.ts"; | |
| 66 | 76 | const isModelSelection = Schema.is(ModelSelection); |
| 67 | 77 | |
| 68 | 78 | export class ProviderService extends Context.Service< |
| 69 | 79 | ProviderService, |
| 70 | 80 | { |
| 71 | − /** Start a provider session for a thread. */ | |
| 81 | + /** Start a provider session. */ | |
| 72 | 82 | readonly startSession: ( |
| 73 | 83 | threadId: ThreadId, |
| 74 | 84 | input: ProviderSessionStartInput, |
| @@ -99,10 +109,14 @@ export class ProviderService extends Context.Service< · 24 unmodified lines | ||
| 99 | 109 | input: ProviderStopSessionInput, |
| 100 | 110 | ) => Effect.Effect<void, ProviderServiceError>; |
| 101 | 111 | |
| 102 | − /** Aggregate the active sessions reported by all registered adapters. */ | |
| 112 | + /** | |
| 113 | + * List active provider sessions. | |
| 114 | + * | |
| 115 | + * Aggregates runtime session lists from all registered adapters. | |
| 116 | + */ | |
| 103 | 117 | readonly listSessions: () => Effect.Effect<ReadonlyArray<ProviderSession>>; |
| 104 | 118 | |
| 105 | − /** Read capabilities for the adapter bound to an instance. */ | |
| 119 | + /** Read capabilities for the adapter bound to a configured provider instance. */ | |
| 106 | 120 | readonly getCapabilities: ( |
| 107 | 121 | instanceId: ProviderInstanceId, |
| 108 | 122 | ) => Effect.Effect<ProviderAdapterCapabilities, ProviderServiceError>; |
| @@ -119,8 +133,9 @@ export class ProviderService extends Context.Service< · 10 unmodified lines | ||
| 119 | 133 | }) => Effect.Effect<void, ProviderServiceError>; |
| 120 | 134 | |
| 121 | 135 | /** |
| 122 | − * Canonical provider runtime event stream. ProviderService owns the | |
| 123 | − * per-instance fan-out rather than delegating it to a separate event bus. | |
| 136 | + * Canonical provider runtime event stream. | |
| 137 | + * | |
| 138 | + * Fan-out is owned by ProviderService (not by a standalone event-bus service). | |
| 124 | 139 | */ |
| 125 | 140 | readonly streamEvents: Stream.Stream<ProviderRuntimeEvent>; |
| 126 | 141 | } |
| @@ -279,16 +294,14 @@ export const make = Effect.fn("ProviderService.make")(function* (options?: Provi · 152 unmodified lines | ||
| 279 | 294 | const runtimeEventPubSub = yield* PubSub.unbounded<ProviderRuntimeEvent>(); |
| 280 | 295 | const nowIso = Effect.map(DateTime.now, DateTime.formatIso); |
| 281 | 296 | const prepareMcpSession = (threadId: ThreadId, providerInstanceId: ProviderInstanceId) => |
| 282 | − McpSessionRegistry.issueActiveMcpCredential({ threadId, providerInstanceId }).pipe( | |
| 297 | + issueActiveMcpCredential({ threadId, providerInstanceId }).pipe( | |
| 283 | 298 | Effect.tap((credential) => |
| 284 | − credential | |
| 285 | − ? Effect.sync(() => McpProviderSession.setMcpProviderSession(credential.config)) | |
| 286 | − : Effect.void, | |
| 299 | + credential ? Effect.sync(() => setMcpProviderSession(credential.config)) : Effect.void, | |
| 287 | 300 | ), |
| 288 | 301 | ); |
| 289 | 302 | const clearMcpSession = (threadId: ThreadId) => |
| 290 | − McpSessionRegistry.revokeActiveMcpThread(threadId).pipe( | |
| 291 | − Effect.tap(() => Effect.sync(() => McpProviderSession.clearMcpProviderSession(threadId))), | |
| 303 | + revokeActiveMcpThread(threadId).pipe( | |
| 304 | + Effect.tap(() => Effect.sync(() => clearMcpProviderSession(threadId))), | |
| 292 | 305 | ); |
| 293 | 306 | |
| 294 | 307 | const publishRuntimeEvent = (event: ProviderRuntimeEvent): Effect.Effect<void> => |
| @@ -1096,8 +1109,8 @@ export const make = Effect.fn("ProviderService.make")(function* (options?: Provi · 801 unmodified lines | ||
| 1096 | 1109 | ), |
| 1097 | 1110 | ).pipe(Effect.asVoid); |
| 1098 | 1111 | yield* Effect.forEach(currentAdapters, ([, adapter]) => adapter.stopAll()).pipe(Effect.asVoid); |
| 1099 | − yield* McpSessionRegistry.revokeAllActiveMcpCredentials(); | |
| 1100 | − McpProviderSession.clearAllMcpProviderSessions(); | |
| 1112 | + yield* revokeAllActiveMcpCredentials(); | |
| 1113 | + clearAllMcpProviderSessions(); | |
| 1101 | 1114 | const bindings = yield* directory.listBindings().pipe(Effect.orElseSucceed(() => [])); |
| 1102 | 1115 | yield* Effect.forEach(bindings, (binding) => |
| 1103 | 1116 | Effect.gen(function* () { |
| @@ -20,9 +20,7 @@ import * as ProviderSessionRuntime from "../persistence/ProviderSessionRuntime.t · 19 unmodified lines | ||
| 20 | 20 | import * as ProviderSessionDirectory from "./ProviderSessionDirectory.ts"; |
| 21 | 21 | |
| 22 | 22 | function makeDirectoryLayer<E, R>(persistenceLayer: Layer.Layer<SqlClient.SqlClient, E, R>) { |
| 23 | − const runtimeRepositoryLayer = ProviderSessionRuntime.layer.pipe( | |
| 24 | − Layer.provide(persistenceLayer), | |
| 25 | − ); | |
| 23 | + const runtimeRepositoryLayer = ProviderSessionRuntime.layer.pipe(Layer.provide(persistenceLayer)); | |
| 26 | 24 | return Layer.mergeAll( |
| 27 | 25 | runtimeRepositoryLayer, |
| 28 | 26 | ProviderSessionDirectory.layer.pipe(Layer.provide(runtimeRepositoryLayer)), |
| @@ -14,7 +14,7 @@ import * as Option from "effect/Option"; · 13 unmodified lines | ||
| 14 | 14 | import * as Schema from "effect/Schema"; |
| 15 | 15 | |
| 16 | 16 | import { ProviderSessionDirectoryPersistenceError, ProviderValidationError } from "./Errors.ts"; |
| 17 | −import * as ProviderSessionRuntime from "../persistence/Services/ProviderSessionRuntime.ts"; | |
| 17 | +import * as ProviderSessionRuntime from "../persistence/ProviderSessionRuntime.ts"; | |
| 18 | 18 | |
| 19 | 19 | export interface ProviderRuntimeBinding { |
| 20 | 20 | readonly threadId: ThreadId; |
| @@ -119,6 +119,9 @@ function toRuntimeBinding( · 98 unmodified lines | ||
| 119 | 119 | ({ |
| 120 | 120 | threadId: runtime.threadId, |
| 121 | 121 | provider, |
| 122 | + // Migration boundary: rows written before provider instances had a | |
| 123 | + // nullable id. Promote them here so runtime routing never has to | |
| 124 | + // infer an instance from its driver kind. | |
| 122 | 125 | providerInstanceId: runtime.providerInstanceId ?? defaultInstanceIdForDriver(provider), |
| 123 | 126 | adapterKey: runtime.adapterKey, |
| 124 | 127 | runtimeMode: runtime.runtimeMode, |
| @@ -14,6 +14,7 @@ import * as ProviderSessionDirectory from "./ProviderSessionDirectory.ts"; · 13 unmodified lines | ||
| 14 | 14 | export class ProviderSessionReaper extends Context.Service< |
| 15 | 15 | ProviderSessionReaper, |
| 16 | 16 | { |
| 17 | + /** Start the background provider session reaper within the provided scope. */ | |
| 17 | 18 | readonly start: () => Effect.Effect<void, never, Scope.Scope>; |
| 18 | 19 | } |
| 19 | 20 | >()("t3/provider/ProviderSessionReaper") {} |
| @@ -1,3 +0,0 @@ | ||
| 1 | −// Compatibility shim for the intentionally excluded orchestration harness. | |
| 2 | −export { ProviderAdapterRegistry } from "../ProviderAdapterRegistry.ts"; | |
| 3 | −export type { ProviderInstanceRoutingInfo } from "../ProviderAdapterRegistry.ts"; | |
| @@ -1,2 +0,0 @@ | ||
| 1 | −// Compatibility shim for the intentionally excluded orchestration modules. | |
| 2 | −export * from "../ProviderRegistry.ts"; | |
| @@ -1,5 +0,0 @@ | ||
| 1 | −// Compatibility shim for the intentionally excluded orchestration modules and harness. | |
| 2 | −export * from "../ProviderService.ts"; | |
| 3 | − | |
| 4 | −/** @deprecated Use `ProviderService["Service"]` from the canonical module. */ | |
| 5 | −export type ProviderServiceShape = import("../ProviderService.ts").ProviderService["Service"]; | |
| @@ -26,7 +26,7 @@ import * as Schema from "effect/Schema"; · 25 unmodified lines | ||
| 26 | 26 | import * as Stream from "effect/Stream"; |
| 27 | 27 | import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; |
| 28 | 28 | |
| 29 | −import * as ProcessRunner from "../processRunner.ts"; | |
| 29 | +import { isWindowsCommandNotFound } from "../processRunner.ts"; | |
| 30 | 30 | import { collectStreamAsString } from "./providerSnapshot.ts"; |
| 31 | 31 | import * as NetService from "@t3tools/shared/Net"; |
| 32 | 32 | import { HostProcessPlatform } from "@t3tools/shared/hostProcess"; |
| @@ -54,12 +54,15 @@ export class OpenCodeRuntimeError extends Schema.TaggedErrorClass<OpenCodeRuntim · 21 unmodified lines | ||
| 54 | 54 | operation: Schema.String, |
| 55 | 55 | detail: Schema.String, |
| 56 | 56 | cause: Schema.optional(Schema.Defect()), |
| 57 | + exitCode: Schema.optionalKey(Schema.Number), | |
| 58 | + stdout: Schema.optionalKey(Schema.String), | |
| 59 | + stderr: Schema.optionalKey(Schema.String), | |
| 57 | 60 | }, |
| 58 | 61 | ) { |
| 59 | 62 | static readonly is = Schema.is(OpenCodeRuntimeError); |
| 60 | 63 | |
| 61 | 64 | override get message(): string { |
| 62 | − return `${this.operation}: ${this.detail}`; | |
| 65 | + return `OpenCode runtime operation ${this.operation} failed.`; | |
| 63 | 66 | } |
| 64 | 67 | } |
| 65 | 68 | |
| @@ -302,7 +305,7 @@ export const make = Effect.gen(function* () { · 236 unmodified lines | ||
| 302 | 305 | { concurrency: "unbounded" }, |
| 303 | 306 | ); |
| 304 | 307 | const exitCode = Number(code); |
| 305 | − if (yield* ProcessRunner.isWindowsCommandNotFound(exitCode, stderr)) { | |
| 308 | + if (yield* isWindowsCommandNotFound(exitCode, stderr)) { | |
| 306 | 309 | return yield* new OpenCodeRuntimeError({ |
| 307 | 310 | operation: "runOpenCodeCommand", |
| 308 | 311 | detail: `spawn ${input.binaryPath} ENOENT`, |
| @@ -437,7 +440,9 @@ export const make = Effect.gen(function* () { · 128 unmodified lines | ||
| 437 | 440 | ] |
| 438 | 441 | .filter(Boolean) |
| 439 | 442 | .join("\n\n"), |
| 440 | − cause: { exitCode, stdout, stderr }, | |
| 443 | + exitCode, | |
| 444 | + stdout, | |
| 445 | + stderr, | |
| 441 | 446 | }), |
| 442 | 447 | ).pipe(Effect.ignore); |
| 443 | 448 | }), |
| @@ -11,6 +11,7 @@ import * as Effect from "effect/Effect"; · 10 unmodified lines | ||
| 11 | 11 | import * as Exit from "effect/Exit"; |
| 12 | 12 | import * as Fiber from "effect/Fiber"; |
| 13 | 13 | import * as Layer from "effect/Layer"; |
| 14 | +import * as PlatformError from "effect/PlatformError"; | |
| 14 | 15 | import * as Ref from "effect/Ref"; |
| 15 | 16 | import * as Schema from "effect/Schema"; |
| 16 | 17 | import * as Sink from "effect/Sink"; |
| @@ -140,6 +141,13 @@ function mockSpawnerLayer( · 123 unmodified lines | ||
| 140 | 141 | ); |
| 141 | 142 | } |
| 142 | 143 | |
| 144 | +function failingSpawnerLayer(cause: PlatformError.PlatformError) { | |
| 145 | + return Layer.succeed( | |
| 146 | + ChildProcessSpawner.ChildProcessSpawner, | |
| 147 | + ChildProcessSpawner.make(() => Effect.fail(cause)), | |
| 148 | + ); | |
| 149 | +} | |
| 150 | + | |
| 143 | 151 | function makeRegistry( |
| 144 | 152 | initialProviders: ServerProvider | ReadonlyArray<ServerProvider> = baseProvider, |
| 145 | 153 | ) { |
| @@ -320,6 +328,29 @@ describe("providerMaintenanceRunner", () => { · 174 unmodified lines | ||
| 320 | 328 | }, |
| 321 | 329 | ); |
| 322 | 330 | |
| 331 | + it.effect("preserves update spawn failure messages", () => { | |
| 332 | + const cause = PlatformError.systemError({ | |
| 333 | + _tag: "PermissionDenied", | |
| 334 | + module: "ChildProcess", | |
| 335 | + method: "spawn", | |
| 336 | + pathOrDescriptor: "npm", | |
| 337 | + description: "", | |
| 338 | + }); | |
| 339 | + return Effect.gen(function* () { | |
| 340 | + const { registry } = yield* makeRegistry(baseProvider); | |
| 341 | + const runner = yield* makeTestRunner(registry); | |
| 342 | + | |
| 343 | + const result = yield* runner.updateProvider(CODEX_DRIVER); | |
| 344 | + assert.strictEqual(result.providers[0]?.updateState?.status, "failed"); | |
| 345 | + assert.strictEqual( | |
| 346 | + result.providers[0]?.updateState?.message, | |
| 347 | + `Failed to run update command npm: ${cause.message}`, | |
| 348 | + ); | |
| 349 | + }).pipe( | |
| 350 | + Effect.provide(Layer.mergeAll(latestVersionHttpClient("0.0.0"), failingSpawnerLayer(cause))), | |
| 351 | + ); | |
| 352 | + }); | |
| 353 | + | |
| 323 | 354 | it.effect("updates a single provider instance without touching sibling instances", () => { |
| 324 | 355 | const calls: Array<{ command: string; args: ReadonlyArray<string> }> = []; |
| 325 | 356 | return Effect.gen(function* () { |
| @@ -14,6 +14,7 @@ import * as Duration from "effect/Duration"; · 13 unmodified lines | ||
| 14 | 14 | import * as Effect from "effect/Effect"; |
| 15 | 15 | import * as Layer from "effect/Layer"; |
| 16 | 16 | import * as Option from "effect/Option"; |
| 17 | +import * as PlatformError from "effect/PlatformError"; | |
| 17 | 18 | import * as Ref from "effect/Ref"; |
| 18 | 19 | import * as Schema from "effect/Schema"; |
| 19 | 20 | import { HttpClient } from "effect/unstable/http"; |
| @@ -52,20 +53,68 @@ export class ProviderMaintenanceRunner extends Context.Service< · 32 unmodified lines | ||
| 52 | 53 | } |
| 53 | 54 | >()("t3/provider/providerMaintenanceRunner") {} |
| 54 | 55 | |
| 55 | −class ProviderMaintenanceCommandError extends Schema.TaggedErrorClass<ProviderMaintenanceCommandError>()( | |
| 56 | − "ProviderMaintenanceCommandError", | |
| 56 | +const platformFailureFields = { | |
| 57 | + failureTag: Schema.NullOr(Schema.String), | |
| 58 | + failureModule: Schema.String, | |
| 59 | + failureMethod: Schema.String, | |
| 60 | + failureDescription: Schema.NullOr(Schema.String), | |
| 61 | + failurePathOrDescriptor: Schema.NullOr(Schema.Union([Schema.String, Schema.Number])), | |
| 62 | +} as const; | |
| 63 | + | |
| 64 | +interface PlatformFailureAttributes { | |
| 65 | + readonly failureTag: string | null; | |
| 66 | + readonly failureModule: string; | |
| 67 | + readonly failureMethod: string; | |
| 68 | + readonly failureDescription: string | null; | |
| 69 | + readonly failurePathOrDescriptor: string | number | null; | |
| 70 | +} | |
| 71 | + | |
| 72 | +function platformFailureAttributes(cause: PlatformError.PlatformError): PlatformFailureAttributes { | |
| 73 | + const { reason } = cause; | |
| 74 | + return { | |
| 75 | + failureTag: reason._tag === "BadArgument" ? null : reason._tag, | |
| 76 | + failureModule: reason.module, | |
| 77 | + failureMethod: reason.method, | |
| 78 | + failureDescription: reason.description || null, | |
| 79 | + failurePathOrDescriptor: | |
| 80 | + "pathOrDescriptor" in reason ? (reason.pathOrDescriptor ?? null) : null, | |
| 81 | + }; | |
| 82 | +} | |
| 83 | + | |
| 84 | +function formatPlatformFailure(attributes: PlatformFailureAttributes): string { | |
| 85 | + const tag = attributes.failureTag === null ? "" : `${attributes.failureTag}: `; | |
| 86 | + const path = | |
| 87 | + attributes.failurePathOrDescriptor === null | |
| 88 | + ? "" | |
| 89 | + : ` (${String(attributes.failurePathOrDescriptor)})`; | |
| 90 | + const description = | |
| 91 | + attributes.failureDescription === null ? "" : `: ${attributes.failureDescription}`; | |
| 92 | + return `${tag}${attributes.failureModule}.${attributes.failureMethod}${path}${description}`; | |
| 93 | +} | |
| 94 | + | |
| 95 | +class ProviderMaintenanceCommandSpawnError extends Schema.TaggedErrorClass<ProviderMaintenanceCommandSpawnError>()( | |
| 96 | + "ProviderMaintenanceCommandSpawnError", | |
| 57 | 97 | { |
| 58 | − operation: Schema.Literals(["spawn", "collect"]), | |
| 59 | − command: Schema.optional(Schema.String), | |
| 98 | + command: Schema.String, | |
| 99 | + ...platformFailureFields, | |
| 60 | 100 | cause: Schema.Defect(), |
| 61 | 101 | }, |
| 62 | 102 | ) { |
| 63 | 103 | override get message(): string { |
| 64 | − const causeMessage = this.cause instanceof Error ? this.cause.message : undefined; | |
| 65 | − if (this.operation === "spawn") { | |
| 66 | − return `Failed to run update command ${this.command ?? "unknown"}${causeMessage ? `: ${causeMessage}` : "."}`; | |
| 67 | − } | |
| 68 | − return causeMessage ?? "Update command failed to run."; | |
| 104 | + return `Failed to run update command ${this.command}: ${formatPlatformFailure(this)}`; | |
| 105 | + } | |
| 106 | +} | |
| 107 | + | |
| 108 | +class ProviderMaintenanceCommandCollectError extends Schema.TaggedErrorClass<ProviderMaintenanceCommandCollectError>()( | |
| 109 | + "ProviderMaintenanceCommandCollectError", | |
| 110 | + { | |
| 111 | + command: Schema.String, | |
| 112 | + ...platformFailureFields, | |
| 113 | + cause: Schema.Defect(), | |
| 114 | + }, | |
| 115 | +) { | |
| 116 | + override get message(): string { | |
| 117 | + return formatPlatformFailure(this); | |
| 69 | 118 | } |
| 70 | 119 | } |
| 71 | 120 | |
| @@ -89,9 +138,9 @@ const runProviderMaintenanceCommandWithSpawner = Effect.fn("ProviderMaintenanceR · 17 unmodified lines | ||
| 89 | 138 | .pipe( |
| 90 | 139 | Effect.mapError( |
| 91 | 140 | (cause) => |
| 92 | − new ProviderMaintenanceCommandError({ | |
| 93 | − operation: "spawn", | |
| 141 | + new ProviderMaintenanceCommandSpawnError({ | |
| 94 | 142 | command: input.command, |
| 143 | + ...platformFailureAttributes(cause), | |
| 95 | 144 | cause, |
| 96 | 145 | }), |
| 97 | 146 | ), |
| @@ -114,8 +163,9 @@ const runProviderMaintenanceCommandWithSpawner = Effect.fn("ProviderMaintenanceR · 16 unmodified lines | ||
| 114 | 163 | ).pipe( |
| 115 | 164 | Effect.mapError( |
| 116 | 165 | (cause) => |
| 117 | − new ProviderMaintenanceCommandError({ | |
| 118 | − operation: "collect", | |
| 166 | + new ProviderMaintenanceCommandCollectError({ | |
| 167 | + command: input.command, | |
| 168 | + ...platformFailureAttributes(cause), | |
| 119 | 169 | cause, |
| 120 | 170 | }), |
| 121 | 171 | ), |
| @@ -132,7 +132,7 @@ sequenceDiagram · 131 unmodified lines | ||
| 132 | 132 | [5]: ../apps/server/src/wsServer/pushBus.ts |
| 133 | 133 | [6]: ../packages/contracts/src/ws.ts |
| 134 | 134 | [7]: ../apps/server/src/serverLayers.ts |
| 135 | −[8]: ../apps/server/src/provider/Layers/ProviderService.ts | |
| 135 | +[8]: ../apps/server/src/provider/ProviderService.ts | |
| 136 | 136 | [9]: ../apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts |
| 137 | 137 | [10]: ../apps/server/src/orchestration/Layers/OrchestrationEngine.ts |
| 138 | 138 | [11]: ../packages/contracts/src/orchestration.ts |
| @@ -41,7 +41,7 @@ Effect.fn("name")( · 40 unmodified lines | ||
| 41 | 41 | |
| 42 | 42 | ## Suggested Order |
| 43 | 43 | |
| 44 | −- [ ] `apps/server/src/provider/Layers/ProviderService.ts` | |
| 44 | +- [ ] `apps/server/src/provider/ProviderService.ts` | |
| 45 | 45 | - [x] `apps/server/src/provider/Layers/ClaudeAdapter.ts` |
| 46 | 46 | - [x] `apps/server/src/provider/Layers/CodexAdapter.ts` |
| 47 | 47 | - [x] `apps/server/src/git/Layers/GitCore.ts` |
| @@ -98,20 +98,20 @@ Effect.fn("name")( · 50 unmodified lines | ||
| 98 | 98 | - [x] `Effect.forEach(..., entry => Effect.gen(...))` callbacks around `L305` |
| 99 | 99 | - [x] Remaining apply helpers in this file |
| 100 | 100 | |
| 101 | −### `apps/server/src/provider/Layers/ProviderService.ts` (`24`) | |
| 102 | − | |
| 103 | −- [ ] [makeProviderService](/Users/julius/Development/Work/codething-mvp/apps/server/src/provider/Layers/ProviderService.ts#L134) | |
| 104 | −- [ ] [recoverSessionForThread](/Users/julius/Development/Work/codething-mvp/apps/server/src/provider/Layers/ProviderService.ts#L196) | |
| 105 | −- [ ] [resolveRoutableSession](/Users/julius/Development/Work/codething-mvp/apps/server/src/provider/Layers/ProviderService.ts#L255) | |
| 106 | −- [ ] [startSession](/Users/julius/Development/Work/codething-mvp/apps/server/src/provider/Layers/ProviderService.ts#L284) | |
| 107 | −- [ ] [sendTurn](/Users/julius/Development/Work/codething-mvp/apps/server/src/provider/Layers/ProviderService.ts#L347) | |
| 108 | −- [ ] [interruptTurn](/Users/julius/Development/Work/codething-mvp/apps/server/src/provider/Layers/ProviderService.ts#L393) | |
| 109 | −- [ ] [respondToRequest](/Users/julius/Development/Work/codething-mvp/apps/server/src/provider/Layers/ProviderService.ts#L411) | |
| 110 | −- [ ] [respondToUserInput](/Users/julius/Development/Work/codething-mvp/apps/server/src/provider/Layers/ProviderService.ts#L430) | |
| 111 | −- [ ] [stopSession](/Users/julius/Development/Work/codething-mvp/apps/server/src/provider/Layers/ProviderService.ts#L445) | |
| 112 | −- [ ] [listSessions](/Users/julius/Development/Work/codething-mvp/apps/server/src/provider/Layers/ProviderService.ts#L466) | |
| 113 | −- [ ] [rollbackConversation](/Users/julius/Development/Work/codething-mvp/apps/server/src/provider/Layers/ProviderService.ts#L516) | |
| 114 | −- [ ] [runStopAll](/Users/julius/Development/Work/codething-mvp/apps/server/src/provider/Layers/ProviderService.ts#L538) | |
| 101 | +### `apps/server/src/provider/ProviderService.ts` (`24`) | |
| 102 | + | |
| 103 | +- [ ] [makeProviderService](/Users/julius/Development/Work/codething-mvp/apps/server/src/provider/ProviderService.ts#L134) | |
| 104 | +- [ ] [recoverSessionForThread](/Users/julius/Development/Work/codething-mvp/apps/server/src/provider/ProviderService.ts#L196) | |
| 105 | +- [ ] [resolveRoutableSession](/Users/julius/Development/Work/codething-mvp/apps/server/src/provider/ProviderService.ts#L255) | |
| 106 | +- [ ] [startSession](/Users/julius/Development/Work/codething-mvp/apps/server/src/provider/ProviderService.ts#L284) | |
| 107 | +- [ ] [sendTurn](/Users/julius/Development/Work/codething-mvp/apps/server/src/provider/ProviderService.ts#L347) | |
| 108 | +- [ ] [interruptTurn](/Users/julius/Development/Work/codething-mvp/apps/server/src/provider/ProviderService.ts#L393) | |
| 109 | +- [ ] [respondToRequest](/Users/julius/Development/Work/codething-mvp/apps/server/src/provider/ProviderService.ts#L411) | |
| 110 | +- [ ] [respondToUserInput](/Users/julius/Development/Work/codething-mvp/apps/server/src/provider/ProviderService.ts#L430) | |
| 111 | +- [ ] [stopSession](/Users/julius/Development/Work/codething-mvp/apps/server/src/provider/ProviderService.ts#L445) | |
| 112 | +- [ ] [listSessions](/Users/julius/Development/Work/codething-mvp/apps/server/src/provider/ProviderService.ts#L466) | |
| 113 | +- [ ] [rollbackConversation](/Users/julius/Development/Work/codething-mvp/apps/server/src/provider/ProviderService.ts#L516) | |
| 114 | +- [ ] [runStopAll](/Users/julius/Development/Work/codething-mvp/apps/server/src/provider/ProviderService.ts#L538) | |
| 115 | 115 | |
| 116 | 116 | ### `apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts` (`14`) |
| 117 | 117 | |
| @@ -174,7 +174,7 @@ Effect.fn("name")( · 56 unmodified lines | ||
| 174 | 174 | - [ ] [packages/shared/src/DrainableWorker.ts](/Users/julius/Development/Work/codething-mvp/packages/shared/src/DrainableWorker.ts) (`4`) |
| 175 | 175 | - [ ] [apps/server/src/wsServer/pushBus.ts](/Users/julius/Development/Work/codething-mvp/apps/server/src/wsServer/pushBus.ts) (`4`) |
| 176 | 176 | - [ ] [apps/server/src/wsServer.ts](/Users/julius/Development/Work/codething-mvp/apps/server/src/wsServer.ts) (`4`) |
| 177 | −- [ ] [apps/server/src/provider/Layers/ProviderRegistry.ts](/Users/julius/Development/Work/codething-mvp/apps/server/src/provider/Layers/ProviderRegistry.ts) (`4`) | |
| 177 | +- [ ] [apps/server/src/provider/ProviderRegistry.ts](/Users/julius/Development/Work/codething-mvp/apps/server/src/provider/ProviderRegistry.ts) (`4`) | |
| 178 | 178 | - [ ] [apps/server/src/persistence/Layers/Sqlite.ts](/Users/julius/Development/Work/codething-mvp/apps/server/src/persistence/Layers/Sqlite.ts) (`4`) |
| 179 | 179 | - [ ] [apps/server/src/orchestration/Layers/ProviderCommandReactor.ts](/Users/julius/Development/Work/codething-mvp/apps/server/src/orchestration/Layers/ProviderCommandReactor.ts) (`4`) |
| 180 | 180 | - [ ] [apps/server/src/main.ts](/Users/julius/Development/Work/codething-mvp/apps/server/src/main.ts) (`4`) |
| @@ -183,7 +183,7 @@ Effect.fn("name")( · 2 unmodified lines | ||
| 183 | 183 | - [ ] [apps/server/src/serverLayers.ts](/Users/julius/Development/Work/codething-mvp/apps/server/src/serverLayers.ts) (`3`) |
| 184 | 184 | - [ ] [apps/server/src/telemetry/Layers/AnalyticsService.ts](/Users/julius/Development/Work/codething-mvp/apps/server/src/telemetry/Layers/AnalyticsService.ts) (`2`) |
| 185 | 185 | - [ ] [apps/server/src/telemetry/Identify.ts](/Users/julius/Development/Work/codething-mvp/apps/server/src/telemetry/Identify.ts) (`2`) |
| 186 | −- [ ] [apps/server/src/provider/Layers/ProviderAdapterRegistry.ts](/Users/julius/Development/Work/codething-mvp/apps/server/src/provider/Layers/ProviderAdapterRegistry.ts) (`2`) | |
| 186 | +- [ ] [apps/server/src/provider/ProviderAdapterRegistry.ts](/Users/julius/Development/Work/codething-mvp/apps/server/src/provider/ProviderAdapterRegistry.ts) (`2`) | |
| 187 | 187 | - [ ] [apps/server/src/provider/Layers/CodexProvider.ts](/Users/julius/Development/Work/codething-mvp/apps/server/src/provider/Layers/CodexProvider.ts) (`2`) |
| 188 | 188 | - [ ] [apps/server/src/provider/Layers/ClaudeProvider.ts](/Users/julius/Development/Work/codething-mvp/apps/server/src/provider/Layers/ClaudeProvider.ts) (`2`) |
| 189 | 189 | - [ ] [apps/server/src/persistence/NodeSqliteClient.ts](/Users/julius/Development/Work/codething-mvp/apps/server/src/persistence/NodeSqliteClient.ts) (`2`) |
| @@ -167,7 +167,7 @@ The file patch and changed-file summary for one turn. It is usually computed in · 166 unmodified lines | ||
| 167 | 167 | [11]: ../apps/server/src/orchestration/Layers/ProjectionPipeline.ts |
| 168 | 168 | [12]: ../apps/server/src/orchestration/Layers/ProviderCommandReactor.ts |
| 169 | 169 | [13]: ../apps/server/src/orchestration/Services/RuntimeReceiptBus.ts |
| 170 | −[14]: ../apps/server/src/provider/Layers/ProviderService.ts | |
| 170 | +[14]: ../apps/server/src/provider/ProviderService.ts | |
| 171 | 171 | [15]: ../apps/server/src/provider/Services/ProviderAdapter.ts |
| 172 | 172 | [16]: ./provider-architecture.md |
| 173 | 173 | [17]: ../apps/server/src/provider/Layers/CodexAdapter.ts |