History

Tighten provider Effect service refactor

Julius Marminge committed 4 months ago

Commit 84207d01 parent 5f81c3a30 files · +323 −205Browse files

Unified · Split

30 files
  1. apps/server/integration/OrchestrationEngineHarness.integration.ts +15 −16
  2. apps/server/integration/providerService.integration.test.ts +1 −3
  3. apps/server/src/orchestration/Layers/CheckpointReactor.test.ts +3 −6
  4. apps/server/src/orchestration/Layers/CheckpointReactor.ts +2 −2
  5. apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts +17 −14
  6. apps/server/src/orchestration/Layers/ProviderCommandReactor.ts +4 −4
  7. apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts +3 −6
  8. apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts +2 −2
  9. apps/server/src/orchestration/Layers/ThreadDeletionReactor.ts +2 −2
  10. apps/server/src/provider/Layers/OpenCodeProvider.test.ts +25 −0
  11. apps/server/src/provider/Layers/ProviderEventLoggers.ts +0 −6
  12. apps/server/src/provider/Layers/ProviderService.ts +0 −11
  13. apps/server/src/provider/Layers/ProviderSessionDirectory.ts +0 −6
  14. apps/server/src/provider/ProviderAdapterRegistry.ts +26 −9
  15. apps/server/src/provider/ProviderInstanceRegistry.ts +29 −16
  16. apps/server/src/provider/ProviderRegistry.ts +20 −22
  17. apps/server/src/provider/ProviderService.test.ts +18 −15
  18. apps/server/src/provider/ProviderService.ts +28 −15
  19. apps/server/src/provider/ProviderSessionDirectory.test.ts +1 −3
  20. apps/server/src/provider/ProviderSessionDirectory.ts +4 −1
  21. apps/server/src/provider/ProviderSessionReaper.ts +1 −0
  22. apps/server/src/provider/Services/ProviderAdapterRegistry.ts +0 −3
  23. apps/server/src/provider/Services/ProviderRegistry.ts +0 −2
  24. apps/server/src/provider/Services/ProviderService.ts +0 −5
  25. apps/server/src/provider/opencodeRuntime.ts +9 −4
  26. apps/server/src/provider/providerMaintenanceRunner.test.ts +31 −0
  27. apps/server/src/provider/providerMaintenanceRunner.ts +63 −13
  28. docs/architecture/overview.md +1 −1
  29. docs/operations/effect-fn-checklist.md +17 −17
  30. docs/reference/encyclopedia.md +1 −1
