| author | |
| committer | |
| log | 740d7a321602963c4fa9da1b25f05c992333620d |
| tree | 82f767fe266c0aa06b4fef9d1255177cbd05eb30 |
| parent | cd7980b4574a611f9f721d0463755684653b65f2 |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: codex <codex@users.noreply.github.com>14 files changed, 176 insertions(+), 55 deletions(-)
apps/server/integration/OrchestrationEngineHarness.integration.ts+3-3| ... | ... | @@ -48,7 +48,7 @@ import { CheckpointReactorLive } from "../src/orchestration/Layers/CheckpointRea |
| 48 | 48 | import { OrchestrationEngineLive } from "../src/orchestration/Layers/OrchestrationEngine.ts"; |
| 49 | 49 | import { OrchestrationProjectionPipelineLive } from "../src/orchestration/Layers/ProjectionPipeline.ts"; |
| 50 | 50 | import { OrchestrationProjectionSnapshotQueryLive } from "../src/orchestration/Layers/ProjectionSnapshotQuery.ts"; |
| 51 | import { RuntimeReceiptBusLive } from "../src/orchestration/Layers/RuntimeReceiptBus.ts"; | |
| 51 | import { RuntimeReceiptBusTest } from "../src/orchestration/Layers/RuntimeReceiptBus.ts"; | |
| 52 | 52 | import { OrchestrationReactorLive } from "../src/orchestration/Layers/OrchestrationReactor.ts"; |
| 53 | 53 | import { ProviderCommandReactorLive } from "../src/orchestration/Layers/ProviderCommandReactor.ts"; |
| 54 | 54 | import { ProviderRuntimeIngestionLive } from "../src/orchestration/Layers/ProviderRuntimeIngestion.ts"; |
| ... | ... | @@ -297,7 +297,7 @@ export const makeOrchestrationIntegrationHarness = ( |
| 297 | 297 | ProjectionPendingApprovalRepositoryLive, |
| 298 | 298 | checkpointStoreLayer, |
| 299 | 299 | providerLayer, |
| 300 | RuntimeReceiptBusLive, | |
| 300 | RuntimeReceiptBusTest, | |
| 301 | 301 | ); |
| 302 | 302 | const serverSettingsLayer = ServerSettingsService.layerTest(); |
| 303 | 303 | const runtimeIngestionLayer = ProviderRuntimeIngestionLive.pipe( |
| ... | ... | @@ -376,7 +376,7 @@ export const makeOrchestrationIntegrationHarness = ( |
| 376 | 376 | runtime.runPromise(reactor.start().pipe(Scope.provide(scope))), |
| 377 | 377 | ).pipe(Effect.orDie); |
| 378 | 378 | const receiptHistory = yield* Ref.make<ReadonlyArray<OrchestrationRuntimeReceipt>>([]); |
| 379 | yield* Stream.runForEach(runtimeReceiptBus.stream, (receipt) => | |
| 379 | yield* Stream.runForEach(runtimeReceiptBus.streamEventsForTest, (receipt) => | |
| 380 | 380 | Ref.update(receiptHistory, (history) => [...history, receipt]).pipe(Effect.asVoid), |
| 381 | 381 | ).pipe(Effect.forkIn(scope)); |
| 382 | 382 | yield* Effect.sleep(10); |
apps/server/integration/providerService.integration.test.ts+5-5| ... | ... | @@ -86,6 +86,7 @@ const collectEventsDuring = <A, E, R>( |
| 86 | 86 | Effect.forkScoped, |
| 87 | 87 | ); |
| 88 | 88 | |
| 89 | yield* Effect.sleep("50 millis"); | |
| 89 | 90 | yield* action; |
| 90 | 91 | |
| 91 | 92 | return yield* Effect.forEach( |
| ... | ... | @@ -104,7 +105,6 @@ const runTurn = (input: { |
| 104 | 105 | }) => |
| 105 | 106 | Effect.gen(function* () { |
| 106 | 107 | yield* input.harness.queueTurnResponse(input.threadId, input.response); |
| 107 | ||
| 108 | 108 | return yield* collectEventsDuring( |
| 109 | 109 | input.provider.streamEvents, |
| 110 | 110 | input.response.events.length, |
| ... | ... | @@ -116,7 +116,7 @@ const runTurn = (input: { |
| 116 | 116 | ); |
| 117 | 117 | }); |
| 118 | 118 | |
| 119 | it.effect("replays typed runtime fixture events", () => | |
| 119 | it.live("replays typed runtime fixture events", () => | |
| 120 | 120 | Effect.gen(function* () { |
| 121 | 121 | const fixture = yield* makeIntegrationFixture; |
| 122 | 122 | |
| ... | ... | @@ -149,7 +149,7 @@ it.effect("replays typed runtime fixture events", () => |
| 149 | 149 | }).pipe(Effect.provide(NodeServices.layer)), |
| 150 | 150 | ); |
| 151 | 151 | |
| 152 | it.effect("replays file-changing fixture turn events", () => | |
| 152 | it.live("replays file-changing fixture turn events", () => | |
| 153 | 153 | Effect.gen(function* () { |
| 154 | 154 | const fixture = yield* makeIntegrationFixture; |
| 155 | 155 | const { join } = yield* Path.Path; |
| ... | ... | @@ -188,7 +188,7 @@ it.effect("replays file-changing fixture turn events", () => |
| 188 | 188 | }).pipe(Effect.provide(NodeServices.layer)), |
| 189 | 189 | ); |
| 190 | 190 | |
| 191 | it.effect("runs multi-turn tool/approval flow", () => | |
| 191 | it.live("runs multi-turn tool/approval flow", () => | |
| 192 | 192 | Effect.gen(function* () { |
| 193 | 193 | const fixture = yield* makeIntegrationFixture; |
| 194 | 194 | const { join } = yield* Path.Path; |
| ... | ... | @@ -242,7 +242,7 @@ it.effect("runs multi-turn tool/approval flow", () => |
| 242 | 242 | }).pipe(Effect.provide(NodeServices.layer)), |
| 243 | 243 | ); |
| 244 | 244 | |
| 245 | it.effect("rolls back provider conversation state only", () => | |
| 245 | it.live("rolls back provider conversation state only", () => | |
| 246 | 246 | Effect.gen(function* () { |
| 247 | 247 | const fixture = yield* makeIntegrationFixture; |
| 248 | 248 | const { join } = yield* Path.Path; |
apps/server/src/orchestration/Layers/CheckpointReactor.test.ts+3-1| ... | ... | @@ -96,7 +96,9 @@ function createProviderServiceHarness( |
| 96 | 96 | listSessions, |
| 97 | 97 | getCapabilities: () => Effect.succeed({ sessionModelSwitch: "in-session" }), |
| 98 | 98 | rollbackConversation, |
| 99 | streamEvents: Stream.fromPubSub(runtimeEventPubSub), | |
| 99 | get streamEvents() { | |
| 100 | return Stream.fromPubSub(runtimeEventPubSub); | |
| 101 | }, | |
| 100 | 102 | }; |
| 101 | 103 | |
| 102 | 104 | const emit = (event: LegacyProviderRuntimeEvent): void => { |
apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts+3-1| ... | ... | @@ -208,7 +208,9 @@ describe("ProviderCommandReactor", () => { |
| 208 | 208 | sessionModelSwitch: input?.sessionModelSwitch ?? "in-session", |
| 209 | 209 | }), |
| 210 | 210 | rollbackConversation: () => unsupported(), |
| 211 | streamEvents: Stream.fromPubSub(runtimeEventPubSub), | |
| 211 | get streamEvents() { | |
| 212 | return Stream.fromPubSub(runtimeEventPubSub); | |
| 213 | }, | |
| 212 | 214 | }; |
| 213 | 215 | |
| 214 | 216 | const orchestrationLayer = OrchestrationEngineLive.pipe( |
apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts+3-1| ... | ... | @@ -98,7 +98,9 @@ function createProviderServiceHarness() { |
| 98 | 98 | listSessions: () => Effect.succeed([...runtimeSessions]), |
| 99 | 99 | getCapabilities: () => Effect.succeed({ sessionModelSwitch: "in-session" }), |
| 100 | 100 | rollbackConversation: () => unsupported(), |
| 101 | streamEvents: Stream.fromPubSub(runtimeEventPubSub), | |
| 101 | get streamEvents() { | |
| 102 | return Stream.fromPubSub(runtimeEventPubSub); | |
| 103 | }, | |
| 102 | 104 | }; |
| 103 | 105 | |
| 104 | 106 | const setSession = (session: ProviderSession): void => { |
apps/server/src/orchestration/Layers/RuntimeReceiptBus.ts+20-2| ... | ... | @@ -1,3 +1,13 @@ |
| 1 | /** | |
| 2 | * RuntimeReceiptBus layers. | |
| 3 | * | |
| 4 | * `RuntimeReceiptBusLive` is the production default and intentionally does not | |
| 5 | * retain or broadcast receipts. `RuntimeReceiptBusTest` installs the in-memory | |
| 6 | * PubSub-backed implementation used by integration tests that need to await | |
| 7 | * checkpoint-reactor milestones precisely. | |
| 8 | * | |
| 9 | * @module RuntimeReceiptBus | |
| 10 | */ | |
| 1 | 11 | import { Effect, Layer, PubSub, Stream } from "effect"; |
| 2 | 12 | |
| 3 | 13 | import { |
| ... | ... | @@ -6,13 +16,21 @@ import { |
| 6 | 16 | type OrchestrationRuntimeReceipt, |
| 7 | 17 | } from "../Services/RuntimeReceiptBus.ts"; |
| 8 | 18 | |
| 9 | const makeRuntimeReceiptBus = Effect.gen(function* () { | |
| 19 | const makeRuntimeReceiptBus = Effect.succeed({ | |
| 20 | publish: () => Effect.void, | |
| 21 | streamEventsForTest: Stream.empty, | |
| 22 | } satisfies RuntimeReceiptBusShape); | |
| 23 | ||
| 24 | const makeRuntimeReceiptBusTest = Effect.gen(function* () { | |
| 10 | 25 | const pubSub = yield* PubSub.unbounded<OrchestrationRuntimeReceipt>(); |
| 11 | 26 | |
| 12 | 27 | return { |
| 13 | 28 | publish: (receipt) => PubSub.publish(pubSub, receipt).pipe(Effect.asVoid), |
| 14 | stream: Stream.fromPubSub(pubSub), | |
| 29 | get streamEventsForTest() { | |
| 30 | return Stream.fromPubSub(pubSub); | |
| 31 | }, | |
| 15 | 32 | } satisfies RuntimeReceiptBusShape; |
| 16 | 33 | }); |
| 17 | 34 | |
| 18 | 35 | export const RuntimeReceiptBusLive = Layer.effect(RuntimeReceiptBus, makeRuntimeReceiptBus); |
| 36 | export const RuntimeReceiptBusTest = Layer.effect(RuntimeReceiptBus, makeRuntimeReceiptBusTest); |
apps/server/src/orchestration/Services/RuntimeReceiptBus.ts+17-1| ... | ... | @@ -1,3 +1,19 @@ |
| 1 | /** | |
| 2 | * RuntimeReceiptBus - Internal checkpoint-reactor synchronization receipts. | |
| 3 | * | |
| 4 | * This service exists to expose short-lived orchestration milestones that are | |
| 5 | * useful in tests and harnesses but are not part of the production runtime | |
| 6 | * event model. `CheckpointReactor` publishes receipts such as baseline capture, | |
| 7 | * diff finalization, and turn-processing quiescence so integration tests can | |
| 8 | * wait for those exact points without inferring them indirectly from persisted | |
| 9 | * state. | |
| 10 | * | |
| 11 | * Production code should only call `publish`. Test code may subscribe via | |
| 12 | * `streamEventsForTest`, which is intentionally named to make the intended | |
| 13 | * usage explicit. | |
| 14 | * | |
| 15 | * @module RuntimeReceiptBus | |
| 16 | */ | |
| 1 | 17 | import { CheckpointRef, IsoDateTime, NonNegativeInt, ThreadId, TurnId } from "@t3tools/contracts"; |
| 2 | 18 | import { Schema, ServiceMap } from "effect"; |
| 3 | 19 | import type { Effect, Stream } from "effect"; |
| ... | ... | @@ -40,7 +56,7 @@ export type OrchestrationRuntimeReceipt = typeof OrchestrationRuntimeReceipt.Typ |
| 40 | 56 | |
| 41 | 57 | export interface RuntimeReceiptBusShape { |
| 42 | 58 | readonly publish: (receipt: OrchestrationRuntimeReceipt) => Effect.Effect<void>; |
| 43 | readonly stream: Stream.Stream<OrchestrationRuntimeReceipt>; | |
| 59 | readonly streamEventsForTest: Stream.Stream<OrchestrationRuntimeReceipt>; | |
| 44 | 60 | } |
| 45 | 61 | |
| 46 | 62 | export class RuntimeReceiptBus extends ServiceMap.Service< |
apps/server/src/provider/Layers/ClaudeAdapter.ts+3-1| ... | ... | @@ -3054,7 +3054,9 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* ( |
| 3054 | 3054 | listSessions, |
| 3055 | 3055 | hasSession, |
| 3056 | 3056 | stopAll, |
| 3057 | streamEvents: Stream.fromQueue(runtimeEventQueue), | |
| 3057 | get streamEvents() { | |
| 3058 | return Stream.fromQueue(runtimeEventQueue); | |
| 3059 | }, | |
| 3058 | 3060 | } satisfies ClaudeAdapterShape; |
| 3059 | 3061 | }); |
| 3060 | 3062 |
apps/server/src/provider/Layers/CodexAdapter.ts+3-1| ... | ... | @@ -1631,7 +1631,9 @@ const makeCodexAdapter = Effect.fn("makeCodexAdapter")(function* ( |
| 1631 | 1631 | listSessions, |
| 1632 | 1632 | hasSession, |
| 1633 | 1633 | stopAll, |
| 1634 | streamEvents: Stream.fromQueue(runtimeEventQueue), | |
| 1634 | get streamEvents() { | |
| 1635 | return Stream.fromQueue(runtimeEventQueue); | |
| 1636 | }, | |
| 1635 | 1637 | } satisfies CodexAdapterShape; |
| 1636 | 1638 | }); |
| 1637 | 1639 |
apps/server/src/provider/Layers/EventNdjsonLogger.test.ts+43| ... | ... | @@ -112,6 +112,49 @@ describe("EventNdjsonLogger", () => { |
| 112 | 112 | }), |
| 113 | 113 | ); |
| 114 | 114 | |
| 115 | it.effect("serializes concurrent first writes for the same segment", () => | |
| 116 | Effect.gen(function* () { | |
| 117 | const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "t3-provider-log-")); | |
| 118 | const basePath = path.join(tempDir, "provider-canonical.ndjson"); | |
| 119 | ||
| 120 | try { | |
| 121 | const logger = yield* makeEventNdjsonLogger(basePath, { | |
| 122 | stream: "canonical", | |
| 123 | batchWindowMs: 0, | |
| 124 | }); | |
| 125 | assert.notEqual(logger, undefined); | |
| 126 | if (!logger) { | |
| 127 | return; | |
| 128 | } | |
| 129 | ||
| 130 | yield* Effect.all( | |
| 131 | [ | |
| 132 | logger.write({ id: "evt-concurrent-1" }, null), | |
| 133 | logger.write({ id: "evt-concurrent-2" }, null), | |
| 134 | ], | |
| 135 | { concurrency: "unbounded" }, | |
| 136 | ); | |
| 137 | yield* logger.close(); | |
| 138 | ||
| 139 | const globalPath = path.join(tempDir, "_global.log"); | |
| 140 | assert.equal(fs.existsSync(globalPath), true); | |
| 141 | const lines = fs | |
| 142 | .readFileSync(globalPath, "utf8") | |
| 143 | .trim() | |
| 144 | .split("\n") | |
| 145 | .map((line) => parseLogLine(line)); | |
| 146 | ||
| 147 | assert.equal(lines.length, 2); | |
| 148 | assert.deepEqual(lines.map((line) => line.payload).toSorted(), [ | |
| 149 | '{"id":"evt-concurrent-1"}', | |
| 150 | '{"id":"evt-concurrent-2"}', | |
| 151 | ]); | |
| 152 | } finally { | |
| 153 | fs.rmSync(tempDir, { recursive: true, force: true }); | |
| 154 | } | |
| 155 | }), | |
| 156 | ); | |
| 157 | ||
| 115 | 158 | it.effect("rotates per-thread files when max size is exceeded", () => |
| 116 | 159 | Effect.gen(function* () { |
| 117 | 160 | const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "t3-provider-log-")); |
apps/server/src/provider/Layers/EventNdjsonLogger.ts+65-27| ... | ... | @@ -10,7 +10,7 @@ import path from "node:path"; |
| 10 | 10 | |
| 11 | 11 | import type { ThreadId } from "@t3tools/contracts"; |
| 12 | 12 | import { RotatingFileSink } from "@t3tools/shared/logging"; |
| 13 | import { Effect, Exit, Logger, Scope } from "effect"; | |
| 13 | import { Effect, Exit, Logger, Scope, SynchronizedRef } from "effect"; | |
| 14 | 14 | |
| 15 | 15 | import { toSafeThreadAttachmentSegment } from "../../attachmentStore.ts"; |
| 16 | 16 | |
| ... | ... | @@ -40,6 +40,11 @@ interface ThreadWriter { |
| 40 | 40 | close: () => Effect.Effect<void>; |
| 41 | 41 | } |
| 42 | 42 | |
| 43 | interface LoggerState { | |
| 44 | readonly threadWriters: Map<string, ThreadWriter>; | |
| 45 | readonly failedSegments: Set<string>; | |
| 46 | } | |
| 47 | ||
| 43 | 48 | function logWarning(message: string, context: Record<string, unknown>): Effect.Effect<void> { |
| 44 | 49 | return Effect.logWarning(message, context).pipe(Effect.annotateLogs({ scope: LOG_SCOPE })); |
| 45 | 50 | } |
| ... | ... | @@ -193,34 +198,56 @@ export const makeEventNdjsonLogger = Effect.fn("makeEventNdjsonLogger")(function |
| 193 | 198 | return undefined; |
| 194 | 199 | } |
| 195 | 200 | |
| 196 | const threadWriters = new Map<string, ThreadWriter>(); | |
| 197 | const failedSegments = new Set<string>(); | |
| 201 | const stateRef = yield* SynchronizedRef.make<LoggerState>({ | |
| 202 | threadWriters: new Map(), | |
| 203 | failedSegments: new Set(), | |
| 204 | }); | |
| 198 | 205 | |
| 199 | 206 | const resolveThreadWriter = Effect.fn("resolveThreadWriter")(function* ( |
| 200 | 207 | threadSegment: string, |
| 201 | 208 | ): Effect.fn.Return<ThreadWriter | undefined> { |
| 202 | if (failedSegments.has(threadSegment)) { | |
| 203 | return undefined; | |
| 204 | } | |
| 205 | const existing = threadWriters.get(threadSegment); | |
| 206 | if (existing) { | |
| 207 | return existing; | |
| 208 | } | |
| 209 | return yield* SynchronizedRef.modifyEffect(stateRef, (state) => { | |
| 210 | if (state.failedSegments.has(threadSegment)) { | |
| 211 | return Effect.succeed([undefined, state] as const); | |
| 212 | } | |
| 209 | 213 | |
| 210 | const writer = yield* makeThreadWriter({ | |
| 211 | filePath: path.join(path.dirname(filePath), `${threadSegment}.log`), | |
| 212 | maxBytes, | |
| 213 | maxFiles, | |
| 214 | batchWindowMs, | |
| 215 | streamLabel, | |
| 216 | }); | |
| 217 | if (!writer) { | |
| 218 | failedSegments.add(threadSegment); | |
| 219 | return undefined; | |
| 220 | } | |
| 214 | const existing = state.threadWriters.get(threadSegment); | |
| 215 | if (existing) { | |
| 216 | return Effect.succeed([existing, state] as const); | |
| 217 | } | |
| 218 | ||
| 219 | return makeThreadWriter({ | |
| 220 | filePath: path.join(path.dirname(filePath), `${threadSegment}.log`), | |
| 221 | maxBytes, | |
| 222 | maxFiles, | |
| 223 | batchWindowMs, | |
| 224 | streamLabel, | |
| 225 | }).pipe( | |
| 226 | Effect.map((writer) => { | |
| 227 | if (!writer) { | |
| 228 | const nextFailedSegments = new Set(state.failedSegments); | |
| 229 | nextFailedSegments.add(threadSegment); | |
| 230 | return [ | |
| 231 | undefined, | |
| 232 | { | |
| 233 | ...state, | |
| 234 | failedSegments: nextFailedSegments, | |
| 235 | }, | |
| 236 | ] as const; | |
| 237 | } | |
| 221 | 238 | |
| 222 | threadWriters.set(threadSegment, writer); | |
| 223 | return writer; | |
| 239 | const nextThreadWriters = new Map(state.threadWriters); | |
| 240 | nextThreadWriters.set(threadSegment, writer); | |
| 241 | return [ | |
| 242 | writer, | |
| 243 | { | |
| 244 | ...state, | |
| 245 | threadWriters: nextThreadWriters, | |
| 246 | }, | |
| 247 | ] as const; | |
| 248 | }), | |
| 249 | ); | |
| 250 | }); | |
| 224 | 251 | }); |
| 225 | 252 | |
| 226 | 253 | const write = Effect.fn("write")(function* (event: unknown, threadId: ThreadId | null) { |
| ... | ... | @@ -239,10 +266,21 @@ export const makeEventNdjsonLogger = Effect.fn("makeEventNdjsonLogger")(function |
| 239 | 266 | }); |
| 240 | 267 | |
| 241 | 268 | const close = Effect.fn("close")(function* () { |
| 242 | for (const writer of threadWriters.values()) { | |
| 243 | yield* writer.close(); | |
| 244 | } | |
| 245 | threadWriters.clear(); | |
| 269 | yield* SynchronizedRef.modifyEffect(stateRef, (state) => | |
| 270 | Effect.gen(function* () { | |
| 271 | for (const writer of state.threadWriters.values()) { | |
| 272 | yield* writer.close(); | |
| 273 | } | |
| 274 | ||
| 275 | return [ | |
| 276 | undefined, | |
| 277 | { | |
| 278 | threadWriters: new Map<string, ThreadWriter>(), | |
| 279 | failedSegments: new Set<string>(), | |
| 280 | }, | |
| 281 | ] as const; | |
| 282 | }), | |
| 283 | ); | |
| 246 | 284 | }); |
| 247 | 285 | |
| 248 | 286 | return { |
apps/server/src/provider/Layers/ProviderRegistry.test.ts+3-1| ... | ... | @@ -115,7 +115,9 @@ function makeMutableServerSettingsService( |
| 115 | 115 | yield* PubSub.publish(changes, next); |
| 116 | 116 | return next; |
| 117 | 117 | }), |
| 118 | streamChanges: Stream.fromPubSub(changes), | |
| 118 | get streamChanges() { | |
| 119 | return Stream.fromPubSub(changes); | |
| 120 | }, | |
| 119 | 121 | } satisfies ServerSettingsShape; |
| 120 | 122 | }); |
| 121 | 123 | } |
apps/server/src/provider/Layers/ProviderService.test.ts+3-1| ... | ... | @@ -191,7 +191,9 @@ function makeFakeCodexAdapter(provider: ProviderKind = "codex") { |
| 191 | 191 | readThread, |
| 192 | 192 | rollbackThread, |
| 193 | 193 | stopAll, |
| 194 | streamEvents: Stream.fromPubSub(runtimeEventPubSub), | |
| 194 | get streamEvents() { | |
| 195 | return Stream.fromPubSub(runtimeEventPubSub); | |
| 196 | }, | |
| 195 | 197 | }; |
| 196 | 198 | |
| 197 | 199 | const emit = (event: LegacyProviderRuntimeEvent): void => { |
apps/server/src/provider/Layers/ProviderService.ts+2-10| ... | ... | @@ -22,7 +22,7 @@ import { |
| 22 | 22 | type ProviderRuntimeEvent, |
| 23 | 23 | type ProviderSession, |
| 24 | 24 | } from "@t3tools/contracts"; |
| 25 | import { Effect, Layer, Option, PubSub, Queue, Schema, SchemaIssue, Stream } from "effect"; | |
| 25 | import { Effect, Layer, Option, PubSub, Schema, SchemaIssue, Stream } from "effect"; | |
| 26 | 26 | |
| 27 | 27 | import { |
| 28 | 28 | increment, |
| ... | ... | @@ -156,7 +156,6 @@ const makeProviderService = Effect.fn("makeProviderService")(function* ( |
| 156 | 156 | |
| 157 | 157 | const registry = yield* ProviderAdapterRegistry; |
| 158 | 158 | const directory = yield* ProviderSessionDirectory; |
| 159 | const runtimeEventQueue = yield* Queue.unbounded<ProviderRuntimeEvent>(); | |
| 160 | 159 | const runtimeEventPubSub = yield* PubSub.unbounded<ProviderRuntimeEvent>(); |
| 161 | 160 | |
| 162 | 161 | const publishRuntimeEvent = (event: ProviderRuntimeEvent): Effect.Effect<void> => |
| ... | ... | @@ -194,15 +193,8 @@ const makeProviderService = Effect.fn("makeProviderService")(function* ( |
| 194 | 193 | eventType: event.type, |
| 195 | 194 | }).pipe(Effect.andThen(publishRuntimeEvent(event))); |
| 196 | 195 | |
| 197 | const worker = Effect.forever( | |
| 198 | Queue.take(runtimeEventQueue).pipe(Effect.flatMap(processRuntimeEvent)), | |
| 199 | ); | |
| 200 | yield* Effect.forkScoped(worker); | |
| 201 | ||
| 202 | 196 | yield* Effect.forEach(adapters, (adapter) => |
| 203 | Stream.runForEach(adapter.streamEvents, (event) => | |
| 204 | Queue.offer(runtimeEventQueue, event).pipe(Effect.asVoid), | |
| 205 | ).pipe(Effect.forkScoped), | |
| 197 | Stream.runForEach(adapter.streamEvents, processRuntimeEvent).pipe(Effect.forkScoped), | |
| 206 | 198 | ).pipe(Effect.asVoid); |
| 207 | 199 | |
| 208 | 200 | const recoverSessionForThread = Effect.fn("recoverSessionForThread")(function* (input: { |