apps/server/integration/OrchestrationEngineHarness.integration.ts+15−16
@@ -33,17 +33,13 @@ import { makeSqlitePersistenceLive } from "../src/persistence/Layers/Sqlite.ts"; · 32 unmodified lines
3333 import { ProjectionCheckpointRepository } from "../src/persistence/Services/ProjectionCheckpoints.ts";
3434 import { ProjectionPendingApprovalRepository } from "../src/persistence/Services/ProjectionPendingApprovals.ts";
3535 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";
3737 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";
3939 import { ServerSettingsService } from "../src/serverSettings.ts";
40−import { makeProviderServiceLive } from "../src/provider/Layers/ProviderService.ts";
40+import * as ProviderService from "../src/provider/ProviderService.ts";
4141 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";
4743 import { AnalyticsService } from "../src/telemetry/Services/AnalyticsService.ts";
4844 import { CheckpointReactorLive } from "../src/orchestration/Layers/CheckpointReactor.ts";
4945 import * as RepositoryIdentityResolver from "../src/project/RepositoryIdentityResolver.ts";
@@ -178,7 +174,7 @@ export interface OrchestrationIntegrationHarness { · 128 unmodified lines
178174 readonly adapterHarness: TestProviderAdapterHarness | null;
179175 readonly engine: OrchestrationEngineShape;
180176 readonly snapshotQuery: ProjectionSnapshotQuery["Service"];
181− readonly providerService: ProviderService["Service"];
177+ readonly providerService: ProviderService.ProviderService["Service"];
182178 readonly checkpointStore: CheckpointStore.CheckpointStore["Service"];
183179 readonly checkpointRepository: ProjectionCheckpointRepository["Service"];
184180 readonly pendingApprovalRepository: ProjectionPendingApprovalRepository["Service"];
@@ -241,7 +237,7 @@ export const makeOrchestrationIntegrationHarness = ( · 56 unmodified lines
241237 });
242238 const fakeRegistry = adapterHarness
243239 ? Layer.succeed(
244− ProviderAdapterRegistry,
240+ ProviderAdapterRegistry.ProviderAdapterRegistry,
245241 makeAdapterRegistryMock({ [adapterHarness.provider]: adapterHarness.adapter }),
246242 )
247243 : null;
@@ -262,11 +258,11 @@ export const makeOrchestrationIntegrationHarness = ( · 14 unmodified lines
262258 Layer.provide(OrchestrationEventStoreLive),
263259 Layer.provide(OrchestrationCommandReceiptRepositoryLive),
264260 );
265− const providerSessionDirectoryLayer = ProviderSessionDirectoryLive.pipe(
261+ const providerSessionDirectoryLayer = ProviderSessionDirectory.layer.pipe(
266262 Layer.provide(ProviderSessionRuntimeRepositoryLive),
267263 );
268264 const realCodexRegistry = Layer.effect(
269− ProviderAdapterRegistry,
265+ ProviderAdapterRegistry.ProviderAdapterRegistry,
270266 Effect.gen(function* () {
271267 const codexSettings = yield* decodeCodexSettings({});
272268 const codexAdapter = yield* makeCodexAdapter(codexSettings);
@@ -279,15 +275,18 @@ export const makeOrchestrationIntegrationHarness = ( · 6 unmodified lines
279275 Layer.provideMerge(NodeServices.layer),
280276 Layer.provideMerge(providerSessionDirectoryLayer),
281277 );
282− const providerEventLoggersLayer = Layer.succeed(ProviderEventLoggers, NoOpProviderEventLoggers);
278+ const providerEventLoggersLayer = Layer.succeed(
279+ ProviderEventLoggers.ProviderEventLoggers,
280+ ProviderEventLoggers.NoOpProviderEventLoggers,
281+ );
283282 const providerLayer = useRealCodex
284− ? makeProviderServiceLive().pipe(
283+ ? ProviderService.layer.pipe(
285284 Layer.provide(providerSessionDirectoryLayer),
286285 Layer.provide(realCodexRegistry),
287286 Layer.provide(AnalyticsService.layerTest),
288287 Layer.provide(providerEventLoggersLayer),
289288 )
290− : makeProviderServiceLive().pipe(
289+ : ProviderService.layer.pipe(
291290 Layer.provide(providerSessionDirectoryLayer),
292291 Layer.provide(fakeRegistry!),
293292 Layer.provide(AnalyticsService.layerTest),
@@ -395,7 +394,7 @@ export const makeOrchestrationIntegrationHarness = ( · 101 unmodified lines
395394 runtime.runPromise(Effect.service(ProjectionSnapshotQuery)),
396395 ).pipe(Effect.orDie);
397396 const providerService = yield* tryRuntimePromise("load ProviderService service", () =>
398− runtime.runPromise(Effect.service(ProviderService)),
397+ runtime.runPromise(Effect.service(ProviderService.ProviderService)),
399398 ).pipe(Effect.orDie);
400399 const checkpointStore = yield* tryRuntimePromise("load CheckpointStore service", () =>
401400 runtime.runPromise(Effect.service(CheckpointStore.CheckpointStore)),
apps/server/integration/providerService.integration.test.ts+1−3
@@ -70,9 +70,7 @@ const makeIntegrationFixture = Effect.gen(function* () { · 69 unmodified lines
7070 ),
7171 ).pipe(Layer.provide(SqlitePersistenceMemory));
7272
73− const layer = Layer.effect(ProviderService.ProviderService, ProviderService.make()).pipe(
74− Layer.provide(shared),
75− );
73+ const layer = ProviderService.layer.pipe(Layer.provide(shared));
7674
7775 return {
7876 cwd,
apps/server/src/orchestration/Layers/CheckpointReactor.test.ts+3−6
@@ -49,10 +49,7 @@ import { · 48 unmodified lines
4949 } from "../Services/OrchestrationEngine.ts";
5050 import { CheckpointReactor } from "../Services/CheckpointReactor.ts";
5151 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";
5653 import { checkpointRefForThreadTurn } from "../../checkpointing/Utils.ts";
5754 import { ServerConfig } from "../../config.ts";
5855 import * as WorkspaceEntries from "../../workspace/WorkspaceEntries.ts";
@@ -102,7 +99,7 @@ function createProviderServiceHarness( · 43 unmodified lines
10299 },
103100 ] satisfies ReadonlyArray<ProviderSession>)
104101 : Effect.succeed([] as ReadonlyArray<ProviderSession>);
105− const service: ProviderServiceShape = {
102+ const service: ProviderService.ProviderService["Service"] = {
106103 startSession: () => unsupported(),
107104 sendTurn: () => unsupported(),
108105 interruptTurn: () => unsupported(),
@@ -328,7 +325,7 @@ describe("CheckpointReactor", () => { · 219 unmodified lines
328325 Layer.provideMerge(orchestrationLayer),
329326 Layer.provideMerge(projectionSnapshotLayer),
330327 Layer.provideMerge(RuntimeReceiptBusLive),
331− Layer.provideMerge(Layer.succeed(ProviderService, provider.service)),
328+ Layer.provideMerge(Layer.succeed(ProviderService.ProviderService, provider.service)),
332329 Layer.provideMerge(vcsStatusBroadcasterLayer),
333330 Layer.provideMerge(CheckpointStore.layer.pipe(Layer.provide(VcsDriverRegistry.layer))),
334331 Layer.provideMerge(
apps/server/src/orchestration/Layers/CheckpointReactor.ts+2−2
@@ -25,7 +25,7 @@ import { · 24 unmodified lines
2525 resolveThreadWorkspaceCwd,
2626 } from "../../checkpointing/Utils.ts";
2727 import * as CheckpointStore from "../../checkpointing/CheckpointStore.ts";
28−import { ProviderService } from "../../provider/Services/ProviderService.ts";
28+import * as ProviderService from "../../provider/ProviderService.ts";
2929 import { CheckpointReactor, type CheckpointReactorShape } from "../Services/CheckpointReactor.ts";
3030 import { OrchestrationEngineService } from "../Services/OrchestrationEngine.ts";
3131 import { ProjectionSnapshotQuery } from "../Services/ProjectionSnapshotQuery.ts";
@@ -80,7 +80,7 @@ const make = Effect.gen(function* () { · 48 unmodified lines
8080 randomUUID.pipe(Effect.map((uuid) => CommandId.make(`server:${tag}:${uuid}`)));
8181 const orchestrationEngine = yield* OrchestrationEngineService;
8282 const projectionSnapshotQuery = yield* ProjectionSnapshotQuery;
83− const providerService = yield* ProviderService;
83+ const providerService = yield* ProviderService.ProviderService;
8484 const checkpointStore = yield* CheckpointStore.CheckpointStore;
8585 const receiptBus = yield* RuntimeReceiptBus;
8686 const workspaceEntries = yield* WorkspaceEntries.WorkspaceEntries;
apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts+17−14
@@ -37,10 +37,7 @@ import { ProviderAdapterRequestError } from "../../provider/Errors.ts"; · 36 unmodified lines
3737 import { OrchestrationEventStoreLive } from "../../persistence/Layers/OrchestrationEventStore.ts";
3838 import { OrchestrationCommandReceiptRepositoryLive } from "../../persistence/Layers/OrchestrationCommandReceipts.ts";
3939 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";
4441 import { makeProviderRegistryLayer } from "../../provider/testUtils/providerRegistryMock.ts";
4542 import { TextGeneration, type TextGenerationShape } from "../../textGeneration/TextGeneration.ts";
4643 import * as RepositoryIdentityResolver from "../../project/RepositoryIdentityResolver.ts";
@@ -222,8 +219,12 @@ describe("ProviderCommandReactor", () => { · 175 unmodified lines
222219 }),
223220 );
224221 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);
227228 const stopSession = vi.fn((input: unknown) =>
228229 Effect.sync(() => {
229230 const threadId =
@@ -294,13 +295,15 @@ describe("ProviderCommandReactor", () => { · 64 unmodified lines
294295 ];
295296
296297 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"],
304307 listSessions: () => Effect.succeed(runtimeSessions),
305308 getCapabilities: (_provider) =>
306309 Effect.succeed({
@@ -346,7 +349,7 @@ describe("ProviderCommandReactor", () => { · 39 unmodified lines
346349 const layer = ProviderCommandReactorLive.pipe(
347350 Layer.provideMerge(orchestrationLayer),
348351 Layer.provideMerge(projectionSnapshotLayer),
349− Layer.provideMerge(Layer.succeed(ProviderService, service)),
352+ Layer.provideMerge(Layer.succeed(ProviderService.ProviderService, service)),
350353 Layer.provideMerge(makeProviderRegistryLayer(providerSnapshots as never)),
351354 Layer.provideMerge(
352355 Layer.mock(GitWorkflowService.GitWorkflowService)({
apps/server/src/orchestration/Layers/ProviderCommandReactor.ts+4−4
@@ -30,8 +30,8 @@ import { increment, orchestrationEventsProcessedTotal } from "../../observabilit · 29 unmodified lines
3030 import { ProviderAdapterRequestError } from "../../provider/Errors.ts";
3131 import type { ProviderServiceError } from "../../provider/Errors.ts";
3232 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";
3535 import { OrchestrationEngineService } from "../Services/OrchestrationEngine.ts";
3636 import { ProjectionSnapshotQuery } from "../Services/ProjectionSnapshotQuery.ts";
3737 import {
@@ -190,8 +190,8 @@ const make = Effect.gen(function* () { · 152 unmodified lines
190190 const crypto = yield* Crypto.Crypto;
191191 const orchestrationEngine = yield* OrchestrationEngineService;
192192 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;
195195 const gitWorkflow = yield* GitWorkflowService;
196196 const vcsStatusBroadcaster = yield* VcsStatusBroadcaster;
197197 const textGeneration = yield* TextGeneration;
apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts+3−6
@@ -35,10 +35,7 @@ import { afterEach, describe, expect, it } from "vite-plus/test"; · 34 unmodified lines
3535 import { OrchestrationEventStoreLive } from "../../persistence/Layers/OrchestrationEventStore.ts";
3636 import { OrchestrationCommandReceiptRepositoryLive } from "../../persistence/Layers/OrchestrationCommandReceipts.ts";
3737 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";
4239 import * as RepositoryIdentityResolver from "../../project/RepositoryIdentityResolver.ts";
4340 import { OrchestrationEngineLive } from "./OrchestrationEngine.ts";
4441 import { OrchestrationProjectionPipelineLive } from "./ProjectionPipeline.ts";
@@ -97,7 +94,7 @@ function createProviderServiceHarness() { · 52 unmodified lines
9794 const runtimeSessions: ProviderSession[] = [];
9895
9996 const unsupported = () => Effect.die(new Error("Unsupported provider call in test")) as never;
100− const service: ProviderServiceShape = {
97+ const service: ProviderService.ProviderService["Service"] = {
10198 startSession: () => unsupported(),
10299 sendTurn: () => unsupported(),
103100 interruptTurn: () => unsupported(),
@@ -237,7 +234,7 @@ describe("ProviderRuntimeIngestion", () => { · 133 unmodified lines
237234 Layer.provideMerge(orchestrationLayer),
238235 Layer.provideMerge(projectionSnapshotLayer),
239236 Layer.provideMerge(SqlitePersistenceMemory),
240− Layer.provideMerge(Layer.succeed(ProviderService, provider.service)),
237+ Layer.provideMerge(Layer.succeed(ProviderService.ProviderService, provider.service)),
241238 Layer.provideMerge(makeTestServerSettingsLayer(options?.serverSettings)),
242239 Layer.provideMerge(ServerConfig.layerTest(process.cwd(), process.cwd())),
243240 Layer.provideMerge(NodeServices.layer),
apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts+2−2
@@ -27,7 +27,7 @@ import * as Option from "effect/Option"; · 26 unmodified lines
2727 import * as Stream from "effect/Stream";
2828 import { makeDrainableWorker } from "@t3tools/shared/DrainableWorker";
2929
30−import { ProviderService } from "../../provider/Services/ProviderService.ts";
30+import * as ProviderService from "../../provider/ProviderService.ts";
3131 import { ProjectionTurnRepository } from "../../persistence/Services/ProjectionTurns.ts";
3232 import { ProjectionTurnRepositoryLive } from "../../persistence/Layers/ProjectionTurns.ts";
3333 import { isGitRepository } from "../../git/Utils.ts";
@@ -631,7 +631,7 @@ const make = Effect.gen(function* () { · 597 unmodified lines
631631 const crypto = yield* Crypto.Crypto;
632632 const orchestrationEngine = yield* OrchestrationEngineService;
633633 const projectionSnapshotQuery = yield* ProjectionSnapshotQuery;
634− const providerService = yield* ProviderService;
634+ const providerService = yield* ProviderService.ProviderService;
635635 const projectionTurnRepository = yield* ProjectionTurnRepository;
636636 const serverSettingsService = yield* ServerSettingsService;
637637 const providerCommandId = (event: ProviderRuntimeEvent, tag: string) =>
apps/server/src/orchestration/Layers/ThreadDeletionReactor.ts+2−2
@@ -5,7 +5,7 @@ import * as Effect from "effect/Effect"; · 4 unmodified lines
55 import * as Layer from "effect/Layer";
66 import * as Stream from "effect/Stream";
77
8−import { ProviderService } from "../../provider/Services/ProviderService.ts";
8+import * as ProviderService from "../../provider/ProviderService.ts";
99 import * as TerminalManager from "../../terminal/Manager.ts";
1010 import { OrchestrationEngineService } from "../Services/OrchestrationEngine.ts";
1111 import {
@@ -38,7 +38,7 @@ export const logCleanupCauseUnlessInterrupted = <R, E>({ · 26 unmodified lines
3838
3939 const make = Effect.gen(function* () {
4040 const orchestrationEngine = yield* OrchestrationEngineService;
41− const providerService = yield* ProviderService;
41+ const providerService = yield* ProviderService.ProviderService;
4242 const terminalManager = yield* TerminalManager.TerminalManager;
4343
4444 const stopProviderSession = (threadId: ThreadDeletedEvent["payload"]["threadId"]) =>
apps/server/src/provider/Layers/OpenCodeProvider.test.ts+25−0
@@ -16,6 +16,31 @@ const decodeOpenCodeSettings = Schema.decodeSync(OpenCodeSettings); · 15 unmodified lines
1616
1717 const DEFAULT_VERSION_STDOUT = "opencode 1.14.19\n";
1818
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+
1944 /**
2045 * The legacy `OpenCodeProviderLive` Layer + `OpenCodeProvider` service tag
2146 * are deleted. The snapshot-producing logic they wrapped now lives in the
apps/server/src/provider/Layers/ProviderEventLoggers.ts+0−6
@@ -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;
apps/server/src/provider/Layers/ProviderService.ts+0−11
@@ -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));
apps/server/src/provider/Layers/ProviderSessionDirectory.ts+0−6
@@ -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;
apps/server/src/provider/ProviderAdapterRegistry.ts+26−9
@@ -12,11 +12,16 @@ · 11 unmodified lines
1212 * - `getByInstance` / `listInstances` — new per-instance routing. Callers
1313 * that already know an `instanceId` (threads, sessions, events)
1414 * should prefer these.
15+ * - `listProviders` — legacy kind-keyed routing for default instances
1516 * (`defaultInstanceIdForDriver(kind) === kind`), matching the pre-Slice-D
1617 * behaviour. New code should not grow additional callers of the kind-keyed
1718 * methods; they exist so the settings UI, WS refresh RPC, and a handful
1819 * of legacy persisted rows can still be routed during the rollout.
1920 *
21+ * Adapter lookups are resolved dynamically through `ProviderInstanceRegistry`.
22+ * Settings-driven hot reload is therefore visible immediately without
23+ * rebuilding this facade.
24+ *
2025 * @module ProviderAdapterRegistry
2126 */
2227 import {
@@ -50,7 +55,10 @@ export class ProviderAdapterRegistry extends Context.Service< · 27 unmodified lines
5055 {
5156 /**
5257 * 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).
5462 */
5563 readonly getByInstance: (
5664 instanceId: ProviderInstanceId,
@@ -62,28 +70,33 @@ export class ProviderAdapterRegistry extends Context.Service< · 5 unmodified lines
6270 ) => Effect.Effect<ProviderInstanceRoutingInfo, ProviderUnsupportedError>;
6371
6472 /**
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`.
6775 */
6876 readonly listInstances: () => Effect.Effect<ReadonlyArray<ProviderInstanceId>>;
6977
7078 /**
7179 * List provider kinds whose default instance is currently registered.
7280 *
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.
7583 */
7684 readonly listProviders: () => Effect.Effect<ReadonlyArray<ProviderDriverKind>>;
7785
7886 /**
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.
8192 */
8293 readonly streamChanges: Stream.Stream<void>;
8394
8495 /**
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`.
87100 */
88101 readonly subscribeChanges: Effect.Effect<PubSub.Subscription<void>, never, Scope.Scope>;
89102 }
@@ -130,6 +143,8 @@ export const make = Effect.gen(function* () { · 40 unmodified lines
130143 const kinds = new Set<ProviderDriverKind>();
131144 for (const instance of instances) {
132145 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.
133148 kinds.add(instance.driverKind);
134149 }
135150 }
@@ -142,6 +157,8 @@ export const make = Effect.gen(function* () { · 6 unmodified lines
142157 getInstanceInfo,
143158 listInstances,
144159 listProviders,
160+ // The facade owns no state; the instance registry already coalesces
161+ // add, remove, and rebuild notifications into a single change channel.
145162 streamChanges: registry.streamChanges,
146163 subscribeChanges: registry.subscribeChanges,
147164 });
apps/server/src/provider/ProviderInstanceRegistry.ts+29−16
@@ -58,38 +58,46 @@ export class ProviderInstanceRegistry extends Context.Service< · 57 unmodified lines
5858 ProviderInstanceRegistry,
5959 {
6060 /**
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.
6364 */
6465 readonly getInstance: (
6566 instanceId: ProviderInstanceId,
6667 ) => Effect.Effect<ProviderInstance | undefined>;
6768
6869 /**
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.
7172 */
7273 readonly listInstances: Effect.Effect<ReadonlyArray<ProviderInstance>>;
7374
7475 /**
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.
7779 */
7880 readonly listUnavailable: Effect.Effect<ReadonlyArray<ServerProvider>>;
7981
8082 /**
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.
8386 *
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.
8792 */
8893 readonly streamChanges: Stream.Stream<void>;
8994
9095 /**
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.
93101 */
94102 readonly subscribeChanges: Effect.Effect<PubSub.Subscription<void>, never, Scope.Scope>;
95103 }
@@ -107,9 +115,14 @@ export class ProviderInstanceRegistryMutator extends Context.Service< · 11 unmodified lines
107115 ProviderInstanceRegistryMutator,
108116 {
109117 /**
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.
113126 */
114127 readonly reconcile: (configMap: ProviderInstanceConfigMap) => Effect.Effect<void>;
115128 }
apps/server/src/provider/ProviderRegistry.ts+20−22
@@ -64,13 +64,18 @@ export class ProviderRegistry extends Context.Service< · 63 unmodified lines
6464 ProviderRegistry,
6565 {
6666 /**
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`.
6970 */
7071 readonly getProviders: Effect.Effect<ReadonlyArray<ServerProvider>>;
7172
7273 /**
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.
7479 *
7580 * @deprecated Prefer `refreshInstance` for new call sites.
7681 */
@@ -80,15 +85,16 @@ export class ProviderRegistry extends Context.Service< · 3 unmodified lines
8085
8186 /**
8287 * 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.
8490 */
8591 readonly refreshInstance: (
8692 instanceId: ProviderInstanceId,
8793 ) => Effect.Effect<ReadonlyArray<ServerProvider>>;
8894
8995 /**
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.
9298 */
9399 readonly getProviderMaintenanceCapabilitiesForInstance: (
94100 instanceId: ProviderInstanceId,
@@ -96,8 +102,10 @@ export class ProviderRegistry extends Context.Service< · 1 unmodified line
96102 ) => Effect.Effect<ProviderMaintenanceCapabilities>;
97103
98104 /**
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.
101109 */
102110 readonly setProviderMaintenanceActionState: (input: {
103111 readonly instanceId: ProviderInstanceId;
@@ -105,7 +113,10 @@ export class ProviderRegistry extends Context.Service< · 1 unmodified line
105113 readonly state: ServerProviderUpdateState | null;
106114 }) => Effect.Effect<ReadonlyArray<ServerProvider>>;
107115
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+ */
109120 readonly streamChanges: Stream.Stream<ReadonlyArray<ServerProvider>>;
110121 }
111122 >()("t3/provider/ProviderRegistry") {}
@@ -671,19 +682,6 @@ export const make = Effect.gen(function* () { · 559 unmodified lines
671682 // and pending fallbacks fill the gaps.
672683 yield* upsertProviders(fallbackProviders, { publish: false });
673684 // 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.
687685 // `subscribeChanges` acquires the `PubSub.Subscription` synchronously
688686 // in this fibre; the subscription is registered with the PubSub the
689687 // instant this `yield*` returns, so any subsequent publish is
apps/server/src/provider/ProviderService.test.ts+18−15
@@ -55,10 +55,13 @@ import { · 54 unmodified lines
5555 } from "../persistence/Layers/Sqlite.ts";
5656 import * as ServerSettings from "../serverSettings.ts";
5757 import * as AnalyticsService from "../telemetry/AnalyticsService.ts";
58+import { makeAdapterRegistryMock } from "./testUtils/providerAdapterRegistryMock.ts";
5859
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) =>
6064 Layer.effect(ProviderService.ProviderService, ProviderService.make(options));
61−import { makeAdapterRegistryMock } from "./testUtils/providerAdapterRegistryMock.ts";
6265
6366 const defaultServerSettingsLayer = ServerSettings.ServerSettingsService.layerTest();
6467
@@ -289,7 +292,7 @@ function makeProviderServiceLayer() { · 224 unmodified lines
289292
290293 const layer = it.layer(
291294 Layer.mergeAll(
292− makeProviderServiceLive().pipe(
295+ makeFreshProviderServiceLayer().pipe(
293296 Layer.provide(providerAdapterLayer),
294297 Layer.provide(directoryLayer),
295298 Layer.provide(defaultServerSettingsLayer),
@@ -342,7 +345,7 @@ it.effect("ProviderServiceLive catches stopAll failures during shutdown", () => · 46 unmodified lines
342345 Layer.provide(runtimeRepositoryLayer),
343346 );
344347 const providerLayer = Layer.mergeAll(
345− makeProviderServiceLive().pipe(
348+ makeFreshProviderServiceLayer().pipe(
346349 Layer.provide(providerAdapterLayer),
347350 Layer.provide(directoryLayer),
348351 Layer.provide(defaultServerSettingsLayer),
@@ -403,7 +406,7 @@ it.effect("ProviderServiceLive rejects new sessions for disabled providers", () · 54 unmodified lines
403406 const directoryLayer = ProviderSessionDirectory.layer.pipe(
404407 Layer.provide(runtimeRepositoryLayer),
405408 );
406− const providerLayer = makeProviderServiceLive().pipe(
409+ const providerLayer = makeFreshProviderServiceLayer().pipe(
407410 Layer.provide(providerAdapterLayer),
408411 Layer.provide(directoryLayer),
409412 Layer.provide(defaultServerSettingsLayer),
@@ -487,7 +490,7 @@ it.effect( · 77 unmodified lines
487490 const directoryLayer = ProviderSessionDirectory.layer.pipe(
488491 Layer.provide(runtimeRepositoryLayer),
489492 );
490− const providerLayer = makeProviderServiceLive().pipe(
493+ const providerLayer = makeFreshProviderServiceLayer().pipe(
491494 Layer.provide(providerAdapterLayer),
492495 Layer.provide(directoryLayer),
493496 Layer.provide(serverSettingsLayer),
@@ -559,7 +562,7 @@ it.effect("ProviderServiceLive rejects new sessions for disabled custom instance · 65 unmodified lines
559562 const directoryLayer = ProviderSessionDirectory.layer.pipe(
560563 Layer.provide(runtimeRepositoryLayer),
561564 );
562− const providerLayer = makeProviderServiceLive().pipe(
565+ const providerLayer = makeFreshProviderServiceLayer().pipe(
563566 Layer.provide(providerAdapterLayer),
564567 Layer.provide(directoryLayer),
565568 Layer.provide(defaultServerSettingsLayer),
@@ -606,7 +609,7 @@ it.effect("ProviderServiceLive writes canonical events to the emitting thread se · 40 unmodified lines
606609 const directoryLayer = ProviderSessionDirectory.layer.pipe(
607610 Layer.provide(runtimeRepositoryLayer),
608611 );
609− const providerLayer = makeProviderServiceLive({
612+ const providerLayer = makeFreshProviderServiceLayer({
610613 canonicalEventLogger: {
611614 filePath: "memory://provider-canonical-events",
612615 write: (event, threadId) => {
@@ -678,7 +681,7 @@ it.effect("ProviderServiceLive keeps persisted resumable sessions on startup", ( · 65 unmodified lines
678681 });
679682 }).pipe(Effect.provide(directoryLayer));
680683
681− const providerLayer = makeProviderServiceLive().pipe(
684+ const providerLayer = makeFreshProviderServiceLayer().pipe(
682685 Layer.provide(Layer.succeed(ProviderAdapterRegistry.ProviderAdapterRegistry, registry)),
683686 Layer.provide(directoryLayer),
684687 Layer.provide(defaultServerSettingsLayer),
@@ -742,7 +745,7 @@ it.effect( · 57 unmodified lines
742745 const firstDirectoryLayer = ProviderSessionDirectory.layer.pipe(
743746 Layer.provide(runtimeRepositoryLayer),
744747 );
745− const firstProviderLayer = makeProviderServiceLive().pipe(
748+ const firstProviderLayer = makeFreshProviderServiceLayer().pipe(
746749 Layer.provide(
747750 Layer.succeed(ProviderAdapterRegistry.ProviderAdapterRegistry, firstRegistry),
748751 ),
@@ -801,7 +804,7 @@ it.effect( · 52 unmodified lines
801804 const secondDirectoryLayer = ProviderSessionDirectory.layer.pipe(
802805 Layer.provide(runtimeRepositoryLayer),
803806 );
804− const secondProviderLayer = makeProviderServiceLive().pipe(
807+ const secondProviderLayer = makeFreshProviderServiceLayer().pipe(
805808 Layer.provide(
806809 Layer.succeed(ProviderAdapterRegistry.ProviderAdapterRegistry, secondRegistry),
807810 ),
@@ -1312,7 +1315,7 @@ routing.layer("ProviderServiceLive routing", (it) => { · 504 unmodified lines
13121315 const firstDirectoryLayer = ProviderSessionDirectory.layer.pipe(
13131316 Layer.provide(runtimeRepositoryLayer),
13141317 );
1315− const firstProviderLayer = makeProviderServiceLive().pipe(
1318+ const firstProviderLayer = makeFreshProviderServiceLayer().pipe(
13161319 Layer.provide(
13171320 Layer.succeed(ProviderAdapterRegistry.ProviderAdapterRegistry, firstRegistry),
13181321 ),
@@ -1350,7 +1353,7 @@ routing.layer("ProviderServiceLive routing", (it) => { · 31 unmodified lines
13501353 const secondDirectoryLayer = ProviderSessionDirectory.layer.pipe(
13511354 Layer.provide(runtimeRepositoryLayer),
13521355 );
1353− const secondProviderLayer = makeProviderServiceLive().pipe(
1356+ const secondProviderLayer = makeFreshProviderServiceLayer().pipe(
13541357 Layer.provide(
13551358 Layer.succeed(ProviderAdapterRegistry.ProviderAdapterRegistry, secondRegistry),
13561359 ),
@@ -1418,7 +1421,7 @@ routing.layer("ProviderServiceLive routing", (it) => { · 61 unmodified lines
14181421 const firstDirectoryLayer = ProviderSessionDirectory.layer.pipe(
14191422 Layer.provide(runtimeRepositoryLayer),
14201423 );
1421− const firstProviderLayer = makeProviderServiceLive().pipe(
1424+ const firstProviderLayer = makeFreshProviderServiceLayer().pipe(
14221425 Layer.provide(
14231426 Layer.succeed(ProviderAdapterRegistry.ProviderAdapterRegistry, firstRegistry),
14241427 ),
@@ -1451,7 +1454,7 @@ routing.layer("ProviderServiceLive routing", (it) => { · 26 unmodified lines
14511454 const secondDirectoryLayer = ProviderSessionDirectory.layer.pipe(
14521455 Layer.provide(runtimeRepositoryLayer),
14531456 );
1454− const secondProviderLayer = makeProviderServiceLive().pipe(
1457+ const secondProviderLayer = makeFreshProviderServiceLayer().pipe(
14551458 Layer.provide(
14561459 Layer.succeed(ProviderAdapterRegistry.ProviderAdapterRegistry, secondRegistry),
14571460 ),
apps/server/src/provider/ProviderService.ts+28−15
@@ -6,6 +6,8 @@ · 5 unmodified lines
66 * unified provider event stream for subscribers.
77 *
88 * 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.
911 *
1012 * @module ProviderService
1113 */
@@ -61,14 +63,22 @@ import * as ProviderSessionDirectory from "./ProviderSessionDirectory.ts"; · 49 unmodified lines
6163 import { type EventNdjsonLogger } from "./Layers/EventNdjsonLogger.ts";
6264 import * as ProviderEventLoggers from "./ProviderEventLoggers.ts";
6365 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";
6676 const isModelSelection = Schema.is(ModelSelection);
6777
6878 export class ProviderService extends Context.Service<
6979 ProviderService,
7080 {
71− /** Start a provider session for a thread. */
81+ /** Start a provider session. */
7282 readonly startSession: (
7383 threadId: ThreadId,
7484 input: ProviderSessionStartInput,
@@ -99,10 +109,14 @@ export class ProviderService extends Context.Service< · 24 unmodified lines
99109 input: ProviderStopSessionInput,
100110 ) => Effect.Effect<void, ProviderServiceError>;
101111
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+ */
103117 readonly listSessions: () => Effect.Effect<ReadonlyArray<ProviderSession>>;
104118
105− /** Read capabilities for the adapter bound to an instance. */
119+ /** Read capabilities for the adapter bound to a configured provider instance. */
106120 readonly getCapabilities: (
107121 instanceId: ProviderInstanceId,
108122 ) => Effect.Effect<ProviderAdapterCapabilities, ProviderServiceError>;
@@ -119,8 +133,9 @@ export class ProviderService extends Context.Service< · 10 unmodified lines
119133 }) => Effect.Effect<void, ProviderServiceError>;
120134
121135 /**
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).
124139 */
125140 readonly streamEvents: Stream.Stream<ProviderRuntimeEvent>;
126141 }
@@ -279,16 +294,14 @@ export const make = Effect.fn("ProviderService.make")(function* (options?: Provi · 152 unmodified lines
279294 const runtimeEventPubSub = yield* PubSub.unbounded<ProviderRuntimeEvent>();
280295 const nowIso = Effect.map(DateTime.now, DateTime.formatIso);
281296 const prepareMcpSession = (threadId: ThreadId, providerInstanceId: ProviderInstanceId) =>
282− McpSessionRegistry.issueActiveMcpCredential({ threadId, providerInstanceId }).pipe(
297+ issueActiveMcpCredential({ threadId, providerInstanceId }).pipe(
283298 Effect.tap((credential) =>
284− credential
285− ? Effect.sync(() => McpProviderSession.setMcpProviderSession(credential.config))
286− : Effect.void,
299+ credential ? Effect.sync(() => setMcpProviderSession(credential.config)) : Effect.void,
287300 ),
288301 );
289302 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))),
292305 );
293306
294307 const publishRuntimeEvent = (event: ProviderRuntimeEvent): Effect.Effect<void> =>
@@ -1096,8 +1109,8 @@ export const make = Effect.fn("ProviderService.make")(function* (options?: Provi · 801 unmodified lines
10961109 ),
10971110 ).pipe(Effect.asVoid);
10981111 yield* Effect.forEach(currentAdapters, ([, adapter]) => adapter.stopAll()).pipe(Effect.asVoid);
1099− yield* McpSessionRegistry.revokeAllActiveMcpCredentials();
1100− McpProviderSession.clearAllMcpProviderSessions();
1112+ yield* revokeAllActiveMcpCredentials();
1113+ clearAllMcpProviderSessions();
11011114 const bindings = yield* directory.listBindings().pipe(Effect.orElseSucceed(() => []));
11021115 yield* Effect.forEach(bindings, (binding) =>
11031116 Effect.gen(function* () {
apps/server/src/provider/ProviderSessionDirectory.test.ts+1−3
@@ -20,9 +20,7 @@ import * as ProviderSessionRuntime from "../persistence/ProviderSessionRuntime.t · 19 unmodified lines
2020 import * as ProviderSessionDirectory from "./ProviderSessionDirectory.ts";
2121
2222 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));
2624 return Layer.mergeAll(
2725 runtimeRepositoryLayer,
2826 ProviderSessionDirectory.layer.pipe(Layer.provide(runtimeRepositoryLayer)),
apps/server/src/provider/ProviderSessionDirectory.ts+4−1
@@ -14,7 +14,7 @@ import * as Option from "effect/Option"; · 13 unmodified lines
1414 import * as Schema from "effect/Schema";
1515
1616 import { ProviderSessionDirectoryPersistenceError, ProviderValidationError } from "./Errors.ts";
17−import * as ProviderSessionRuntime from "../persistence/Services/ProviderSessionRuntime.ts";
17+import * as ProviderSessionRuntime from "../persistence/ProviderSessionRuntime.ts";
1818
1919 export interface ProviderRuntimeBinding {
2020 readonly threadId: ThreadId;
@@ -119,6 +119,9 @@ function toRuntimeBinding( · 98 unmodified lines
119119 ({
120120 threadId: runtime.threadId,
121121 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.
122125 providerInstanceId: runtime.providerInstanceId ?? defaultInstanceIdForDriver(provider),
123126 adapterKey: runtime.adapterKey,
124127 runtimeMode: runtime.runtimeMode,
apps/server/src/provider/ProviderSessionReaper.ts+1−0
@@ -14,6 +14,7 @@ import * as ProviderSessionDirectory from "./ProviderSessionDirectory.ts"; · 13 unmodified lines
1414 export class ProviderSessionReaper extends Context.Service<
1515 ProviderSessionReaper,
1616 {
17+ /** Start the background provider session reaper within the provided scope. */
1718 readonly start: () => Effect.Effect<void, never, Scope.Scope>;
1819 }
1920 >()("t3/provider/ProviderSessionReaper") {}
apps/server/src/provider/Services/ProviderAdapterRegistry.ts+0−3
@@ -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";
apps/server/src/provider/Services/ProviderRegistry.ts+0−2
@@ -1,2 +0,0 @@
1−// Compatibility shim for the intentionally excluded orchestration modules.
2−export * from "../ProviderRegistry.ts";
apps/server/src/provider/Services/ProviderService.ts+0−5
@@ -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"];
apps/server/src/provider/opencodeRuntime.ts+9−4
@@ -26,7 +26,7 @@ import * as Schema from "effect/Schema"; · 25 unmodified lines
2626 import * as Stream from "effect/Stream";
2727 import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
2828
29−import * as ProcessRunner from "../processRunner.ts";
29+import { isWindowsCommandNotFound } from "../processRunner.ts";
3030 import { collectStreamAsString } from "./providerSnapshot.ts";
3131 import * as NetService from "@t3tools/shared/Net";
3232 import { HostProcessPlatform } from "@t3tools/shared/hostProcess";
@@ -54,12 +54,15 @@ export class OpenCodeRuntimeError extends Schema.TaggedErrorClass<OpenCodeRuntim · 21 unmodified lines
5454 operation: Schema.String,
5555 detail: Schema.String,
5656 cause: Schema.optional(Schema.Defect()),
57+ exitCode: Schema.optionalKey(Schema.Number),
58+ stdout: Schema.optionalKey(Schema.String),
59+ stderr: Schema.optionalKey(Schema.String),
5760 },
5861 ) {
5962 static readonly is = Schema.is(OpenCodeRuntimeError);
6063
6164 override get message(): string {
62− return `${this.operation}: ${this.detail}`;
65+ return `OpenCode runtime operation ${this.operation} failed.`;
6366 }
6467 }
6568
@@ -302,7 +305,7 @@ export const make = Effect.gen(function* () { · 236 unmodified lines
302305 { concurrency: "unbounded" },
303306 );
304307 const exitCode = Number(code);
305− if (yield* ProcessRunner.isWindowsCommandNotFound(exitCode, stderr)) {
308+ if (yield* isWindowsCommandNotFound(exitCode, stderr)) {
306309 return yield* new OpenCodeRuntimeError({
307310 operation: "runOpenCodeCommand",
308311 detail: `spawn ${input.binaryPath} ENOENT`,
@@ -437,7 +440,9 @@ export const make = Effect.gen(function* () { · 128 unmodified lines
437440 ]
438441 .filter(Boolean)
439442 .join("\n\n"),
440− cause: { exitCode, stdout, stderr },
443+ exitCode,
444+ stdout,
445+ stderr,
441446 }),
442447 ).pipe(Effect.ignore);
443448 }),
apps/server/src/provider/providerMaintenanceRunner.test.ts+31−0
@@ -11,6 +11,7 @@ import * as Effect from "effect/Effect"; · 10 unmodified lines
1111 import * as Exit from "effect/Exit";
1212 import * as Fiber from "effect/Fiber";
1313 import * as Layer from "effect/Layer";
14+import * as PlatformError from "effect/PlatformError";
1415 import * as Ref from "effect/Ref";
1516 import * as Schema from "effect/Schema";
1617 import * as Sink from "effect/Sink";
@@ -140,6 +141,13 @@ function mockSpawnerLayer( · 123 unmodified lines
140141 );
141142 }
142143
144+function failingSpawnerLayer(cause: PlatformError.PlatformError) {
145+ return Layer.succeed(
146+ ChildProcessSpawner.ChildProcessSpawner,
147+ ChildProcessSpawner.make(() => Effect.fail(cause)),
148+ );
149+}
150+
143151 function makeRegistry(
144152 initialProviders: ServerProvider | ReadonlyArray<ServerProvider> = baseProvider,
145153 ) {
@@ -320,6 +328,29 @@ describe("providerMaintenanceRunner", () => { · 174 unmodified lines
320328 },
321329 );
322330
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+
323354 it.effect("updates a single provider instance without touching sibling instances", () => {
324355 const calls: Array<{ command: string; args: ReadonlyArray<string> }> = [];
325356 return Effect.gen(function* () {
apps/server/src/provider/providerMaintenanceRunner.ts+63−13
@@ -14,6 +14,7 @@ import * as Duration from "effect/Duration"; · 13 unmodified lines
1414 import * as Effect from "effect/Effect";
1515 import * as Layer from "effect/Layer";
1616 import * as Option from "effect/Option";
17+import * as PlatformError from "effect/PlatformError";
1718 import * as Ref from "effect/Ref";
1819 import * as Schema from "effect/Schema";
1920 import { HttpClient } from "effect/unstable/http";
@@ -52,20 +53,68 @@ export class ProviderMaintenanceRunner extends Context.Service< · 32 unmodified lines
5253 }
5354 >()("t3/provider/providerMaintenanceRunner") {}
5455
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",
5797 {
58− operation: Schema.Literals(["spawn", "collect"]),
59− command: Schema.optional(Schema.String),
98+ command: Schema.String,
99+ ...platformFailureFields,
60100 cause: Schema.Defect(),
61101 },
62102 ) {
63103 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);
69118 }
70119 }
71120
@@ -89,9 +138,9 @@ const runProviderMaintenanceCommandWithSpawner = Effect.fn("ProviderMaintenanceR · 17 unmodified lines
89138 .pipe(
90139 Effect.mapError(
91140 (cause) =>
92− new ProviderMaintenanceCommandError({
93− operation: "spawn",
141+ new ProviderMaintenanceCommandSpawnError({
94142 command: input.command,
143+ ...platformFailureAttributes(cause),
95144 cause,
96145 }),
97146 ),
@@ -114,8 +163,9 @@ const runProviderMaintenanceCommandWithSpawner = Effect.fn("ProviderMaintenanceR · 16 unmodified lines
114163 ).pipe(
115164 Effect.mapError(
116165 (cause) =>
117− new ProviderMaintenanceCommandError({
118− operation: "collect",
166+ new ProviderMaintenanceCommandCollectError({
167+ command: input.command,
168+ ...platformFailureAttributes(cause),
119169 cause,
120170 }),
121171 ),
docs/architecture/overview.md+1−1
@@ -132,7 +132,7 @@ sequenceDiagram · 131 unmodified lines
132132 [5]: ../apps/server/src/wsServer/pushBus.ts
133133 [6]: ../packages/contracts/src/ws.ts
134134 [7]: ../apps/server/src/serverLayers.ts
135−[8]: ../apps/server/src/provider/Layers/ProviderService.ts
135+[8]: ../apps/server/src/provider/ProviderService.ts
136136 [9]: ../apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts
137137 [10]: ../apps/server/src/orchestration/Layers/OrchestrationEngine.ts
138138 [11]: ../packages/contracts/src/orchestration.ts
docs/operations/effect-fn-checklist.md+17−17
@@ -41,7 +41,7 @@ Effect.fn("name")( · 40 unmodified lines
4141
4242 ## Suggested Order
4343
44−- [ ] `apps/server/src/provider/Layers/ProviderService.ts`
44+- [ ] `apps/server/src/provider/ProviderService.ts`
4545 - [x] `apps/server/src/provider/Layers/ClaudeAdapter.ts`
4646 - [x] `apps/server/src/provider/Layers/CodexAdapter.ts`
4747 - [x] `apps/server/src/git/Layers/GitCore.ts`
@@ -98,20 +98,20 @@ Effect.fn("name")( · 50 unmodified lines
9898 - [x] `Effect.forEach(..., entry => Effect.gen(...))` callbacks around `L305`
9999 - [x] Remaining apply helpers in this file
100100
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)
115115
116116 ### `apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts` (`14`)
117117
@@ -174,7 +174,7 @@ Effect.fn("name")( · 56 unmodified lines
174174 - [ ] [packages/shared/src/DrainableWorker.ts](/Users/julius/Development/Work/codething-mvp/packages/shared/src/DrainableWorker.ts) (`4`)
175175 - [ ] [apps/server/src/wsServer/pushBus.ts](/Users/julius/Development/Work/codething-mvp/apps/server/src/wsServer/pushBus.ts) (`4`)
176176 - [ ] [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`)
178178 - [ ] [apps/server/src/persistence/Layers/Sqlite.ts](/Users/julius/Development/Work/codething-mvp/apps/server/src/persistence/Layers/Sqlite.ts) (`4`)
179179 - [ ] [apps/server/src/orchestration/Layers/ProviderCommandReactor.ts](/Users/julius/Development/Work/codething-mvp/apps/server/src/orchestration/Layers/ProviderCommandReactor.ts) (`4`)
180180 - [ ] [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
183183 - [ ] [apps/server/src/serverLayers.ts](/Users/julius/Development/Work/codething-mvp/apps/server/src/serverLayers.ts) (`3`)
184184 - [ ] [apps/server/src/telemetry/Layers/AnalyticsService.ts](/Users/julius/Development/Work/codething-mvp/apps/server/src/telemetry/Layers/AnalyticsService.ts) (`2`)
185185 - [ ] [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`)
187187 - [ ] [apps/server/src/provider/Layers/CodexProvider.ts](/Users/julius/Development/Work/codething-mvp/apps/server/src/provider/Layers/CodexProvider.ts) (`2`)
188188 - [ ] [apps/server/src/provider/Layers/ClaudeProvider.ts](/Users/julius/Development/Work/codething-mvp/apps/server/src/provider/Layers/ClaudeProvider.ts) (`2`)
189189 - [ ] [apps/server/src/persistence/NodeSqliteClient.ts](/Users/julius/Development/Work/codething-mvp/apps/server/src/persistence/NodeSqliteClient.ts) (`2`)
docs/reference/encyclopedia.md+1−1
@@ -167,7 +167,7 @@ The file patch and changed-file summary for one turn. It is usually computed in · 166 unmodified lines
167167 [11]: ../apps/server/src/orchestration/Layers/ProjectionPipeline.ts
168168 [12]: ../apps/server/src/orchestration/Layers/ProviderCommandReactor.ts
169169 [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
171171 [15]: ../apps/server/src/provider/Services/ProviderAdapter.ts
172172 [16]: ./provider-architecture.md
173173 [17]: ../apps/server/src/provider/Layers/CodexAdapter.ts