| author | |
| committer | |
| log | ecfdda5fa804582066c1aa62afea419677c24883 |
| tree | f55f08cf47c81cfa55d716e05a210c5534e2905a |
| parent | 4df84a7d03ff084ff4c6dd7aef54623984690fb7 |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>401 files changed, 7288 insertions(+), 7368 deletions(-)
apps/desktop/src/app/DesktopAppIdentity.test.ts+6-6| ... | ... | @@ -38,7 +38,7 @@ interface ElectronAppCalls { |
| 38 | 38 | readonly setName: string[]; |
| 39 | 39 | } |
| 40 | 40 | |
| 41 | const makeElectronAppLayer = (calls: ElectronAppCalls) => | |
| 41 | const layerElectronApp = (calls: ElectronAppCalls) => | |
| 42 | 42 | Layer.succeed(ElectronApp.ElectronApp, { |
| 43 | 43 | metadata: Effect.die("unexpected metadata read"), |
| 44 | 44 | name: Effect.succeed("T3 Code"), |
| ... | ... | @@ -70,7 +70,7 @@ const makeElectronAppLayer = (calls: ElectronAppCalls) => |
| 70 | 70 | on: () => Effect.void, |
| 71 | 71 | } satisfies ElectronApp.ElectronApp["Service"]); |
| 72 | 72 | |
| 73 | const makeAssetsLayer = (png: Option.Option<string>) => | |
| 73 | const layerAssets = (png: Option.Option<string>) => | |
| 74 | 74 | Layer.succeed(DesktopAssets.DesktopAssets, { |
| 75 | 75 | iconPaths: Effect.succeed({ |
| 76 | 76 | ico: Option.none(), |
| ... | ... | @@ -80,7 +80,7 @@ const makeAssetsLayer = (png: Option.Option<string>) => |
| 80 | 80 | resolveResourcePath: () => Effect.succeedNone, |
| 81 | 81 | } satisfies DesktopAssets.DesktopAssets["Service"]); |
| 82 | 82 | |
| 83 | const makeEnvironmentLayer = (overrides: TestEnvironmentInput = {}) => { | |
| 83 | const layerEnvironment = (overrides: TestEnvironmentInput = {}) => { | |
| 84 | 84 | const { env, ...environmentOverrides } = overrides; |
| 85 | 85 | return DesktopEnvironment.layer({ |
| 86 | 86 | ...defaultEnvironmentInput, |
| ... | ... | @@ -138,9 +138,9 @@ const withIdentity = <A, E, R>( |
| 138 | 138 | Effect.succeed(input.packageJson ?? '{"t3codeCommitHash":"abcdef1234567890"}'), |
| 139 | 139 | }), |
| 140 | 140 | ), |
| 141 | Layer.provideMerge(makeAssetsLayer(input.pngIconPath ?? Option.none())), | |
| 142 | Layer.provideMerge(makeElectronAppLayer(calls)), | |
| 143 | Layer.provideMerge(makeEnvironmentLayer(input.environment)), | |
| 141 | Layer.provideMerge(layerAssets(input.pngIconPath ?? Option.none())), | |
| 142 | Layer.provideMerge(layerElectronApp(calls)), | |
| 143 | Layer.provideMerge(layerEnvironment(input.environment)), | |
| 144 | 144 | ), |
| 145 | 145 | ), |
| 146 | 146 | ); |
apps/desktop/src/app/DesktopAssets.test.ts+8-8| ... | ... | @@ -11,7 +11,7 @@ import * as DesktopAssets from "./DesktopAssets.ts"; |
| 11 | 11 | import * as DesktopConfig from "./DesktopConfig.ts"; |
| 12 | 12 | import * as DesktopEnvironment from "./DesktopEnvironment.ts"; |
| 13 | 13 | |
| 14 | const environmentLayer = DesktopEnvironment.layer({ | |
| 14 | const layerEnvironment = DesktopEnvironment.layer({ | |
| 15 | 15 | dirname: "/repo/apps/desktop/dist-electron", |
| 16 | 16 | homeDirectory: "/Users/alice", |
| 17 | 17 | platform: "darwin", |
| ... | ... | @@ -30,7 +30,7 @@ const environmentLayer = DesktopEnvironment.layer({ |
| 30 | 30 | describe("DesktopAssets", () => { |
| 31 | 31 | it.effect("uses canonical source-tree icons for unpackaged development", () => |
| 32 | 32 | Effect.gen(function* () { |
| 33 | const developmentEnvironmentLayer = DesktopEnvironment.layer({ | |
| 33 | const layerDevelopmentEnvironment = DesktopEnvironment.layer({ | |
| 34 | 34 | dirname: "/repo/apps/desktop/dist-electron", |
| 35 | 35 | homeDirectory: "/Users/alice", |
| 36 | 36 | platform: "linux", |
| ... | ... | @@ -49,13 +49,13 @@ describe("DesktopAssets", () => { |
| 49 | 49 | ), |
| 50 | 50 | ), |
| 51 | 51 | ); |
| 52 | const fileSystemLayer = FileSystem.layerNoop({ | |
| 52 | const layerFileSystem = FileSystem.layerNoop({ | |
| 53 | 53 | exists: (path) => Effect.succeed(String(path).includes("/assets/dev/")), |
| 54 | 54 | }); |
| 55 | 55 | const assets = yield* DesktopAssets.DesktopAssets.pipe( |
| 56 | 56 | Effect.provide( |
| 57 | 57 | DesktopAssets.layer.pipe( |
| 58 | Layer.provide(Layer.merge(fileSystemLayer, developmentEnvironmentLayer)), | |
| 58 | Layer.provide(Layer.merge(layerFileSystem, layerDevelopmentEnvironment)), | |
| 59 | 59 | ), |
| 60 | 60 | ), |
| 61 | 61 | ); |
| ... | ... | @@ -79,13 +79,13 @@ describe("DesktopAssets", () => { |
| 79 | 79 | pathOrDescriptor: candidatePath, |
| 80 | 80 | description: "private filesystem diagnostic", |
| 81 | 81 | }); |
| 82 | const fileSystemLayer = FileSystem.layerNoop({ | |
| 82 | const layerFileSystem = FileSystem.layerNoop({ | |
| 83 | 83 | exists: (path) => (path === candidatePath ? Effect.fail(cause) : Effect.succeed(false)), |
| 84 | 84 | }); |
| 85 | const assetsLayer = DesktopAssets.layer.pipe( | |
| 86 | Layer.provide(Layer.merge(fileSystemLayer, environmentLayer)), | |
| 85 | const layerAssets = DesktopAssets.layer.pipe( | |
| 86 | Layer.provide(Layer.merge(layerFileSystem, layerEnvironment)), | |
| 87 | 87 | ); |
| 88 | const assets = yield* DesktopAssets.DesktopAssets.pipe(Effect.provide(assetsLayer)); | |
| 88 | const assets = yield* DesktopAssets.DesktopAssets.pipe(Effect.provide(layerAssets)); | |
| 89 | 89 | |
| 90 | 90 | const error = yield* assets.resolveResourcePath(fileName).pipe(Effect.flip); |
| 91 | 91 |
apps/desktop/src/app/DesktopClerk.test.ts+9-9| ... | ... | @@ -38,7 +38,7 @@ import * as DesktopClerk from "./DesktopClerk.ts"; |
| 38 | 38 | import * as DesktopEnvironment from "./DesktopEnvironment.ts"; |
| 39 | 39 | import * as DesktopPreReadyFileSystem from "./DesktopPreReadyFileSystem.ts"; |
| 40 | 40 | |
| 41 | const makeDesktopClerkLayer = ( | |
| 41 | const layerDesktopClerk = ( | |
| 42 | 42 | isDevelopment = true, |
| 43 | 43 | events: string[] = [], |
| 44 | 44 | platform: NodeJS.Platform = "darwin", |
| ... | ... | @@ -94,7 +94,7 @@ describe("DesktopClerk", () => { |
| 94 | 94 | }); |
| 95 | 95 | |
| 96 | 96 | return Effect.gen(function* () { |
| 97 | yield* Effect.scoped(Layer.build(makeDesktopClerkLayer(true, events))); | |
| 97 | yield* Effect.scoped(Layer.build(layerDesktopClerk(true, events))); | |
| 98 | 98 | |
| 99 | 99 | assert.deepEqual(createClerkBridgeMock.mock.calls, [ |
| 100 | 100 | [ |
| ... | ... | @@ -143,7 +143,7 @@ describe("DesktopClerk", () => { |
| 143 | 143 | Effect.runSync( |
| 144 | 144 | Effect.scoped( |
| 145 | 145 | Layer.build( |
| 146 | makeDesktopClerkLayer(isDevelopment, events, platform, DesktopPreReadyFileSystem.layer), | |
| 146 | layerDesktopClerk(isDevelopment, events, platform, DesktopPreReadyFileSystem.layer), | |
| 147 | 147 | ), |
| 148 | 148 | ), |
| 149 | 149 | ); |
| ... | ... | @@ -160,7 +160,7 @@ describe("DesktopClerk", () => { |
| 160 | 160 | }); |
| 161 | 161 | |
| 162 | 162 | return Effect.gen(function* () { |
| 163 | const error = yield* Effect.scoped(Layer.build(makeDesktopClerkLayer())).pipe(Effect.flip); | |
| 163 | const error = yield* Effect.scoped(Layer.build(layerDesktopClerk())).pipe(Effect.flip); | |
| 164 | 164 | |
| 165 | 165 | assert.instanceOf(error, DesktopClerk.DesktopClerkBridgeInitializationError); |
| 166 | 166 | assert.equal(error.stateDir, "/tmp/t3-state"); |
| ... | ... | @@ -183,7 +183,7 @@ describe("DesktopClerk", () => { |
| 183 | 183 | }); |
| 184 | 184 | |
| 185 | 185 | return Effect.gen(function* () { |
| 186 | const exit = yield* Effect.exit(Effect.scoped(Layer.build(makeDesktopClerkLayer(false)))); | |
| 186 | const exit = yield* Effect.exit(Effect.scoped(Layer.build(layerDesktopClerk(false)))); | |
| 187 | 187 | |
| 188 | 188 | assert.equal(exit._tag, "Failure"); |
| 189 | 189 | if (exit._tag === "Failure") { |
| ... | ... | @@ -222,7 +222,7 @@ describe("DesktopClerk", () => { |
| 222 | 222 | assert.equal(quit.mock.calls.length, 0); |
| 223 | 223 | assert.deepEqual(registeredEvents, ["open-url", "second-instance"]); |
| 224 | 224 | }).pipe( |
| 225 | Effect.provide(makeDesktopClerkLayer()), | |
| 225 | Effect.provide(layerDesktopClerk()), | |
| 226 | 226 | Effect.provideService(ElectronApp.ElectronApp, electronApp), |
| 227 | 227 | Effect.provideService(ElectronWindow.ElectronWindow, electronWindow), |
| 228 | 228 | ); |
| ... | ... | @@ -250,7 +250,7 @@ describe("DesktopClerk", () => { |
| 250 | 250 | assert.equal(quit.mock.calls.length, 1); |
| 251 | 251 | assert.deepEqual(registeredEvents, []); |
| 252 | 252 | }).pipe( |
| 253 | Effect.provide(makeDesktopClerkLayer()), | |
| 253 | Effect.provide(layerDesktopClerk()), | |
| 254 | 254 | Effect.provideService(ElectronApp.ElectronApp, electronApp), |
| 255 | 255 | Effect.provideService(ElectronWindow.ElectronWindow, electronWindow), |
| 256 | 256 | ); |
| ... | ... | @@ -296,7 +296,7 @@ it.effect( |
| 296 | 296 | assert.equal(event.preventDefault.mock.calls.length, 1); |
| 297 | 297 | }).pipe( |
| 298 | 298 | Effect.scoped, |
| 299 | Effect.provide(makeDesktopClerkLayer()), | |
| 299 | Effect.provide(layerDesktopClerk()), | |
| 300 | 300 | Effect.provideService(ElectronApp.ElectronApp, electronApp), |
| 301 | 301 | Effect.provideService(ElectronWindow.ElectronWindow, electronWindow), |
| 302 | 302 | ); |
| ... | ... | @@ -374,7 +374,7 @@ it.effect.each(["startup", "open-url"] as const)( |
| 374 | 374 | assert.strictEqual(delivery?.flowId, request.flowId); |
| 375 | 375 | assert.strictEqual(delivery?.returnUrl, request.returnUrl); |
| 376 | 376 | }).pipe( |
| 377 | Effect.provide(makeDesktopClerkLayer(true, [], "darwin", undefined, shell)), | |
| 377 | Effect.provide(layerDesktopClerk(true, [], "darwin", undefined, shell)), | |
| 378 | 378 | Effect.provideService(HostProcessArguments, entry === "startup" ? ["t3", link] : ["t3"]), |
| 379 | 379 | Effect.provideService(ElectronApp.ElectronApp, electronApp), |
| 380 | 380 | Effect.provideService( |
apps/desktop/src/app/DesktopConnectionCatalogStore.test.ts+17-17| ... | ... | @@ -28,7 +28,7 @@ const encodeLegacySavedEnvironments = Schema.encodeEffect( |
| 28 | 28 | Schema.Struct({ version: Schema.Literal(1), records: Schema.Array(Schema.Unknown) }), |
| 29 | 29 | ), |
| 30 | 30 | ); |
| 31 | function makeSafeStorageLayer(available: boolean, failDecrypt: Ref.Ref<boolean> | null = null) { | |
| 31 | function layerSafeStorageFor(available: boolean, failDecrypt: Ref.Ref<boolean> | null = null) { | |
| 32 | 32 | return Layer.succeed(ElectronSafeStorage.ElectronSafeStorage, { |
| 33 | 33 | isEncryptionAvailable: Effect.succeed(available), |
| 34 | 34 | encryptString: (value) => Effect.succeed(textEncoder.encode(`encrypted:${value}`)), |
| ... | ... | @@ -50,13 +50,13 @@ function makeSafeStorageLayer(available: boolean, failDecrypt: Ref.Ref<boolean> |
| 50 | 50 | } satisfies ElectronSafeStorage.ElectronSafeStorage["Service"]); |
| 51 | 51 | } |
| 52 | 52 | |
| 53 | function makeLayer( | |
| 53 | function layerFor( | |
| 54 | 54 | baseDir: string, |
| 55 | 55 | encryptionAvailable = true, |
| 56 | 56 | failDecrypt: Ref.Ref<boolean> | null = null, |
| 57 | 57 | fileSystemLayer: Layer.Layer<FileSystem.FileSystem> = NodeServices.layer, |
| 58 | 58 | ) { |
| 59 | const environmentLayer = DesktopEnvironment.layer({ | |
| 59 | const layerEnvironment = DesktopEnvironment.layer({ | |
| 60 | 60 | dirname: "/repo/apps/desktop/src", |
| 61 | 61 | homeDirectory: baseDir, |
| 62 | 62 | platform: "darwin", |
| ... | ... | @@ -71,20 +71,20 @@ function makeLayer( |
| 71 | 71 | Layer.mergeAll(NodeServices.layer, DesktopConfig.layerTest({ T3CODE_HOME: baseDir })), |
| 72 | 72 | ), |
| 73 | 73 | ); |
| 74 | const safeStorageLayer = makeSafeStorageLayer(encryptionAvailable, failDecrypt); | |
| 75 | const dependencies = Layer.mergeAll( | |
| 76 | environmentLayer, | |
| 77 | safeStorageLayer, | |
| 74 | const layerSafeStorage = layerSafeStorageFor(encryptionAvailable, failDecrypt); | |
| 75 | const layerDependencies = Layer.mergeAll( | |
| 76 | layerEnvironment, | |
| 77 | layerSafeStorage, | |
| 78 | 78 | NodeServices.layer, |
| 79 | 79 | fileSystemLayer, |
| 80 | 80 | ); |
| 81 | const savedEnvironmentsLayer = DesktopSavedEnvironments.layer.pipe( | |
| 82 | Layer.provideMerge(dependencies), | |
| 81 | const layerSavedEnvironments = DesktopSavedEnvironments.layer.pipe( | |
| 82 | Layer.provideMerge(layerDependencies), | |
| 83 | 83 | ); |
| 84 | 84 | |
| 85 | 85 | return DesktopConnectionCatalogStore.layer.pipe( |
| 86 | Layer.provideMerge(savedEnvironmentsLayer), | |
| 87 | Layer.provideMerge(dependencies), | |
| 86 | Layer.provideMerge(layerSavedEnvironments), | |
| 87 | Layer.provideMerge(layerDependencies), | |
| 88 | 88 | ); |
| 89 | 89 | } |
| 90 | 90 | |
| ... | ... | @@ -97,7 +97,7 @@ const withStore = <A, E, R>( |
| 97 | 97 | const baseDir = yield* fileSystem.makeTempDirectoryScoped({ |
| 98 | 98 | prefix: "t3-desktop-connection-catalog-test-", |
| 99 | 99 | }); |
| 100 | return yield* effect.pipe(Effect.provide(makeLayer(baseDir, encryptionAvailable))); | |
| 100 | return yield* effect.pipe(Effect.provide(layerFor(baseDir, encryptionAvailable))); | |
| 101 | 101 | }).pipe(Effect.provide(NodeServices.layer), Effect.scoped); |
| 102 | 102 | |
| 103 | 103 | describe("DesktopConnectionCatalogStore", () => { |
| ... | ... | @@ -280,14 +280,14 @@ describe("DesktopConnectionCatalogStore", () => { |
| 280 | 280 | method: "readFileString", |
| 281 | 281 | pathOrDescriptor: path.join(baseDir, "userdata", "connection-catalog.json"), |
| 282 | 282 | }); |
| 283 | const fileSystemLayer = Layer.succeed( | |
| 283 | const layerFileSystem = Layer.succeed( | |
| 284 | 284 | FileSystem.FileSystem, |
| 285 | 285 | FileSystem.makeNoop({ |
| 286 | 286 | readFileString: () => Effect.fail(permissionError), |
| 287 | 287 | }), |
| 288 | 288 | ); |
| 289 | 289 | const store = yield* DesktopConnectionCatalogStore.DesktopConnectionCatalogStore.pipe( |
| 290 | Effect.provide(makeLayer(baseDir, true, null, fileSystemLayer)), | |
| 290 | Effect.provide(layerFor(baseDir, true, null, layerFileSystem)), | |
| 291 | 291 | ); |
| 292 | 292 | |
| 293 | 293 | const error = yield* store.get.pipe(Effect.flip); |
| ... | ... | @@ -318,14 +318,14 @@ describe("DesktopConnectionCatalogStore", () => { |
| 318 | 318 | method: "makeDirectory", |
| 319 | 319 | pathOrDescriptor: path.join(baseDir, "userdata"), |
| 320 | 320 | }); |
| 321 | const fileSystemLayer = Layer.succeed( | |
| 321 | const layerFileSystem = Layer.succeed( | |
| 322 | 322 | FileSystem.FileSystem, |
| 323 | 323 | FileSystem.makeNoop({ |
| 324 | 324 | makeDirectory: () => Effect.fail(permissionError), |
| 325 | 325 | }), |
| 326 | 326 | ); |
| 327 | 327 | const store = yield* DesktopConnectionCatalogStore.DesktopConnectionCatalogStore.pipe( |
| 328 | Effect.provide(makeLayer(baseDir, true, null, fileSystemLayer)), | |
| 328 | Effect.provide(layerFor(baseDir, true, null, layerFileSystem)), | |
| 329 | 329 | ); |
| 330 | 330 | |
| 331 | 331 | const error = yield* store.set("{}").pipe(Effect.flip); |
| ... | ... | @@ -413,7 +413,7 @@ describe("DesktopConnectionCatalogStore", () => { |
| 413 | 413 | prefix: "t3-desktop-connection-catalog-test-", |
| 414 | 414 | }); |
| 415 | 415 | const failDecrypt = yield* Ref.make(false); |
| 416 | const layer = makeLayer(baseDir, true, failDecrypt); | |
| 416 | const layer = layerFor(baseDir, true, failDecrypt); | |
| 417 | 417 | const store = yield* DesktopConnectionCatalogStore.DesktopConnectionCatalogStore.pipe( |
| 418 | 418 | Effect.provide(layer), |
| 419 | 419 | ); |
apps/desktop/src/app/DesktopEnvironment.test.ts+2-3| ... | ... | @@ -20,7 +20,7 @@ const defaultInput = { |
| 20 | 20 | runningUnderArm64Translation: false, |
| 21 | 21 | } satisfies DesktopEnvironment.MakeDesktopEnvironmentInput; |
| 22 | 22 | |
| 23 | const makeEnvironmentLayer = ( | |
| 23 | const layerEnvironment = ( | |
| 24 | 24 | overrides: Partial<DesktopEnvironment.MakeDesktopEnvironmentInput> = {}, |
| 25 | 25 | env: Record<string, string | undefined> = {}, |
| 26 | 26 | ) => |
| ... | ... | @@ -36,8 +36,7 @@ const makeEnvironmentLayer = ( |
| 36 | 36 | const makeEnvironment = ( |
| 37 | 37 | overrides: Partial<DesktopEnvironment.MakeDesktopEnvironmentInput> = {}, |
| 38 | 38 | env: Record<string, string | undefined> = {}, |
| 39 | ) => | |
| 40 | DesktopEnvironment.DesktopEnvironment.pipe(Effect.provide(makeEnvironmentLayer(overrides, env))); | |
| 39 | ) => DesktopEnvironment.DesktopEnvironment.pipe(Effect.provide(layerEnvironment(overrides, env))); | |
| 41 | 40 | |
| 42 | 41 | describe("DesktopEnvironment", () => { |
| 43 | 42 | it.effect("derives state paths and development identity inside Effect", () => |
apps/desktop/src/app/DesktopLifecycle.test.ts+24-24| ... | ... | @@ -15,7 +15,7 @@ import * as DesktopShutdown from "./DesktopShutdown.ts"; |
| 15 | 15 | import * as DesktopState from "./DesktopState.ts"; |
| 16 | 16 | import * as DesktopWindow from "../window/DesktopWindow.ts"; |
| 17 | 17 | |
| 18 | function makeElectronAppLayer( | |
| 18 | function layerElectronApp( | |
| 19 | 19 | appListeners: Map<string, (...args: readonly unknown[]) => void>, |
| 20 | 20 | quit: Effect.Effect<void> = Effect.void, |
| 21 | 21 | ) { |
| ... | ... | @@ -54,13 +54,13 @@ function makeElectronAppLayer( |
| 54 | 54 | } satisfies ElectronApp.ElectronApp["Service"]); |
| 55 | 55 | } |
| 56 | 56 | |
| 57 | const electronThemeLayer = Layer.succeed(ElectronTheme.ElectronTheme, { | |
| 57 | const layerElectronTheme = Layer.succeed(ElectronTheme.ElectronTheme, { | |
| 58 | 58 | shouldUseDarkColors: Effect.succeed(false), |
| 59 | 59 | setSource: () => Effect.void, |
| 60 | 60 | onUpdated: () => Effect.void, |
| 61 | 61 | }); |
| 62 | 62 | |
| 63 | function makeElectronWindowLayer(destroyAll: Effect.Effect<void> = Effect.void) { | |
| 63 | function layerElectronWindow(destroyAll: Effect.Effect<void> = Effect.void) { | |
| 64 | 64 | return Layer.succeed(ElectronWindow.ElectronWindow, { |
| 65 | 65 | create: () => Effect.die("unexpected window creation"), |
| 66 | 66 | main: Effect.die("unexpected main window read"), |
| ... | ... | @@ -76,7 +76,7 @@ function makeElectronWindowLayer(destroyAll: Effect.Effect<void> = Effect.void) |
| 76 | 76 | }); |
| 77 | 77 | } |
| 78 | 78 | |
| 79 | function makeDesktopWindowLayer( | |
| 79 | function layerDesktopWindow( | |
| 80 | 80 | input: { |
| 81 | 81 | readonly activate?: Effect.Effect<void>; |
| 82 | 82 | readonly flushMainWindowBounds?: Effect.Effect<void>; |
| ... | ... | @@ -106,23 +106,23 @@ describe("DesktopLifecycle", () => { |
| 106 | 106 | (platform) => { |
| 107 | 107 | const appListeners = new Map<string, (...args: readonly unknown[]) => void>(); |
| 108 | 108 | let windowsDestroyed = false; |
| 109 | const environmentLayer = Layer.succeed(DesktopEnvironment.DesktopEnvironment, { | |
| 109 | const layerEnvironment = Layer.succeed(DesktopEnvironment.DesktopEnvironment, { | |
| 110 | 110 | platform, |
| 111 | 111 | isDevelopment: false, |
| 112 | 112 | } as DesktopEnvironment.DesktopEnvironment["Service"]); |
| 113 | 113 | |
| 114 | 114 | const layer = DesktopLifecycle.layer.pipe( |
| 115 | Layer.provideMerge(makeElectronAppLayer(appListeners)), | |
| 116 | Layer.provideMerge(electronThemeLayer), | |
| 115 | Layer.provideMerge(layerElectronApp(appListeners)), | |
| 116 | Layer.provideMerge(layerElectronTheme), | |
| 117 | 117 | Layer.provideMerge( |
| 118 | makeElectronWindowLayer( | |
| 118 | layerElectronWindow( | |
| 119 | 119 | Effect.sync(() => { |
| 120 | 120 | windowsDestroyed = true; |
| 121 | 121 | }), |
| 122 | 122 | ), |
| 123 | 123 | ), |
| 124 | Layer.provideMerge(makeDesktopWindowLayer()), | |
| 125 | Layer.provideMerge(environmentLayer), | |
| 124 | Layer.provideMerge(layerDesktopWindow()), | |
| 125 | Layer.provideMerge(layerEnvironment), | |
| 126 | 126 | Layer.provideMerge(DesktopShutdown.layer), |
| 127 | 127 | Layer.provideMerge(DesktopState.layer), |
| 128 | 128 | ); |
| ... | ... | @@ -174,7 +174,7 @@ describe("DesktopLifecycle", () => { |
| 174 | 174 | events.push("flush"); |
| 175 | 175 | }); |
| 176 | 176 | |
| 177 | const desktopShutdownLayer = Layer.succeed(DesktopShutdown.DesktopShutdown, { | |
| 177 | const layerDesktopShutdown = Layer.succeed(DesktopShutdown.DesktopShutdown, { | |
| 178 | 178 | request: Effect.sync(() => { |
| 179 | 179 | events.push("request"); |
| 180 | 180 | }).pipe(Effect.andThen(Deferred.succeed(shutdownRequested, undefined)), Effect.asVoid), |
| ... | ... | @@ -184,18 +184,18 @@ describe("DesktopLifecycle", () => { |
| 184 | 184 | isComplete: Deferred.isDone(allowShutdown), |
| 185 | 185 | }); |
| 186 | 186 | |
| 187 | const environmentLayer = Layer.succeed(DesktopEnvironment.DesktopEnvironment, { | |
| 187 | const layerEnvironment = Layer.succeed(DesktopEnvironment.DesktopEnvironment, { | |
| 188 | 188 | platform: "darwin", |
| 189 | 189 | isDevelopment: false, |
| 190 | 190 | } as DesktopEnvironment.DesktopEnvironment["Service"]); |
| 191 | 191 | |
| 192 | 192 | const layer = DesktopLifecycle.layer.pipe( |
| 193 | Layer.provideMerge(makeElectronAppLayer(appListeners, quit)), | |
| 194 | Layer.provideMerge(electronThemeLayer), | |
| 195 | Layer.provideMerge(makeElectronWindowLayer(destroyAll)), | |
| 196 | Layer.provideMerge(makeDesktopWindowLayer({ flushMainWindowBounds })), | |
| 197 | Layer.provideMerge(environmentLayer), | |
| 198 | Layer.provideMerge(desktopShutdownLayer), | |
| 193 | Layer.provideMerge(layerElectronApp(appListeners, quit)), | |
| 194 | Layer.provideMerge(layerElectronTheme), | |
| 195 | Layer.provideMerge(layerElectronWindow(destroyAll)), | |
| 196 | Layer.provideMerge(layerDesktopWindow({ flushMainWindowBounds })), | |
| 197 | Layer.provideMerge(layerEnvironment), | |
| 198 | Layer.provideMerge(layerDesktopShutdown), | |
| 199 | 199 | Layer.provideMerge(DesktopState.layer), |
| 200 | 200 | ); |
| 201 | 201 | |
| ... | ... | @@ -226,16 +226,16 @@ describe("DesktopLifecycle", () => { |
| 226 | 226 | const activate = Effect.sync(() => { |
| 227 | 227 | activationCount += 1; |
| 228 | 228 | }); |
| 229 | const environmentLayer = Layer.succeed(DesktopEnvironment.DesktopEnvironment, { | |
| 229 | const layerEnvironment = Layer.succeed(DesktopEnvironment.DesktopEnvironment, { | |
| 230 | 230 | platform: "darwin", |
| 231 | 231 | isDevelopment: false, |
| 232 | 232 | } as DesktopEnvironment.DesktopEnvironment["Service"]); |
| 233 | 233 | const layer = DesktopLifecycle.layer.pipe( |
| 234 | Layer.provideMerge(makeElectronAppLayer(appListeners)), | |
| 235 | Layer.provideMerge(electronThemeLayer), | |
| 236 | Layer.provideMerge(makeElectronWindowLayer()), | |
| 237 | Layer.provideMerge(makeDesktopWindowLayer({ activate })), | |
| 238 | Layer.provideMerge(environmentLayer), | |
| 234 | Layer.provideMerge(layerElectronApp(appListeners)), | |
| 235 | Layer.provideMerge(layerElectronTheme), | |
| 236 | Layer.provideMerge(layerElectronWindow()), | |
| 237 | Layer.provideMerge(layerDesktopWindow({ activate })), | |
| 238 | Layer.provideMerge(layerEnvironment), | |
| 239 | 239 | Layer.provideMerge(DesktopShutdown.layer), |
| 240 | 240 | Layer.provideMerge(DesktopState.layer), |
| 241 | 241 | ); |
apps/desktop/src/app/DesktopLinuxUrlHandler.test.ts+3-3| ... | ... | @@ -52,7 +52,7 @@ const mockProcess = (exitCode: number, stalled = false) => |
| 52 | 52 | getOutputFd: () => Stream.empty, |
| 53 | 53 | }); |
| 54 | 54 | |
| 55 | const makeHandlerLayer = ( | |
| 55 | const layerHandler = ( | |
| 56 | 56 | recorded: RecordedRegistration, |
| 57 | 57 | input: { |
| 58 | 58 | readonly environment?: Record<string, unknown>; |
| ... | ... | @@ -143,12 +143,12 @@ const makeHandlerLayer = ( |
| 143 | 143 | |
| 144 | 144 | const runRegister = ( |
| 145 | 145 | recorded: RecordedRegistration, |
| 146 | input: Parameters<typeof makeHandlerLayer>[1] = {}, | |
| 146 | input: Parameters<typeof layerHandler>[1] = {}, | |
| 147 | 147 | ) => |
| 148 | 148 | Effect.gen(function* () { |
| 149 | 149 | const handler = yield* DesktopLinuxUrlHandler.DesktopLinuxUrlHandler; |
| 150 | 150 | yield* handler.register; |
| 151 | }).pipe(Effect.provide(makeHandlerLayer(recorded, input))); | |
| 151 | }).pipe(Effect.provide(layerHandler(recorded, input))); | |
| 152 | 152 | |
| 153 | 153 | const emptyRecording = (): RecordedRegistration => ({ |
| 154 | 154 | directories: [], |
apps/desktop/src/app/DesktopObservability.test.ts+54-54| ... | ... | @@ -52,7 +52,7 @@ const environmentInput = (baseDir: string) => |
| 52 | 52 | runningUnderArm64Translation: false, |
| 53 | 53 | }) satisfies DesktopEnvironment.MakeDesktopEnvironmentInput; |
| 54 | 54 | |
| 55 | const makeEnvironmentLayer = ( | |
| 55 | const layerEnvironmentFor = ( | |
| 56 | 56 | baseDir: string, |
| 57 | 57 | isDevelopment = true, |
| 58 | 58 | env: Readonly<Record<string, string | undefined>> = {}, |
| ... | ... | @@ -77,7 +77,7 @@ interface ExportedRequest { |
| 77 | 77 | } |
| 78 | 78 | |
| 79 | 79 | /** Answers every export with a 200 and keeps what was posted for assertions. */ |
| 80 | const collectorLayer = (requests: Array<ExportedRequest>) => | |
| 80 | const layerCollector = (requests: Array<ExportedRequest>) => | |
| 81 | 81 | Layer.succeed( |
| 82 | 82 | HttpClient.HttpClient, |
| 83 | 83 | HttpClient.make((request) => |
| ... | ... | @@ -94,7 +94,7 @@ const collectorLayer = (requests: Array<ExportedRequest>) => |
| 94 | 94 | ); |
| 95 | 95 | |
| 96 | 96 | // A developer's own OTEL_* variables would otherwise pick the endpoints. |
| 97 | const emptyEnv = ConfigProvider.layer(ConfigProvider.fromEnv({ env: {} })); | |
| 97 | const layerEmptyEnv = ConfigProvider.layer(ConfigProvider.fromEnv({ env: {} })); | |
| 98 | 98 | |
| 99 | 99 | const encodeObservabilitySettingsFile = Schema.encodeSync( |
| 100 | 100 | Schema.fromJsonString( |
| ... | ... | @@ -103,7 +103,7 @@ const encodeObservabilitySettingsFile = Schema.encodeSync( |
| 103 | 103 | ); |
| 104 | 104 | |
| 105 | 105 | const writeObservabilitySettings = Effect.fn(function* ( |
| 106 | environmentLayer: ReturnType<typeof makeEnvironmentLayer>, | |
| 106 | environmentLayer: ReturnType<typeof layerEnvironmentFor>, | |
| 107 | 107 | observability: Readonly<Record<string, string>>, |
| 108 | 108 | ) { |
| 109 | 109 | const fileSystem = yield* FileSystem.FileSystem; |
| ... | ... | @@ -146,15 +146,15 @@ describe("DesktopObservability", () => { |
| 146 | 146 | const baseDir = yield* fileSystem.makeTempDirectoryScoped({ |
| 147 | 147 | prefix: "t3-desktop-observability-test-", |
| 148 | 148 | }); |
| 149 | const environmentLayer = makeEnvironmentLayer(baseDir); | |
| 149 | const layerEnvironment = layerEnvironmentFor(baseDir); | |
| 150 | 150 | const tracePath = yield* Effect.gen(function* () { |
| 151 | 151 | const environment = yield* DesktopEnvironment.DesktopEnvironment; |
| 152 | 152 | return environment.path.join(environment.logDir, "desktop.trace.ndjson"); |
| 153 | }).pipe(Effect.provide(environmentLayer)); | |
| 153 | }).pipe(Effect.provide(layerEnvironment)); | |
| 154 | 154 | const logPath = yield* Effect.gen(function* () { |
| 155 | 155 | const environment = yield* DesktopEnvironment.DesktopEnvironment; |
| 156 | 156 | return environment.path.join(environment.logDir, "desktop-main.log"); |
| 157 | }).pipe(Effect.provide(environmentLayer)); | |
| 157 | }).pipe(Effect.provide(layerEnvironment)); | |
| 158 | 158 | |
| 159 | 159 | yield* Effect.scoped( |
| 160 | 160 | Effect.gen(function* () { |
| ... | ... | @@ -162,7 +162,7 @@ describe("DesktopObservability", () => { |
| 162 | 162 | yield* Effect.logInfo("desktop trace event"); |
| 163 | 163 | }).pipe( |
| 164 | 164 | Effect.withSpan("desktop-observability-test"), |
| 165 | Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(environmentLayer))), | |
| 165 | Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(layerEnvironment))), | |
| 166 | 166 | ), |
| 167 | 167 | ); |
| 168 | 168 | |
| ... | ... | @@ -185,7 +185,7 @@ describe("DesktopObservability", () => { |
| 185 | 185 | assert.isFalse(yield* fileSystem.exists(logPath)); |
| 186 | 186 | }).pipe( |
| 187 | 187 | Effect.scoped, |
| 188 | Effect.provide(Layer.mergeAll(NodeServices.layer, NodeHttpClient.layerUndici, emptyEnv)), | |
| 188 | Effect.provide(Layer.mergeAll(NodeServices.layer, NodeHttpClient.layerUndici, layerEmptyEnv)), | |
| 189 | 189 | ), |
| 190 | 190 | ); |
| 191 | 191 | |
| ... | ... | @@ -195,15 +195,15 @@ describe("DesktopObservability", () => { |
| 195 | 195 | const baseDir = yield* fileSystem.makeTempDirectoryScoped({ |
| 196 | 196 | prefix: "t3-desktop-backend-output-log-test-", |
| 197 | 197 | }); |
| 198 | const environmentLayer = makeEnvironmentLayer(baseDir, false); | |
| 198 | const layerEnvironment = layerEnvironmentFor(baseDir, false); | |
| 199 | 199 | const logPath = yield* Effect.gen(function* () { |
| 200 | 200 | const environment = yield* DesktopEnvironment.DesktopEnvironment; |
| 201 | 201 | return environment.path.join(environment.logDir, "server-child.log"); |
| 202 | }).pipe(Effect.provide(environmentLayer)); | |
| 202 | }).pipe(Effect.provide(layerEnvironment)); | |
| 203 | 203 | const tracePath = yield* Effect.gen(function* () { |
| 204 | 204 | const environment = yield* DesktopEnvironment.DesktopEnvironment; |
| 205 | 205 | return environment.path.join(environment.logDir, "desktop.trace.ndjson"); |
| 206 | }).pipe(Effect.provide(environmentLayer)); | |
| 206 | }).pipe(Effect.provide(layerEnvironment)); | |
| 207 | 207 | |
| 208 | 208 | yield* Effect.gen(function* () { |
| 209 | 209 | const factory = yield* DesktopObservability.DesktopBackendOutputLogFactory; |
| ... | ... | @@ -219,7 +219,7 @@ describe("DesktopObservability", () => { |
| 219 | 219 | yield* outputLog.discardSession; |
| 220 | 220 | }).pipe( |
| 221 | 221 | Effect.annotateLogs({ runId: "test-run" }), |
| 222 | Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(environmentLayer))), | |
| 222 | Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(layerEnvironment))), | |
| 223 | 223 | ); |
| 224 | 224 | |
| 225 | 225 | const log = yield* fileSystem.readFileString(logPath); |
| ... | ... | @@ -263,7 +263,7 @@ describe("DesktopObservability", () => { |
| 263 | 263 | ); |
| 264 | 264 | }).pipe( |
| 265 | 265 | Effect.scoped, |
| 266 | Effect.provide(Layer.mergeAll(NodeServices.layer, NodeHttpClient.layerUndici, emptyEnv)), | |
| 266 | Effect.provide(Layer.mergeAll(NodeServices.layer, NodeHttpClient.layerUndici, layerEmptyEnv)), | |
| 267 | 267 | ), |
| 268 | 268 | ); |
| 269 | 269 | |
| ... | ... | @@ -273,11 +273,11 @@ describe("DesktopObservability", () => { |
| 273 | 273 | const baseDir = yield* fileSystem.makeTempDirectoryScoped({ |
| 274 | 274 | prefix: "t3-desktop-backend-output-snapshot-test-", |
| 275 | 275 | }); |
| 276 | const environmentLayer = makeEnvironmentLayer(baseDir, false); | |
| 276 | const layerEnvironment = layerEnvironmentFor(baseDir, false); | |
| 277 | 277 | const logPath = yield* Effect.gen(function* () { |
| 278 | 278 | const environment = yield* DesktopEnvironment.DesktopEnvironment; |
| 279 | 279 | return environment.path.join(environment.logDir, "server-child.log"); |
| 280 | }).pipe(Effect.provide(environmentLayer)); | |
| 280 | }).pipe(Effect.provide(layerEnvironment)); | |
| 281 | 281 | |
| 282 | 282 | yield* Effect.gen(function* () { |
| 283 | 283 | const factory = yield* DesktopObservability.DesktopBackendOutputLogFactory; |
| ... | ... | @@ -289,7 +289,7 @@ describe("DesktopObservability", () => { |
| 289 | 289 | yield* outputLog.persistFailure({ details: "code=1" }); |
| 290 | 290 | }).pipe( |
| 291 | 291 | Effect.annotateLogs({ runId: "test-run" }), |
| 292 | Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(environmentLayer))), | |
| 292 | Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(layerEnvironment))), | |
| 293 | 293 | ); |
| 294 | 294 | |
| 295 | 295 | const records = yield* Effect.forEach( |
| ... | ... | @@ -303,7 +303,7 @@ describe("DesktopObservability", () => { |
| 303 | 303 | assert.equal(records.at(-1)?.annotations.details, "code=1"); |
| 304 | 304 | }).pipe( |
| 305 | 305 | Effect.scoped, |
| 306 | Effect.provide(Layer.mergeAll(NodeServices.layer, NodeHttpClient.layerUndici, emptyEnv)), | |
| 306 | Effect.provide(Layer.mergeAll(NodeServices.layer, NodeHttpClient.layerUndici, layerEmptyEnv)), | |
| 307 | 307 | ), |
| 308 | 308 | ); |
| 309 | 309 | |
| ... | ... | @@ -313,11 +313,11 @@ describe("DesktopObservability", () => { |
| 313 | 313 | const baseDir = yield* fileSystem.makeTempDirectoryScoped({ |
| 314 | 314 | prefix: "t3-desktop-backend-output-bound-test-", |
| 315 | 315 | }); |
| 316 | const environmentLayer = makeEnvironmentLayer(baseDir, false); | |
| 316 | const layerEnvironment = layerEnvironmentFor(baseDir, false); | |
| 317 | 317 | const logPath = yield* Effect.gen(function* () { |
| 318 | 318 | const environment = yield* DesktopEnvironment.DesktopEnvironment; |
| 319 | 319 | return environment.path.join(environment.logDir, "server-child.log"); |
| 320 | }).pipe(Effect.provide(environmentLayer)); | |
| 320 | }).pipe(Effect.provide(layerEnvironment)); | |
| 321 | 321 | const maxBufferedBytes = 1024 * 1024; |
| 322 | 322 | const discardedPrefixBytes = 128; |
| 323 | 323 | const output = new Uint8Array(maxBufferedBytes + discardedPrefixBytes); |
| ... | ... | @@ -332,7 +332,7 @@ describe("DesktopObservability", () => { |
| 332 | 332 | yield* outputLog.writeOutputChunk("stderr", output); |
| 333 | 333 | yield* outputLog.persistFailure({ details: "code=1" }); |
| 334 | 334 | }).pipe( |
| 335 | Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(environmentLayer))), | |
| 335 | Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(layerEnvironment))), | |
| 336 | 336 | ), |
| 337 | 337 | ); |
| 338 | 338 | |
| ... | ... | @@ -347,7 +347,7 @@ describe("DesktopObservability", () => { |
| 347 | 347 | assert.isFalse(text.includes("y")); |
| 348 | 348 | }).pipe( |
| 349 | 349 | Effect.scoped, |
| 350 | Effect.provide(Layer.mergeAll(NodeServices.layer, NodeHttpClient.layerUndici, emptyEnv)), | |
| 350 | Effect.provide(Layer.mergeAll(NodeServices.layer, NodeHttpClient.layerUndici, layerEmptyEnv)), | |
| 351 | 351 | ), |
| 352 | 352 | ); |
| 353 | 353 | |
| ... | ... | @@ -357,11 +357,11 @@ describe("DesktopObservability", () => { |
| 357 | 357 | const baseDir = yield* fileSystem.makeTempDirectoryScoped({ |
| 358 | 358 | prefix: "t3-desktop-backend-output-chunks-test-", |
| 359 | 359 | }); |
| 360 | const environmentLayer = makeEnvironmentLayer(baseDir, false); | |
| 360 | const layerEnvironment = layerEnvironmentFor(baseDir, false); | |
| 361 | 361 | const logPath = yield* Effect.gen(function* () { |
| 362 | 362 | const environment = yield* DesktopEnvironment.DesktopEnvironment; |
| 363 | 363 | return environment.path.join(environment.logDir, "server-child.log"); |
| 364 | }).pipe(Effect.provide(environmentLayer)); | |
| 364 | }).pipe(Effect.provide(layerEnvironment)); | |
| 365 | 365 | |
| 366 | 366 | yield* Effect.scoped( |
| 367 | 367 | Effect.gen(function* () { |
| ... | ... | @@ -373,7 +373,7 @@ describe("DesktopObservability", () => { |
| 373 | 373 | } |
| 374 | 374 | yield* outputLog.persistFailure({ details: "code=1" }); |
| 375 | 375 | }).pipe( |
| 376 | Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(environmentLayer))), | |
| 376 | Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(layerEnvironment))), | |
| 377 | 377 | ), |
| 378 | 378 | ); |
| 379 | 379 | |
| ... | ... | @@ -381,7 +381,7 @@ describe("DesktopObservability", () => { |
| 381 | 381 | assert.equal(lines.length, 258); |
| 382 | 382 | }).pipe( |
| 383 | 383 | Effect.scoped, |
| 384 | Effect.provide(Layer.mergeAll(NodeServices.layer, NodeHttpClient.layerUndici, emptyEnv)), | |
| 384 | Effect.provide(Layer.mergeAll(NodeServices.layer, NodeHttpClient.layerUndici, layerEmptyEnv)), | |
| 385 | 385 | ), |
| 386 | 386 | ); |
| 387 | 387 | |
| ... | ... | @@ -392,19 +392,19 @@ describe("DesktopObservability", () => { |
| 392 | 392 | const baseDir = yield* fileSystem.makeTempDirectoryScoped({ |
| 393 | 393 | prefix: "t3-desktop-observability-test-", |
| 394 | 394 | }); |
| 395 | const environmentLayer = makeEnvironmentLayer(baseDir, true, { | |
| 395 | const layerEnvironment = layerEnvironmentFor(baseDir, true, { | |
| 396 | 396 | T3CODE_OTLP_LOGS_URL: "https://collector.example.com/v1/logs", |
| 397 | 397 | T3CODE_OTLP_HEADERS: "x-scope=desktop", |
| 398 | 398 | }); |
| 399 | 399 | const tracePath = yield* Effect.gen(function* () { |
| 400 | 400 | const environment = yield* DesktopEnvironment.DesktopEnvironment; |
| 401 | 401 | return environment.path.join(environment.logDir, "desktop.trace.ndjson"); |
| 402 | }).pipe(Effect.provide(environmentLayer)); | |
| 402 | }).pipe(Effect.provide(layerEnvironment)); | |
| 403 | 403 | |
| 404 | 404 | yield* Effect.scoped( |
| 405 | 405 | Effect.logInfo("desktop log export").pipe( |
| 406 | 406 | Effect.withSpan("desktop-log-export-test"), |
| 407 | Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(environmentLayer))), | |
| 407 | Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(layerEnvironment))), | |
| 408 | 408 | ), |
| 409 | 409 | ); |
| 410 | 410 | |
| ... | ... | @@ -427,7 +427,7 @@ describe("DesktopObservability", () => { |
| 427 | 427 | assert.lengthOf(record?.events ?? [], 0); |
| 428 | 428 | }).pipe( |
| 429 | 429 | Effect.scoped, |
| 430 | Effect.provide(Layer.mergeAll(NodeServices.layer, collectorLayer(requests), emptyEnv)), | |
| 430 | Effect.provide(Layer.mergeAll(NodeServices.layer, layerCollector(requests), layerEmptyEnv)), | |
| 431 | 431 | ); |
| 432 | 432 | }); |
| 433 | 433 | |
| ... | ... | @@ -438,16 +438,16 @@ describe("DesktopObservability", () => { |
| 438 | 438 | const baseDir = yield* fileSystem.makeTempDirectoryScoped({ |
| 439 | 439 | prefix: "t3-desktop-observability-test-", |
| 440 | 440 | }); |
| 441 | const environmentLayer = makeEnvironmentLayer(baseDir, true, { | |
| 441 | const layerEnvironment = layerEnvironmentFor(baseDir, true, { | |
| 442 | 442 | T3CODE_OTLP_HEADERS: "x-scope=desktop", |
| 443 | 443 | }); |
| 444 | yield* writeObservabilitySettings(environmentLayer, { | |
| 444 | yield* writeObservabilitySettings(layerEnvironment, { | |
| 445 | 445 | otlpLogsUrl: "https://settings.example.com/v1/logs", |
| 446 | 446 | }); |
| 447 | 447 | |
| 448 | 448 | yield* Effect.scoped( |
| 449 | 449 | Effect.logInfo("desktop otel export").pipe( |
| 450 | Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(environmentLayer))), | |
| 450 | Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(layerEnvironment))), | |
| 451 | 451 | ), |
| 452 | 452 | ); |
| 453 | 453 | |
| ... | ... | @@ -462,7 +462,7 @@ describe("DesktopObservability", () => { |
| 462 | 462 | Effect.provide( |
| 463 | 463 | Layer.mergeAll( |
| 464 | 464 | NodeServices.layer, |
| 465 | collectorLayer(requests), | |
| 465 | layerCollector(requests), | |
| 466 | 466 | ConfigProvider.layer( |
| 467 | 467 | ConfigProvider.fromEnv({ |
| 468 | 468 | env: { |
| ... | ... | @@ -484,13 +484,13 @@ describe("DesktopObservability", () => { |
| 484 | 484 | const baseDir = yield* fileSystem.makeTempDirectoryScoped({ |
| 485 | 485 | prefix: "t3-desktop-observability-test-", |
| 486 | 486 | }); |
| 487 | const environmentLayer = makeEnvironmentLayer(baseDir, true, { | |
| 487 | const layerEnvironment = layerEnvironmentFor(baseDir, true, { | |
| 488 | 488 | T3CODE_OTLP_LOGS_URL: "https://collector.example.com/v1/logs", |
| 489 | 489 | }); |
| 490 | 490 | |
| 491 | 491 | yield* Effect.scoped( |
| 492 | 492 | Effect.logInfo("desktop service name").pipe( |
| 493 | Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(environmentLayer))), | |
| 493 | Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(layerEnvironment))), | |
| 494 | 494 | ), |
| 495 | 495 | ); |
| 496 | 496 | |
| ... | ... | @@ -505,7 +505,7 @@ describe("DesktopObservability", () => { |
| 505 | 505 | Effect.provide( |
| 506 | 506 | Layer.mergeAll( |
| 507 | 507 | NodeServices.layer, |
| 508 | collectorLayer(requests), | |
| 508 | layerCollector(requests), | |
| 509 | 509 | ConfigProvider.layer( |
| 510 | 510 | ConfigProvider.fromEnv({ |
| 511 | 511 | env: { |
| ... | ... | @@ -527,16 +527,16 @@ describe("DesktopObservability", () => { |
| 527 | 527 | const baseDir = yield* fileSystem.makeTempDirectoryScoped({ |
| 528 | 528 | prefix: "t3-desktop-observability-test-", |
| 529 | 529 | }); |
| 530 | const environmentLayer = makeEnvironmentLayer(baseDir, true, { | |
| 530 | const layerEnvironment = layerEnvironmentFor(baseDir, true, { | |
| 531 | 531 | T3CODE_OTLP_HEADERS: "x-scope=desktop", |
| 532 | 532 | }); |
| 533 | yield* writeObservabilitySettings(environmentLayer, { | |
| 533 | yield* writeObservabilitySettings(layerEnvironment, { | |
| 534 | 534 | otlpLogsUrl: "https://settings.example.com/v1/logs", |
| 535 | 535 | }); |
| 536 | 536 | |
| 537 | 537 | yield* Effect.scoped( |
| 538 | 538 | Effect.logInfo("desktop otel off").pipe( |
| 539 | Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(environmentLayer))), | |
| 539 | Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(layerEnvironment))), | |
| 540 | 540 | ), |
| 541 | 541 | ); |
| 542 | 542 | |
| ... | ... | @@ -546,7 +546,7 @@ describe("DesktopObservability", () => { |
| 546 | 546 | Effect.provide( |
| 547 | 547 | Layer.mergeAll( |
| 548 | 548 | NodeServices.layer, |
| 549 | collectorLayer(requests), | |
| 549 | layerCollector(requests), | |
| 550 | 550 | ConfigProvider.layer( |
| 551 | 551 | ConfigProvider.fromEnv({ |
| 552 | 552 | env: { |
| ... | ... | @@ -567,13 +567,13 @@ describe("DesktopObservability", () => { |
| 567 | 567 | const baseDir = yield* fileSystem.makeTempDirectoryScoped({ |
| 568 | 568 | prefix: "t3-desktop-observability-test-", |
| 569 | 569 | }); |
| 570 | const environmentLayer = makeEnvironmentLayer(baseDir, true, { | |
| 570 | const layerEnvironment = layerEnvironmentFor(baseDir, true, { | |
| 571 | 571 | T3CODE_OTLP_LOGS_URL: "https://collector.example.com/v1/logs", |
| 572 | 572 | }); |
| 573 | 573 | |
| 574 | 574 | yield* Effect.scoped( |
| 575 | 575 | Effect.void.pipe( |
| 576 | Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(environmentLayer))), | |
| 576 | Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(layerEnvironment))), | |
| 577 | 577 | ), |
| 578 | 578 | ); |
| 579 | 579 | |
| ... | ... | @@ -583,7 +583,7 @@ describe("DesktopObservability", () => { |
| 583 | 583 | Effect.provide( |
| 584 | 584 | Layer.mergeAll( |
| 585 | 585 | NodeServices.layer, |
| 586 | collectorLayer(requests), | |
| 586 | layerCollector(requests), | |
| 587 | 587 | ConfigProvider.layer(ConfigProvider.fromEnv({ env: { OTEL_SDK_DISABLED: "1" } })), |
| 588 | 588 | ), |
| 589 | 589 | ), |
| ... | ... | @@ -597,8 +597,8 @@ describe("DesktopObservability", () => { |
| 597 | 597 | const baseDir = yield* fileSystem.makeTempDirectoryScoped({ |
| 598 | 598 | prefix: "t3-desktop-observability-test-", |
| 599 | 599 | }); |
| 600 | const environmentLayer = makeEnvironmentLayer(baseDir); | |
| 601 | yield* writeObservabilitySettings(environmentLayer, { | |
| 600 | const layerEnvironment = layerEnvironmentFor(baseDir); | |
| 601 | yield* writeObservabilitySettings(layerEnvironment, { | |
| 602 | 602 | otlpTracesUrl: "https://settings.example.com/v1/traces", |
| 603 | 603 | otlpLogsUrl: "https://settings.example.com/v1/logs", |
| 604 | 604 | // The main process records no metrics yet, so this endpoint must |
| ... | ... | @@ -609,7 +609,7 @@ describe("DesktopObservability", () => { |
| 609 | 609 | yield* Effect.scoped( |
| 610 | 610 | Effect.logInfo("desktop log export from settings").pipe( |
| 611 | 611 | Effect.withSpan("desktop-settings-export-test"), |
| 612 | Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(environmentLayer))), | |
| 612 | Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(layerEnvironment))), | |
| 613 | 613 | ), |
| 614 | 614 | ); |
| 615 | 615 | |
| ... | ... | @@ -627,7 +627,7 @@ describe("DesktopObservability", () => { |
| 627 | 627 | ); |
| 628 | 628 | }).pipe( |
| 629 | 629 | Effect.scoped, |
| 630 | Effect.provide(Layer.mergeAll(NodeServices.layer, collectorLayer(requests), emptyEnv)), | |
| 630 | Effect.provide(Layer.mergeAll(NodeServices.layer, layerCollector(requests), layerEmptyEnv)), | |
| 631 | 631 | ); |
| 632 | 632 | }); |
| 633 | 633 | |
| ... | ... | @@ -643,7 +643,7 @@ describe("DesktopObservability", () => { |
| 643 | 643 | Effect.logInfo("desktop log stays local").pipe( |
| 644 | 644 | Effect.withSpan("desktop-offline-test"), |
| 645 | 645 | Effect.provide( |
| 646 | DesktopObservability.layer.pipe(Layer.provideMerge(makeEnvironmentLayer(baseDir))), | |
| 646 | DesktopObservability.layer.pipe(Layer.provideMerge(layerEnvironmentFor(baseDir))), | |
| 647 | 647 | ), |
| 648 | 648 | ), |
| 649 | 649 | ); |
| ... | ... | @@ -651,7 +651,7 @@ describe("DesktopObservability", () => { |
| 651 | 651 | assert.lengthOf(requests, 0); |
| 652 | 652 | }).pipe( |
| 653 | 653 | Effect.scoped, |
| 654 | Effect.provide(Layer.mergeAll(NodeServices.layer, collectorLayer(requests), emptyEnv)), | |
| 654 | Effect.provide(Layer.mergeAll(NodeServices.layer, layerCollector(requests), layerEmptyEnv)), | |
| 655 | 655 | ); |
| 656 | 656 | }); |
| 657 | 657 | |
| ... | ... | @@ -662,8 +662,8 @@ describe("DesktopObservability", () => { |
| 662 | 662 | const baseDir = yield* fileSystem.makeTempDirectoryScoped({ |
| 663 | 663 | prefix: "t3-desktop-observability-test-", |
| 664 | 664 | }); |
| 665 | const environmentLayer = makeEnvironmentLayer(baseDir); | |
| 666 | yield* writeObservabilitySettings(environmentLayer, { | |
| 665 | const layerEnvironment = layerEnvironmentFor(baseDir); | |
| 666 | yield* writeObservabilitySettings(layerEnvironment, { | |
| 667 | 667 | otlpTracesUrl: "https://settings.example.com/v1/traces", |
| 668 | 668 | otlpLogsUrl: "https://settings.example.com/v1/logs", |
| 669 | 669 | }); |
| ... | ... | @@ -671,7 +671,7 @@ describe("DesktopObservability", () => { |
| 671 | 671 | yield* Effect.scoped( |
| 672 | 672 | Effect.logInfo("desktop log stays local when disabled").pipe( |
| 673 | 673 | Effect.withSpan("desktop-disabled-test"), |
| 674 | Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(environmentLayer))), | |
| 674 | Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(layerEnvironment))), | |
| 675 | 675 | ), |
| 676 | 676 | ); |
| 677 | 677 | |
| ... | ... | @@ -681,7 +681,7 @@ describe("DesktopObservability", () => { |
| 681 | 681 | Effect.provide( |
| 682 | 682 | Layer.mergeAll( |
| 683 | 683 | NodeServices.layer, |
| 684 | collectorLayer(requests), | |
| 684 | layerCollector(requests), | |
| 685 | 685 | ConfigProvider.layer(ConfigProvider.fromEnv({ env: { OTEL_SDK_DISABLED: "true" } })), |
| 686 | 686 | ), |
| 687 | 687 | ), |
apps/desktop/src/app/DesktopObservability.ts+19-13| ... | ... | @@ -2,9 +2,9 @@ import { PRIMARY_LOCAL_ENVIRONMENT_ID } from "@t3tools/contracts"; |
| 2 | 2 | import { |
| 3 | 3 | makeLocalFileTracer, |
| 4 | 4 | makeTraceSink, |
| 5 | otlpSerializationLayer, | |
| 6 | 5 | type SignalExport, |
| 7 | 6 | } from "@t3tools/shared/observability"; |
| 7 | import * as SharedObservability from "@t3tools/shared/observability"; | |
| 8 | 8 | import * as OtelEnvironment from "@t3tools/shared/otelEnvironment"; |
| 9 | 9 | import { |
| 10 | 10 | parsePersistedServerObservabilitySettings, |
| ... | ... | @@ -559,7 +559,7 @@ const makeBackendOutputLogShape = ( |
| 559 | 559 | }), |
| 560 | 560 | }); |
| 561 | 561 | |
| 562 | const backendOutputLogFactoryLayer = Layer.effect( | |
| 562 | const layerBackendOutputLogFactory = Layer.effect( | |
| 563 | 563 | DesktopBackendOutputLogFactory, |
| 564 | 564 | Effect.gen(function* () { |
| 565 | 565 | const environment = yield* DesktopEnvironment.DesktopEnvironment; |
| ... | ... | @@ -621,7 +621,7 @@ const backendOutputLogFactoryLayer = Layer.effect( |
| 621 | 621 | * one read of the environment and Settings, and because a process gets exactly |
| 622 | 622 | * one logger set. |
| 623 | 623 | */ |
| 624 | const telemetryLayer = Layer.unwrap( | |
| 624 | const layerTelemetry = Layer.unwrap( | |
| 625 | 625 | Effect.gen(function* () { |
| 626 | 626 | const environment = yield* DesktopEnvironment.DesktopEnvironment; |
| 627 | 627 | const endpoints = yield* resolveOtlpEndpoints; |
| ... | ... | @@ -645,7 +645,7 @@ const telemetryLayer = Layer.unwrap( |
| 645 | 645 | // while the OTLP logger carries every message as a log record stamped |
| 646 | 646 | // with its trace and span ids. Keeping both would export every in-span |
| 647 | 647 | // message twice. |
| 648 | const loggerLayer = Logger.layer( | |
| 648 | const layerLogger = Logger.layer( | |
| 649 | 649 | endpoints.logs === undefined |
| 650 | 650 | ? [Logger.consolePretty(), Logger.tracerLogger] |
| 651 | 651 | : [ |
| ... | ... | @@ -661,11 +661,13 @@ const telemetryLayer = Layer.unwrap( |
| 661 | 661 | ).pipe( |
| 662 | 662 | Layer.provide(OtlpExporter.layerFlusher), |
| 663 | 663 | Layer.provide( |
| 664 | otlpSerializationLayer(endpoints.logs?.export.protocol ?? environment.otlpProtocol), | |
| 664 | SharedObservability.layerOtlpSerialization( | |
| 665 | endpoints.logs?.export.protocol ?? environment.otlpProtocol, | |
| 666 | ), | |
| 665 | 667 | ), |
| 666 | 668 | ); |
| 667 | 669 | |
| 668 | const tracerLayer = Layer.unwrap( | |
| 670 | const layerTracer = Layer.unwrap( | |
| 669 | 671 | Effect.gen(function* () { |
| 670 | 672 | const tracePath = environment.path.join(environment.logDir, "desktop.trace.ndjson"); |
| 671 | 673 | const sink = yield* makeTraceSink({ |
| ... | ... | @@ -682,7 +684,11 @@ const telemetryLayer = Layer.unwrap( |
| 682 | 684 | exportInterval: `${endpoints.traces.export.exportIntervalMs} millis`, |
| 683 | 685 | headers: endpoints.traces.export.headers, |
| 684 | 686 | resource, |
| 685 | }).pipe(Effect.provide(otlpSerializationLayer(endpoints.traces.export.protocol))); | |
| 687 | }).pipe( | |
| 688 | Effect.provide( | |
| 689 | SharedObservability.layerOtlpSerialization(endpoints.traces.export.protocol), | |
| 690 | ), | |
| 691 | ); | |
| 686 | 692 | const tracer = yield* makeLocalFileTracer({ |
| 687 | 693 | filePath: tracePath, |
| 688 | 694 | maxBytes: DESKTOP_LOG_FILE_MAX_BYTES, |
| ... | ... | @@ -710,23 +716,23 @@ const telemetryLayer = Layer.unwrap( |
| 710 | 716 | // exportInterval: `${endpoints.metrics.export.exportIntervalMs} millis`, |
| 711 | 717 | // headers: endpoints.metrics.export.headers, |
| 712 | 718 | // resource, |
| 713 | // }).pipe(Layer.provide(otlpSerializationLayer(endpoints.metrics.export.protocol))); | |
| 719 | // }).pipe(Layer.provide(SharedObservability.layerOtlpSerialization(endpoints.metrics.export.protocol))); | |
| 714 | 720 | |
| 715 | 721 | // Logged once the loggers above are installed, so the warnings use them. |
| 716 | const otelWarningsLayer = Layer.effectDiscard( | |
| 722 | const layerOtelWarnings = Layer.effectDiscard( | |
| 717 | 723 | Effect.forEach(endpoints.warnings, (warning) => Effect.logWarning(warning)), |
| 718 | 724 | ); |
| 719 | 725 | |
| 720 | return otelWarningsLayer.pipe( | |
| 721 | Layer.provideMerge(Layer.mergeAll(loggerLayer, tracerLayer)), | |
| 726 | return layerOtelWarnings.pipe( | |
| 727 | Layer.provideMerge(Layer.mergeAll(layerLogger, layerTracer)), | |
| 722 | 728 | Layer.provide(OtelEnvironment.layerResourceAttributes(endpoints.resourceAttributes)), |
| 723 | 729 | ); |
| 724 | 730 | }), |
| 725 | 731 | ); |
| 726 | 732 | |
| 727 | 733 | export const layer = Layer.mergeAll( |
| 728 | backendOutputLogFactoryLayer, | |
| 729 | telemetryLayer, | |
| 734 | layerBackendOutputLogFactory, | |
| 735 | layerTelemetry, | |
| 730 | 736 | Layer.succeed(References.MinimumLogLevel, "Info"), |
| 731 | 737 | Layer.succeed(Tracer.MinimumTraceLevel, "Info"), |
| 732 | 738 | Layer.succeed(References.TracerTimingEnabled, true), |
apps/desktop/src/app/DesktopPreReadyPlatform.test.ts+5-5| ... | ... | @@ -167,11 +167,11 @@ describe("DesktopPreReadyPlatform", () => { |
| 167 | 167 | events.push("pre-ready"); |
| 168 | 168 | }); |
| 169 | 169 | |
| 170 | const preReadyLayer = DesktopPreReadyPlatform.layer.pipe( | |
| 170 | const layerPreReady = DesktopPreReadyPlatform.layer.pipe( | |
| 171 | 171 | Layer.provide(Layer.succeed(HostProcessPlatform, "darwin")), |
| 172 | 172 | ); |
| 173 | 173 | |
| 174 | const clerkShapedLayer = Layer.effect( | |
| 174 | const layerClerkShaped = Layer.effect( | |
| 175 | 175 | ClerkShaped, |
| 176 | 176 | Effect.promise(() => Promise.resolve()).pipe( |
| 177 | 177 | Effect.map(() => { |
| ... | ... | @@ -181,15 +181,15 @@ describe("DesktopPreReadyPlatform", () => { |
| 181 | 181 | ), |
| 182 | 182 | ); |
| 183 | 183 | |
| 184 | const runtimeLayer = clerkShapedLayer.pipe( | |
| 184 | const layerRuntime = layerClerkShaped.pipe( | |
| 185 | 185 | Layer.flatMap((clerkContext) => Layer.succeedContext(clerkContext)), |
| 186 | Layer.provideMerge(preReadyLayer), | |
| 186 | Layer.provideMerge(layerPreReady), | |
| 187 | 187 | ); |
| 188 | 188 | |
| 189 | 189 | const result = yield* Effect.all({ |
| 190 | 190 | clerk: ClerkShaped, |
| 191 | 191 | preReady: DesktopPreReadyPlatform.DesktopPreReadyElectronOptions, |
| 192 | }).pipe(Effect.provide(runtimeLayer)); | |
| 192 | }).pipe(Effect.provide(layerRuntime)); | |
| 193 | 193 | |
| 194 | 194 | assert.deepEqual(result, { |
| 195 | 195 | clerk: { ready: true }, |
apps/desktop/src/backend/DesktopBackendConfiguration.test.ts+52-52| ... | ... | @@ -35,7 +35,7 @@ const isDesktopBackendObservabilitySettingsReadError = Schema.is( |
| 35 | 35 | DesktopBackendConfiguration.DesktopBackendObservabilitySettingsReadError, |
| 36 | 36 | ); |
| 37 | 37 | |
| 38 | const serverExposureLayer = Layer.succeed(DesktopServerExposure.DesktopServerExposure, { | |
| 38 | const layerServerExposure = Layer.succeed(DesktopServerExposure.DesktopServerExposure, { | |
| 39 | 39 | getState: Effect.die("unexpected getState"), |
| 40 | 40 | backendConfig: Effect.succeed({ |
| 41 | 41 | port: 4888, |
| ... | ... | @@ -50,7 +50,7 @@ const serverExposureLayer = Layer.succeed(DesktopServerExposure.DesktopServerExp |
| 50 | 50 | getAdvertisedEndpoints: Effect.succeed([]), |
| 51 | 51 | } satisfies DesktopServerExposure.DesktopServerExposure["Service"]); |
| 52 | 52 | |
| 53 | function makeEnvironmentLayer( | |
| 53 | function layerEnvironment( | |
| 54 | 54 | baseDir: string, |
| 55 | 55 | options?: { |
| 56 | 56 | readonly appPath?: string; |
| ... | ... | @@ -122,11 +122,11 @@ const withHarness = <A, E, R>( |
| 122 | 122 | return yield* effect.pipe( |
| 123 | 123 | Effect.provide( |
| 124 | 124 | DesktopBackendConfiguration.layer.pipe( |
| 125 | Layer.provideMerge(serverExposureLayer), | |
| 125 | Layer.provideMerge(layerServerExposure), | |
| 126 | 126 | Layer.provideMerge(DesktopAppSettings.layerTest()), |
| 127 | 127 | Layer.provideMerge(DesktopWslEnvironment.layerTest()), |
| 128 | 128 | Layer.provideMerge(DesktopWslServerTree.layerTest()), |
| 129 | Layer.provideMerge(makeEnvironmentLayer(baseDir)), | |
| 129 | Layer.provideMerge(layerEnvironment(baseDir)), | |
| 130 | 130 | ), |
| 131 | 131 | ), |
| 132 | 132 | ); |
| ... | ... | @@ -182,7 +182,7 @@ const withPackagedWslHarness = <A, E, R>( |
| 182 | 182 | mountedAppRoot, |
| 183 | 183 | mountedEntryPath, |
| 184 | 184 | } satisfies PackagedWslHarnessContext; |
| 185 | const serverTreeLayer = input.forbidFallback | |
| 185 | const layerServerTree = input.forbidFallback | |
| 186 | 186 | ? Layer.succeed( |
| 187 | 187 | DesktopWslServerTree.DesktopWslServerTree, |
| 188 | 188 | DesktopWslServerTree.DesktopWslServerTree.of({ |
| ... | ... | @@ -200,9 +200,9 @@ const withPackagedWslHarness = <A, E, R>( |
| 200 | 200 | return yield* effect(context).pipe( |
| 201 | 201 | Effect.provide( |
| 202 | 202 | DesktopBackendConfiguration.layer.pipe( |
| 203 | Layer.provideMerge(serverExposureLayer), | |
| 203 | Layer.provideMerge(layerServerExposure), | |
| 204 | 204 | Layer.provideMerge(DesktopAppSettings.layerTest()), |
| 205 | Layer.provideMerge(serverTreeLayer), | |
| 205 | Layer.provideMerge(layerServerTree), | |
| 206 | 206 | Layer.provideMerge( |
| 207 | 207 | DesktopWslEnvironment.layerTest({ |
| 208 | 208 | isAvailable: true, |
| ... | ... | @@ -213,7 +213,7 @@ const withPackagedWslHarness = <A, E, R>( |
| 213 | 213 | }), |
| 214 | 214 | ), |
| 215 | 215 | Layer.provideMerge( |
| 216 | makeEnvironmentLayer(baseDir, { | |
| 216 | layerEnvironment(baseDir, { | |
| 217 | 217 | appPath: baseDir, |
| 218 | 218 | platform: "win32", |
| 219 | 219 | resourcesPath: baseDir, |
| ... | ... | @@ -276,7 +276,7 @@ describe("DesktopBackendConfiguration", () => { |
| 276 | 276 | }).pipe( |
| 277 | 277 | Effect.provide( |
| 278 | 278 | DesktopBackendConfiguration.layer.pipe( |
| 279 | Layer.provideMerge(serverExposureLayer), | |
| 279 | Layer.provideMerge(layerServerExposure), | |
| 280 | 280 | Layer.provideMerge(DesktopAppSettings.layerTest()), |
| 281 | 281 | Layer.provideMerge(DesktopWslEnvironment.layerTest()), |
| 282 | 282 | Layer.provideMerge( |
| ... | ... | @@ -289,7 +289,7 @@ describe("DesktopBackendConfiguration", () => { |
| 289 | 289 | ), |
| 290 | 290 | ), |
| 291 | 291 | Layer.provideMerge( |
| 292 | makeEnvironmentLayer(baseDir, { | |
| 292 | layerEnvironment(baseDir, { | |
| 293 | 293 | appPath: `${resourcesPath}/app.asar`, |
| 294 | 294 | platform: "win32", |
| 295 | 295 | resourcesPath, |
| ... | ... | @@ -338,7 +338,7 @@ describe("DesktopBackendConfiguration", () => { |
| 338 | 338 | }).pipe( |
| 339 | 339 | Effect.provide( |
| 340 | 340 | DesktopBackendConfiguration.layer.pipe( |
| 341 | Layer.provideMerge(serverExposureLayer), | |
| 341 | Layer.provideMerge(layerServerExposure), | |
| 342 | 342 | Layer.provideMerge(DesktopAppSettings.layerTest()), |
| 343 | 343 | Layer.provideMerge(DesktopWslServerTree.layerTest()), |
| 344 | 344 | Layer.provideMerge( |
| ... | ... | @@ -363,7 +363,7 @@ describe("DesktopBackendConfiguration", () => { |
| 363 | 363 | }), |
| 364 | 364 | ), |
| 365 | 365 | Layer.provideMerge( |
| 366 | makeEnvironmentLayer(baseDir, { | |
| 366 | layerEnvironment(baseDir, { | |
| 367 | 367 | appPath: baseDir, |
| 368 | 368 | platform: "win32", |
| 369 | 369 | resourcesPath: baseDir, |
| ... | ... | @@ -660,7 +660,7 @@ describe("DesktopBackendConfiguration", () => { |
| 660 | 660 | }).pipe( |
| 661 | 661 | Effect.provide( |
| 662 | 662 | DesktopBackendConfiguration.layer.pipe( |
| 663 | Layer.provideMerge(serverExposureLayer), | |
| 663 | Layer.provideMerge(layerServerExposure), | |
| 664 | 664 | Layer.provideMerge(DesktopAppSettings.layerTest()), |
| 665 | 665 | Layer.provideMerge(DesktopWslServerTree.layerTest()), |
| 666 | 666 | Layer.provideMerge( |
| ... | ... | @@ -673,7 +673,7 @@ describe("DesktopBackendConfiguration", () => { |
| 673 | 673 | }), |
| 674 | 674 | ), |
| 675 | 675 | Layer.provideMerge( |
| 676 | makeEnvironmentLayer(baseDir, { | |
| 676 | layerEnvironment(baseDir, { | |
| 677 | 677 | appPath: baseDir, |
| 678 | 678 | devServerUrl, |
| 679 | 679 | isPackaged: true, |
| ... | ... | @@ -788,7 +788,7 @@ describe("DesktopBackendConfiguration", () => { |
| 788 | 788 | }).pipe( |
| 789 | 789 | Effect.provide( |
| 790 | 790 | DesktopBackendConfiguration.layer.pipe( |
| 791 | Layer.provideMerge(serverExposureLayer), | |
| 791 | Layer.provideMerge(layerServerExposure), | |
| 792 | 792 | Layer.provideMerge(DesktopAppSettings.layerTest()), |
| 793 | 793 | Layer.provideMerge(DesktopWslServerTree.layerTest()), |
| 794 | 794 | Layer.provideMerge( |
| ... | ... | @@ -799,7 +799,7 @@ describe("DesktopBackendConfiguration", () => { |
| 799 | 799 | }), |
| 800 | 800 | ), |
| 801 | 801 | Layer.provideMerge( |
| 802 | makeEnvironmentLayer(baseDir, { | |
| 802 | layerEnvironment(baseDir, { | |
| 803 | 803 | platform: "win32", |
| 804 | 804 | otlpTracesUrl: " http://127.0.0.1:4318/v1/traces ", |
| 805 | 805 | otlpMetricsUrl: " http://127.0.0.1:4318/v1/metrics ", |
| ... | ... | @@ -846,12 +846,12 @@ describe("DesktopBackendConfiguration", () => { |
| 846 | 846 | }).pipe( |
| 847 | 847 | Effect.provide( |
| 848 | 848 | DesktopBackendConfiguration.layer.pipe( |
| 849 | Layer.provideMerge(serverExposureLayer), | |
| 849 | Layer.provideMerge(layerServerExposure), | |
| 850 | 850 | Layer.provideMerge(DesktopAppSettings.layerTest()), |
| 851 | 851 | Layer.provideMerge(DesktopWslServerTree.layerTest()), |
| 852 | 852 | Layer.provideMerge(DesktopWslEnvironment.layerTest()), |
| 853 | 853 | Layer.provideMerge( |
| 854 | makeEnvironmentLayer(baseDir, { otlpLogsUrl: "http://env:4318/v1/logs" }), | |
| 854 | layerEnvironment(baseDir, { otlpLogsUrl: "http://env:4318/v1/logs" }), | |
| 855 | 855 | ), |
| 856 | 856 | ), |
| 857 | 857 | ), |
| ... | ... | @@ -877,7 +877,7 @@ describe("DesktopBackendConfiguration", () => { |
| 877 | 877 | const logger = Logger.make(({ message }) => { |
| 878 | 878 | messages.push(message); |
| 879 | 879 | }); |
| 880 | const failingFileSystemLayer = Layer.succeed( | |
| 880 | const layerFailingFileSystem = Layer.succeed( | |
| 881 | 881 | FileSystem.FileSystem, |
| 882 | 882 | FileSystem.makeNoop({ |
| 883 | 883 | readFileString: () => Effect.fail(cause), |
| ... | ... | @@ -891,12 +891,12 @@ describe("DesktopBackendConfiguration", () => { |
| 891 | 891 | Effect.provide( |
| 892 | 892 | Layer.mergeAll( |
| 893 | 893 | DesktopBackendConfiguration.layer.pipe( |
| 894 | Layer.provideMerge(serverExposureLayer), | |
| 894 | Layer.provideMerge(layerServerExposure), | |
| 895 | 895 | Layer.provideMerge(DesktopAppSettings.layerTest()), |
| 896 | 896 | Layer.provideMerge(DesktopWslServerTree.layerTest()), |
| 897 | 897 | Layer.provideMerge(DesktopWslEnvironment.layerTest()), |
| 898 | Layer.provideMerge(makeEnvironmentLayer(baseDir)), | |
| 899 | Layer.provideMerge(failingFileSystemLayer), | |
| 898 | Layer.provideMerge(layerEnvironment(baseDir)), | |
| 899 | Layer.provideMerge(layerFailingFileSystem), | |
| 900 | 900 | ), |
| 901 | 901 | Logger.layer([logger], { mergeWithExisting: false }), |
| 902 | 902 | ), |
| ... | ... | @@ -936,12 +936,12 @@ describe("DesktopBackendConfiguration", () => { |
| 936 | 936 | }).pipe( |
| 937 | 937 | Effect.provide( |
| 938 | 938 | DesktopBackendConfiguration.layer.pipe( |
| 939 | Layer.provideMerge(serverExposureLayer), | |
| 939 | Layer.provideMerge(layerServerExposure), | |
| 940 | 940 | Layer.provideMerge(DesktopAppSettings.layerTest()), |
| 941 | 941 | Layer.provideMerge(DesktopWslServerTree.layerTest()), |
| 942 | 942 | Layer.provideMerge(DesktopWslEnvironment.layerTest()), |
| 943 | 943 | Layer.provideMerge( |
| 944 | makeEnvironmentLayer(baseDir, { | |
| 944 | layerEnvironment(baseDir, { | |
| 945 | 945 | isPackaged: false, |
| 946 | 946 | devServerUrl: "http://127.0.0.1:5733", |
| 947 | 947 | }), |
| ... | ... | @@ -974,7 +974,7 @@ describe("DesktopBackendConfiguration", () => { |
| 974 | 974 | }).pipe( |
| 975 | 975 | Effect.provide( |
| 976 | 976 | DesktopBackendConfiguration.layer.pipe( |
| 977 | Layer.provideMerge(serverExposureLayer), | |
| 977 | Layer.provideMerge(layerServerExposure), | |
| 978 | 978 | Layer.provideMerge(DesktopAppSettings.layerTest()), |
| 979 | 979 | Layer.provideMerge(DesktopWslServerTree.layerTest()), |
| 980 | 980 | Layer.provideMerge( |
| ... | ... | @@ -984,7 +984,7 @@ describe("DesktopBackendConfiguration", () => { |
| 984 | 984 | getDistroIp: () => Option.some("172.27.0.99"), |
| 985 | 985 | }), |
| 986 | 986 | ), |
| 987 | Layer.provideMerge(makeEnvironmentLayer(baseDir, { platform: "win32" })), | |
| 987 | Layer.provideMerge(layerEnvironment(baseDir, { platform: "win32" })), | |
| 988 | 988 | ), |
| 989 | 989 | ), |
| 990 | 990 | ); |
| ... | ... | @@ -1040,7 +1040,7 @@ describe("DesktopBackendConfiguration", () => { |
| 1040 | 1040 | }).pipe( |
| 1041 | 1041 | Effect.provide( |
| 1042 | 1042 | DesktopBackendConfiguration.layer.pipe( |
| 1043 | Layer.provideMerge(serverExposureLayer), | |
| 1043 | Layer.provideMerge(layerServerExposure), | |
| 1044 | 1044 | Layer.provideMerge(DesktopAppSettings.layerTest()), |
| 1045 | 1045 | Layer.provideMerge(DesktopWslServerTree.layerTest()), |
| 1046 | 1046 | Layer.provideMerge( |
| ... | ... | @@ -1050,7 +1050,7 @@ describe("DesktopBackendConfiguration", () => { |
| 1050 | 1050 | getDistroIp: () => Option.some("172.27.0.99"), |
| 1051 | 1051 | }), |
| 1052 | 1052 | ), |
| 1053 | Layer.provideMerge(makeEnvironmentLayer(baseDir, { platform: "win32" })), | |
| 1053 | Layer.provideMerge(layerEnvironment(baseDir, { platform: "win32" })), | |
| 1054 | 1054 | ), |
| 1055 | 1055 | ), |
| 1056 | 1056 | ); |
| ... | ... | @@ -1114,7 +1114,7 @@ describe("DesktopBackendConfiguration", () => { |
| 1114 | 1114 | }).pipe( |
| 1115 | 1115 | Effect.provide( |
| 1116 | 1116 | DesktopBackendConfiguration.layer.pipe( |
| 1117 | Layer.provideMerge(serverExposureLayer), | |
| 1117 | Layer.provideMerge(layerServerExposure), | |
| 1118 | 1118 | Layer.provideMerge(DesktopAppSettings.layerTest()), |
| 1119 | 1119 | Layer.provideMerge(DesktopWslServerTree.layerTest()), |
| 1120 | 1120 | Layer.provideMerge( |
| ... | ... | @@ -1124,7 +1124,7 @@ describe("DesktopBackendConfiguration", () => { |
| 1124 | 1124 | getDistroIp: () => Option.some("172.27.0.99"), |
| 1125 | 1125 | }), |
| 1126 | 1126 | ), |
| 1127 | Layer.provideMerge(makeEnvironmentLayer(baseDir, { platform: "win32" })), | |
| 1127 | Layer.provideMerge(layerEnvironment(baseDir, { platform: "win32" })), | |
| 1128 | 1128 | ), |
| 1129 | 1129 | ), |
| 1130 | 1130 | ); |
| ... | ... | @@ -1162,7 +1162,7 @@ describe("DesktopBackendConfiguration", () => { |
| 1162 | 1162 | }).pipe( |
| 1163 | 1163 | Effect.provide( |
| 1164 | 1164 | DesktopBackendConfiguration.layer.pipe( |
| 1165 | Layer.provideMerge(serverExposureLayer), | |
| 1165 | Layer.provideMerge(layerServerExposure), | |
| 1166 | 1166 | Layer.provideMerge( |
| 1167 | 1167 | DesktopAppSettings.layerTest({ |
| 1168 | 1168 | ...DesktopAppSettings.DEFAULT_DESKTOP_SETTINGS, |
| ... | ... | @@ -1172,7 +1172,7 @@ describe("DesktopBackendConfiguration", () => { |
| 1172 | 1172 | ), |
| 1173 | 1173 | Layer.provideMerge(DesktopWslServerTree.layerTest()), |
| 1174 | 1174 | Layer.provideMerge(DesktopWslEnvironment.layerTest({ isAvailable: false })), |
| 1175 | Layer.provideMerge(makeEnvironmentLayer(baseDir, { platform: "win32" })), | |
| 1175 | Layer.provideMerge(layerEnvironment(baseDir, { platform: "win32" })), | |
| 1176 | 1176 | ), |
| 1177 | 1177 | ), |
| 1178 | 1178 | ); |
| ... | ... | @@ -1199,7 +1199,7 @@ describe("DesktopBackendConfiguration", () => { |
| 1199 | 1199 | }).pipe( |
| 1200 | 1200 | Effect.provide( |
| 1201 | 1201 | DesktopBackendConfiguration.layer.pipe( |
| 1202 | Layer.provideMerge(serverExposureLayer), | |
| 1202 | Layer.provideMerge(layerServerExposure), | |
| 1203 | 1203 | Layer.provideMerge( |
| 1204 | 1204 | DesktopAppSettings.layerTest({ |
| 1205 | 1205 | ...DesktopAppSettings.DEFAULT_DESKTOP_SETTINGS, |
| ... | ... | @@ -1215,7 +1215,7 @@ describe("DesktopBackendConfiguration", () => { |
| 1215 | 1215 | distros: [{ name: "Ubuntu", isDefault: true, version: 2 }], |
| 1216 | 1216 | }), |
| 1217 | 1217 | ), |
| 1218 | Layer.provideMerge(makeEnvironmentLayer(baseDir, { platform: "win32" })), | |
| 1218 | Layer.provideMerge(layerEnvironment(baseDir, { platform: "win32" })), | |
| 1219 | 1219 | ), |
| 1220 | 1220 | ), |
| 1221 | 1221 | ); |
| ... | ... | @@ -1240,7 +1240,7 @@ describe("DesktopBackendConfiguration", () => { |
| 1240 | 1240 | }).pipe( |
| 1241 | 1241 | Effect.provide( |
| 1242 | 1242 | DesktopBackendConfiguration.layer.pipe( |
| 1243 | Layer.provideMerge(serverExposureLayer), | |
| 1243 | Layer.provideMerge(layerServerExposure), | |
| 1244 | 1244 | Layer.provideMerge(DesktopAppSettings.layerTest()), |
| 1245 | 1245 | Layer.provideMerge(DesktopWslServerTree.layerTest()), |
| 1246 | 1246 | Layer.provideMerge( |
| ... | ... | @@ -1251,7 +1251,7 @@ describe("DesktopBackendConfiguration", () => { |
| 1251 | 1251 | }), |
| 1252 | 1252 | }), |
| 1253 | 1253 | ), |
| 1254 | Layer.provideMerge(makeEnvironmentLayer(baseDir, { platform: "win32" })), | |
| 1254 | Layer.provideMerge(layerEnvironment(baseDir, { platform: "win32" })), | |
| 1255 | 1255 | ), |
| 1256 | 1256 | ), |
| 1257 | 1257 | ); |
| ... | ... | @@ -1275,7 +1275,7 @@ describe("DesktopBackendConfiguration", () => { |
| 1275 | 1275 | }).pipe( |
| 1276 | 1276 | Effect.provide( |
| 1277 | 1277 | DesktopBackendConfiguration.layer.pipe( |
| 1278 | Layer.provideMerge(serverExposureLayer), | |
| 1278 | Layer.provideMerge(layerServerExposure), | |
| 1279 | 1279 | Layer.provideMerge(DesktopAppSettings.layerTest()), |
| 1280 | 1280 | Layer.provideMerge(DesktopWslServerTree.layerTest()), |
| 1281 | 1281 | Layer.provideMerge( |
| ... | ... | @@ -1284,7 +1284,7 @@ describe("DesktopBackendConfiguration", () => { |
| 1284 | 1284 | distros: [{ name: "Ubuntu", isDefault: true, version: 2 }], |
| 1285 | 1285 | }), |
| 1286 | 1286 | ), |
| 1287 | Layer.provideMerge(makeEnvironmentLayer(baseDir, { platform: "win32" })), | |
| 1287 | Layer.provideMerge(layerEnvironment(baseDir, { platform: "win32" })), | |
| 1288 | 1288 | ), |
| 1289 | 1289 | ), |
| 1290 | 1290 | ); |
| ... | ... | @@ -1309,7 +1309,7 @@ describe("DesktopBackendConfiguration", () => { |
| 1309 | 1309 | }).pipe( |
| 1310 | 1310 | Effect.provide( |
| 1311 | 1311 | DesktopBackendConfiguration.layer.pipe( |
| 1312 | Layer.provideMerge(serverExposureLayer), | |
| 1312 | Layer.provideMerge(layerServerExposure), | |
| 1313 | 1313 | Layer.provideMerge(DesktopAppSettings.layerTest()), |
| 1314 | 1314 | Layer.provideMerge( |
| 1315 | 1315 | DesktopWslServerTree.layerTest({ |
| ... | ... | @@ -1326,7 +1326,7 @@ describe("DesktopBackendConfiguration", () => { |
| 1326 | 1326 | distros: [{ name: "Ubuntu", isDefault: true, version: 2 }], |
| 1327 | 1327 | }), |
| 1328 | 1328 | ), |
| 1329 | Layer.provideMerge(makeEnvironmentLayer(baseDir, { platform: "win32" })), | |
| 1329 | Layer.provideMerge(layerEnvironment(baseDir, { platform: "win32" })), | |
| 1330 | 1330 | ), |
| 1331 | 1331 | ), |
| 1332 | 1332 | ); |
| ... | ... | @@ -1350,7 +1350,7 @@ describe("DesktopBackendConfiguration", () => { |
| 1350 | 1350 | }).pipe( |
| 1351 | 1351 | Effect.provide( |
| 1352 | 1352 | DesktopBackendConfiguration.layer.pipe( |
| 1353 | Layer.provideMerge(serverExposureLayer), | |
| 1353 | Layer.provideMerge(layerServerExposure), | |
| 1354 | 1354 | Layer.provideMerge(DesktopAppSettings.layerTest()), |
| 1355 | 1355 | Layer.provideMerge(DesktopWslServerTree.layerTest()), |
| 1356 | 1356 | Layer.provideMerge( |
| ... | ... | @@ -1359,7 +1359,7 @@ describe("DesktopBackendConfiguration", () => { |
| 1359 | 1359 | distros: [{ name: "Ubuntu", isDefault: true, version: 2 }], |
| 1360 | 1360 | }), |
| 1361 | 1361 | ), |
| 1362 | Layer.provideMerge(makeEnvironmentLayer(baseDir, { platform: "win32" })), | |
| 1362 | Layer.provideMerge(layerEnvironment(baseDir, { platform: "win32" })), | |
| 1363 | 1363 | ), |
| 1364 | 1364 | ), |
| 1365 | 1365 | ); |
| ... | ... | @@ -1380,7 +1380,7 @@ describe("DesktopBackendConfiguration", () => { |
| 1380 | 1380 | }).pipe( |
| 1381 | 1381 | Effect.provide( |
| 1382 | 1382 | DesktopBackendConfiguration.layer.pipe( |
| 1383 | Layer.provideMerge(serverExposureLayer), | |
| 1383 | Layer.provideMerge(layerServerExposure), | |
| 1384 | 1384 | Layer.provideMerge( |
| 1385 | 1385 | DesktopAppSettings.layerTest({ |
| 1386 | 1386 | ...DesktopAppSettings.DEFAULT_DESKTOP_SETTINGS, |
| ... | ... | @@ -1391,7 +1391,7 @@ describe("DesktopBackendConfiguration", () => { |
| 1391 | 1391 | ), |
| 1392 | 1392 | Layer.provideMerge(DesktopWslServerTree.layerTest()), |
| 1393 | 1393 | Layer.provideMerge(DesktopWslEnvironment.layerTest({ isAvailable: true })), |
| 1394 | Layer.provideMerge(makeEnvironmentLayer(baseDir, { platform: "win32" })), | |
| 1394 | Layer.provideMerge(layerEnvironment(baseDir, { platform: "win32" })), | |
| 1395 | 1395 | ), |
| 1396 | 1396 | ), |
| 1397 | 1397 | ); |
| ... | ... | @@ -1429,12 +1429,12 @@ describe("DesktopBackendConfiguration", () => { |
| 1429 | 1429 | }).pipe( |
| 1430 | 1430 | Effect.provide( |
| 1431 | 1431 | DesktopBackendConfiguration.layer.pipe( |
| 1432 | Layer.provideMerge(serverExposureLayer), | |
| 1432 | Layer.provideMerge(layerServerExposure), | |
| 1433 | 1433 | Layer.provideMerge(DesktopAppSettings.layerTest()), |
| 1434 | 1434 | Layer.provideMerge(DesktopWslServerTree.layerTest()), |
| 1435 | 1435 | Layer.provideMerge(DesktopWslEnvironment.layerTest()), |
| 1436 | 1436 | Layer.provideMerge( |
| 1437 | makeEnvironmentLayer(baseDir, { | |
| 1437 | layerEnvironment(baseDir, { | |
| 1438 | 1438 | appPath: `${resourcesPath}/app.asar`, |
| 1439 | 1439 | dirname, |
| 1440 | 1440 | isPackaged: true, |
| ... | ... | @@ -1475,12 +1475,12 @@ describe("DesktopBackendConfiguration", () => { |
| 1475 | 1475 | }).pipe( |
| 1476 | 1476 | Effect.provide( |
| 1477 | 1477 | DesktopBackendConfiguration.layer.pipe( |
| 1478 | Layer.provideMerge(serverExposureLayer), | |
| 1478 | Layer.provideMerge(layerServerExposure), | |
| 1479 | 1479 | Layer.provideMerge(DesktopAppSettings.layerTest()), |
| 1480 | 1480 | Layer.provideMerge(DesktopWslServerTree.layerTest()), |
| 1481 | 1481 | Layer.provideMerge(DesktopWslEnvironment.layerTest()), |
| 1482 | 1482 | Layer.provideMerge( |
| 1483 | makeEnvironmentLayer(baseDir, { | |
| 1483 | layerEnvironment(baseDir, { | |
| 1484 | 1484 | dirname, |
| 1485 | 1485 | devServerUrl: "http://127.0.0.1:5733", |
| 1486 | 1486 | isPackaged: false, |
| ... | ... | @@ -1519,7 +1519,7 @@ describe("DesktopBackendConfiguration", () => { |
| 1519 | 1519 | }).pipe( |
| 1520 | 1520 | Effect.provide( |
| 1521 | 1521 | DesktopBackendConfiguration.layer.pipe( |
| 1522 | Layer.provideMerge(serverExposureLayer), | |
| 1522 | Layer.provideMerge(layerServerExposure), | |
| 1523 | 1523 | Layer.provideMerge( |
| 1524 | 1524 | DesktopAppSettings.layerTest({ |
| 1525 | 1525 | ...DesktopAppSettings.DEFAULT_DESKTOP_SETTINGS, |
| ... | ... | @@ -1530,7 +1530,7 @@ describe("DesktopBackendConfiguration", () => { |
| 1530 | 1530 | ), |
| 1531 | 1531 | Layer.provideMerge(DesktopWslServerTree.layerTest()), |
| 1532 | 1532 | Layer.provideMerge(DesktopWslEnvironment.layerTest({ isAvailable: false })), |
| 1533 | Layer.provideMerge(makeEnvironmentLayer(baseDir, { platform: "win32" })), | |
| 1533 | Layer.provideMerge(layerEnvironment(baseDir, { platform: "win32" })), | |
| 1534 | 1534 | ), |
| 1535 | 1535 | ), |
| 1536 | 1536 | ); |
| ... | ... | @@ -1548,7 +1548,7 @@ describe("DesktopBackendConfiguration", () => { |
| 1548 | 1548 | // oxlint-disable-next-line t3code/no-manual-effect-runtime-in-tests -- This test intentionally replicates the sync IPC handler's runSync path to catch a regression to async-only resolution; it.effect would mask it. |
| 1549 | 1549 | const runtime = ManagedRuntime.make( |
| 1550 | 1550 | DesktopBackendConfiguration.layer.pipe( |
| 1551 | Layer.provideMerge(serverExposureLayer), | |
| 1551 | Layer.provideMerge(layerServerExposure), | |
| 1552 | 1552 | Layer.provideMerge(DesktopAppSettings.layerTest()), |
| 1553 | 1553 | Layer.provideMerge(DesktopWslServerTree.layerTest()), |
| 1554 | 1554 | Layer.provideMerge(DesktopWslEnvironment.layer), |
| ... | ... | @@ -1562,7 +1562,7 @@ describe("DesktopBackendConfiguration", () => { |
| 1562 | 1562 | ), |
| 1563 | 1563 | ), |
| 1564 | 1564 | ), |
| 1565 | Layer.provideMerge(makeEnvironmentLayer("/tmp/t3-wsl-isavailable", { platform: "win32" })), | |
| 1565 | Layer.provideMerge(layerEnvironment("/tmp/t3-wsl-isavailable", { platform: "win32" })), | |
| 1566 | 1566 | Layer.provide(NodeServices.layer), |
| 1567 | 1567 | ), |
| 1568 | 1568 | ); |
apps/desktop/src/backend/DesktopBackendManager.test.ts+67-67| ... | ... | @@ -98,7 +98,7 @@ function responseForRequest( |
| 98 | 98 | return HttpClientResponse.fromWeb(request, new Response(null, { status })); |
| 99 | 99 | } |
| 100 | 100 | |
| 101 | function httpClientLayer( | |
| 101 | function layerHttpClient( | |
| 102 | 102 | handler: ( |
| 103 | 103 | request: HttpClientRequest.HttpClientRequest, |
| 104 | 104 | ) => Effect.Effect<HttpClientResponse.HttpClientResponse>, |
| ... | ... | @@ -109,7 +109,7 @@ function httpClientLayer( |
| 109 | 109 | ); |
| 110 | 110 | } |
| 111 | 111 | |
| 112 | const healthyHttpClientLayer = httpClientLayer((request) => | |
| 112 | const layerHealthyHttpClient = layerHttpClient((request) => | |
| 113 | 113 | Effect.succeed(responseForRequest(request, 200)), |
| 114 | 114 | ); |
| 115 | 115 | |
| ... | ... | @@ -152,12 +152,12 @@ function makeTestInstance(input: MakeInstanceInput) { |
| 152 | 152 | discardSession: Effect.void, |
| 153 | 153 | ...input.backendOutputLog, |
| 154 | 154 | }; |
| 155 | const servicesLayer = Layer.mergeAll( | |
| 155 | const layerServices = Layer.mergeAll( | |
| 156 | 156 | FileSystem.layerNoop({ |
| 157 | 157 | exists: () => Effect.succeed(true), |
| 158 | 158 | }), |
| 159 | 159 | input.spawnerLayer, |
| 160 | input.httpClientLayer ?? healthyHttpClientLayer, | |
| 160 | input.httpClientLayer ?? layerHealthyHttpClient, | |
| 161 | 161 | Layer.succeed(DesktopObservability.DesktopBackendOutputLogFactory, { |
| 162 | 162 | forInstance: () => Effect.succeed(stubLog), |
| 163 | 163 | } satisfies DesktopObservability.DesktopBackendOutputLogFactory["Service"]), |
| ... | ... | @@ -187,7 +187,7 @@ function makeTestInstance(input: MakeInstanceInput) { |
| 187 | 187 | ...(input.onPreflightFailed ? { onPreflightFailed: input.onPreflightFailed } : {}), |
| 188 | 188 | }); |
| 189 | 189 | |
| 190 | return instance.pipe(Effect.provide(servicesLayer)); | |
| 190 | return instance.pipe(Effect.provide(layerServices)); | |
| 191 | 191 | } |
| 192 | 192 | |
| 193 | 193 | describe("DesktopBackendManager", () => { |
| ... | ... | @@ -201,7 +201,7 @@ describe("DesktopBackendManager", () => { |
| 201 | 201 | const ready = yield* Deferred.make<void>(); |
| 202 | 202 | const exited = yield* Queue.unbounded<void>(); |
| 203 | 203 | |
| 204 | const spawnerLayer = Layer.succeed( | |
| 204 | const layerSpawner = Layer.succeed( | |
| 205 | 205 | ChildProcessSpawner.ChildProcessSpawner, |
| 206 | 206 | ChildProcessSpawner.make((command) => |
| 207 | 207 | Effect.gen(function* () { |
| ... | ... | @@ -229,7 +229,7 @@ describe("DesktopBackendManager", () => { |
| 229 | 229 | ...baseConfig, |
| 230 | 230 | bootstrap: configWithObservability, |
| 231 | 231 | }, |
| 232 | spawnerLayer, | |
| 232 | spawnerLayer: layerSpawner, | |
| 233 | 233 | desktopTelemetryStream: Stream.encodeText( |
| 234 | 234 | Stream.make('{"version":1,"type":"desktopTelemetryHello","electronPid":123}\n'), |
| 235 | 235 | ), |
| ... | ... | @@ -279,7 +279,7 @@ describe("DesktopBackendManager", () => { |
| 279 | 279 | const requested = yield* Deferred.make<HttpClientRequest.HttpClientRequest>(); |
| 280 | 280 | const layer = Layer.merge( |
| 281 | 281 | TestClock.layer(), |
| 282 | httpClientLayer((request) => | |
| 282 | layerHttpClient((request) => | |
| 283 | 283 | Deferred.succeed(requested, request).pipe(Effect.andThen(Effect.never)), |
| 284 | 284 | ), |
| 285 | 285 | ); |
| ... | ... | @@ -317,7 +317,7 @@ describe("DesktopBackendManager", () => { |
| 317 | 317 | |
| 318 | 318 | it.effect("reports bootstrap encoding failures with stable process context", () => |
| 319 | 319 | Effect.gen(function* () { |
| 320 | const spawnerLayer = Layer.succeed( | |
| 320 | const layerSpawner = Layer.succeed( | |
| 321 | 321 | ChildProcessSpawner.ChildProcessSpawner, |
| 322 | 322 | ChildProcessSpawner.make(() => Effect.die("unexpected backend spawn")), |
| 323 | 323 | ); |
| ... | ... | @@ -331,7 +331,7 @@ describe("DesktopBackendManager", () => { |
| 331 | 331 | }).pipe( |
| 332 | 332 | Effect.flip, |
| 333 | 333 | Effect.scoped, |
| 334 | Effect.provide(Layer.merge(spawnerLayer, healthyHttpClientLayer)), | |
| 334 | Effect.provide(Layer.merge(layerSpawner, layerHealthyHttpClient)), | |
| 335 | 335 | ); |
| 336 | 336 | |
| 337 | 337 | if (error._tag !== "BackendProcessBootstrapEncodeError") { |
| ... | ... | @@ -359,7 +359,7 @@ describe("DesktopBackendManager", () => { |
| 359 | 359 | pathOrDescriptor: baseConfig.executablePath, |
| 360 | 360 | description: "low-level detail that must not become the public message", |
| 361 | 361 | }); |
| 362 | const spawnerLayer = Layer.succeed( | |
| 362 | const layerSpawner = Layer.succeed( | |
| 363 | 363 | ChildProcessSpawner.ChildProcessSpawner, |
| 364 | 364 | ChildProcessSpawner.make(() => Effect.fail(spawnCause)), |
| 365 | 365 | ); |
| ... | ... | @@ -369,7 +369,7 @@ describe("DesktopBackendManager", () => { |
| 369 | 369 | }).pipe( |
| 370 | 370 | Effect.flip, |
| 371 | 371 | Effect.scoped, |
| 372 | Effect.provide(Layer.merge(spawnerLayer, healthyHttpClientLayer)), | |
| 372 | Effect.provide(Layer.merge(layerSpawner, layerHealthyHttpClient)), | |
| 373 | 373 | ); |
| 374 | 374 | |
| 375 | 375 | if (error._tag !== "BackendProcessSpawnError") { |
| ... | ... | @@ -397,7 +397,7 @@ describe("DesktopBackendManager", () => { |
| 397 | 397 | method: "exitCode", |
| 398 | 398 | description: "exit-status-secret-sentinel", |
| 399 | 399 | }); |
| 400 | const spawnerLayer = Layer.succeed( | |
| 400 | const layerSpawner = Layer.succeed( | |
| 401 | 401 | ChildProcessSpawner.ChildProcessSpawner, |
| 402 | 402 | ChildProcessSpawner.make(() => |
| 403 | 403 | Effect.succeed( |
| ... | ... | @@ -413,7 +413,7 @@ describe("DesktopBackendManager", () => { |
| 413 | 413 | }).pipe( |
| 414 | 414 | Effect.flip, |
| 415 | 415 | Effect.scoped, |
| 416 | Effect.provide(Layer.merge(spawnerLayer, healthyHttpClientLayer)), | |
| 416 | Effect.provide(Layer.merge(layerSpawner, layerHealthyHttpClient)), | |
| 417 | 417 | ); |
| 418 | 418 | |
| 419 | 419 | if (error._tag !== "BackendProcessExitStatusError") { |
| ... | ... | @@ -440,7 +440,7 @@ describe("DesktopBackendManager", () => { |
| 440 | 440 | description: "output-stream-secret-sentinel", |
| 441 | 441 | }); |
| 442 | 442 | const reported = yield* Deferred.make<DesktopBackendManager.BackendProcessOutputError>(); |
| 443 | const spawnerLayer = Layer.succeed( | |
| 443 | const layerSpawner = Layer.succeed( | |
| 444 | 444 | ChildProcessSpawner.ChildProcessSpawner, |
| 445 | 445 | ChildProcessSpawner.make(() => |
| 446 | 446 | Effect.succeed( |
| ... | ... | @@ -456,7 +456,7 @@ describe("DesktopBackendManager", () => { |
| 456 | 456 | ...baseConfig, |
| 457 | 457 | desktopTelemetryStream: Stream.empty, |
| 458 | 458 | onOutputFailure: (error) => Deferred.succeed(reported, error).pipe(Effect.asVoid), |
| 459 | }).pipe(Effect.scoped, Effect.provide(Layer.merge(spawnerLayer, healthyHttpClientLayer))); | |
| 459 | }).pipe(Effect.scoped, Effect.provide(Layer.merge(layerSpawner, layerHealthyHttpClient))); | |
| 460 | 460 | const error = yield* Deferred.await(reported); |
| 461 | 461 | |
| 462 | 462 | assert.equal(exit.code.pipe(Option.getOrUndefined), 0); |
| ... | ... | @@ -483,7 +483,7 @@ describe("DesktopBackendManager", () => { |
| 483 | 483 | const reported = yield* Deferred.make<DesktopBackendManager.BackendProcessOutputError>(); |
| 484 | 484 | const drained = yield* Deferred.make<void>(); |
| 485 | 485 | let outputCount = 0; |
| 486 | const spawnerLayer = Layer.succeed( | |
| 486 | const layerSpawner = Layer.succeed( | |
| 487 | 487 | ChildProcessSpawner.ChildProcessSpawner, |
| 488 | 488 | ChildProcessSpawner.make(() => |
| 489 | 489 | Effect.succeed( |
| ... | ... | @@ -505,7 +505,7 @@ describe("DesktopBackendManager", () => { |
| 505 | 505 | : Deferred.succeed(drained, void 0).pipe(Effect.asVoid); |
| 506 | 506 | }, |
| 507 | 507 | onOutputFailure: (error) => Deferred.succeed(reported, error).pipe(Effect.asVoid), |
| 508 | }).pipe(Effect.scoped, Effect.provide(Layer.merge(spawnerLayer, healthyHttpClientLayer))); | |
| 508 | }).pipe(Effect.scoped, Effect.provide(Layer.merge(layerSpawner, layerHealthyHttpClient))); | |
| 509 | 509 | const error = yield* Deferred.await(reported); |
| 510 | 510 | |
| 511 | 511 | assert.equal(exit.code.pipe(Option.getOrUndefined), 0); |
| ... | ... | @@ -534,7 +534,7 @@ describe("DesktopBackendManager", () => { |
| 534 | 534 | Effect.gen(function* () { |
| 535 | 535 | const exitObserved = yield* Deferred.make<void>(); |
| 536 | 536 | const finishOutputDrain = yield* Deferred.make<void>(); |
| 537 | const spawnerLayer = Layer.succeed( | |
| 537 | const layerSpawner = Layer.succeed( | |
| 538 | 538 | ChildProcessSpawner.ChildProcessSpawner, |
| 539 | 539 | ChildProcessSpawner.make(() => |
| 540 | 540 | Effect.succeed( |
| ... | ... | @@ -555,7 +555,7 @@ describe("DesktopBackendManager", () => { |
| 555 | 555 | desktopTelemetryStream: Stream.empty, |
| 556 | 556 | onExitObserved: () => Deferred.succeed(exitObserved, void 0).pipe(Effect.asVoid), |
| 557 | 557 | }).pipe( |
| 558 | Effect.provide(Layer.merge(spawnerLayer, healthyHttpClientLayer)), | |
| 558 | Effect.provide(Layer.merge(layerSpawner, layerHealthyHttpClient)), | |
| 559 | 559 | Effect.forkChild, |
| 560 | 560 | ); |
| 561 | 561 | |
| ... | ... | @@ -577,7 +577,7 @@ describe("DesktopBackendManager", () => { |
| 577 | 577 | type: "setDiagnosticsDemand", |
| 578 | 578 | enabled: true, |
| 579 | 579 | }); |
| 580 | const spawnerLayer = Layer.succeed( | |
| 580 | const layerSpawner = Layer.succeed( | |
| 581 | 581 | ChildProcessSpawner.ChildProcessSpawner, |
| 582 | 582 | ChildProcessSpawner.make(() => |
| 583 | 583 | Effect.succeed( |
| ... | ... | @@ -592,7 +592,7 @@ describe("DesktopBackendManager", () => { |
| 592 | 592 | ), |
| 593 | 593 | ); |
| 594 | 594 | const instance = yield* makeTestInstance({ |
| 595 | spawnerLayer, | |
| 595 | spawnerLayer: layerSpawner, | |
| 596 | 596 | desktopTelemetryPublisher: { |
| 597 | 597 | handleControlForSource: (_sourceId, message) => |
| 598 | 598 | message.type === "setDiagnosticsDemand" |
| ... | ... | @@ -613,7 +613,7 @@ describe("DesktopBackendManager", () => { |
| 613 | 613 | const persistedOutput = yield* Deferred.make<ReadonlyArray<string>>(); |
| 614 | 614 | const outputDrainStarted = yield* Deferred.make<void>(); |
| 615 | 615 | const outputChunks = yield* Ref.make<Array<string>>([]); |
| 616 | const spawnerLayer = Layer.succeed( | |
| 616 | const layerSpawner = Layer.succeed( | |
| 617 | 617 | ChildProcessSpawner.ChildProcessSpawner, |
| 618 | 618 | ChildProcessSpawner.make(() => |
| 619 | 619 | Effect.succeed( |
| ... | ... | @@ -630,8 +630,8 @@ describe("DesktopBackendManager", () => { |
| 630 | 630 | ), |
| 631 | 631 | ); |
| 632 | 632 | const instance = yield* makeTestInstance({ |
| 633 | spawnerLayer, | |
| 634 | httpClientLayer: httpClientLayer(() => Effect.never), | |
| 633 | spawnerLayer: layerSpawner, | |
| 634 | httpClientLayer: layerHttpClient(() => Effect.never), | |
| 635 | 635 | backendOutputLog: { |
| 636 | 636 | writeOutputChunk: (_streamName, chunk) => |
| 637 | 637 | Ref.update(outputChunks, (current) => [...current, new TextDecoder().decode(chunk)]), |
| ... | ... | @@ -665,7 +665,7 @@ describe("DesktopBackendManager", () => { |
| 665 | 665 | const pruneComplete = yield* Deferred.make<void>(); |
| 666 | 666 | const exited = yield* Queue.unbounded<void>(); |
| 667 | 667 | |
| 668 | const spawnerLayer = Layer.succeed( | |
| 668 | const layerSpawner = Layer.succeed( | |
| 669 | 669 | ChildProcessSpawner.ChildProcessSpawner, |
| 670 | 670 | ChildProcessSpawner.make(() => |
| 671 | 671 | Effect.succeed( |
| ... | ... | @@ -679,7 +679,7 @@ describe("DesktopBackendManager", () => { |
| 679 | 679 | ); |
| 680 | 680 | |
| 681 | 681 | const instance = yield* makeTestInstance({ |
| 682 | spawnerLayer, | |
| 682 | spawnerLayer: layerSpawner, | |
| 683 | 683 | config: { |
| 684 | 684 | ...baseConfig, |
| 685 | 685 | runningDistro: "Ubuntu", |
| ... | ... | @@ -689,7 +689,7 @@ describe("DesktopBackendManager", () => { |
| 689 | 689 | Effect.sync(() => { |
| 690 | 690 | prunedRuntimes.push([distro, runtimeId]); |
| 691 | 691 | }).pipe(Effect.andThen(Deferred.succeed(pruneComplete, void 0)), Effect.asVoid), |
| 692 | httpClientLayer: httpClientLayer((request) => | |
| 692 | httpClientLayer: layerHttpClient((request) => | |
| 693 | 693 | Effect.gen(function* () { |
| 694 | 694 | const status = statuses.shift(); |
| 695 | 695 | assert.isDefined(status); |
| ... | ... | @@ -741,7 +741,7 @@ describe("DesktopBackendManager", () => { |
| 741 | 741 | const firstProbe = yield* Deferred.make<void>(); |
| 742 | 742 | const childExit = yield* Deferred.make<void>(); |
| 743 | 743 | |
| 744 | const spawnerLayer = Layer.succeed( | |
| 744 | const layerSpawner = Layer.succeed( | |
| 745 | 745 | ChildProcessSpawner.ChildProcessSpawner, |
| 746 | 746 | ChildProcessSpawner.make(() => |
| 747 | 747 | Effect.succeed( |
| ... | ... | @@ -757,7 +757,7 @@ describe("DesktopBackendManager", () => { |
| 757 | 757 | // The backend stays 503 through the first *two* readiness budgets |
| 758 | 758 | // and only becomes healthy (200) for the third round, i.e. it comes |
| 759 | 759 | // up well after the initial 50ms budget has expired. |
| 760 | const httpLayer = httpClientLayer((request) => | |
| 760 | const layerHttp = layerHttpClient((request) => | |
| 761 | 761 | Effect.gen(function* () { |
| 762 | 762 | requestCount += 1; |
| 763 | 763 | requestUrls.push(request.url); |
| ... | ... | @@ -778,7 +778,7 @@ describe("DesktopBackendManager", () => { |
| 778 | 778 | Effect.sync(() => { |
| 779 | 779 | readinessTimeoutCount += 1; |
| 780 | 780 | }), |
| 781 | }).pipe(Effect.provide(Layer.merge(spawnerLayer, httpLayer)), Effect.forkChild); | |
| 781 | }).pipe(Effect.provide(Layer.merge(layerSpawner, layerHttp)), Effect.forkChild); | |
| 782 | 782 | |
| 783 | 783 | yield* Deferred.await(firstProbe); |
| 784 | 784 | assert.equal(readyCount, 0); |
| ... | ... | @@ -823,7 +823,7 @@ describe("DesktopBackendManager", () => { |
| 823 | 823 | let discardedSessionCount = 0; |
| 824 | 824 | let removedTelemetrySources = 0; |
| 825 | 825 | |
| 826 | const spawnerLayer = Layer.succeed( | |
| 826 | const layerSpawner = Layer.succeed( | |
| 827 | 827 | ChildProcessSpawner.ChildProcessSpawner, |
| 828 | 828 | ChildProcessSpawner.make(() => |
| 829 | 829 | Effect.gen(function* () { |
| ... | ... | @@ -852,7 +852,7 @@ describe("DesktopBackendManager", () => { |
| 852 | 852 | ); |
| 853 | 853 | |
| 854 | 854 | const instance = yield* makeTestInstance({ |
| 855 | spawnerLayer, | |
| 855 | spawnerLayer: layerSpawner, | |
| 856 | 856 | onReady: Ref.set(backendReadyFlag, true).pipe( |
| 857 | 857 | Effect.andThen(Deferred.succeed(ready, void 0)), |
| 858 | 858 | Effect.asVoid, |
| ... | ... | @@ -922,7 +922,7 @@ describe("DesktopBackendManager", () => { |
| 922 | 922 | const finishTeardown = yield* Deferred.make<void>(); |
| 923 | 923 | let startCount = 0; |
| 924 | 924 | |
| 925 | const spawnerLayer = Layer.succeed( | |
| 925 | const layerSpawner = Layer.succeed( | |
| 926 | 926 | ChildProcessSpawner.ChildProcessSpawner, |
| 927 | 927 | ChildProcessSpawner.make(() => |
| 928 | 928 | Effect.gen(function* () { |
| ... | ... | @@ -953,8 +953,8 @@ describe("DesktopBackendManager", () => { |
| 953 | 953 | ); |
| 954 | 954 | |
| 955 | 955 | const instance = yield* makeTestInstance({ |
| 956 | spawnerLayer, | |
| 957 | httpClientLayer: httpClientLayer(() => Effect.never), | |
| 956 | spawnerLayer: layerSpawner, | |
| 957 | httpClientLayer: layerHttpClient(() => Effect.never), | |
| 958 | 958 | }); |
| 959 | 959 | |
| 960 | 960 | yield* instance.start; |
| ... | ... | @@ -992,7 +992,7 @@ describe("DesktopBackendManager", () => { |
| 992 | 992 | method: "configResolve", |
| 993 | 993 | description: "transient configuration failure", |
| 994 | 994 | }); |
| 995 | const spawnerLayer = Layer.succeed( | |
| 995 | const layerSpawner = Layer.succeed( | |
| 996 | 996 | ChildProcessSpawner.ChildProcessSpawner, |
| 997 | 997 | ChildProcessSpawner.make(() => |
| 998 | 998 | Effect.gen(function* () { |
| ... | ... | @@ -1027,9 +1027,9 @@ describe("DesktopBackendManager", () => { |
| 1027 | 1027 | ), |
| 1028 | 1028 | ); |
| 1029 | 1029 | const instance = yield* makeTestInstance({ |
| 1030 | spawnerLayer, | |
| 1030 | spawnerLayer: layerSpawner, | |
| 1031 | 1031 | configResolve, |
| 1032 | httpClientLayer: httpClientLayer(() => Effect.never), | |
| 1032 | httpClientLayer: layerHttpClient(() => Effect.never), | |
| 1033 | 1033 | }); |
| 1034 | 1034 | |
| 1035 | 1035 | yield* instance.start; |
| ... | ... | @@ -1062,7 +1062,7 @@ describe("DesktopBackendManager", () => { |
| 1062 | 1062 | const finishTeardown = yield* Deferred.make<void>(); |
| 1063 | 1063 | let startCount = 0; |
| 1064 | 1064 | |
| 1065 | const spawnerLayer = Layer.succeed( | |
| 1065 | const layerSpawner = Layer.succeed( | |
| 1066 | 1066 | ChildProcessSpawner.ChildProcessSpawner, |
| 1067 | 1067 | ChildProcessSpawner.make(() => |
| 1068 | 1068 | Effect.gen(function* () { |
| ... | ... | @@ -1093,8 +1093,8 @@ describe("DesktopBackendManager", () => { |
| 1093 | 1093 | ); |
| 1094 | 1094 | |
| 1095 | 1095 | const instance = yield* makeTestInstance({ |
| 1096 | spawnerLayer, | |
| 1097 | httpClientLayer: httpClientLayer(() => Effect.never), | |
| 1096 | spawnerLayer: layerSpawner, | |
| 1097 | httpClientLayer: layerHttpClient(() => Effect.never), | |
| 1098 | 1098 | }); |
| 1099 | 1099 | |
| 1100 | 1100 | yield* instance.start; |
| ... | ... | @@ -1128,7 +1128,7 @@ describe("DesktopBackendManager", () => { |
| 1128 | 1128 | const closed = yield* Deferred.make<void>(); |
| 1129 | 1129 | const startedPids = yield* Queue.unbounded<number>(); |
| 1130 | 1130 | |
| 1131 | const spawnerLayer = Layer.succeed( | |
| 1131 | const layerSpawner = Layer.succeed( | |
| 1132 | 1132 | ChildProcessSpawner.ChildProcessSpawner, |
| 1133 | 1133 | ChildProcessSpawner.make(() => |
| 1134 | 1134 | Effect.gen(function* () { |
| ... | ... | @@ -1143,8 +1143,8 @@ describe("DesktopBackendManager", () => { |
| 1143 | 1143 | ); |
| 1144 | 1144 | |
| 1145 | 1145 | const instance = yield* makeTestInstance({ |
| 1146 | spawnerLayer, | |
| 1147 | httpClientLayer: httpClientLayer(() => Effect.never), | |
| 1146 | spawnerLayer: layerSpawner, | |
| 1147 | httpClientLayer: layerHttpClient(() => Effect.never), | |
| 1148 | 1148 | onShutdown: Effect.sync(() => { |
| 1149 | 1149 | shutdownCount += 1; |
| 1150 | 1150 | }), |
| ... | ... | @@ -1164,7 +1164,7 @@ describe("DesktopBackendManager", () => { |
| 1164 | 1164 | const failures = yield* Queue.unbounded<string>(); |
| 1165 | 1165 | let startCount = 0; |
| 1166 | 1166 | |
| 1167 | const spawnerLayer = Layer.succeed( | |
| 1167 | const layerSpawner = Layer.succeed( | |
| 1168 | 1168 | ChildProcessSpawner.ChildProcessSpawner, |
| 1169 | 1169 | ChildProcessSpawner.make(() => |
| 1170 | 1170 | Effect.sync(() => { |
| ... | ... | @@ -1179,8 +1179,8 @@ describe("DesktopBackendManager", () => { |
| 1179 | 1179 | ); |
| 1180 | 1180 | |
| 1181 | 1181 | const instance = yield* makeTestInstance({ |
| 1182 | spawnerLayer, | |
| 1183 | httpClientLayer: httpClientLayer(() => Effect.never), | |
| 1182 | spawnerLayer: layerSpawner, | |
| 1183 | httpClientLayer: layerHttpClient(() => Effect.never), | |
| 1184 | 1184 | backendOutputLog: { |
| 1185 | 1185 | persistFailure: ({ details }) => Queue.offer(failures, details).pipe(Effect.asVoid), |
| 1186 | 1186 | }, |
| ... | ... | @@ -1209,13 +1209,13 @@ describe("DesktopBackendManager", () => { |
| 1209 | 1209 | Effect.gen(function* () { |
| 1210 | 1210 | let shutdownCount = 0; |
| 1211 | 1211 | |
| 1212 | const spawnerLayer = Layer.succeed( | |
| 1212 | const layerSpawner = Layer.succeed( | |
| 1213 | 1213 | ChildProcessSpawner.ChildProcessSpawner, |
| 1214 | 1214 | ChildProcessSpawner.make(() => Effect.die("unexpected backend spawn")), |
| 1215 | 1215 | ); |
| 1216 | 1216 | |
| 1217 | 1217 | const instance = yield* makeTestInstance({ |
| 1218 | spawnerLayer, | |
| 1218 | spawnerLayer: layerSpawner, | |
| 1219 | 1219 | config: { |
| 1220 | 1220 | ...baseConfig, |
| 1221 | 1221 | preflightFailure: Option.some({ reason: "preflight failed", fatal: false }), |
| ... | ... | @@ -1238,13 +1238,13 @@ describe("DesktopBackendManager", () => { |
| 1238 | 1238 | Effect.scoped( |
| 1239 | 1239 | Effect.gen(function* () { |
| 1240 | 1240 | const failures: string[] = []; |
| 1241 | const spawnerLayer = Layer.succeed( | |
| 1241 | const layerSpawner = Layer.succeed( | |
| 1242 | 1242 | ChildProcessSpawner.ChildProcessSpawner, |
| 1243 | 1243 | ChildProcessSpawner.make(() => Effect.die("unexpected backend spawn")), |
| 1244 | 1244 | ); |
| 1245 | 1245 | |
| 1246 | 1246 | const instance = yield* makeTestInstance({ |
| 1247 | spawnerLayer, | |
| 1247 | spawnerLayer: layerSpawner, | |
| 1248 | 1248 | config: { |
| 1249 | 1249 | ...baseConfig, |
| 1250 | 1250 | preflightFailure: Option.some({ reason: "Node.js not found", fatal: true }), |
| ... | ... | @@ -1279,7 +1279,7 @@ describe("DesktopBackendManager", () => { |
| 1279 | 1279 | Effect.gen(function* () { |
| 1280 | 1280 | const failing = yield* Ref.make(true); |
| 1281 | 1281 | const starts = yield* Queue.unbounded<number>(); |
| 1282 | const spawnerLayer = Layer.succeed( | |
| 1282 | const layerSpawner = Layer.succeed( | |
| 1283 | 1283 | ChildProcessSpawner.ChildProcessSpawner, |
| 1284 | 1284 | ChildProcessSpawner.make(() => |
| 1285 | 1285 | Queue.offer(starts, 123).pipe( |
| ... | ... | @@ -1293,7 +1293,7 @@ describe("DesktopBackendManager", () => { |
| 1293 | 1293 | ); |
| 1294 | 1294 | |
| 1295 | 1295 | const instance = yield* makeTestInstance({ |
| 1296 | spawnerLayer, | |
| 1296 | spawnerLayer: layerSpawner, | |
| 1297 | 1297 | configResolve: Ref.get(failing).pipe( |
| 1298 | 1298 | Effect.map((isFailing) => |
| 1299 | 1299 | isFailing |
| ... | ... | @@ -1338,13 +1338,13 @@ describe("DesktopBackendManager", () => { |
| 1338 | 1338 | Effect.scoped( |
| 1339 | 1339 | Effect.gen(function* () { |
| 1340 | 1340 | const failures: string[] = []; |
| 1341 | const spawnerLayer = Layer.succeed( | |
| 1341 | const layerSpawner = Layer.succeed( | |
| 1342 | 1342 | ChildProcessSpawner.ChildProcessSpawner, |
| 1343 | 1343 | ChildProcessSpawner.make(() => Effect.die("unexpected backend spawn")), |
| 1344 | 1344 | ); |
| 1345 | 1345 | |
| 1346 | 1346 | const instance = yield* makeTestInstance({ |
| 1347 | spawnerLayer, | |
| 1347 | spawnerLayer: layerSpawner, | |
| 1348 | 1348 | config: { |
| 1349 | 1349 | ...baseConfig, |
| 1350 | 1350 | preflightFailure: Option.some({ reason: "wslpath conversion failed", fatal: false }), |
| ... | ... | @@ -1368,13 +1368,13 @@ describe("DesktopBackendManager", () => { |
| 1368 | 1368 | Effect.scoped( |
| 1369 | 1369 | Effect.gen(function* () { |
| 1370 | 1370 | const failures: string[] = []; |
| 1371 | const spawnerLayer = Layer.succeed( | |
| 1371 | const layerSpawner = Layer.succeed( | |
| 1372 | 1372 | ChildProcessSpawner.ChildProcessSpawner, |
| 1373 | 1373 | ChildProcessSpawner.make(() => Effect.die("unexpected backend spawn")), |
| 1374 | 1374 | ); |
| 1375 | 1375 | |
| 1376 | 1376 | const instance = yield* makeTestInstance({ |
| 1377 | spawnerLayer, | |
| 1377 | spawnerLayer: layerSpawner, | |
| 1378 | 1378 | config: { |
| 1379 | 1379 | ...baseConfig, |
| 1380 | 1380 | preflightFailure: Option.some({ |
| ... | ... | @@ -1406,7 +1406,7 @@ describe("DesktopBackendManager", () => { |
| 1406 | 1406 | const secondClosed = yield* Deferred.make<void>(); |
| 1407 | 1407 | let startCount = 0; |
| 1408 | 1408 | |
| 1409 | const spawnerLayer = Layer.succeed( | |
| 1409 | const layerSpawner = Layer.succeed( | |
| 1410 | 1410 | ChildProcessSpawner.ChildProcessSpawner, |
| 1411 | 1411 | ChildProcessSpawner.make(() => |
| 1412 | 1412 | Effect.gen(function* () { |
| ... | ... | @@ -1433,8 +1433,8 @@ describe("DesktopBackendManager", () => { |
| 1433 | 1433 | ); |
| 1434 | 1434 | |
| 1435 | 1435 | const instance = yield* makeTestInstance({ |
| 1436 | spawnerLayer, | |
| 1437 | httpClientLayer: httpClientLayer(() => Effect.never), | |
| 1436 | spawnerLayer: layerSpawner, | |
| 1437 | httpClientLayer: layerHttpClient(() => Effect.never), | |
| 1438 | 1438 | }); |
| 1439 | 1439 | |
| 1440 | 1440 | yield* instance.start; |
| ... | ... | @@ -1465,7 +1465,7 @@ describe("DesktopBackendManager", () => { |
| 1465 | 1465 | const starts = yield* Queue.unbounded<number>(); |
| 1466 | 1466 | let startCount = 0; |
| 1467 | 1467 | |
| 1468 | const spawnerLayer = Layer.succeed( | |
| 1468 | const layerSpawner = Layer.succeed( | |
| 1469 | 1469 | ChildProcessSpawner.ChildProcessSpawner, |
| 1470 | 1470 | ChildProcessSpawner.make(() => |
| 1471 | 1471 | Effect.sync(() => { |
| ... | ... | @@ -1480,8 +1480,8 @@ describe("DesktopBackendManager", () => { |
| 1480 | 1480 | ); |
| 1481 | 1481 | |
| 1482 | 1482 | const instance = yield* makeTestInstance({ |
| 1483 | spawnerLayer, | |
| 1484 | httpClientLayer: httpClientLayer(() => Effect.never), | |
| 1483 | spawnerLayer: layerSpawner, | |
| 1484 | httpClientLayer: layerHttpClient(() => Effect.never), | |
| 1485 | 1485 | }); |
| 1486 | 1486 | |
| 1487 | 1487 | yield* instance.start; |
| ... | ... | @@ -1534,7 +1534,7 @@ describe("DesktopBackendManager", () => { |
| 1534 | 1534 | }), |
| 1535 | 1535 | ), |
| 1536 | 1536 | ), |
| 1537 | httpClientLayer: httpClientLayer(() => Effect.never), | |
| 1537 | httpClientLayer: layerHttpClient(() => Effect.never), | |
| 1538 | 1538 | }); |
| 1539 | 1539 | |
| 1540 | 1540 | const instance1 = yield* makeInstance("instance1"); |
| ... | ... | @@ -1543,7 +1543,7 @@ describe("DesktopBackendManager", () => { |
| 1543 | 1543 | yield* instance1.start; |
| 1544 | 1544 | yield* instance2.start; |
| 1545 | 1545 | |
| 1546 | const mockPool = Layer.succeed(DesktopBackendPool.DesktopBackendPool, { | |
| 1546 | const layerMockPool = Layer.succeed(DesktopBackendPool.DesktopBackendPool, { | |
| 1547 | 1547 | list: Effect.succeed([instance1, instance2]), |
| 1548 | 1548 | get: () => Effect.succeedNone, |
| 1549 | 1549 | primary: Effect.die(new Error("primary not implemented")), |
| ... | ... | @@ -1556,7 +1556,7 @@ describe("DesktopBackendManager", () => { |
| 1556 | 1556 | // as an ordinary interruptible effect. |
| 1557 | 1557 | const quitFiber = yield* Effect.scoped( |
| 1558 | 1558 | Effect.addFinalizer(() => DesktopApp.stopAllPoolInstances()), |
| 1559 | ).pipe(Effect.provide(mockPool), Effect.forkChild); | |
| 1559 | ).pipe(Effect.provide(layerMockPool), Effect.forkChild); | |
| 1560 | 1560 | |
| 1561 | 1561 | const started = yield* Queue.takeN(teardownStarted, 2); |
| 1562 | 1562 | assert.deepEqual(started.toSorted(), ["instance1", "instance2"]); |
apps/desktop/src/backend/DesktopBackendPool.test.ts+2-4| ... | ... | @@ -41,9 +41,7 @@ function makeStubInstance( |
| 41 | 41 | }; |
| 42 | 42 | } |
| 43 | 43 | |
| 44 | function makePoolLayer( | |
| 45 | labelRef: Ref.Ref<string>, | |
| 46 | ): Layer.Layer<DesktopBackendPool.DesktopBackendPool> { | |
| 44 | function layerPool(labelRef: Ref.Ref<string>): Layer.Layer<DesktopBackendPool.DesktopBackendPool> { | |
| 47 | 45 | return DesktopBackendPool.layer.pipe( |
| 48 | 46 | Layer.provideMerge( |
| 49 | 47 | Layer.mergeAll( |
| ... | ... | @@ -144,7 +142,7 @@ describe("DesktopBackendPool", () => { |
| 144 | 142 | Effect.gen(function* () { |
| 145 | 143 | const labelRef = yield* Ref.make("Windows"); |
| 146 | 144 | const pool = yield* DesktopBackendPool.DesktopBackendPool.pipe( |
| 147 | Effect.provide(makePoolLayer(labelRef)), | |
| 145 | Effect.provide(layerPool(labelRef)), | |
| 148 | 146 | ); |
| 149 | 147 | const primary = yield* pool.primary; |
| 150 | 148 |
apps/desktop/src/backend/DesktopLocalEnvironmentAuth.test.ts+5-5| ... | ... | @@ -32,7 +32,7 @@ describe("DesktopLocalEnvironmentAuth", () => { |
| 32 | 32 | it.effect("exchanges the desktop bootstrap credential only once", () => |
| 33 | 33 | Effect.gen(function* () { |
| 34 | 34 | const requestCount = yield* Ref.make(0); |
| 35 | const httpClientLayer = Layer.succeed( | |
| 35 | const layerHttpClient = Layer.succeed( | |
| 36 | 36 | HttpClient.HttpClient, |
| 37 | 37 | HttpClient.make((request) => |
| 38 | 38 | Ref.update(requestCount, (count) => count + 1).pipe( |
| ... | ... | @@ -54,7 +54,7 @@ describe("DesktopLocalEnvironmentAuth", () => { |
| 54 | 54 | ), |
| 55 | 55 | ), |
| 56 | 56 | ); |
| 57 | const poolLayer = Layer.succeed(DesktopBackendPool.DesktopBackendPool, { | |
| 57 | const layerPool = Layer.succeed(DesktopBackendPool.DesktopBackendPool, { | |
| 58 | 58 | list: Effect.succeed([ |
| 59 | 59 | { |
| 60 | 60 | id: PRIMARY_LOCAL_ENVIRONMENT_ID, |
| ... | ... | @@ -63,14 +63,14 @@ describe("DesktopLocalEnvironmentAuth", () => { |
| 63 | 63 | }, |
| 64 | 64 | ]), |
| 65 | 65 | } as unknown as DesktopBackendPool.DesktopBackendPool["Service"]); |
| 66 | const testLayer = DesktopLocalEnvironmentAuth.layer.pipe( | |
| 67 | Layer.provide(Layer.mergeAll(poolLayer, httpClientLayer)), | |
| 66 | const layerTest = DesktopLocalEnvironmentAuth.layer.pipe( | |
| 67 | Layer.provide(Layer.mergeAll(layerPool, layerHttpClient)), | |
| 68 | 68 | ); |
| 69 | 69 | |
| 70 | 70 | const [first, second] = yield* Effect.gen(function* () { |
| 71 | 71 | const auth = yield* DesktopLocalEnvironmentAuth.DesktopLocalEnvironmentAuth; |
| 72 | 72 | return yield* Effect.all([auth.getBearerToken, auth.getBearerToken]); |
| 73 | }).pipe(Effect.provide(testLayer)); | |
| 73 | }).pipe(Effect.provide(layerTest)); | |
| 74 | 74 | |
| 75 | 75 | assert.strictEqual(first, "desktop-bearer-token"); |
| 76 | 76 | assert.strictEqual(second, "desktop-bearer-token"); |
apps/desktop/src/backend/DesktopNetworkInterfaces.test.ts+3-3| ... | ... | @@ -15,7 +15,7 @@ vi.mock("node:os", () => ({ |
| 15 | 15 | |
| 16 | 16 | import * as DesktopNetworkInterfaces from "./DesktopNetworkInterfaces.ts"; |
| 17 | 17 | |
| 18 | const TestLayer = DesktopNetworkInterfaces.layer.pipe( | |
| 18 | const layerTest = DesktopNetworkInterfaces.layer.pipe( | |
| 19 | 19 | Layer.provide(Layer.succeed(HostProcessPlatform, "linux")), |
| 20 | 20 | ); |
| 21 | 21 | |
| ... | ... | @@ -39,7 +39,7 @@ describe("DesktopNetworkInterfaces", () => { |
| 39 | 39 | return Effect.gen(function* () { |
| 40 | 40 | const service = yield* DesktopNetworkInterfaces.DesktopNetworkInterfaces; |
| 41 | 41 | assert.strictEqual(yield* service.read, interfaces); |
| 42 | }).pipe(Effect.provide(TestLayer)); | |
| 42 | }).pipe(Effect.provide(layerTest)); | |
| 43 | 43 | }); |
| 44 | 44 | |
| 45 | 45 | it.effect("preserves network interface read failures as structured defects", () => { |
| ... | ... | @@ -60,6 +60,6 @@ describe("DesktopNetworkInterfaces", () => { |
| 60 | 60 | assert.strictEqual(error.cause, cause); |
| 61 | 61 | assert.equal(error.message, "Failed to read desktop network interfaces on linux."); |
| 62 | 62 | } |
| 63 | }).pipe(Effect.provide(TestLayer)); | |
| 63 | }).pipe(Effect.provide(layerTest)); | |
| 64 | 64 | }); |
| 65 | 65 | }); |
apps/desktop/src/backend/DesktopServerExposure.test.ts+13-13| ... | ... | @@ -38,7 +38,7 @@ const tailnetNetworkInterfaces: DesktopNetworkInterfaces.NetworkInterfaces = { |
| 38 | 38 | ], |
| 39 | 39 | }; |
| 40 | 40 | |
| 41 | function mockSpawnerLayer(statusJson = "{}") { | |
| 41 | function layerMockSpawner(statusJson = "{}") { | |
| 42 | 42 | return Layer.succeed( |
| 43 | 43 | ChildProcessSpawner.ChildProcessSpawner, |
| 44 | 44 | ChildProcessSpawner.make(() => |
| ... | ... | @@ -61,14 +61,14 @@ function mockSpawnerLayer(statusJson = "{}") { |
| 61 | 61 | ); |
| 62 | 62 | } |
| 63 | 63 | |
| 64 | function dieOnSpawnLayer() { | |
| 64 | function layerDieOnSpawn() { | |
| 65 | 65 | return Layer.succeed( |
| 66 | 66 | ChildProcessSpawner.ChildProcessSpawner, |
| 67 | 67 | ChildProcessSpawner.make(() => Effect.die("unexpected tailscale spawn")), |
| 68 | 68 | ); |
| 69 | 69 | } |
| 70 | 70 | |
| 71 | function makeEnvironmentLayer(baseDir: string, env: Record<string, string | undefined> = {}) { | |
| 71 | function layerEnvironmentFor(baseDir: string, env: Record<string, string | undefined> = {}) { | |
| 72 | 72 | return DesktopEnvironment.layer({ |
| 73 | 73 | dirname: "/repo/apps/desktop/src", |
| 74 | 74 | homeDirectory: baseDir, |
| ... | ... | @@ -86,7 +86,7 @@ function makeEnvironmentLayer(baseDir: string, env: Record<string, string | unde |
| 86 | 86 | ); |
| 87 | 87 | } |
| 88 | 88 | |
| 89 | function makeLayer(input: { | |
| 89 | function layer(input: { | |
| 90 | 90 | readonly baseDir: string; |
| 91 | 91 | readonly networkInterfaces?: DesktopNetworkInterfaces.NetworkInterfaces; |
| 92 | 92 | readonly env?: Record<string, string | undefined>; |
| ... | ... | @@ -94,8 +94,8 @@ function makeLayer(input: { |
| 94 | 94 | readonly desktopSettingsLayer?: Layer.Layer<DesktopAppSettings.DesktopAppSettings>; |
| 95 | 95 | }) { |
| 96 | 96 | const env = { T3CODE_HOME: input.baseDir, ...input.env }; |
| 97 | const environmentLayer = makeEnvironmentLayer(input.baseDir, env); | |
| 98 | const networkLayer = Layer.succeed(DesktopNetworkInterfaces.DesktopNetworkInterfaces, { | |
| 97 | const layerEnvironment = layerEnvironmentFor(input.baseDir, env); | |
| 98 | const layerNetwork = Layer.succeed(DesktopNetworkInterfaces.DesktopNetworkInterfaces, { | |
| 99 | 99 | read: Effect.succeed(input.networkInterfaces ?? emptyNetworkInterfaces), |
| 100 | 100 | }); |
| 101 | 101 | |
| ... | ... | @@ -103,10 +103,10 @@ function makeLayer(input: { |
| 103 | 103 | Layer.provideMerge(input.desktopSettingsLayer ?? DesktopAppSettings.layer), |
| 104 | 104 | Layer.provideMerge(NodeFileSystem.layer), |
| 105 | 105 | Layer.provideMerge(NodeHttpClient.layerUndici), |
| 106 | Layer.provideMerge(input.spawnerLayer ?? mockSpawnerLayer()), | |
| 107 | Layer.provideMerge(networkLayer), | |
| 106 | Layer.provideMerge(input.spawnerLayer ?? layerMockSpawner()), | |
| 107 | Layer.provideMerge(layerNetwork), | |
| 108 | 108 | Layer.provideMerge(DesktopConfig.layerTest(env)), |
| 109 | Layer.provideMerge(environmentLayer), | |
| 109 | Layer.provideMerge(layerEnvironment), | |
| 110 | 110 | ); |
| 111 | 111 | } |
| 112 | 112 | |
| ... | ... | @@ -132,7 +132,7 @@ const withHarness = <A, E, R>( |
| 132 | 132 | }); |
| 133 | 133 | return yield* effect.pipe( |
| 134 | 134 | Effect.provide( |
| 135 | makeLayer({ | |
| 135 | layer({ | |
| 136 | 136 | baseDir, |
| 137 | 137 | networkInterfaces, |
| 138 | 138 | env, |
| ... | ... | @@ -247,7 +247,7 @@ describe("DesktopServerExposure", () => { |
| 247 | 247 | path: "/tmp/desktop-settings.json", |
| 248 | 248 | cause: diskFailure, |
| 249 | 249 | }); |
| 250 | const settingsLayer = Layer.succeed(DesktopAppSettings.DesktopAppSettings, { | |
| 250 | const layerSettings = Layer.succeed(DesktopAppSettings.DesktopAppSettings, { | |
| 251 | 251 | get: Effect.succeed(DesktopAppSettings.DEFAULT_DESKTOP_SETTINGS), |
| 252 | 252 | load: Effect.succeed(DesktopAppSettings.DEFAULT_DESKTOP_SETTINGS), |
| 253 | 253 | setMainWindowBounds: () => Effect.die("unexpected main window bounds update"), |
| ... | ... | @@ -301,7 +301,7 @@ describe("DesktopServerExposure", () => { |
| 301 | 301 | }), |
| 302 | 302 | {}, |
| 303 | 303 | undefined, |
| 304 | settingsLayer, | |
| 304 | layerSettings, | |
| 305 | 305 | ); |
| 306 | 306 | }); |
| 307 | 307 | |
| ... | ... | @@ -365,7 +365,7 @@ describe("DesktopServerExposure", () => { |
| 365 | 365 | ); |
| 366 | 366 | }), |
| 367 | 367 | {}, |
| 368 | dieOnSpawnLayer(), | |
| 368 | layerDieOnSpawn(), | |
| 369 | 369 | ), |
| 370 | 370 | ); |
| 371 | 371 |
apps/desktop/src/backend/tailscaleEndpointProvider.test.ts+4-4| ... | ... | @@ -9,7 +9,7 @@ import { |
| 9 | 9 | resolveTailscaleAdvertisedEndpoints, |
| 10 | 10 | } from "./tailscaleEndpointProvider.ts"; |
| 11 | 11 | |
| 12 | const unusedTailscaleExternalServicesLayer = Layer.mergeAll( | |
| 12 | const layerUnusedTailscaleExternalServices = Layer.mergeAll( | |
| 13 | 13 | Layer.succeed( |
| 14 | 14 | HttpClient.HttpClient, |
| 15 | 15 | HttpClient.make(() => Effect.die("unexpected Tailscale HTTPS probe")), |
| ... | ... | @@ -93,7 +93,7 @@ describe("tailscale endpoint provider", () => { |
| 93 | 93 | description: "MagicDNS hostname. Configure Tailscale Serve for HTTPS access.", |
| 94 | 94 | }, |
| 95 | 95 | ]); |
| 96 | }).pipe(Effect.provide(unusedTailscaleExternalServicesLayer)), | |
| 96 | }).pipe(Effect.provide(layerUnusedTailscaleExternalServices)), | |
| 97 | 97 | ); |
| 98 | 98 | |
| 99 | 99 | it.effect("uses an injected magic DNS name reader instead of spawning tailscale", () => |
| ... | ... | @@ -112,7 +112,7 @@ describe("tailscale endpoint provider", () => { |
| 112 | 112 | endpoints.map((endpoint) => endpoint.httpBaseUrl), |
| 113 | 113 | ["https://desktop.tail.ts.net/"], |
| 114 | 114 | ); |
| 115 | }).pipe(Effect.provide(unusedTailscaleExternalServicesLayer)), | |
| 115 | }).pipe(Effect.provide(layerUnusedTailscaleExternalServices)), | |
| 116 | 116 | ); |
| 117 | 117 | |
| 118 | 118 | it.effect( |
| ... | ... | @@ -148,6 +148,6 @@ describe("tailscale endpoint provider", () => { |
| 148 | 148 | description: "HTTPS endpoint served by Tailscale Serve.", |
| 149 | 149 | }, |
| 150 | 150 | ]); |
| 151 | }).pipe(Effect.provide(unusedTailscaleExternalServicesLayer)), | |
| 151 | }).pipe(Effect.provide(layerUnusedTailscaleExternalServices)), | |
| 152 | 152 | ); |
| 153 | 153 | }); |
apps/desktop/src/electron/ElectronMenu.test.ts+9-9| ... | ... | @@ -27,7 +27,7 @@ vi.mock("electron", () => ({ |
| 27 | 27 | |
| 28 | 28 | import * as ElectronMenu from "./ElectronMenu.ts"; |
| 29 | 29 | |
| 30 | const TestLayer = ElectronMenu.layer.pipe( | |
| 30 | const layerTest = ElectronMenu.layer.pipe( | |
| 31 | 31 | Layer.provide(Layer.succeed(HostProcessPlatform, "linux")), |
| 32 | 32 | ); |
| 33 | 33 | |
| ... | ... | @@ -55,7 +55,7 @@ describe("ElectronMenu", () => { |
| 55 | 55 | |
| 56 | 56 | assert.isTrue(Option.isNone(selectedItemId)); |
| 57 | 57 | assert.equal(buildFromTemplateMock.mock.calls.length, 0); |
| 58 | }).pipe(Effect.provide(TestLayer)), | |
| 58 | }).pipe(Effect.provide(layerTest)), | |
| 59 | 59 | ); |
| 60 | 60 | |
| 61 | 61 | it.effect("resolves with the clicked leaf item id", () => |
| ... | ... | @@ -82,7 +82,7 @@ describe("ElectronMenu", () => { |
| 82 | 82 | }); |
| 83 | 83 | |
| 84 | 84 | assert.equal(Option.getOrNull(selectedItemId), "copy"); |
| 85 | }).pipe(Effect.provide(TestLayer)), | |
| 85 | }).pipe(Effect.provide(layerTest)), | |
| 86 | 86 | ); |
| 87 | 87 | |
| 88 | 88 | it.effect("resolves with none when the menu closes without a click", () => |
| ... | ... | @@ -119,7 +119,7 @@ describe("ElectronMenu", () => { |
| 119 | 119 | ), |
| 120 | 120 | ["Copy", "separator", "Delete"], |
| 121 | 121 | ); |
| 122 | }).pipe(Effect.provide(TestLayer)), | |
| 122 | }).pipe(Effect.provide(layerTest)), | |
| 123 | 123 | ); |
| 124 | 124 | |
| 125 | 125 | it.effect("keeps a preceding non-destructive action in the destructive section", () => |
| ... | ... | @@ -145,7 +145,7 @@ describe("ElectronMenu", () => { |
| 145 | 145 | ), |
| 146 | 146 | ["Copy", "separator", "Archive", "Delete"], |
| 147 | 147 | ); |
| 148 | }).pipe(Effect.provide(TestLayer)), | |
| 148 | }).pipe(Effect.provide(layerTest)), | |
| 149 | 149 | ); |
| 150 | 150 | |
| 151 | 151 | it.effect("defers popupTemplate side effects until the returned Effect runs", () => |
| ... | ... | @@ -169,7 +169,7 @@ describe("ElectronMenu", () => { |
| 169 | 169 | assert.equal(buildFromTemplateMock.mock.calls.length, 1); |
| 170 | 170 | assert.equal(popupMock.mock.calls.length, 1); |
| 171 | 171 | assert.strictEqual(popupMock.mock.calls[0]?.[0].frame, frame); |
| 172 | }).pipe(Effect.provide(TestLayer)), | |
| 172 | }).pipe(Effect.provide(layerTest)), | |
| 173 | 173 | ); |
| 174 | 174 | |
| 175 | 175 | it.effect("preserves application-menu failures as structured defects", () => |
| ... | ... | @@ -195,7 +195,7 @@ describe("ElectronMenu", () => { |
| 195 | 195 | assert.strictEqual(error.cause, cause); |
| 196 | 196 | assert.notInclude(error.message, cause.message); |
| 197 | 197 | } |
| 198 | }).pipe(Effect.provide(TestLayer)), | |
| 198 | }).pipe(Effect.provide(layerTest)), | |
| 199 | 199 | ); |
| 200 | 200 | |
| 201 | 201 | it.effect("preserves popup-template failures with window context", () => |
| ... | ... | @@ -224,7 +224,7 @@ describe("ElectronMenu", () => { |
| 224 | 224 | assert.equal(error.itemCount, 1); |
| 225 | 225 | assert.strictEqual(error.cause, cause); |
| 226 | 226 | } |
| 227 | }).pipe(Effect.provide(TestLayer)), | |
| 227 | }).pipe(Effect.provide(layerTest)), | |
| 228 | 228 | ); |
| 229 | 229 | |
| 230 | 230 | it.effect("preserves context-menu failures with normalized item context", () => |
| ... | ... | @@ -252,6 +252,6 @@ describe("ElectronMenu", () => { |
| 252 | 252 | assert.equal(error.itemCount, 1); |
| 253 | 253 | assert.strictEqual(error.cause, cause); |
| 254 | 254 | } |
| 255 | }).pipe(Effect.provide(TestLayer)), | |
| 255 | }).pipe(Effect.provide(layerTest)), | |
| 256 | 256 | ); |
| 257 | 257 | }); |
apps/desktop/src/electron/ElectronProtocol.test.ts+7-7| ... | ... | @@ -19,7 +19,7 @@ vi.mock("electron", () => ({ |
| 19 | 19 | |
| 20 | 20 | import * as ElectronProtocol from "./ElectronProtocol.ts"; |
| 21 | 21 | |
| 22 | const protocolLayer = ElectronProtocol.layer.pipe(Layer.provide(NodeServices.layer)); | |
| 22 | const layerProtocol = ElectronProtocol.layer.pipe(Layer.provide(NodeServices.layer)); | |
| 23 | 23 | |
| 24 | 24 | describe("ElectronProtocol", () => { |
| 25 | 25 | beforeEach(() => { |
| ... | ... | @@ -65,7 +65,7 @@ describe("ElectronProtocol", () => { |
| 65 | 65 | assert.equal((yield* request("/%invalid")).status, 400); |
| 66 | 66 | assert.equal((yield* request("/", { method: "POST" })).status, 405); |
| 67 | 67 | assert.equal(netFetchMock.mock.calls.length, 0); |
| 68 | }).pipe(Effect.provide(Layer.merge(protocolLayer, NodeServices.layer)), Effect.scoped), | |
| 68 | }).pipe(Effect.provide(Layer.merge(layerProtocol, NodeServices.layer)), Effect.scoped), | |
| 69 | 69 | ); |
| 70 | 70 | |
| 71 | 71 | it.effect("proxies the stable renderer origin to the current app server", () => |
| ... | ... | @@ -129,7 +129,7 @@ describe("ElectronProtocol", () => { |
| 129 | 129 | assert.isNull(forwardedHeaders.get("referer")); |
| 130 | 130 | assert.isNull(forwardedHeaders.get("sec-fetch-site")); |
| 131 | 131 | assert.deepEqual(unhandleMock.mock.calls, [["t3code-dev"]]); |
| 132 | }).pipe(Effect.provide(protocolLayer)), | |
| 132 | }).pipe(Effect.provide(layerProtocol)), | |
| 133 | 133 | ); |
| 134 | 134 | |
| 135 | 135 | it.effect("rejects custom protocol requests for another host", () => |
| ... | ... | @@ -153,7 +153,7 @@ describe("ElectronProtocol", () => { |
| 153 | 153 | |
| 154 | 154 | assert.equal(response.status, 404); |
| 155 | 155 | assert.equal(netFetchMock.mock.calls.length, 0); |
| 156 | }).pipe(Effect.provide(protocolLayer)), | |
| 156 | }).pipe(Effect.provide(layerProtocol)), | |
| 157 | 157 | ); |
| 158 | 158 | |
| 159 | 159 | it.effect("retries transient renderer target failures", () => |
| ... | ... | @@ -180,7 +180,7 @@ describe("ElectronProtocol", () => { |
| 180 | 180 | |
| 181 | 181 | assert.equal(yield* Effect.promise(() => response.text()), "ready"); |
| 182 | 182 | assert.equal(netFetchMock.mock.calls.length, 2); |
| 183 | }).pipe(Effect.provide(protocolLayer)), | |
| 183 | }).pipe(Effect.provide(layerProtocol)), | |
| 184 | 184 | ); |
| 185 | 185 | |
| 186 | 186 | it.effect("preserves protocol registration failures", () => |
| ... | ... | @@ -203,7 +203,7 @@ describe("ElectronProtocol", () => { |
| 203 | 203 | assert.equal(error.scheme, "t3code-dev"); |
| 204 | 204 | assert.strictEqual(error.cause, cause); |
| 205 | 205 | assert.equal(error.message, 'Failed to register Electron protocol scheme "t3code-dev".'); |
| 206 | }).pipe(Effect.provide(protocolLayer)), | |
| 206 | }).pipe(Effect.provide(layerProtocol)), | |
| 207 | 207 | ); |
| 208 | 208 | |
| 209 | 209 | it.effect("preserves protocol unregistration failures", () => |
| ... | ... | @@ -232,7 +232,7 @@ describe("ElectronProtocol", () => { |
| 232 | 232 | assert.strictEqual(error.cause, cause); |
| 233 | 233 | assert.equal(error.message, 'Failed to unregister Electron protocol scheme "t3code".'); |
| 234 | 234 | } |
| 235 | }).pipe(Effect.provide(protocolLayer)), | |
| 235 | }).pipe(Effect.provide(layerProtocol)), | |
| 236 | 236 | ); |
| 237 | 237 | |
| 238 | 238 | it("keeps executable sources host-restricted while allowing runtime network resources", () => { |
apps/desktop/src/electron/ElectronWindow.test.ts+21-21| ... | ... | @@ -57,10 +57,10 @@ vi.mock("electron", () => ({ |
| 57 | 57 | |
| 58 | 58 | import * as ElectronWindow from "./ElectronWindow.ts"; |
| 59 | 59 | |
| 60 | const testLayer = (platform: NodeJS.Platform) => | |
| 60 | const layerTestFor = (platform: NodeJS.Platform) => | |
| 61 | 61 | ElectronWindow.layer.pipe(Layer.provide(Layer.succeed(HostProcessPlatform, platform))); |
| 62 | 62 | |
| 63 | const TestLayer = testLayer("linux"); | |
| 63 | const layerTest = layerTestFor("linux"); | |
| 64 | 64 | |
| 65 | 65 | function makeBrowserWindow(input: { readonly id: number; readonly destroyed: boolean }) { |
| 66 | 66 | return { |
| ... | ... | @@ -169,7 +169,7 @@ describe("ElectronWindow", () => { |
| 169 | 169 | assert.equal(error.message, 'Failed to create Electron BrowserWindow "T3 Code" (1100x780).'); |
| 170 | 170 | assert.notInclude(error.message, cause.message); |
| 171 | 171 | assert.deepEqual(browserWindowMock.mock.calls, [[options]]); |
| 172 | }).pipe(Effect.provide(TestLayer)), | |
| 172 | }).pipe(Effect.provide(layerTest)), | |
| 173 | 173 | ); |
| 174 | 174 | |
| 175 | 175 | it.effect("skips windows destroyed before appearance sync runs", () => |
| ... | ... | @@ -187,7 +187,7 @@ describe("ElectronWindow", () => { |
| 187 | 187 | ); |
| 188 | 188 | |
| 189 | 189 | assert.deepEqual(syncedWindows, [liveWindow]); |
| 190 | }).pipe(Effect.provide(TestLayer)), | |
| 190 | }).pipe(Effect.provide(layerTest)), | |
| 191 | 191 | ); |
| 192 | 192 | |
| 193 | 193 | it.effect("preserves window enumeration failures as structured defects", () => |
| ... | ... | @@ -211,7 +211,7 @@ describe("ElectronWindow", () => { |
| 211 | 211 | assert.strictEqual(error.cause, cause); |
| 212 | 212 | assert.notInclude(error.message, cause.message); |
| 213 | 213 | } |
| 214 | }).pipe(Effect.provide(TestLayer)), | |
| 214 | }).pipe(Effect.provide(layerTest)), | |
| 215 | 215 | ); |
| 216 | 216 | |
| 217 | 217 | it.effect("preserves reveal failures with the target window", () => |
| ... | ... | @@ -238,7 +238,7 @@ describe("ElectronWindow", () => { |
| 238 | 238 | assert.isNull(error.channel); |
| 239 | 239 | assert.strictEqual(error.cause, cause); |
| 240 | 240 | } |
| 241 | }).pipe(Effect.provide(TestLayer)), | |
| 241 | }).pipe(Effect.provide(layerTest)), | |
| 242 | 242 | ); |
| 243 | 243 | |
| 244 | 244 | it.effect("uses native Windows activation without querying the foreground window", () => |
| ... | ... | @@ -262,7 +262,7 @@ describe("ElectronWindow", () => { |
| 262 | 262 | assert.deepEqual(operations, ["app-focus", "show", "move-top", "focus", "native-activation"]); |
| 263 | 263 | assert.lengthOf(loadWindowsForegroundApiMock.mock.calls, 0); |
| 264 | 264 | assert.lengthOf(windowsForegroundFocusMock.mock.calls, 0); |
| 265 | }).pipe(Effect.provide(testLayer("win32"))), | |
| 265 | }).pipe(Effect.provide(layerTestFor("win32"))), | |
| 266 | 266 | ); |
| 267 | 267 | |
| 268 | 268 | it.effect("focuses the exact T3 window before activating from a shell-hosted app", () => |
| ... | ... | @@ -308,7 +308,7 @@ describe("ElectronWindow", () => { |
| 308 | 308 | }, |
| 309 | 309 | ], |
| 310 | 310 | ]); |
| 311 | }).pipe(Effect.provide(testLayer("win32"))), | |
| 311 | }).pipe(Effect.provide(layerTestFor("win32"))), | |
| 312 | 312 | ); |
| 313 | 313 | |
| 314 | 314 | it.effect("prepares the exact T3 window before a capture overlay", () => |
| ... | ... | @@ -335,7 +335,7 @@ describe("ElectronWindow", () => { |
| 335 | 335 | ]); |
| 336 | 336 | assert.lengthOf(windowsForegroundFocusMock.mock.calls, 0); |
| 337 | 337 | assert.lengthOf(activateWindowsForegroundMock.mock.calls, 0); |
| 338 | }).pipe(Effect.provide(testLayer("win32"))), | |
| 338 | }).pipe(Effect.provide(layerTestFor("win32"))), | |
| 339 | 339 | ); |
| 340 | 340 | |
| 341 | 341 | it.effect.each([4, 8])( |
| ... | ... | @@ -362,7 +362,7 @@ describe("ElectronWindow", () => { |
| 362 | 362 | assert.lengthOf(windowsForegroundFocusMock.mock.calls, 0); |
| 363 | 363 | assert.lengthOf(window.getTitle.mock.calls, 0); |
| 364 | 364 | assert.lengthOf(activateWindowsForegroundMock.mock.calls, 2); |
| 365 | }).pipe(Effect.provide(testLayer("win32"))), | |
| 365 | }).pipe(Effect.provide(layerTestFor("win32"))), | |
| 366 | 366 | ); |
| 367 | 367 | |
| 368 | 368 | it.effect.each([42n, 0n])( |
| ... | ... | @@ -381,7 +381,7 @@ describe("ElectronWindow", () => { |
| 381 | 381 | |
| 382 | 382 | assert.lengthOf(windowsForegroundFocusMock.mock.calls, 1); |
| 383 | 383 | assert.lengthOf(activateWindowsForegroundMock.mock.calls, 2); |
| 384 | }).pipe(Effect.provide(testLayer("win32"))), | |
| 384 | }).pipe(Effect.provide(layerTestFor("win32"))), | |
| 385 | 385 | ); |
| 386 | 386 | |
| 387 | 387 | it.effect("continues native focus when the foreground query fails", () => |
| ... | ... | @@ -398,7 +398,7 @@ describe("ElectronWindow", () => { |
| 398 | 398 | |
| 399 | 399 | assert.lengthOf(windowsForegroundFocusMock.mock.calls, 1); |
| 400 | 400 | assert.lengthOf(activateWindowsForegroundMock.mock.calls, 2); |
| 401 | }).pipe(Effect.provide(testLayer("win32"))), | |
| 401 | }).pipe(Effect.provide(layerTestFor("win32"))), | |
| 402 | 402 | ); |
| 403 | 403 | |
| 404 | 404 | it.effect("does not fail reveal when native focus rejects", () => |
| ... | ... | @@ -415,7 +415,7 @@ describe("ElectronWindow", () => { |
| 415 | 415 | |
| 416 | 416 | assert.lengthOf(windowsForegroundFocusMock.mock.calls, 1); |
| 417 | 417 | assert.lengthOf(activateWindowsForegroundMock.mock.calls, 2); |
| 418 | }).pipe(Effect.provide(testLayer("win32"))), | |
| 418 | }).pipe(Effect.provide(layerTestFor("win32"))), | |
| 419 | 419 | ); |
| 420 | 420 | |
| 421 | 421 | it.effect("fails reveal when Windows refuses foreground activation", () => |
| ... | ... | @@ -441,7 +441,7 @@ describe("ElectronWindow", () => { |
| 441 | 441 | [window.getNativeWindowHandle.mock.results[0]?.value], |
| 442 | 442 | [window.getNativeWindowHandle.mock.results[1]?.value], |
| 443 | 443 | ]); |
| 444 | }).pipe(Effect.provide(testLayer("win32"))), | |
| 444 | }).pipe(Effect.provide(layerTestFor("win32"))), | |
| 445 | 445 | ); |
| 446 | 446 | |
| 447 | 447 | it.effect("cancels native focus when destroyed during the foreground query", () => |
| ... | ... | @@ -471,7 +471,7 @@ describe("ElectronWindow", () => { |
| 471 | 471 | assert.lengthOf(windowsForegroundFocusMock.mock.calls, 0); |
| 472 | 472 | assert.lengthOf(window.getNativeWindowHandle.mock.calls, 1); |
| 473 | 473 | assert.lengthOf(window.getTitle.mock.calls, 0); |
| 474 | }).pipe(Effect.provide(testLayer("win32"))), | |
| 474 | }).pipe(Effect.provide(layerTestFor("win32"))), | |
| 475 | 475 | ); |
| 476 | 476 | |
| 477 | 477 | it.effect("preserves message delivery failures with window and channel context", () => |
| ... | ... | @@ -500,7 +500,7 @@ describe("ElectronWindow", () => { |
| 500 | 500 | assert.equal(error.channel, "desktop:update"); |
| 501 | 501 | assert.strictEqual(error.cause, cause); |
| 502 | 502 | } |
| 503 | }).pipe(Effect.provide(TestLayer)), | |
| 503 | }).pipe(Effect.provide(layerTest)), | |
| 504 | 504 | ); |
| 505 | 505 | |
| 506 | 506 | it.effect("preserves destroy failures and continues with later windows", () => |
| ... | ... | @@ -531,7 +531,7 @@ describe("ElectronWindow", () => { |
| 531 | 531 | assert.strictEqual(error.cause, cause); |
| 532 | 532 | } |
| 533 | 533 | assert.equal(vi.mocked(laterWindow.destroy).mock.calls.length, 1); |
| 534 | }).pipe(Effect.provide(TestLayer)), | |
| 534 | }).pipe(Effect.provide(layerTest)), | |
| 535 | 535 | ); |
| 536 | 536 | it.effect("an ordinary reveal on Windows does not touch the Win32 foreground helpers", () => |
| 537 | 537 | Effect.gen(function* () { |
| ... | ... | @@ -545,7 +545,7 @@ describe("ElectronWindow", () => { |
| 545 | 545 | assert.lengthOf(shellHostedForegroundMock.mock.calls, 0); |
| 546 | 546 | assert.lengthOf(startWindowsForegroundFocusThreadMock.mock.calls, 0); |
| 547 | 547 | assert.lengthOf(window.focus.mock.calls, 1); |
| 548 | }).pipe(Effect.provide(testLayer("win32"))), | |
| 548 | }).pipe(Effect.provide(layerTestFor("win32"))), | |
| 549 | 549 | ); |
| 550 | 550 | |
| 551 | 551 | it.effect("a capture reveal on Windows uses the Win32 path only once", () => |
| ... | ... | @@ -559,12 +559,12 @@ describe("ElectronWindow", () => { |
| 559 | 559 | |
| 560 | 560 | assert.lengthOf(activateWindowsForegroundMock.mock.calls, 1); |
| 561 | 561 | assert.lengthOf(window.focus.mock.calls, 2); |
| 562 | }).pipe(Effect.provide(testLayer("win32"))), | |
| 562 | }).pipe(Effect.provide(layerTestFor("win32"))), | |
| 563 | 563 | ); |
| 564 | 564 | |
| 565 | 565 | it.effect("starts the Windows focus worker lazily and closes it with the layer", () => |
| 566 | 566 | Effect.gen(function* () { |
| 567 | yield* ElectronWindow.ElectronWindow.pipe(Effect.provide(testLayer("win32"))); | |
| 567 | yield* ElectronWindow.ElectronWindow.pipe(Effect.provide(layerTestFor("win32"))); | |
| 568 | 568 | assert.lengthOf(startWindowsForegroundFocusThreadMock.mock.calls, 0); |
| 569 | 569 | assert.lengthOf(windowsForegroundCloseMock.mock.calls, 0); |
| 570 | 570 | |
| ... | ... | @@ -575,7 +575,7 @@ describe("ElectronWindow", () => { |
| 575 | 575 | yield* electronWindow.prepareReveal(window); |
| 576 | 576 | assert.lengthOf(startWindowsForegroundFocusThreadMock.mock.calls, 1); |
| 577 | 577 | assert.lengthOf(windowsForegroundCloseMock.mock.calls, 0); |
| 578 | }).pipe(Effect.provide(testLayer("win32"))); | |
| 578 | }).pipe(Effect.provide(layerTestFor("win32"))); | |
| 579 | 579 | assert.lengthOf(windowsForegroundCloseMock.mock.calls, 1); |
| 580 | 580 | }), |
| 581 | 581 | ); |
apps/desktop/src/ipc/methods/localEnvironment.test.ts+2-2| ... | ... | @@ -14,7 +14,7 @@ import { getLocalEnvironmentEnabled, setLocalEnvironmentEnabled } from "./localE |
| 14 | 14 | |
| 15 | 15 | // `relaunch` declares the lifecycle runtime services as requirements even |
| 16 | 16 | // though the mocked relaunch never touches them. |
| 17 | const unusedLifecycleRuntimeLayer = Layer.mergeAll( | |
| 17 | const layerUnusedLifecycleRuntime = Layer.mergeAll( | |
| 18 | 18 | DesktopShutdown.layer, |
| 19 | 19 | DesktopState.layer, |
| 20 | 20 | Layer.succeed( |
| ... | ... | @@ -42,7 +42,7 @@ describe("local environment IPC", () => { |
| 42 | 42 | relaunchReasons.push(reason); |
| 43 | 43 | }), |
| 44 | 44 | }), |
| 45 | unusedLifecycleRuntimeLayer, | |
| 45 | layerUnusedLifecycleRuntime, | |
| 46 | 46 | ); |
| 47 | 47 | return Effect.gen(function* () { |
| 48 | 48 | yield* setLocalEnvironmentEnabled.handler(false); |
apps/desktop/src/ipc/methods/sshEnvironment.test.ts+4-4| ... | ... | @@ -22,7 +22,7 @@ function jsonResponse(request: HttpClientRequest.HttpClientRequest, body: unknow |
| 22 | 22 | ); |
| 23 | 23 | } |
| 24 | 24 | |
| 25 | function makeHttpClientLayer( | |
| 25 | function layerHttpClient( | |
| 26 | 26 | handler: ( |
| 27 | 27 | request: HttpClientRequest.HttpClientRequest, |
| 28 | 28 | ) => Effect.Effect<HttpClientResponse.HttpClientResponse, never>, |
| ... | ... | @@ -36,7 +36,7 @@ function makeHttpClientLayer( |
| 36 | 36 | describe("SSH environment IPC", () => { |
| 37 | 37 | it.effect("fetches and decodes the remote environment descriptor", () => { |
| 38 | 38 | const requestUrls: string[] = []; |
| 39 | const layer = makeHttpClientLayer((request) => | |
| 39 | const layer = layerHttpClient((request) => | |
| 40 | 40 | Effect.sync(() => { |
| 41 | 41 | requestUrls.push(request.url); |
| 42 | 42 | return jsonResponse(request, { |
| ... | ... | @@ -66,7 +66,7 @@ describe("SSH environment IPC", () => { |
| 66 | 66 | }); |
| 67 | 67 | |
| 68 | 68 | it.effect("wraps schema decode failures in a typed request error", () => { |
| 69 | const layer = makeHttpClientLayer((request) => | |
| 69 | const layer = layerHttpClient((request) => | |
| 70 | 70 | Effect.succeed(jsonResponse(request, { environmentId: "remote-env" })), |
| 71 | 71 | ); |
| 72 | 72 | |
| ... | ... | @@ -89,7 +89,7 @@ describe("SSH environment IPC", () => { |
| 89 | 89 | |
| 90 | 90 | it.effect("rejects non-loopback HTTP endpoints before issuing a request", () => { |
| 91 | 91 | let requestCount = 0; |
| 92 | const layer = makeHttpClientLayer((request) => | |
| 92 | const layer = layerHttpClient((request) => | |
| 93 | 93 | Effect.sync(() => { |
| 94 | 94 | requestCount += 1; |
| 95 | 95 | return jsonResponse(request, {}); |
apps/desktop/src/ipc/methods/wsl.test.ts+22-22| ... | ... | @@ -28,7 +28,7 @@ const invokeSetWslDistro = (distro: string | null) => |
| 28 | 28 | const invokeSetWslOnly = (enabled: boolean) => |
| 29 | 29 | setWslOnly.handler(enabled).pipe(Effect.flatMap(decodeWslState)); |
| 30 | 30 | |
| 31 | function makeWslBackendLayer(input: { readonly onReconcile?: Effect.Effect<void> } = {}) { | |
| 31 | function layerWslBackend(input: { readonly onReconcile?: Effect.Effect<void> } = {}) { | |
| 32 | 32 | return Layer.succeed( |
| 33 | 33 | DesktopWslBackend.DesktopWslBackend, |
| 34 | 34 | DesktopWslBackend.DesktopWslBackend.of({ |
| ... | ... | @@ -38,7 +38,7 @@ function makeWslBackendLayer(input: { readonly onReconcile?: Effect.Effect<void> |
| 38 | 38 | ); |
| 39 | 39 | } |
| 40 | 40 | |
| 41 | function makeLifecycleLayer(relaunchReasons: Array<string>) { | |
| 41 | function layerLifecycle(relaunchReasons: Array<string>) { | |
| 42 | 42 | return Layer.succeed( |
| 43 | 43 | DesktopLifecycle.DesktopLifecycle, |
| 44 | 44 | DesktopLifecycle.DesktopLifecycle.of({ |
| ... | ... | @@ -51,7 +51,7 @@ function makeLifecycleLayer(relaunchReasons: Array<string>) { |
| 51 | 51 | ); |
| 52 | 52 | } |
| 53 | 53 | |
| 54 | const unusedLifecycleRuntimeLayer = Layer.mergeAll( | |
| 54 | const layerUnusedLifecycleRuntime = Layer.mergeAll( | |
| 55 | 55 | DesktopShutdown.layer, |
| 56 | 56 | DesktopState.layer, |
| 57 | 57 | Layer.succeed( |
| ... | ... | @@ -114,7 +114,7 @@ describe("WSL IPC", () => { |
| 114 | 114 | wslOnly: true, |
| 115 | 115 | }), |
| 116 | 116 | DesktopWslEnvironment.layerTest(), |
| 117 | makeWslBackendLayer(), | |
| 117 | layerWslBackend(), | |
| 118 | 118 | ), |
| 119 | 119 | ), |
| 120 | 120 | ), |
| ... | ... | @@ -129,9 +129,9 @@ describe("WSL IPC", () => { |
| 129 | 129 | wslOnly: true, |
| 130 | 130 | }), |
| 131 | 131 | DesktopWslEnvironment.layerTest({ isAvailable: true }), |
| 132 | makeWslBackendLayer(), | |
| 133 | makeLifecycleLayer(relaunchReasons), | |
| 134 | unusedLifecycleRuntimeLayer, | |
| 132 | layerWslBackend(), | |
| 133 | layerLifecycle(relaunchReasons), | |
| 134 | layerUnusedLifecycleRuntime, | |
| 135 | 135 | ); |
| 136 | 136 | |
| 137 | 137 | return Effect.gen(function* () { |
| ... | ... | @@ -160,9 +160,9 @@ describe("WSL IPC", () => { |
| 160 | 160 | wslOnly: false, |
| 161 | 161 | }), |
| 162 | 162 | DesktopWslEnvironment.layerTest({ isAvailable: true }), |
| 163 | makeWslBackendLayer(), | |
| 164 | makeLifecycleLayer(relaunchReasons), | |
| 165 | unusedLifecycleRuntimeLayer, | |
| 163 | layerWslBackend(), | |
| 164 | layerLifecycle(relaunchReasons), | |
| 165 | layerUnusedLifecycleRuntime, | |
| 166 | 166 | ); |
| 167 | 167 | |
| 168 | 168 | return Effect.gen(function* () { |
| ... | ... | @@ -197,13 +197,13 @@ describe("WSL IPC", () => { |
| 197 | 197 | wslOnly: true, |
| 198 | 198 | }), |
| 199 | 199 | DesktopWslEnvironment.layerTest({ isAvailable: true }), |
| 200 | makeWslBackendLayer({ | |
| 200 | layerWslBackend({ | |
| 201 | 201 | onReconcile: Effect.sync(() => { |
| 202 | 202 | reconcileCount += 1; |
| 203 | 203 | }), |
| 204 | 204 | }), |
| 205 | makeLifecycleLayer(relaunchReasons), | |
| 206 | unusedLifecycleRuntimeLayer, | |
| 205 | layerLifecycle(relaunchReasons), | |
| 206 | layerUnusedLifecycleRuntime, | |
| 207 | 207 | ); |
| 208 | 208 | |
| 209 | 209 | return Effect.gen(function* () { |
| ... | ... | @@ -232,13 +232,13 @@ describe("WSL IPC", () => { |
| 232 | 232 | wslOnly: false, |
| 233 | 233 | }), |
| 234 | 234 | DesktopWslEnvironment.layerTest({ isAvailable: true }), |
| 235 | makeWslBackendLayer({ | |
| 235 | layerWslBackend({ | |
| 236 | 236 | onReconcile: Effect.sync(() => { |
| 237 | 237 | reconcileCount += 1; |
| 238 | 238 | }), |
| 239 | 239 | }), |
| 240 | makeLifecycleLayer(relaunchReasons), | |
| 241 | unusedLifecycleRuntimeLayer, | |
| 240 | layerLifecycle(relaunchReasons), | |
| 241 | layerUnusedLifecycleRuntime, | |
| 242 | 242 | ); |
| 243 | 243 | |
| 244 | 244 | return Effect.gen(function* () { |
| ... | ... | @@ -261,13 +261,13 @@ describe("WSL IPC", () => { |
| 261 | 261 | wslOnly: true, |
| 262 | 262 | }), |
| 263 | 263 | DesktopWslEnvironment.layerTest({ isAvailable: true }), |
| 264 | makeWslBackendLayer({ | |
| 264 | layerWslBackend({ | |
| 265 | 265 | onReconcile: Effect.sync(() => { |
| 266 | 266 | reconcileCount += 1; |
| 267 | 267 | }), |
| 268 | 268 | }), |
| 269 | makeLifecycleLayer(relaunchReasons), | |
| 270 | unusedLifecycleRuntimeLayer, | |
| 269 | layerLifecycle(relaunchReasons), | |
| 270 | layerUnusedLifecycleRuntime, | |
| 271 | 271 | ); |
| 272 | 272 | |
| 273 | 273 | return Effect.gen(function* () { |
| ... | ... | @@ -300,13 +300,13 @@ describe("WSL IPC", () => { |
| 300 | 300 | wslOnly: false, |
| 301 | 301 | }), |
| 302 | 302 | DesktopWslEnvironment.layerTest({ isAvailable: true }), |
| 303 | makeWslBackendLayer({ | |
| 303 | layerWslBackend({ | |
| 304 | 304 | onReconcile: Effect.sync(() => { |
| 305 | 305 | reconcileCount += 1; |
| 306 | 306 | }), |
| 307 | 307 | }), |
| 308 | makeLifecycleLayer(relaunchReasons), | |
| 309 | unusedLifecycleRuntimeLayer, | |
| 308 | layerLifecycle(relaunchReasons), | |
| 309 | layerUnusedLifecycleRuntime, | |
| 310 | 310 | ); |
| 311 | 311 | |
| 312 | 312 | return Effect.gen(function* () { |
apps/desktop/src/main.ts+37-37| ... | ... | @@ -82,7 +82,7 @@ if (process.argv.includes("--version")) { |
| 82 | 82 | Electron.app.exit(0); |
| 83 | 83 | } |
| 84 | 84 | |
| 85 | const desktopEnvironmentLayer = Layer.unwrap( | |
| 85 | const layerDesktopEnvironment = Layer.unwrap( | |
| 86 | 86 | Effect.gen(function* () { |
| 87 | 87 | const metadata = yield* Effect.service(ElectronApp.ElectronApp).pipe( |
| 88 | 88 | Effect.flatMap((app) => app.metadata), |
| ... | ... | @@ -115,7 +115,7 @@ const resolveDesktopSshCliRunner = ( |
| 115 | 115 | return { archiveVersion: environment.appVersion }; |
| 116 | 116 | }; |
| 117 | 117 | |
| 118 | const desktopSshEnvironmentLayer = Layer.unwrap( | |
| 118 | const layerDesktopSshEnvironment = Layer.unwrap( | |
| 119 | 119 | Effect.gen(function* () { |
| 120 | 120 | const environment = yield* DesktopEnvironment.DesktopEnvironment; |
| 121 | 121 | return DesktopSshEnvironment.layer({ |
| ... | ... | @@ -124,7 +124,7 @@ const desktopSshEnvironmentLayer = Layer.unwrap( |
| 124 | 124 | }), |
| 125 | 125 | ); |
| 126 | 126 | |
| 127 | const electronLayer = Layer.mergeAll( | |
| 127 | const layerElectron = Layer.mergeAll( | |
| 128 | 128 | ElectronApp.layer, |
| 129 | 129 | ElectronDialog.layer, |
| 130 | 130 | ElectronMenu.layer, |
| ... | ... | @@ -138,7 +138,7 @@ const electronLayer = Layer.mergeAll( |
| 138 | 138 | DesktopIpc.layer(Electron.ipcMain), |
| 139 | 139 | ); |
| 140 | 140 | |
| 141 | const desktopFoundationLayer = Layer.mergeAll( | |
| 141 | const layerDesktopFoundation = Layer.mergeAll( | |
| 142 | 142 | MacPermissions.layer, |
| 143 | 143 | DesktopState.layer, |
| 144 | 144 | DesktopShutdown.layer, |
| ... | ... | @@ -149,36 +149,36 @@ const desktopFoundationLayer = Layer.mergeAll( |
| 149 | 149 | DesktopAssets.layer, |
| 150 | 150 | DesktopObservability.layer, |
| 151 | 151 | DesktopRendererHistory.layer, |
| 152 | ).pipe(Layer.provideMerge(desktopEnvironmentLayer)); | |
| 152 | ).pipe(Layer.provideMerge(layerDesktopEnvironment)); | |
| 153 | 153 | |
| 154 | const desktopSshLayer = desktopSshEnvironmentLayer.pipe( | |
| 154 | const layerDesktopSsh = layerDesktopSshEnvironment.pipe( | |
| 155 | 155 | Layer.provideMerge(DesktopSshPasswordPrompts.layer()), |
| 156 | 156 | ); |
| 157 | 157 | |
| 158 | const desktopServerExposureLayer = DesktopServerExposure.layer.pipe( | |
| 158 | const layerDesktopServerExposure = DesktopServerExposure.layer.pipe( | |
| 159 | 159 | Layer.provideMerge(DesktopNetworkInterfaces.layer), |
| 160 | Layer.provideMerge(desktopFoundationLayer), | |
| 160 | Layer.provideMerge(layerDesktopFoundation), | |
| 161 | 161 | ); |
| 162 | 162 | |
| 163 | const desktopPreviewLayer = PreviewManager.layer.pipe( | |
| 163 | const layerDesktopPreview = PreviewManager.layer.pipe( | |
| 164 | 164 | // Merged rather than provided so the IPC handlers can reach the import |
| 165 | 165 | // service alongside the manager; both sit on the same BrowserSession. |
| 166 | 166 | Layer.provideMerge(BrowserImport.layer.pipe(Layer.provide(LinuxBrowserSecret.layer))), |
| 167 | 167 | Layer.provideMerge(BrowserSession.layer), |
| 168 | Layer.provideMerge(desktopFoundationLayer), | |
| 168 | Layer.provideMerge(layerDesktopFoundation), | |
| 169 | 169 | ); |
| 170 | 170 | |
| 171 | const desktopWindowLayer = DesktopWindow.layer.pipe( | |
| 172 | Layer.provideMerge(desktopServerExposureLayer), | |
| 173 | Layer.provideMerge(desktopPreviewLayer), | |
| 171 | const layerDesktopWindow = DesktopWindow.layer.pipe( | |
| 172 | Layer.provideMerge(layerDesktopServerExposure), | |
| 173 | Layer.provideMerge(layerDesktopPreview), | |
| 174 | 174 | ); |
| 175 | 175 | |
| 176 | const desktopSnapShotLayer = DesktopSnapShot.layer.pipe( | |
| 177 | Layer.provideMerge(desktopWindowLayer), | |
| 178 | Layer.provideMerge(desktopFoundationLayer), | |
| 176 | const layerDesktopSnapShot = DesktopSnapShot.layer.pipe( | |
| 177 | Layer.provideMerge(layerDesktopWindow), | |
| 178 | Layer.provideMerge(layerDesktopFoundation), | |
| 179 | 179 | ); |
| 180 | const desktopAppActivationLayer = DesktopAppActivation.layer.pipe( | |
| 181 | Layer.provide(desktopWindowLayer), | |
| 180 | const layerDesktopAppActivation = DesktopAppActivation.layer.pipe( | |
| 181 | Layer.provide(layerDesktopWindow), | |
| 182 | 182 | ); |
| 183 | 183 | |
| 184 | 184 | // Pool layer instantiates the backend factory once for the Windows |
| ... | ... | @@ -186,63 +186,63 @@ const desktopAppActivationLayer = DesktopAppActivation.layer.pipe( |
| 186 | 186 | // the pool now; the legacy DesktopBackendManager service is gone. The |
| 187 | 187 | // WSL second instance gets registered later in the migration. See |
| 188 | 188 | // DesktopBackendPool.ts header for the full rollout plan. |
| 189 | const desktopBackendLayer = DesktopBackendPool.layer.pipe( | |
| 189 | const layerDesktopBackend = DesktopBackendPool.layer.pipe( | |
| 190 | 190 | Layer.provideMerge(DesktopAppIdentity.layer), |
| 191 | 191 | Layer.provideMerge(DesktopBackendConfiguration.layer), |
| 192 | 192 | Layer.provideMerge(DesktopWslEnvironment.layer), |
| 193 | 193 | Layer.provideMerge(DesktopWslServerTree.layer), |
| 194 | 194 | Layer.provideMerge(DesktopTelemetryPublisher.layer), |
| 195 | Layer.provideMerge(desktopWindowLayer), | |
| 195 | Layer.provideMerge(layerDesktopWindow), | |
| 196 | 196 | ); |
| 197 | 197 | |
| 198 | 198 | // WSL orchestrator hangs off the backend layer because it needs the |
| 199 | 199 | // pool + configuration + serverExposure; it pulls NetService and the |
| 200 | 200 | // foundation services through the same provideMerge chain. |
| 201 | const desktopWslBackendLayer = DesktopWslBackend.layer.pipe( | |
| 202 | Layer.provideMerge(desktopBackendLayer), | |
| 201 | const layerDesktopWslBackend = DesktopWslBackend.layer.pipe( | |
| 202 | Layer.provideMerge(layerDesktopBackend), | |
| 203 | 203 | ); |
| 204 | 204 | |
| 205 | const desktopLocalEnvironmentAuthLayer = DesktopLocalEnvironmentAuth.layer.pipe( | |
| 206 | Layer.provideMerge(desktopBackendLayer), | |
| 205 | const layerDesktopLocalEnvironmentAuth = DesktopLocalEnvironmentAuth.layer.pipe( | |
| 206 | Layer.provideMerge(layerDesktopBackend), | |
| 207 | 207 | ); |
| 208 | 208 | |
| 209 | const desktopApplicationLayer = Layer.mergeAll( | |
| 209 | const layerDesktopApplication = Layer.mergeAll( | |
| 210 | 210 | DesktopLifecycle.layer, |
| 211 | desktopAppActivationLayer, | |
| 211 | layerDesktopAppActivation, | |
| 212 | 212 | DesktopApplicationMenu.layer, |
| 213 | 213 | DesktopLinuxUrlHandler.layer, |
| 214 | 214 | DesktopShellEnvironment.layer, |
| 215 | desktopSshLayer, | |
| 215 | layerDesktopSsh, | |
| 216 | 216 | ).pipe( |
| 217 | Layer.provideMerge(desktopSnapShotLayer), | |
| 217 | Layer.provideMerge(layerDesktopSnapShot), | |
| 218 | 218 | Layer.provideMerge(DesktopUpdates.layer), |
| 219 | Layer.provideMerge(desktopWslBackendLayer), | |
| 220 | Layer.provideMerge(desktopLocalEnvironmentAuthLayer), | |
| 219 | Layer.provideMerge(layerDesktopWslBackend), | |
| 220 | Layer.provideMerge(layerDesktopLocalEnvironmentAuth), | |
| 221 | 221 | ); |
| 222 | 222 | |
| 223 | 223 | // Clerk resolves userData before Electron is ready, so it gets the synchronous FileSystem. |
| 224 | const desktopClerkLayer = DesktopClerk.layer.pipe( | |
| 224 | const layerDesktopClerk = DesktopClerk.layer.pipe( | |
| 225 | 225 | Layer.provide(DesktopPreReadyFileSystem.layer), |
| 226 | 226 | Layer.provideMerge(ElectronShell.layer), |
| 227 | Layer.provideMerge(desktopEnvironmentLayer), | |
| 227 | Layer.provideMerge(layerDesktopEnvironment), | |
| 228 | 228 | Layer.provideMerge(NodeServices.layer), |
| 229 | 229 | Layer.provideMerge(ElectronApp.layer), |
| 230 | 230 | ); |
| 231 | 231 | |
| 232 | const desktopApplicationRuntimeLayer = desktopApplicationLayer.pipe( | |
| 232 | const layerDesktopApplicationRuntime = layerDesktopApplication.pipe( | |
| 233 | 233 | Layer.provideMerge(NodeServices.layer), |
| 234 | 234 | Layer.provideMerge(NodeHttpClient.layerUndici), |
| 235 | 235 | Layer.provideMerge(NetService.layer), |
| 236 | Layer.provideMerge(electronLayer), | |
| 236 | Layer.provideMerge(layerElectron), | |
| 237 | 237 | ); |
| 238 | 238 | |
| 239 | 239 | // Acquire strict pre-ready setup before Clerk. Nothing before the Clerk bridge |
| 240 | 240 | // may yield, or Electron can emit ready before Clerk registers its scheme. |
| 241 | const desktopRuntimeLayer = desktopClerkLayer.pipe( | |
| 241 | const layerDesktopRuntime = layerDesktopClerk.pipe( | |
| 242 | 242 | Layer.flatMap((clerkContext) => |
| 243 | desktopApplicationRuntimeLayer.pipe(Layer.provideMerge(Layer.succeedContext(clerkContext))), | |
| 243 | layerDesktopApplicationRuntime.pipe(Layer.provideMerge(Layer.succeedContext(clerkContext))), | |
| 244 | 244 | ), |
| 245 | 245 | Layer.provideMerge(DesktopPreReadyPlatform.layer), |
| 246 | 246 | ); |
| 247 | 247 | |
| 248 | DesktopApp.program.pipe(Effect.provide(desktopRuntimeLayer), NodeRuntime.runMain); | |
| 248 | DesktopApp.program.pipe(Effect.provide(layerDesktopRuntime), NodeRuntime.runMain); |
apps/desktop/src/preview/BrowserImport/BrowserImport.test.ts+4-4| ... | ... | @@ -34,7 +34,7 @@ const cookie = { |
| 34 | 34 | * Dies if the import reaches session work: every case here covers a request |
| 35 | 35 | * that must be rejected before a cookie is read or written. |
| 36 | 36 | */ |
| 37 | const rejectedBeforeSession = Layer.succeed( | |
| 37 | const layerRejectedBeforeSession = Layer.succeed( | |
| 38 | 38 | BrowserSession.BrowserSession, |
| 39 | 39 | BrowserSession.BrowserSession.of({ |
| 40 | 40 | getPartition: () => Effect.die("getPartition must not be reached"), |
| ... | ... | @@ -52,7 +52,7 @@ const rejectedBeforeSession = Layer.succeed( |
| 52 | 52 | const withImporter = Effect.fnUntraced(function* () { |
| 53 | 53 | const fileSystem = yield* FileSystem.FileSystem; |
| 54 | 54 | const home = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3code-import-" }); |
| 55 | const environment = Layer.succeed(HostProcessEnvironment, { HOME: home }); | |
| 55 | const layerEnvironment = Layer.succeed(HostProcessEnvironment, { HOME: home }); | |
| 56 | 56 | const context = yield* sourcePathContext.pipe( |
| 57 | 57 | Effect.provideService(HostProcessEnvironment, { HOME: home }), |
| 58 | 58 | Effect.provideService(HostProcessPlatform, "darwin"), |
| ... | ... | @@ -67,8 +67,8 @@ const withImporter = Effect.fnUntraced(function* () { |
| 67 | 67 | const importer = yield* BrowserImport.BrowserImport.pipe( |
| 68 | 68 | Effect.provide( |
| 69 | 69 | BrowserImport.layer.pipe( |
| 70 | Layer.provide(rejectedBeforeSession), | |
| 71 | Layer.provide(environment), | |
| 70 | Layer.provide(layerRejectedBeforeSession), | |
| 71 | Layer.provide(layerEnvironment), | |
| 72 | 72 | Layer.provide(Layer.succeed(HostProcessPlatform, "darwin")), |
| 73 | 73 | Layer.provide(Layer.succeed(HostProcessExecutablePath, "/Applications/T3 Code.app")), |
| 74 | 74 | Layer.provide(NodeServices.layer), |
apps/desktop/src/preview/BrowserImport/ChromiumKeys.test.ts+15-15| ... | ... | @@ -39,7 +39,7 @@ type CapturedCommand = { |
| 39 | 39 | }; |
| 40 | 40 | }; |
| 41 | 41 | |
| 42 | const helperLayer = (input: { | |
| 42 | const layerHelper = (input: { | |
| 43 | 43 | readonly stdout?: string; |
| 44 | 44 | readonly stderr?: string; |
| 45 | 45 | readonly stdoutStream?: Stream.Stream<Uint8Array>; |
| ... | ... | @@ -83,7 +83,7 @@ describe("macOS Chromium secrets", () => { |
| 83 | 83 | keychainAccount: "Chrome", |
| 84 | 84 | linuxSecretApplication: undefined, |
| 85 | 85 | } as const; |
| 86 | const noProcesses = Layer.succeed( | |
| 86 | const layerNoProcesses = Layer.succeed( | |
| 87 | 87 | ChildProcessSpawner.ChildProcessSpawner, |
| 88 | 88 | ChildProcessSpawner.make(() => Effect.die("must not spawn")), |
| 89 | 89 | ); |
| ... | ... | @@ -93,7 +93,7 @@ describe("macOS Chromium secrets", () => { |
| 93 | 93 | getPassword.mockReturnValue("macos-secret"); |
| 94 | 94 | const keys = yield* resolveChromiumKeys(request); |
| 95 | 95 | expect(keys.cbcV10?.toString("hex")).toBe("3df7306fb1eac353289565a2f6b64f74"); |
| 96 | }).pipe(Effect.provide(noProcesses)), | |
| 96 | }).pipe(Effect.provide(layerNoProcesses)), | |
| 97 | 97 | ); |
| 98 | 98 | |
| 99 | 99 | it.effect("reports a missing keychain entry", () => |
| ... | ... | @@ -101,7 +101,7 @@ describe("macOS Chromium secrets", () => { |
| 101 | 101 | getPassword.mockReturnValue(null); |
| 102 | 102 | const error = yield* resolveChromiumKeys(request).pipe(Effect.flip); |
| 103 | 103 | expect(error.reason).toBe("keychainItemMissing"); |
| 104 | }).pipe(Effect.provide(noProcesses)), | |
| 104 | }).pipe(Effect.provide(layerNoProcesses)), | |
| 105 | 105 | ); |
| 106 | 106 | |
| 107 | 107 | it.effect("preserves a denied keychain approval", () => |
| ... | ... | @@ -113,7 +113,7 @@ describe("macOS Chromium secrets", () => { |
| 113 | 113 | const error = yield* resolveChromiumKeys(request).pipe(Effect.flip); |
| 114 | 114 | expect(error.reason).toBe("needsKeychainApproval"); |
| 115 | 115 | expect(error.cause).toBe(denied); |
| 116 | }).pipe(Effect.provide(noProcesses)), | |
| 116 | }).pipe(Effect.provide(layerNoProcesses)), | |
| 117 | 117 | ); |
| 118 | 118 | }); |
| 119 | 119 | |
| ... | ... | @@ -131,7 +131,7 @@ describe("Linux Chromium secrets", () => { |
| 131 | 131 | expect(keys.cbcV11Error?.reason).toBe("keychainUnavailable"); |
| 132 | 132 | }).pipe( |
| 133 | 133 | Effect.provide( |
| 134 | helperLayer({ | |
| 134 | layerHelper({ | |
| 135 | 135 | spawnError: PlatformError.systemError({ |
| 136 | 136 | _tag: "NotFound", |
| 137 | 137 | module: "ChildProcess", |
| ... | ... | @@ -171,7 +171,7 @@ describe("Linux Chromium secrets", () => { |
| 171 | 171 | expect(keys.cbcV11).toHaveLength(16); |
| 172 | 172 | }).pipe( |
| 173 | 173 | Effect.provide( |
| 174 | helperLayer({ stdout: "linux-secret", capture: (value) => (captured = value) }), | |
| 174 | layerHelper({ stdout: "linux-secret", capture: (value) => (captured = value) }), | |
| 175 | 175 | ), |
| 176 | 176 | ); |
| 177 | 177 | }); |
| ... | ... | @@ -183,7 +183,7 @@ describe("Linux Chromium secrets", () => { |
| 183 | 183 | expect(error.reason).toBe("keychainUnavailable"); |
| 184 | 184 | }).pipe( |
| 185 | 185 | Effect.provide( |
| 186 | helperLayer({ stderr: "Cannot autolaunch D-Bus without X11 $DISPLAY", exitCode: 1 }), | |
| 186 | layerHelper({ stderr: "Cannot autolaunch D-Bus without X11 $DISPLAY", exitCode: 1 }), | |
| 187 | 187 | ), |
| 188 | 188 | ), |
| 189 | 189 | ); |
| ... | ... | @@ -192,7 +192,7 @@ describe("Linux Chromium secrets", () => { |
| 192 | 192 | Effect.gen(function* () { |
| 193 | 193 | const secret = yield* readLinuxSecret("chrome"); |
| 194 | 194 | expect(secret).toBe("linux-secret \t\n"); |
| 195 | }).pipe(Effect.provide(helperLayer({ stdout: "linux-secret \t\n" }))), | |
| 195 | }).pipe(Effect.provide(layerHelper({ stdout: "linux-secret \t\n" }))), | |
| 196 | 196 | ); |
| 197 | 197 | |
| 198 | 198 | it.effect("drains stdout and stderr concurrently", () => |
| ... | ... | @@ -206,7 +206,7 @@ describe("Linux Chromium secrets", () => { |
| 206 | 206 | ); |
| 207 | 207 | |
| 208 | 208 | const secret = yield* readLinuxSecret("chrome").pipe( |
| 209 | Effect.provide(helperLayer({ stdoutStream: stdout, stderrStream: stderr })), | |
| 209 | Effect.provide(layerHelper({ stdoutStream: stdout, stderrStream: stderr })), | |
| 210 | 210 | ); |
| 211 | 211 | |
| 212 | 212 | expect(secret).toBe("linux-secret"); |
| ... | ... | @@ -226,7 +226,7 @@ describe("Linux Chromium secrets", () => { |
| 226 | 226 | expect(captured?.options.env?.SESSION_MARKER).toBe("kept"); |
| 227 | 227 | }).pipe( |
| 228 | 228 | Effect.provide( |
| 229 | helperLayer({ | |
| 229 | layerHelper({ | |
| 230 | 230 | stderr: "Zugriff verweigert", |
| 231 | 231 | exitCode: 3, |
| 232 | 232 | capture: (value) => (captured = value), |
| ... | ... | @@ -250,7 +250,7 @@ describe("Linux Chromium secrets", () => { |
| 250 | 250 | linuxSecretApplication: "brave", |
| 251 | 251 | }).pipe(Effect.flip); |
| 252 | 252 | expect(error.reason).toBe("needsKeychainApproval"); |
| 253 | }).pipe(Effect.provide(helperLayer({ stderr: "Keyring is locked", exitCode: 3 }))), | |
| 253 | }).pipe(Effect.provide(layerHelper({ stderr: "Keyring is locked", exitCode: 3 }))), | |
| 254 | 254 | ); |
| 255 | 255 | |
| 256 | 256 | it.effect("keeps the v10 fallback when the Secret Service backend is unavailable", () => |
| ... | ... | @@ -265,7 +265,7 @@ describe("Linux Chromium secrets", () => { |
| 265 | 265 | expect(keys.cbcV11).toBeUndefined(); |
| 266 | 266 | }).pipe( |
| 267 | 267 | Effect.provide( |
| 268 | helperLayer({ | |
| 268 | layerHelper({ | |
| 269 | 269 | stderr: "Cannot autolaunch D-Bus without X11 $DISPLAY", |
| 270 | 270 | exitCode: 1, |
| 271 | 271 | }), |
| ... | ... | @@ -283,7 +283,7 @@ describe("Linux Chromium secrets", () => { |
| 283 | 283 | }); |
| 284 | 284 | expect(keys.cbcV10).toHaveLength(16); |
| 285 | 285 | expect(keys.cbcV11).toBeUndefined(); |
| 286 | }).pipe(Effect.provide(helperLayer({ exitCode: 2 }))), | |
| 286 | }).pipe(Effect.provide(layerHelper({ exitCode: 2 }))), | |
| 287 | 287 | ); |
| 288 | 288 | }); |
| 289 | 289 | |
| ... | ... | @@ -320,7 +320,7 @@ describe("Windows Chromium secrets", () => { |
| 320 | 320 | expect(captured?.args.join(" ")).not.toContain(wrapped.toString("base64")); |
| 321 | 321 | }).pipe( |
| 322 | 322 | Effect.provide( |
| 323 | helperLayer({ stdout: key.toString("base64"), capture: (value) => (captured = value) }), | |
| 323 | layerHelper({ stdout: key.toString("base64"), capture: (value) => (captured = value) }), | |
| 324 | 324 | ), |
| 325 | 325 | Effect.provideService(HostProcessEnvironment, { SystemRoot: "C:\\Windows" }), |
| 326 | 326 | ); |
apps/desktop/src/preview/BrowserImport/LinuxBrowserSecret.test.ts+2-2| ... | ... | @@ -38,7 +38,7 @@ it.layer(NodeServices.layer)("Linux browser secret path", (it) => { |
| 38 | 38 | yield* fileSystem.writeFileString(filename, "helper"); |
| 39 | 39 | } |
| 40 | 40 | const resolve = (isPackaged: boolean, platform: NodeJS.Platform = "linux") => { |
| 41 | const environment = DesktopEnvironment.layer({ | |
| 41 | const layerEnvironment = DesktopEnvironment.layer({ | |
| 42 | 42 | dirname: path.join(root, "apps", "desktop", "dist-electron"), |
| 43 | 43 | homeDirectory: root, |
| 44 | 44 | platform, |
| ... | ... | @@ -50,7 +50,7 @@ it.layer(NodeServices.layer)("Linux browser secret path", (it) => { |
| 50 | 50 | runningUnderArm64Translation: false, |
| 51 | 51 | }).pipe(Layer.provide(DesktopConfig.layerTest({}))); |
| 52 | 52 | return LinuxBrowserSecret.LinuxBrowserSecretPath.pipe( |
| 53 | Effect.provide(LinuxBrowserSecret.layer.pipe(Layer.provide(environment))), | |
| 53 | Effect.provide(LinuxBrowserSecret.layer.pipe(Layer.provide(layerEnvironment))), | |
| 54 | 54 | ); |
| 55 | 55 | }; |
| 56 | 56 |
apps/desktop/src/preview/BrowserSession.test.ts+2-2| ... | ... | @@ -197,7 +197,7 @@ describe("BrowserSession", () => { |
| 197 | 197 | method: "digest", |
| 198 | 198 | cause: nativeCause, |
| 199 | 199 | }); |
| 200 | const failingCryptoLayer = Layer.succeed( | |
| 200 | const layerFailingCrypto = Layer.succeed( | |
| 201 | 201 | Crypto.Crypto, |
| 202 | 202 | Crypto.make({ |
| 203 | 203 | randomBytes: (size) => new Uint8Array(size), |
| ... | ... | @@ -218,7 +218,7 @@ describe("BrowserSession", () => { |
| 218 | 218 | "Failed to derive a desktop preview browser partition for scope environment-a.", |
| 219 | 219 | ); |
| 220 | 220 | assert.notInclude(error.message, nativeCause.message); |
| 221 | }).pipe(Effect.provide(BrowserSession.layer.pipe(Layer.provide(failingCryptoLayer)))); | |
| 221 | }).pipe(Effect.provide(BrowserSession.layer.pipe(Layer.provide(layerFailingCrypto)))); | |
| 222 | 222 | }); |
| 223 | 223 | |
| 224 | 224 | it.effect("preserves session scope, partition, and the Electron failure", () => |
apps/desktop/src/preview/Manager.test.ts+6-6| ... | ... | @@ -234,7 +234,7 @@ vi.mock("electron", () => ({ |
| 234 | 234 | }, |
| 235 | 235 | })); |
| 236 | 236 | |
| 237 | const browserSessionLayer = Layer.succeed( | |
| 237 | const layerBrowserSession = Layer.succeed( | |
| 238 | 238 | BrowserSession.BrowserSession, |
| 239 | 239 | BrowserSession.BrowserSession.of({ |
| 240 | 240 | getPartition: () => Effect.succeed("persist:t3code-preview-test"), |
| ... | ... | @@ -245,7 +245,7 @@ const browserSessionLayer = Layer.succeed( |
| 245 | 245 | }), |
| 246 | 246 | ); |
| 247 | 247 | |
| 248 | const environmentLayer = Layer.succeed( | |
| 248 | const layerEnvironment = Layer.succeed( | |
| 249 | 249 | DesktopEnvironment.DesktopEnvironment, |
| 250 | 250 | DesktopEnvironment.DesktopEnvironment.of({ |
| 251 | 251 | browserArtifactsDir: "/tmp/t3/dev/browser-artifacts", |
| ... | ... | @@ -256,7 +256,7 @@ const environmentLayer = Layer.succeed( |
| 256 | 256 | } as DesktopEnvironment.DesktopEnvironment["Service"]), |
| 257 | 257 | ); |
| 258 | 258 | |
| 259 | const fileSystemLayer = FileSystem.layerNoop({ | |
| 259 | const layerFileSystem = FileSystem.layerNoop({ | |
| 260 | 260 | makeDirectory: (path) => |
| 261 | 261 | Effect.sync(() => { |
| 262 | 262 | mkdir(path); |
| ... | ... | @@ -275,9 +275,9 @@ const layer = PreviewManager.layer.pipe( |
| 275 | 275 | shutdown: Effect.void, |
| 276 | 276 | }), |
| 277 | 277 | ), |
| 278 | Layer.provideMerge(browserSessionLayer), | |
| 279 | Layer.provideMerge(environmentLayer), | |
| 280 | Layer.provideMerge(fileSystemLayer), | |
| 278 | Layer.provideMerge(layerBrowserSession), | |
| 279 | Layer.provideMerge(layerEnvironment), | |
| 280 | Layer.provideMerge(layerFileSystem), | |
| 281 | 281 | Layer.provideMerge(Path.layer), |
| 282 | 282 | Layer.provideMerge(Layer.succeed(HostProcessPlatform, "darwin")), |
| 283 | 283 | ); |
apps/desktop/src/settings/DesktopAppSettings.test.ts+2-2| ... | ... | @@ -39,7 +39,7 @@ const DesktopSettingsPatch = Schema.Struct({ |
| 39 | 39 | const decodeDesktopSettingsPatch = Schema.decodeEffect(Schema.fromJsonString(DesktopSettingsPatch)); |
| 40 | 40 | const encodeDesktopSettingsPatch = Schema.encodeEffect(Schema.fromJsonString(DesktopSettingsPatch)); |
| 41 | 41 | |
| 42 | function makeEnvironmentLayer(baseDir: string, appVersion = "0.0.17") { | |
| 42 | function layerEnvironment(baseDir: string, appVersion = "0.0.17") { | |
| 43 | 43 | return DesktopEnvironment.layer({ |
| 44 | 44 | dirname: "/repo/apps/desktop/src", |
| 45 | 45 | homeDirectory: baseDir, |
| ... | ... | @@ -73,7 +73,7 @@ const withSettings = <A, E, R>( |
| 73 | 73 | return yield* effect.pipe( |
| 74 | 74 | Effect.provide( |
| 75 | 75 | DesktopAppSettings.layer.pipe( |
| 76 | Layer.provideMerge(makeEnvironmentLayer(baseDir, options?.appVersion)), | |
| 76 | Layer.provideMerge(layerEnvironment(baseDir, options?.appVersion)), | |
| 77 | 77 | Layer.provideMerge(NodeServices.layer), |
| 78 | 78 | ), |
| 79 | 79 | ), |
apps/desktop/src/settings/DesktopClientSettings.diagnostics.test.ts+4-7| ... | ... | @@ -19,8 +19,8 @@ interface LogRecord { |
| 19 | 19 | |
| 20 | 20 | const baseDir = "/virtual-home"; |
| 21 | 21 | |
| 22 | function makeLayer(fileSystemLayer: Layer.Layer<FileSystem.FileSystem>) { | |
| 23 | const environmentLayer = DesktopEnvironment.layer({ | |
| 22 | function layer(fileSystemLayer: Layer.Layer<FileSystem.FileSystem>) { | |
| 23 | const layerEnvironment = DesktopEnvironment.layer({ | |
| 24 | 24 | dirname: "/repo/apps/desktop/src", |
| 25 | 25 | homeDirectory: baseDir, |
| 26 | 26 | platform: "darwin", |
| ... | ... | @@ -37,7 +37,7 @@ function makeLayer(fileSystemLayer: Layer.Layer<FileSystem.FileSystem>) { |
| 37 | 37 | ); |
| 38 | 38 | |
| 39 | 39 | return DesktopClientSettings.layer.pipe( |
| 40 | Layer.provideMerge(Layer.mergeAll(environmentLayer, NodeServices.layer, fileSystemLayer)), | |
| 40 | Layer.provideMerge(Layer.mergeAll(layerEnvironment, NodeServices.layer, fileSystemLayer)), | |
| 41 | 41 | ); |
| 42 | 42 | } |
| 43 | 43 | |
| ... | ... | @@ -60,10 +60,7 @@ const readWithLogs = (fileSystemLayer: Layer.Layer<FileSystem.FileSystem>) => { |
| 60 | 60 | }; |
| 61 | 61 | }).pipe( |
| 62 | 62 | Effect.provide( |
| 63 | Layer.mergeAll( | |
| 64 | makeLayer(fileSystemLayer), | |
| 65 | Logger.layer([logger], { mergeWithExisting: false }), | |
| 66 | ), | |
| 63 | Layer.mergeAll(layer(fileSystemLayer), Logger.layer([logger], { mergeWithExisting: false })), | |
| 67 | 64 | ), |
| 68 | 65 | ); |
| 69 | 66 | }; |
apps/desktop/src/settings/DesktopClientSettings.test.ts+4-4| ... | ... | @@ -78,8 +78,8 @@ const decodeClientSettingsJson = Schema.decodeEffect(Schema.fromJsonString(Clien |
| 78 | 78 | const decodeRecordJson = Schema.decodeEffect( |
| 79 | 79 | Schema.fromJsonString(Schema.Record(Schema.String, Schema.Unknown)), |
| 80 | 80 | ); |
| 81 | function makeLayer(baseDir: string) { | |
| 82 | const environmentLayer = DesktopEnvironment.layer({ | |
| 81 | function layer(baseDir: string) { | |
| 82 | const layerEnvironment = DesktopEnvironment.layer({ | |
| 83 | 83 | dirname: "/repo/apps/desktop/src", |
| 84 | 84 | homeDirectory: baseDir, |
| 85 | 85 | platform: "darwin", |
| ... | ... | @@ -96,7 +96,7 @@ function makeLayer(baseDir: string) { |
| 96 | 96 | ); |
| 97 | 97 | |
| 98 | 98 | return DesktopClientSettings.layer.pipe( |
| 99 | Layer.provideMerge(environmentLayer), | |
| 99 | Layer.provideMerge(layerEnvironment), | |
| 100 | 100 | Layer.provideMerge(NodeServices.layer), |
| 101 | 101 | ); |
| 102 | 102 | } |
| ... | ... | @@ -109,7 +109,7 @@ const withClientSettings = <A, E, R>( |
| 109 | 109 | const baseDir = yield* fileSystem.makeTempDirectoryScoped({ |
| 110 | 110 | prefix: "t3-desktop-client-settings-test-", |
| 111 | 111 | }); |
| 112 | return yield* effect.pipe(Effect.provide(makeLayer(baseDir))); | |
| 112 | return yield* effect.pipe(Effect.provide(layer(baseDir))); | |
| 113 | 113 | }).pipe(Effect.provide(NodeServices.layer), Effect.scoped); |
| 114 | 114 | |
| 115 | 115 | describe("DesktopClientSettings", () => { |
apps/desktop/src/settings/DesktopSavedEnvironments.test.ts+11-11| ... | ... | @@ -60,7 +60,7 @@ const seedSavedEnvironmentRegistry = Effect.fn(function* (encryptedBearerToken?: |
| 60 | 60 | yield* fileSystem.writeFileString(environment.savedEnvironmentRegistryPath, `${encoded}\n`); |
| 61 | 61 | }); |
| 62 | 62 | |
| 63 | function makeSafeStorageLayer(input: { | |
| 63 | function layerSafeStorageFor(input: { | |
| 64 | 64 | readonly available: boolean; |
| 65 | 65 | readonly availabilityError?: unknown; |
| 66 | 66 | readonly decryptError?: unknown; |
| ... | ... | @@ -98,7 +98,7 @@ function makeSafeStorageLayer(input: { |
| 98 | 98 | } satisfies ElectronSafeStorage.ElectronSafeStorage["Service"]); |
| 99 | 99 | } |
| 100 | 100 | |
| 101 | function makeLayer( | |
| 101 | function layer( | |
| 102 | 102 | baseDir: string, |
| 103 | 103 | options?: { |
| 104 | 104 | readonly availableSecretStorage?: boolean; |
| ... | ... | @@ -107,7 +107,7 @@ function makeLayer( |
| 107 | 107 | }, |
| 108 | 108 | fileSystemLayer: Layer.Layer<FileSystem.FileSystem> = NodeServices.layer, |
| 109 | 109 | ) { |
| 110 | const environmentLayer = DesktopEnvironment.layer({ | |
| 110 | const layerEnvironment = DesktopEnvironment.layer({ | |
| 111 | 111 | dirname: "/repo/apps/desktop/src", |
| 112 | 112 | homeDirectory: baseDir, |
| 113 | 113 | platform: "darwin", |
| ... | ... | @@ -123,19 +123,19 @@ function makeLayer( |
| 123 | 123 | ), |
| 124 | 124 | ); |
| 125 | 125 | |
| 126 | const safeStorageLayer = makeSafeStorageLayer({ | |
| 126 | const layerSafeStorage = layerSafeStorageFor({ | |
| 127 | 127 | available: options?.availableSecretStorage ?? true, |
| 128 | 128 | availabilityError: options?.availabilityError, |
| 129 | 129 | decryptError: options?.decryptError, |
| 130 | 130 | }); |
| 131 | const dependencies = Layer.mergeAll( | |
| 132 | environmentLayer, | |
| 133 | safeStorageLayer, | |
| 131 | const layerDependencies = Layer.mergeAll( | |
| 132 | layerEnvironment, | |
| 133 | layerSafeStorage, | |
| 134 | 134 | NodeServices.layer, |
| 135 | 135 | fileSystemLayer, |
| 136 | 136 | ); |
| 137 | 137 | |
| 138 | return DesktopSavedEnvironments.layer.pipe(Layer.provideMerge(dependencies)); | |
| 138 | return DesktopSavedEnvironments.layer.pipe(Layer.provideMerge(layerDependencies)); | |
| 139 | 139 | } |
| 140 | 140 | |
| 141 | 141 | const withSavedEnvironments = <A, E, R>( |
| ... | ... | @@ -151,7 +151,7 @@ const withSavedEnvironments = <A, E, R>( |
| 151 | 151 | const baseDir = yield* fileSystem.makeTempDirectoryScoped({ |
| 152 | 152 | prefix: "t3-desktop-saved-environments-test-", |
| 153 | 153 | }); |
| 154 | return yield* effect.pipe(Effect.provide(makeLayer(baseDir, options))); | |
| 154 | return yield* effect.pipe(Effect.provide(layer(baseDir, options))); | |
| 155 | 155 | }).pipe(Effect.provide(NodeServices.layer), Effect.scoped); |
| 156 | 156 | |
| 157 | 157 | describe("DesktopSavedEnvironments", () => { |
| ... | ... | @@ -346,14 +346,14 @@ describe("DesktopSavedEnvironments", () => { |
| 346 | 346 | method: "readFileString", |
| 347 | 347 | pathOrDescriptor: registryPath, |
| 348 | 348 | }); |
| 349 | const fileSystemLayer = Layer.succeed( | |
| 349 | const layerFileSystem = Layer.succeed( | |
| 350 | 350 | FileSystem.FileSystem, |
| 351 | 351 | FileSystem.makeNoop({ |
| 352 | 352 | readFileString: () => Effect.fail(permissionError), |
| 353 | 353 | }), |
| 354 | 354 | ); |
| 355 | 355 | const savedEnvironments = yield* DesktopSavedEnvironments.DesktopSavedEnvironments.pipe( |
| 356 | Effect.provide(makeLayer(baseDir, undefined, fileSystemLayer)), | |
| 356 | Effect.provide(layer(baseDir, undefined, layerFileSystem)), | |
| 357 | 357 | ); |
| 358 | 358 | |
| 359 | 359 | const error = yield* savedEnvironments.getRegistry.pipe(Effect.flip); |
apps/desktop/src/shell/DesktopShellEnvironment.test.ts+3-3| ... | ... | @@ -70,13 +70,13 @@ function runShellEnvironment(input: { |
| 70 | 70 | readonly handler: (command: ChildProcess.Command) => string; |
| 71 | 71 | readonly failure?: PlatformError.PlatformError; |
| 72 | 72 | }) { |
| 73 | const environmentLayer = Layer.succeed( | |
| 73 | const layerEnvironment = Layer.succeed( | |
| 74 | 74 | DesktopEnvironment.DesktopEnvironment, |
| 75 | 75 | DesktopEnvironment.DesktopEnvironment.of({ |
| 76 | 76 | platform: input.platform, |
| 77 | 77 | } as DesktopEnvironment.DesktopEnvironment["Service"]), |
| 78 | 78 | ); |
| 79 | const spawnerLayer = Layer.succeed( | |
| 79 | const layerSpawner = Layer.succeed( | |
| 80 | 80 | ChildProcessSpawner.ChildProcessSpawner, |
| 81 | 81 | ChildProcessSpawner.make((command) => |
| 82 | 82 | input.failure === undefined |
| ... | ... | @@ -91,7 +91,7 @@ function runShellEnvironment(input: { |
| 91 | 91 | }).pipe( |
| 92 | 92 | Effect.provide( |
| 93 | 93 | DesktopShellEnvironment.layer.pipe( |
| 94 | Layer.provide(Layer.mergeAll(environmentLayer, NodeServices.layer, spawnerLayer)), | |
| 94 | Layer.provide(Layer.mergeAll(layerEnvironment, NodeServices.layer, layerSpawner)), | |
| 95 | 95 | ), |
| 96 | 96 | ), |
| 97 | 97 | ); |
apps/desktop/src/snapShot/DesktopSnapShot.test.ts+49-49| ... | ... | @@ -484,7 +484,7 @@ import { |
| 484 | 484 | snapShotAnimationDisplayBounds, |
| 485 | 485 | snapShotAnimationOverlayBounds, |
| 486 | 486 | } from "./SnapShotTransition.ts"; |
| 487 | const testLayer = ( | |
| 487 | const layerTest = ( | |
| 488 | 488 | platform: NodeJS.Platform, |
| 489 | 489 | fileSystemOverrides: Parameters<typeof FileSystem.layerNoop>[0] = {}, |
| 490 | 490 | initialSettings: Option.Option<ClientSettings> = Option.none(), |
| ... | ... | @@ -639,7 +639,7 @@ function concurrentCaptureFixture(platform: NodeJS.Platform, animations: boolean |
| 639 | 639 | const handoff = Effect.sync(() => captures[state.handoffs++]!.handoff.resolve()); |
| 640 | 640 | let randomByte = 0; |
| 641 | 641 | const layer = Layer.mergeAll( |
| 642 | testLayer(platform, { | |
| 642 | layerTest(platform, { | |
| 643 | 643 | makeDirectory: () => Effect.void, |
| 644 | 644 | writeFile: (path, bytes) => |
| 645 | 645 | Effect.sync(() => { |
| ... | ... | @@ -776,7 +776,7 @@ it.effect("rejects desktop config changes outside Niri or Hyprland", () => |
| 776 | 776 | assert.equal(error.message, "Config setup requires a Niri or Hyprland session."); |
| 777 | 777 | } |
| 778 | 778 | }), |
| 779 | ).pipe(Effect.provide(testLayer("win32"))), | |
| 779 | ).pipe(Effect.provide(layerTest("win32"))), | |
| 780 | 780 | ); |
| 781 | 781 | |
| 782 | 782 | it.effect("reads and acknowledges queued captures through Effect services", () => { |
| ... | ... | @@ -797,7 +797,7 @@ it.effect("reads and acknowledges queued captures through Effect services", () = |
| 797 | 797 | windowTitle: "main.ts", |
| 798 | 798 | }, |
| 799 | 799 | }); |
| 800 | const layer = testLayer("linux", { | |
| 800 | const layer = layerTest("linux", { | |
| 801 | 801 | readDirectory: () => Effect.succeed([captureId + ".json", "invalid.json"]), |
| 802 | 802 | readFileString: (filePath) => Effect.succeed(filePath === metadataPath ? metadata : "invalid"), |
| 803 | 803 | readFile: () => Effect.succeed(new Uint8Array([1, 2, 3])), |
| ... | ... | @@ -841,7 +841,7 @@ it.effect("captures the active Windows window without enumerating desktop source |
| 841 | 841 | getFileIconMock.mockReset().mockResolvedValue(fakeIcon("file")); |
| 842 | 842 | const writtenFiles: Array<[string, Uint8Array]> = []; |
| 843 | 843 | let metadata = ""; |
| 844 | const layer = testLayer("win32", { | |
| 844 | const layer = layerTest("win32", { | |
| 845 | 845 | makeDirectory: () => Effect.void, |
| 846 | 846 | rename: () => Effect.void, |
| 847 | 847 | writeFile: (path, bytes) => |
| ... | ... | @@ -897,7 +897,7 @@ it.effect.each([ |
| 897 | 897 | result: Promise.resolve(undefined), |
| 898 | 898 | }); |
| 899 | 899 | let metadata = ""; |
| 900 | const layer = testLayer("darwin", { | |
| 900 | const layer = layerTest("darwin", { | |
| 901 | 901 | makeDirectory: () => Effect.void, |
| 902 | 902 | rename: () => Effect.void, |
| 903 | 903 | writeFileString: (_, text) => |
| ... | ... | @@ -1027,7 +1027,7 @@ it.effect.each(["win32", "darwin", "linux"] as const)( |
| 1027 | 1027 | }), |
| 1028 | 1028 | ).pipe( |
| 1029 | 1029 | Effect.provide( |
| 1030 | testLayer(platform, { | |
| 1030 | layerTest(platform, { | |
| 1031 | 1031 | makeDirectory: () => Effect.void, |
| 1032 | 1032 | rename: () => Effect.void, |
| 1033 | 1033 | writeFile: (_, bytes) => |
| ... | ... | @@ -1288,7 +1288,7 @@ it.effect("matches Windows accessibility windows on a scaled display", () => { |
| 1288 | 1288 | }), |
| 1289 | 1289 | ).pipe( |
| 1290 | 1290 | Effect.provide( |
| 1291 | testLayer("win32", { | |
| 1291 | layerTest("win32", { | |
| 1292 | 1292 | makeDirectory: () => Effect.void, |
| 1293 | 1293 | rename: () => Effect.void, |
| 1294 | 1294 | writeFile: () => Effect.void, |
| ... | ... | @@ -1320,7 +1320,7 @@ it.effect("skips accessibility capture when the setting is disabled", () => { |
| 1320 | 1320 | accessibilityProcessWarmMock.mockClear(); |
| 1321 | 1321 | accessibilityProcessReadMock.mockClear(); |
| 1322 | 1322 | accessibilityByPidMock.mockClear(); |
| 1323 | const layer = testLayer("win32", { | |
| 1323 | const layer = layerTest("win32", { | |
| 1324 | 1324 | makeDirectory: () => Effect.void, |
| 1325 | 1325 | rename: () => Effect.void, |
| 1326 | 1326 | writeFile: () => Effect.void, |
| ... | ... | @@ -1364,7 +1364,7 @@ it.effect("keeps an accessibility helper warm only while capture data is enabled |
| 1364 | 1364 | |
| 1365 | 1365 | assert.lengthOf(accessibilityProcessCoolMock.mock.calls, 1); |
| 1366 | 1366 | }), |
| 1367 | ).pipe(Effect.provide(testLayer("win32"))); | |
| 1367 | ).pipe(Effect.provide(layerTest("win32"))); | |
| 1368 | 1368 | }); |
| 1369 | 1369 | |
| 1370 | 1370 | it.effect("rejects X11 capture without registering shortcuts or loading capture backends", () => { |
| ... | ... | @@ -1389,7 +1389,7 @@ it.effect("rejects X11 capture without registering shortcuts or loading capture |
| 1389 | 1389 | assert.lengthOf(activeWindowMock.mock.calls, 0); |
| 1390 | 1390 | }), |
| 1391 | 1391 | ).pipe( |
| 1392 | Effect.provide(testLayer("linux")), | |
| 1392 | Effect.provide(layerTest("linux")), | |
| 1393 | 1393 | Effect.ensuring(Effect.sync(() => vi.unstubAllEnvs())), |
| 1394 | 1394 | ); |
| 1395 | 1395 | }); |
| ... | ... | @@ -1476,7 +1476,7 @@ it.effect.each(["gnome", "niri", "kde", "hyprland"] as const)( |
| 1476 | 1476 | }), |
| 1477 | 1477 | ).pipe( |
| 1478 | 1478 | Effect.provide( |
| 1479 | testLayer("linux", { | |
| 1479 | layerTest("linux", { | |
| 1480 | 1480 | makeDirectory: () => Effect.void, |
| 1481 | 1481 | rename: () => Effect.void, |
| 1482 | 1482 | writeFile: () => Effect.void, |
| ... | ... | @@ -1543,7 +1543,7 @@ it.effect("persists a window capture from a portal activation without a setup te |
| 1543 | 1543 | }), |
| 1544 | 1544 | ).pipe( |
| 1545 | 1545 | Effect.provide( |
| 1546 | testLayer("linux", { | |
| 1546 | layerTest("linux", { | |
| 1547 | 1547 | makeDirectory: () => Effect.void, |
| 1548 | 1548 | rename: () => Effect.void, |
| 1549 | 1549 | writeFile: (_, bytes) => |
| ... | ... | @@ -1574,7 +1574,7 @@ it.effect("does not fall back to the picker when an automatic Wayland capture fa |
| 1574 | 1574 | }), |
| 1575 | 1575 | ).pipe( |
| 1576 | 1576 | Effect.provide( |
| 1577 | testLayer("linux", { | |
| 1577 | layerTest("linux", { | |
| 1578 | 1578 | makeDirectory: () => Effect.void, |
| 1579 | 1579 | remove: () => Effect.void, |
| 1580 | 1580 | }), |
| ... | ... | @@ -1649,7 +1649,7 @@ it.effect( |
| 1649 | 1649 | ).pipe( |
| 1650 | 1650 | Effect.provide( |
| 1651 | 1651 | Layer.mergeAll( |
| 1652 | testLayer("linux", { | |
| 1652 | layerTest("linux", { | |
| 1653 | 1653 | makeDirectory: () => Effect.void, |
| 1654 | 1654 | rename: () => Effect.void, |
| 1655 | 1655 | writeFile: () => Effect.void, |
| ... | ... | @@ -1686,7 +1686,7 @@ it.effect("does not read unverified accessibility context for a Wayland portal c |
| 1686 | 1686 | thumbnail: { isEmpty: () => false, toPNG: () => png }, |
| 1687 | 1687 | }, |
| 1688 | 1688 | ]); |
| 1689 | const layer = testLayer("linux", { | |
| 1689 | const layer = layerTest("linux", { | |
| 1690 | 1690 | makeDirectory: () => Effect.void, |
| 1691 | 1691 | rename: () => Effect.void, |
| 1692 | 1692 | writeFile: () => Effect.void, |
| ... | ... | @@ -1728,7 +1728,7 @@ it.effect("uses display-local macOS capture surfaces across the source and main |
| 1728 | 1728 | isDestroyed: () => false, |
| 1729 | 1729 | }); |
| 1730 | 1730 | flashWindows.length = 0; |
| 1731 | const layer = testLayer("darwin", { | |
| 1731 | const layer = layerTest("darwin", { | |
| 1732 | 1732 | makeDirectory: () => Effect.void, |
| 1733 | 1733 | rename: () => Effect.void, |
| 1734 | 1734 | writeFileString: () => Effect.void, |
| ... | ... | @@ -1807,7 +1807,7 @@ it.effect.each(["ready", "failed"] as const)( |
| 1807 | 1807 | }), |
| 1808 | 1808 | ).pipe( |
| 1809 | 1809 | Effect.provide( |
| 1810 | testLayer("win32", { | |
| 1810 | layerTest("win32", { | |
| 1811 | 1811 | makeDirectory: () => Effect.void, |
| 1812 | 1812 | rename: () => Effect.void, |
| 1813 | 1813 | writeFile: () => Effect.void, |
| ... | ... | @@ -1848,7 +1848,7 @@ it.effect("uses the unfocused main window for a macOS cross-display transition", |
| 1848 | 1848 | }, |
| 1849 | 1849 | ]); |
| 1850 | 1850 | flashWindows.length = 0; |
| 1851 | const layer = testLayer("darwin", { | |
| 1851 | const layer = layerTest("darwin", { | |
| 1852 | 1852 | makeDirectory: () => Effect.void, |
| 1853 | 1853 | rename: () => Effect.void, |
| 1854 | 1854 | writeFileString: () => Effect.void, |
| ... | ... | @@ -3048,7 +3048,7 @@ it.effect("does not request permissions or create the flash during desktop start |
| 3048 | 3048 | assert.lengthOf(getSourcesMock.mock.calls, 0); |
| 3049 | 3049 | assert.lengthOf(openExternalMock.mock.calls, 0); |
| 3050 | 3050 | }), |
| 3051 | ).pipe(Effect.provide(testLayer("darwin", {}, Option.some(settings)))); | |
| 3051 | ).pipe(Effect.provide(layerTest("darwin", {}, Option.some(settings)))); | |
| 3052 | 3052 | }); |
| 3053 | 3053 | |
| 3054 | 3054 | it.effect("keeps snapshots disabled when client settings cannot be read at startup", () => { |
| ... | ... | @@ -3064,7 +3064,7 @@ it.effect("keeps snapshots disabled when client settings cannot be read at start |
| 3064 | 3064 | yield* service.initialize; |
| 3065 | 3065 | assert.isFalse((yield* service.state).shortcutRegistered); |
| 3066 | 3066 | }), |
| 3067 | ).pipe(Effect.provide(testLayer("win32", {}, Option.none(), Effect.fail(readError)))); | |
| 3067 | ).pipe(Effect.provide(layerTest("win32", {}, Option.none(), Effect.fail(readError)))); | |
| 3068 | 3068 | }); |
| 3069 | 3069 | |
| 3070 | 3070 | it.effect("does not request macOS permissions while synchronizing enabled settings", () => { |
| ... | ... | @@ -3083,7 +3083,7 @@ it.effect("does not request macOS permissions while synchronizing enabled settin |
| 3083 | 3083 | assert.lengthOf(getSourcesMock.mock.calls, 0); |
| 3084 | 3084 | assert.lengthOf(openExternalMock.mock.calls, 0); |
| 3085 | 3085 | }), |
| 3086 | ).pipe(Effect.provide(testLayer("darwin"))); | |
| 3086 | ).pipe(Effect.provide(layerTest("darwin"))); | |
| 3087 | 3087 | }); |
| 3088 | 3088 | |
| 3089 | 3089 | it.effect("requests macOS permissions only for an explicit enable action", () => { |
| ... | ... | @@ -3101,7 +3101,7 @@ it.effect("requests macOS permissions only for an explicit enable action", () => |
| 3101 | 3101 | assert.lengthOf(getSourcesMock.mock.calls, 1); |
| 3102 | 3102 | assert.lengthOf(openExternalMock.mock.calls, 1); |
| 3103 | 3103 | }), |
| 3104 | ).pipe(Effect.provide(testLayer("darwin"))); | |
| 3104 | ).pipe(Effect.provide(layerTest("darwin"))); | |
| 3105 | 3105 | }); |
| 3106 | 3106 | |
| 3107 | 3107 | it.effect("does not request macOS accessibility permission when capture data is disabled", () => { |
| ... | ... | @@ -3119,7 +3119,7 @@ it.effect("does not request macOS accessibility permission when capture data is |
| 3119 | 3119 | assert.lengthOf(getSourcesMock.mock.calls, 1); |
| 3120 | 3120 | assert.lengthOf(openExternalMock.mock.calls, 1); |
| 3121 | 3121 | }), |
| 3122 | ).pipe(Effect.provide(testLayer("darwin"))); | |
| 3122 | ).pipe(Effect.provide(layerTest("darwin"))); | |
| 3123 | 3123 | }); |
| 3124 | 3124 | |
| 3125 | 3125 | it.effect("reports macOS permission status and requests each permission on its own", () => { |
| ... | ... | @@ -3143,7 +3143,7 @@ it.effect("reports macOS permission status and requests each permission on its o |
| 3143 | 3143 | assert.lengthOf(getSourcesMock.mock.calls, 1); |
| 3144 | 3144 | assert.lengthOf(openExternalMock.mock.calls, 1); |
| 3145 | 3145 | }), |
| 3146 | ).pipe(Effect.provide(testLayer("darwin"))); | |
| 3146 | ).pipe(Effect.provide(layerTest("darwin"))); | |
| 3147 | 3147 | }); |
| 3148 | 3148 | |
| 3149 | 3149 | it.effect("flags revoked macOS permissions on read and re-registers once they return", () => { |
| ... | ... | @@ -3183,7 +3183,7 @@ it.effect("flags revoked macOS permissions on read and re-registers once they re |
| 3183 | 3183 | assert.isNull(recovered.message); |
| 3184 | 3184 | assert.isTrue(recovered.shortcutRegistered); |
| 3185 | 3185 | }), |
| 3186 | ).pipe(Effect.provide(testLayer("darwin"))); | |
| 3186 | ).pipe(Effect.provide(layerTest("darwin"))); | |
| 3187 | 3187 | }); |
| 3188 | 3188 | |
| 3189 | 3189 | it.effect("rejects macOS permission actions off macOS and omits macPermissions there", () => { |
| ... | ... | @@ -3196,7 +3196,7 @@ it.effect("rejects macOS permission actions off macOS and omits macPermissions t |
| 3196 | 3196 | const error = yield* service.setup("allow-screen-recording").pipe(Effect.flip); |
| 3197 | 3197 | assert.equal(error.reason, "unsupported-session"); |
| 3198 | 3198 | }), |
| 3199 | ).pipe(Effect.provide(testLayer("win32"))); | |
| 3199 | ).pipe(Effect.provide(layerTest("win32"))); | |
| 3200 | 3200 | }); |
| 3201 | 3201 | |
| 3202 | 3202 | it.effect("registers macOS capture without accessibility permission when data is disabled", () => { |
| ... | ... | @@ -3216,7 +3216,7 @@ it.effect("registers macOS capture without accessibility permission when data is |
| 3216 | 3216 | assert.lengthOf(accessibilityTrustedMock.mock.calls, 0); |
| 3217 | 3217 | assert.isTrue((yield* service.state).shortcutRegistered); |
| 3218 | 3218 | }), |
| 3219 | ).pipe(Effect.provide(testLayer("darwin"))); | |
| 3219 | ).pipe(Effect.provide(layerTest("darwin"))); | |
| 3220 | 3220 | }); |
| 3221 | 3221 | |
| 3222 | 3222 | it.effect("starts the Shift listener outside the Electron main process", () => { |
| ... | ... | @@ -3240,7 +3240,7 @@ it.effect("starts the Shift listener outside the Electron main process", () => { |
| 3240 | 3240 | yield* Effect.promise(() => new Promise<void>((resolve) => queueMicrotask(resolve))); |
| 3241 | 3241 | assert.isFalse((yield* service.state).shortcutRegistered); |
| 3242 | 3242 | }), |
| 3243 | ).pipe(Effect.provide(testLayer("win32"))); | |
| 3243 | ).pipe(Effect.provide(layerTest("win32"))); | |
| 3244 | 3244 | }); |
| 3245 | 3245 | |
| 3246 | 3246 | it.effect("passes the configured modifier pair to the listener process", () => { |
| ... | ... | @@ -3261,7 +3261,7 @@ it.effect("passes the configured modifier pair to the listener process", () => { |
| 3261 | 3261 | assert.isTrue(state.shortcutRegistered); |
| 3262 | 3262 | assert.deepEqual(shortcutForkArgs[0], ["meta"]); |
| 3263 | 3263 | }), |
| 3264 | ).pipe(Effect.provide(testLayer("win32"))); | |
| 3264 | ).pipe(Effect.provide(layerTest("win32"))); | |
| 3265 | 3265 | }); |
| 3266 | 3266 | |
| 3267 | 3267 | it.effect("registers a configured key chord instead of the Shift listener", () => { |
| ... | ... | @@ -3289,7 +3289,7 @@ it.effect("registers a configured key chord instead of the Shift listener", () = |
| 3289 | 3289 | assert.strictEqual(registerShortcutMock.mock.calls[0]?.[0], "Control+Alt+K"); |
| 3290 | 3290 | assert.lengthOf(shortcutProcesses, 0); |
| 3291 | 3291 | }), |
| 3292 | ).pipe(Effect.provide(testLayer("win32"))); | |
| 3292 | ).pipe(Effect.provide(layerTest("win32"))); | |
| 3293 | 3293 | }); |
| 3294 | 3294 | |
| 3295 | 3295 | it.effect("an unrelated client-setting change keeps the registered shortcut", () => { |
| ... | ... | @@ -3330,7 +3330,7 @@ it.effect("an unrelated client-setting change keeps the registered shortcut", () |
| 3330 | 3330 | ["Control+Alt+K", "Control+Alt+J"], |
| 3331 | 3331 | ); |
| 3332 | 3332 | }), |
| 3333 | ).pipe(Effect.provide(testLayer("win32"))); | |
| 3333 | ).pipe(Effect.provide(layerTest("win32"))); | |
| 3334 | 3334 | }); |
| 3335 | 3335 | |
| 3336 | 3336 | it.effect("capture fails closed while snapshots are disabled", () => { |
| ... | ... | @@ -3348,7 +3348,7 @@ it.effect("capture fails closed while snapshots are disabled", () => { |
| 3348 | 3348 | assert.lengthOf(getSourcesMock.mock.calls, 0); |
| 3349 | 3349 | assert.lengthOf(regionCaptureMock.mock.calls, 0); |
| 3350 | 3350 | }), |
| 3351 | ).pipe(Effect.provide(testLayer("win32"))); | |
| 3351 | ).pipe(Effect.provide(layerTest("win32"))); | |
| 3352 | 3352 | }); |
| 3353 | 3353 | |
| 3354 | 3354 | it.effect("keeps shortcut registration errors off the capture status", () => { |
| ... | ... | @@ -3378,7 +3378,7 @@ it.effect("keeps shortcut registration errors off the capture status", () => { |
| 3378 | 3378 | "This shortcut is already used by the system or another app.", |
| 3379 | 3379 | ); |
| 3380 | 3380 | }), |
| 3381 | ).pipe(Effect.provide(testLayer("win32"))); | |
| 3381 | ).pipe(Effect.provide(layerTest("win32"))); | |
| 3382 | 3382 | }); |
| 3383 | 3383 | |
| 3384 | 3384 | it.effect("uses an external Niri shortcut without registering an Electron accelerator", () => { |
| ... | ... | @@ -3411,7 +3411,7 @@ it.effect("uses an external Niri shortcut without registering an Electron accele |
| 3411 | 3411 | assert.isUndefined((yield* service.state).shortcutBinding); |
| 3412 | 3412 | }), |
| 3413 | 3413 | ).pipe( |
| 3414 | Effect.provide(testLayer("linux")), | |
| 3414 | Effect.provide(layerTest("linux")), | |
| 3415 | 3415 | Effect.ensuring(Effect.sync(() => vi.unstubAllEnvs())), |
| 3416 | 3416 | ); |
| 3417 | 3417 | }); |
| ... | ... | @@ -3452,7 +3452,7 @@ it.effect("defers ordinary Wayland shortcut registration until settings are appl |
| 3452 | 3452 | assert.lengthOf(registerShortcutMock.mock.calls, 0); |
| 3453 | 3453 | }), |
| 3454 | 3454 | ).pipe( |
| 3455 | Effect.provide(testLayer("linux")), | |
| 3455 | Effect.provide(layerTest("linux")), | |
| 3456 | 3456 | Effect.ensuring(Effect.sync(() => vi.unstubAllEnvs())), |
| 3457 | 3457 | ); |
| 3458 | 3458 | }); |
| ... | ... | @@ -3472,7 +3472,7 @@ it.effect.each(["GNOME", "KDE", "niri", "Hyprland"] as const)( |
| 3472 | 3472 | assert.isFalse(state.shortcutVerified); |
| 3473 | 3473 | }), |
| 3474 | 3474 | ).pipe( |
| 3475 | Effect.provide(testLayer("linux")), | |
| 3475 | Effect.provide(layerTest("linux")), | |
| 3476 | 3476 | Effect.ensuring(Effect.sync(() => vi.unstubAllEnvs())), |
| 3477 | 3477 | ); |
| 3478 | 3478 | }, |
| ... | ... | @@ -3539,7 +3539,7 @@ it.effect( |
| 3539 | 3539 | }); |
| 3540 | 3540 | assert.isTrue((yield* service.state).shortcutVerified); |
| 3541 | 3541 | }), |
| 3542 | ).pipe(Effect.provide(testLayer("linux", {}, Option.some(settings)))); | |
| 3542 | ).pipe(Effect.provide(layerTest("linux", {}, Option.some(settings)))); | |
| 3543 | 3543 | } |
| 3544 | 3544 | }).pipe( |
| 3545 | 3545 | Effect.ensuring( |
| ... | ... | @@ -3569,7 +3569,7 @@ it.effect("does not register a Wayland modifier-pair shortcut when enabled", () |
| 3569 | 3569 | assert.match(state.shortcutMessage ?? "", /Modifier-pair shortcuts aren't available/); |
| 3570 | 3570 | }), |
| 3571 | 3571 | ).pipe( |
| 3572 | Effect.provide(testLayer("linux")), | |
| 3572 | Effect.provide(layerTest("linux")), | |
| 3573 | 3573 | Effect.ensuring(Effect.sync(() => vi.unstubAllEnvs())), |
| 3574 | 3574 | ); |
| 3575 | 3575 | }); |
| ... | ... | @@ -3626,7 +3626,7 @@ it.effect("exposes pending portal permission and then the real assigned shortcut |
| 3626 | 3626 | assert.equal(failed.shortcutMessage, "Permission denied"); |
| 3627 | 3627 | }), |
| 3628 | 3628 | ).pipe( |
| 3629 | Effect.provide(testLayer("linux")), | |
| 3629 | Effect.provide(layerTest("linux")), | |
| 3630 | 3630 | Effect.ensuring(Effect.sync(() => vi.unstubAllEnvs())), |
| 3631 | 3631 | ); |
| 3632 | 3632 | }); |
| ... | ... | @@ -3683,7 +3683,7 @@ it.effect("verifies only native shortcut delivery and captures normally", () => |
| 3683 | 3683 | }), |
| 3684 | 3684 | ).pipe( |
| 3685 | 3685 | Effect.provide( |
| 3686 | testLayer("linux", { | |
| 3686 | layerTest("linux", { | |
| 3687 | 3687 | makeDirectory: () => Effect.void, |
| 3688 | 3688 | rename: () => Effect.void, |
| 3689 | 3689 | writeFile: () => Effect.void, |
| ... | ... | @@ -3743,7 +3743,7 @@ it.effect( |
| 3743 | 3743 | assert.isFalse((yield* service.state).shortcutRegistered); |
| 3744 | 3744 | }), |
| 3745 | 3745 | ).pipe( |
| 3746 | Effect.provide(testLayer("linux")), | |
| 3746 | Effect.provide(layerTest("linux")), | |
| 3747 | 3747 | Effect.ensuring(Effect.sync(() => vi.unstubAllEnvs())), |
| 3748 | 3748 | ); |
| 3749 | 3749 | }, |
| ... | ... | @@ -3781,7 +3781,7 @@ it.effect( |
| 3781 | 3781 | assert.notEqual((yield* service.state).shortcutActionRegistered, true); |
| 3782 | 3782 | }), |
| 3783 | 3783 | ).pipe( |
| 3784 | Effect.provide(testLayer("linux")), | |
| 3784 | Effect.provide(layerTest("linux")), | |
| 3785 | 3785 | Effect.ensuring(Effect.sync(() => vi.unstubAllEnvs())), |
| 3786 | 3786 | ); |
| 3787 | 3787 | }, |
| ... | ... | @@ -3796,7 +3796,7 @@ it.effect("advises about the system menu for a meta pair on Windows", () => |
| 3796 | 3796 | assert.match(result.message ?? "", /Super \+ Super is observed/); |
| 3797 | 3797 | assert.match(result.message ?? "", /system's own menu/); |
| 3798 | 3798 | }), |
| 3799 | ).pipe(Effect.provide(testLayer("win32"))), | |
| 3799 | ).pipe(Effect.provide(layerTest("win32"))), | |
| 3800 | 3800 | ); |
| 3801 | 3801 | |
| 3802 | 3802 | it.effect("probes macOS modifier pairs with the flags poller", () => { |
| ... | ... | @@ -3812,7 +3812,7 @@ it.effect("probes macOS modifier pairs with the flags poller", () => { |
| 3812 | 3812 | assert.deepEqual(spawnedPollers[0]?.args.slice(-2), ["2", "4"]); |
| 3813 | 3813 | assert.strictEqual(spawnedPollers[0]?.kill.mock.calls.length, 1); |
| 3814 | 3814 | }), |
| 3815 | ).pipe(Effect.provide(testLayer("darwin"))); | |
| 3815 | ).pipe(Effect.provide(layerTest("darwin"))); | |
| 3816 | 3816 | }); |
| 3817 | 3817 | |
| 3818 | 3818 | it.effect("registers macOS modifier pairs through the flags poller", () => { |
| ... | ... | @@ -3836,7 +3836,7 @@ it.effect("registers macOS modifier pairs through the flags poller", () => { |
| 3836 | 3836 | assert.deepEqual(spawnedPollers[0]?.args.slice(-2), ["8", "16"]); |
| 3837 | 3837 | assert.isTrue(state.shortcutRegistered); |
| 3838 | 3838 | }), |
| 3839 | ).pipe(Effect.provide(testLayer("darwin"))); | |
| 3839 | ).pipe(Effect.provide(layerTest("darwin"))); | |
| 3840 | 3840 | }); |
| 3841 | 3841 | |
| 3842 | 3842 | it.effect("waits to apply settings while permissions are pending", () => { |
| ... | ... | @@ -3849,7 +3849,7 @@ it.effect("waits to apply settings while permissions are pending", () => { |
| 3849 | 3849 | finishPermissionRequest = () => resolve([]); |
| 3850 | 3850 | }), |
| 3851 | 3851 | ); |
| 3852 | const layer = testLayer("darwin"); | |
| 3852 | const layer = layerTest("darwin"); | |
| 3853 | 3853 | |
| 3854 | 3854 | return Effect.scoped( |
| 3855 | 3855 | Effect.gen(function* () { |
| ... | ... | @@ -3905,7 +3905,7 @@ it.effect.each([false, true])( |
| 3905 | 3905 | }), |
| 3906 | 3906 | ).pipe( |
| 3907 | 3907 | Effect.provide( |
| 3908 | testLayer("darwin", { | |
| 3908 | layerTest("darwin", { | |
| 3909 | 3909 | makeTempDirectoryScoped: () => |
| 3910 | 3910 | Effect.acquireRelease(Effect.succeed("/tmp/setup-test"), () => |
| 3911 | 3911 | Effect.sync(() => { |
| ... | ... | @@ -3926,5 +3926,5 @@ it.effect("rejects macOS test capture on other platforms", () => |
| 3926 | 3926 | const error = yield* service.setup("test-mac-capture").pipe(Effect.flip); |
| 3927 | 3927 | assert.equal(error.reason, "unsupported-session"); |
| 3928 | 3928 | }), |
| 3929 | ).pipe(Effect.provide(testLayer("win32"))), | |
| 3929 | ).pipe(Effect.provide(layerTest("win32"))), | |
| 3930 | 3930 | ); |
apps/desktop/src/ssh/DesktopSshPasswordPrompts.test.ts+8-8| ... | ... | @@ -86,7 +86,7 @@ function makeTestWindow( |
| 86 | 86 | }; |
| 87 | 87 | } |
| 88 | 88 | |
| 89 | function makeElectronWindowLayer(window: ReturnType<typeof makeTestWindow>["window"]) { | |
| 89 | function layerElectronWindow(window: ReturnType<typeof makeTestWindow>["window"]) { | |
| 90 | 90 | return Layer.succeed( |
| 91 | 91 | ElectronWindow.ElectronWindow, |
| 92 | 92 | ElectronWindow.ElectronWindow.of({ |
| ... | ... | @@ -105,9 +105,9 @@ function makeElectronWindowLayer(window: ReturnType<typeof makeTestWindow>["wind |
| 105 | 105 | ); |
| 106 | 106 | } |
| 107 | 107 | |
| 108 | function makeLayer(window: ReturnType<typeof makeTestWindow>["window"]) { | |
| 108 | function layer(window: ReturnType<typeof makeTestWindow>["window"]) { | |
| 109 | 109 | return DesktopSshPasswordPrompts.layer({ passwordPromptTimeoutMs: 1_000 }).pipe( |
| 110 | Layer.provide(makeElectronWindowLayer(window)), | |
| 110 | Layer.provide(layerElectronWindow(window)), | |
| 111 | 111 | Layer.provide(NodeServices.layer), |
| 112 | 112 | Layer.provideMerge(TestClock.layer()), |
| 113 | 113 | ); |
| ... | ... | @@ -141,7 +141,7 @@ describe("DesktopSshPasswordPrompts", () => { |
| 141 | 141 | |
| 142 | 142 | yield* prompts.resolve({ requestId: request.requestId, password: "secret" }); |
| 143 | 143 | assert.equal(yield* Fiber.join(fiber), "secret"); |
| 144 | }).pipe(Effect.provide(makeLayer(testWindow.window)), Effect.scoped); | |
| 144 | }).pipe(Effect.provide(layer(testWindow.window)), Effect.scoped); | |
| 145 | 145 | }); |
| 146 | 146 | |
| 147 | 147 | it.effect("times out pending renderer prompts with a typed error", () => { |
| ... | ... | @@ -163,7 +163,7 @@ describe("DesktopSshPasswordPrompts", () => { |
| 163 | 163 | const error = yield* Fiber.join(fiber).pipe(Effect.flip); |
| 164 | 164 | assert.instanceOf(error, DesktopSshPasswordPrompts.DesktopSshPromptTimedOutError); |
| 165 | 165 | assert.equal(error.destination, "devbox"); |
| 166 | }).pipe(Effect.provide(makeLayer(testWindow.window)), Effect.scoped); | |
| 166 | }).pipe(Effect.provide(layer(testWindow.window)), Effect.scoped); | |
| 167 | 167 | }); |
| 168 | 168 | |
| 169 | 169 | it.effect("cleans up a prompt that fails during renderer delivery", () => { |
| ... | ... | @@ -194,7 +194,7 @@ describe("DesktopSshPasswordPrompts", () => { |
| 194 | 194 | .resolve({ requestId, password: "secret" }) |
| 195 | 195 | .pipe(Effect.flip); |
| 196 | 196 | assert.instanceOf(resolveError, DesktopSshPasswordPrompts.DesktopSshPromptExpiredError); |
| 197 | }).pipe(Effect.provide(makeLayer(testWindow.window)), Effect.scoped); | |
| 197 | }).pipe(Effect.provide(layer(testWindow.window)), Effect.scoped); | |
| 198 | 198 | }); |
| 199 | 199 | |
| 200 | 200 | it.effect("keeps a submitted password when a later presentation step fails", () => { |
| ... | ... | @@ -223,7 +223,7 @@ describe("DesktopSshPasswordPrompts", () => { |
| 223 | 223 | assert.equal(password, "secret"); |
| 224 | 224 | assert.equal(testWindow.isFocused(), false); |
| 225 | 225 | assert.equal(testWindow.closedListenerCount(), 0); |
| 226 | }).pipe(Effect.provide(makeLayer(testWindow.window)), Effect.scoped); | |
| 226 | }).pipe(Effect.provide(layer(testWindow.window)), Effect.scoped); | |
| 227 | 227 | }); |
| 228 | 228 | |
| 229 | 229 | it.effect("classifies a failed initial window availability check", () => { |
| ... | ... | @@ -244,6 +244,6 @@ describe("DesktopSshPasswordPrompts", () => { |
| 244 | 244 | assert.equal(error.operation, "check-window-before-request"); |
| 245 | 245 | assert.equal(error.requestId, null); |
| 246 | 246 | assert.deepEqual(testWindow.sentMessages, []); |
| 247 | }).pipe(Effect.provide(makeLayer(testWindow.window)), Effect.scoped); | |
| 247 | }).pipe(Effect.provide(layer(testWindow.window)), Effect.scoped); | |
| 248 | 248 | }); |
| 249 | 249 | }); |
apps/desktop/src/telemetry/DesktopTelemetryPublisher.test.ts+13-13| ... | ... | @@ -24,13 +24,13 @@ import * as ElectronPowerMonitor from "../electron/ElectronPowerMonitor.ts"; |
| 24 | 24 | import * as DesktopTelemetryPublisher from "./DesktopTelemetryPublisher.ts"; |
| 25 | 25 | import * as DesktopRendererHistory from "./DesktopRendererHistory.ts"; |
| 26 | 26 | |
| 27 | const historyLayer = Layer.succeed(DesktopRendererHistory.DesktopRendererHistory, { | |
| 27 | const layerHistory = Layer.succeed(DesktopRendererHistory.DesktopRendererHistory, { | |
| 28 | 28 | register: () => Effect.void, |
| 29 | 29 | recordMetrics: () => Effect.void, |
| 30 | 30 | shutdown: Effect.void, |
| 31 | 31 | }); |
| 32 | 32 | |
| 33 | function makeElectronAppLayer( | |
| 33 | function layerElectronApp( | |
| 34 | 34 | metrics: ReadonlyArray<Electron.ProcessMetric>, |
| 35 | 35 | onMetricsRead: () => void = () => undefined, |
| 36 | 36 | ) { |
| ... | ... | @@ -65,7 +65,7 @@ describe("DesktopTelemetryPublisher", () => { |
| 65 | 65 | Effect.gen(function* () { |
| 66 | 66 | const pollStarted = yield* Deferred.make<void>(); |
| 67 | 67 | const blockPoll = yield* Deferred.make<void>(); |
| 68 | const powerLayer = Layer.succeed( | |
| 68 | const layerPower = Layer.succeed( | |
| 69 | 69 | ElectronPowerMonitor.ElectronPowerMonitor, |
| 70 | 70 | ElectronPowerMonitor.ElectronPowerMonitor.of({ |
| 71 | 71 | isOnBatteryPower: Effect.succeed(false), |
| ... | ... | @@ -81,7 +81,7 @@ describe("DesktopTelemetryPublisher", () => { |
| 81 | 81 | }), |
| 82 | 82 | ); |
| 83 | 83 | const layer = DesktopTelemetryPublisher.layer.pipe( |
| 84 | Layer.provide(Layer.mergeAll(makeElectronAppLayer([]), powerLayer, historyLayer)), | |
| 84 | Layer.provide(Layer.mergeAll(layerElectronApp([]), layerPower, layerHistory)), | |
| 85 | 85 | ); |
| 86 | 86 | const scope = yield* Scope.make(); |
| 87 | 87 | |
| ... | ... | @@ -124,7 +124,7 @@ describe("DesktopTelemetryPublisher", () => { |
| 124 | 124 | }, |
| 125 | 125 | } as Electron.ProcessMetric, |
| 126 | 126 | ]; |
| 127 | const powerLayer = Layer.succeed( | |
| 127 | const layerPower = Layer.succeed( | |
| 128 | 128 | ElectronPowerMonitor.ElectronPowerMonitor, |
| 129 | 129 | ElectronPowerMonitor.ElectronPowerMonitor.of({ |
| 130 | 130 | isOnBatteryPower: Ref.get(onBattery), |
| ... | ... | @@ -149,10 +149,10 @@ describe("DesktopTelemetryPublisher", () => { |
| 149 | 149 | const layer = DesktopTelemetryPublisher.layer.pipe( |
| 150 | 150 | Layer.provide( |
| 151 | 151 | Layer.mergeAll( |
| 152 | makeElectronAppLayer(metrics, () => { | |
| 152 | layerElectronApp(metrics, () => { | |
| 153 | 153 | metricsReadCount += 1; |
| 154 | 154 | }), |
| 155 | powerLayer, | |
| 155 | layerPower, | |
| 156 | 156 | Layer.succeed(DesktopRendererHistory.DesktopRendererHistory, { |
| 157 | 157 | register: () => Effect.void, |
| 158 | 158 | shutdown: Effect.void, |
| ... | ... | @@ -413,7 +413,7 @@ describe("DesktopTelemetryPublisher", () => { |
| 413 | 413 | |
| 414 | 414 | it.effect("routes requestDesktopUpdate control messages and replays update reports", () => |
| 415 | 415 | Effect.gen(function* () { |
| 416 | const powerLayer = Layer.succeed( | |
| 416 | const layerPower = Layer.succeed( | |
| 417 | 417 | ElectronPowerMonitor.ElectronPowerMonitor, |
| 418 | 418 | ElectronPowerMonitor.ElectronPowerMonitor.of({ |
| 419 | 419 | isOnBatteryPower: Effect.succeed(false), |
| ... | ... | @@ -426,7 +426,7 @@ describe("DesktopTelemetryPublisher", () => { |
| 426 | 426 | }), |
| 427 | 427 | ); |
| 428 | 428 | const layer = DesktopTelemetryPublisher.layer.pipe( |
| 429 | Layer.provide(Layer.mergeAll(makeElectronAppLayer([]), powerLayer, historyLayer)), | |
| 429 | Layer.provide(Layer.mergeAll(layerElectronApp([]), layerPower, layerHistory)), | |
| 430 | 430 | ); |
| 431 | 431 | |
| 432 | 432 | yield* Effect.gen(function* () { |
| ... | ... | @@ -562,7 +562,7 @@ describe("DesktopRendererHistory", () => { |
| 562 | 562 | readonly written: Deferred.Deferred<Record>; |
| 563 | 563 | } |
| 564 | 564 | | undefined; |
| 565 | const fileLayer = Layer.succeed(FileSystem.FileSystem, { | |
| 565 | const layerFile = Layer.succeed(FileSystem.FileSystem, { | |
| 566 | 566 | ...fileSystem, |
| 567 | 567 | writeFile: (filePath, bytes, options) => |
| 568 | 568 | fileSystem.writeFile(filePath, bytes, options).pipe( |
| ... | ... | @@ -590,7 +590,7 @@ describe("DesktopRendererHistory", () => { |
| 590 | 590 | logDir: directory, |
| 591 | 591 | } as DesktopEnvironment.DesktopEnvironment["Service"]), |
| 592 | 592 | ), |
| 593 | fileLayer, | |
| 593 | layerFile, | |
| 594 | 594 | ), |
| 595 | 595 | ), |
| 596 | 596 | ); |
| ... | ... | @@ -735,7 +735,7 @@ describe("DesktopRendererHistory", () => { |
| 735 | 735 | ); |
| 736 | 736 | } |
| 737 | 737 | const written = yield* Deferred.make<void>(); |
| 738 | const fileLayer = Layer.succeed(FileSystem.FileSystem, { | |
| 738 | const layerFile = Layer.succeed(FileSystem.FileSystem, { | |
| 739 | 739 | ...fileSystem, |
| 740 | 740 | writeFile: (target, bytes, options) => |
| 741 | 741 | fileSystem |
| ... | ... | @@ -751,7 +751,7 @@ describe("DesktopRendererHistory", () => { |
| 751 | 751 | logDir: directory, |
| 752 | 752 | } as DesktopEnvironment.DesktopEnvironment["Service"]), |
| 753 | 753 | ), |
| 754 | fileLayer, | |
| 754 | layerFile, | |
| 755 | 755 | ), |
| 756 | 756 | ), |
| 757 | 757 | ); |
apps/desktop/src/updates/updatesTestHarness.ts+12-12| ... | ... | @@ -67,7 +67,7 @@ export function makeHarness(options: UpdatesHarnessOptions = {}) { |
| 67 | 67 | } |
| 68 | 68 | }; |
| 69 | 69 | |
| 70 | const updaterLayer = Layer.succeed(ElectronUpdater.ElectronUpdater, { | |
| 70 | const layerUpdater = Layer.succeed(ElectronUpdater.ElectronUpdater, { | |
| 71 | 71 | setFeedURL: (options) => |
| 72 | 72 | Effect.sync(() => { |
| 73 | 73 | feedUrls.push(options); |
| ... | ... | @@ -109,7 +109,7 @@ export function makeHarness(options: UpdatesHarnessOptions = {}) { |
| 109 | 109 | ).pipe(Effect.asVoid), |
| 110 | 110 | } satisfies ElectronUpdater.ElectronUpdater["Service"]); |
| 111 | 111 | |
| 112 | const windowLayer = Layer.succeed(ElectronWindow.ElectronWindow, { | |
| 112 | const layerWindow = Layer.succeed(ElectronWindow.ElectronWindow, { | |
| 113 | 113 | create: () => Effect.die("unexpected BrowserWindow creation"), |
| 114 | 114 | main: Effect.succeedNone, |
| 115 | 115 | currentMainOrFirst: Effect.succeedNone, |
| ... | ... | @@ -145,9 +145,9 @@ export function makeHarness(options: UpdatesHarnessOptions = {}) { |
| 145 | 145 | }), |
| 146 | 146 | waitForReady: () => Effect.succeed(true), |
| 147 | 147 | }; |
| 148 | const backendLayer = DesktopBackendPool.layerTest([stubBackendInstance]); | |
| 148 | const layerBackend = DesktopBackendPool.layerTest([stubBackendInstance]); | |
| 149 | 149 | |
| 150 | const environmentLayer = DesktopEnvironment.layer({ | |
| 150 | const layerEnvironment = DesktopEnvironment.layer({ | |
| 151 | 151 | dirname: "/repo/apps/desktop/src", |
| 152 | 152 | homeDirectory: `/tmp/t3-desktop-updates-home-${process.pid}`, |
| 153 | 153 | platform: options.platform ?? "darwin", |
| ... | ... | @@ -175,7 +175,7 @@ export function makeHarness(options: UpdatesHarnessOptions = {}) { |
| 175 | 175 | ...DesktopAppSettings.DEFAULT_DESKTOP_SETTINGS, |
| 176 | 176 | }; |
| 177 | 177 | const setUpdateChannelError = options.setUpdateChannelError; |
| 178 | const settingsLayer = | |
| 178 | const layerSettings = | |
| 179 | 179 | setUpdateChannelError || options.beforeSetUpdateChannel |
| 180 | 180 | ? Layer.succeed(DesktopAppSettings.DesktopAppSettings, { |
| 181 | 181 | get: Effect.sync(() => testSettings), |
| ... | ... | @@ -211,7 +211,7 @@ export function makeHarness(options: UpdatesHarnessOptions = {}) { |
| 211 | 211 | // Tracks the restart markers installs leave, so installs stay free of real |
| 212 | 212 | // disk I/O that would outrun the tests' settle loops. |
| 213 | 213 | const updateRestartMarkers = new Set<string>(); |
| 214 | const fileSystemLayer = FileSystem.layerNoop({ | |
| 214 | const layerFileSystem = FileSystem.layerNoop({ | |
| 215 | 215 | readFileString: (path) => |
| 216 | 216 | path === "/missing/resources/package-type" && options.packageType !== undefined |
| 217 | 217 | ? Effect.succeed(options.packageType) |
| ... | ... | @@ -235,12 +235,12 @@ export function makeHarness(options: UpdatesHarnessOptions = {}) { |
| 235 | 235 | }); |
| 236 | 236 | |
| 237 | 237 | const layer = DesktopUpdates.layer.pipe( |
| 238 | Layer.provide(fileSystemLayer), | |
| 239 | Layer.provideMerge(updaterLayer), | |
| 240 | Layer.provideMerge(windowLayer), | |
| 241 | Layer.provideMerge(backendLayer), | |
| 238 | Layer.provide(layerFileSystem), | |
| 239 | Layer.provideMerge(layerUpdater), | |
| 240 | Layer.provideMerge(layerWindow), | |
| 241 | Layer.provideMerge(layerBackend), | |
| 242 | 242 | Layer.provideMerge(DesktopState.layer), |
| 243 | Layer.provideMerge(settingsLayer), | |
| 243 | Layer.provideMerge(layerSettings), | |
| 244 | 244 | Layer.provideMerge( |
| 245 | 245 | DesktopConfig.layerTest({ |
| 246 | 246 | T3CODE_HOME: `/tmp/t3-desktop-updates-test-${process.pid}`, |
| ... | ... | @@ -249,7 +249,7 @@ export function makeHarness(options: UpdatesHarnessOptions = {}) { |
| 249 | 249 | ...options.env, |
| 250 | 250 | }), |
| 251 | 251 | ), |
| 252 | Layer.provideMerge(environmentLayer), | |
| 252 | Layer.provideMerge(layerEnvironment), | |
| 253 | 253 | Layer.provideMerge(NodeServices.layer), |
| 254 | 254 | ); |
| 255 | 255 |
apps/desktop/src/window/DesktopApplicationMenu.test.ts+10-10| ... | ... | @@ -27,7 +27,7 @@ const environmentInput = { |
| 27 | 27 | runningUnderArm64Translation: false, |
| 28 | 28 | } satisfies DesktopEnvironment.MakeDesktopEnvironmentInput; |
| 29 | 29 | |
| 30 | const electronAppLayer = Layer.succeed(ElectronApp.ElectronApp, { | |
| 30 | const layerElectronApp = Layer.succeed(ElectronApp.ElectronApp, { | |
| 31 | 31 | metadata: Effect.die("unexpected metadata read"), |
| 32 | 32 | name: Effect.succeed("T3 Code"), |
| 33 | 33 | systemLocale: Effect.succeed("en-US"), |
| ... | ... | @@ -49,14 +49,14 @@ const electronAppLayer = Layer.succeed(ElectronApp.ElectronApp, { |
| 49 | 49 | on: () => Effect.void, |
| 50 | 50 | } satisfies ElectronApp.ElectronApp["Service"]); |
| 51 | 51 | |
| 52 | const electronDialogLayer = Layer.succeed(ElectronDialog.ElectronDialog, { | |
| 52 | const layerElectronDialog = Layer.succeed(ElectronDialog.ElectronDialog, { | |
| 53 | 53 | pickFolder: () => Effect.succeedNone, |
| 54 | 54 | pickFiles: () => Effect.succeed([]), |
| 55 | 55 | showMessageBox: () => Effect.succeed({ response: 0, checkboxChecked: false }), |
| 56 | 56 | showErrorBox: () => Effect.void, |
| 57 | 57 | } satisfies ElectronDialog.ElectronDialog["Service"]); |
| 58 | 58 | |
| 59 | const desktopUpdatesLayer = Layer.succeed(DesktopUpdates.DesktopUpdates, { | |
| 59 | const layerDesktopUpdates = Layer.succeed(DesktopUpdates.DesktopUpdates, { | |
| 60 | 60 | getState: Effect.die("unexpected getState"), |
| 61 | 61 | isActionActive: Effect.succeed(false), |
| 62 | 62 | isInstallActive: Effect.succeed(false), |
| ... | ... | @@ -71,7 +71,7 @@ const desktopUpdatesLayer = Layer.succeed(DesktopUpdates.DesktopUpdates, { |
| 71 | 71 | installPrepared: () => Effect.die("unexpected installPrepared"), |
| 72 | 72 | } satisfies DesktopUpdates.DesktopUpdates["Service"]); |
| 73 | 73 | |
| 74 | const makeDesktopWindowLayer = (selectedAction: Deferred.Deferred<string>) => | |
| 74 | const layerDesktopWindow = (selectedAction: Deferred.Deferred<string>) => | |
| 75 | 75 | Layer.succeed(DesktopWindow.DesktopWindow, { |
| 76 | 76 | createMain: Effect.die("unexpected createMain"), |
| 77 | 77 | ensureMain: Effect.die("unexpected ensureMain"), |
| ... | ... | @@ -90,7 +90,7 @@ const makeDesktopWindowLayer = (selectedAction: Deferred.Deferred<string>) => |
| 90 | 90 | syncAppearance: Effect.void, |
| 91 | 91 | } satisfies DesktopWindow.DesktopWindow["Service"]); |
| 92 | 92 | |
| 93 | const makeElectronMenuLayer = ( | |
| 93 | const layerElectronMenu = ( | |
| 94 | 94 | applicationMenuTemplate: Deferred.Deferred<readonly Electron.MenuItemConstructorOptions[]>, |
| 95 | 95 | ) => |
| 96 | 96 | Layer.succeed(ElectronMenu.ElectronMenu, { |
| ... | ... | @@ -110,11 +110,11 @@ const configureMenu = ( |
| 110 | 110 | }).pipe( |
| 111 | 111 | Effect.provide( |
| 112 | 112 | DesktopApplicationMenu.layer.pipe( |
| 113 | Layer.provideMerge(makeElectronMenuLayer(applicationMenuTemplate)), | |
| 114 | Layer.provideMerge(makeDesktopWindowLayer(selectedAction)), | |
| 115 | Layer.provideMerge(desktopUpdatesLayer), | |
| 116 | Layer.provideMerge(electronDialogLayer), | |
| 117 | Layer.provideMerge(electronAppLayer), | |
| 113 | Layer.provideMerge(layerElectronMenu(applicationMenuTemplate)), | |
| 114 | Layer.provideMerge(layerDesktopWindow(selectedAction)), | |
| 115 | Layer.provideMerge(layerDesktopUpdates), | |
| 116 | Layer.provideMerge(layerElectronDialog), | |
| 117 | Layer.provideMerge(layerElectronApp), | |
| 118 | 118 | Layer.provideMerge( |
| 119 | 119 | DesktopEnvironment.layer(environmentInput).pipe( |
| 120 | 120 | Layer.provide(Layer.mergeAll(NodeServices.layer, DesktopConfig.layerTest({}))), |
apps/desktop/src/window/DesktopWindow.test.ts+51-51| ... | ... | @@ -151,15 +151,15 @@ function makeFakeBrowserWindow() { |
| 151 | 151 | }; |
| 152 | 152 | } |
| 153 | 153 | |
| 154 | const desktopClientSettingsLayer = Layer.mock(DesktopClientSettings.DesktopClientSettings)({ | |
| 154 | const layerDesktopClientSettings = Layer.mock(DesktopClientSettings.DesktopClientSettings)({ | |
| 155 | 155 | get: Effect.succeedNone, |
| 156 | 156 | }); |
| 157 | 157 | |
| 158 | const electronAppLayer = Layer.mock(ElectronApp.ElectronApp)({ | |
| 158 | const layerElectronApp = Layer.mock(ElectronApp.ElectronApp)({ | |
| 159 | 159 | quit: Effect.void, |
| 160 | 160 | }); |
| 161 | 161 | |
| 162 | const desktopAssetsLayer = Layer.succeed(DesktopAssets.DesktopAssets, { | |
| 162 | const layerDesktopAssets = Layer.succeed(DesktopAssets.DesktopAssets, { | |
| 163 | 163 | iconPaths: Effect.succeed({ |
| 164 | 164 | ico: Option.none<string>(), |
| 165 | 165 | icns: Option.none<string>(), |
| ... | ... | @@ -168,7 +168,7 @@ const desktopAssetsLayer = Layer.succeed(DesktopAssets.DesktopAssets, { |
| 168 | 168 | resolveResourcePath: () => Effect.succeed(Option.none<string>()), |
| 169 | 169 | } satisfies DesktopAssets.DesktopAssets["Service"]); |
| 170 | 170 | |
| 171 | const desktopServerExposureLayer = Layer.succeed(DesktopServerExposure.DesktopServerExposure, { | |
| 171 | const layerDesktopServerExposure = Layer.succeed(DesktopServerExposure.DesktopServerExposure, { | |
| 172 | 172 | getState: Effect.die("unexpected getState"), |
| 173 | 173 | backendConfig: Effect.succeed({ |
| 174 | 174 | port: 3773, |
| ... | ... | @@ -183,19 +183,19 @@ const desktopServerExposureLayer = Layer.succeed(DesktopServerExposure.DesktopSe |
| 183 | 183 | getAdvertisedEndpoints: Effect.die("unexpected getAdvertisedEndpoints"), |
| 184 | 184 | } satisfies DesktopServerExposure.DesktopServerExposure["Service"]); |
| 185 | 185 | |
| 186 | const electronMenuLayer = Layer.succeed(ElectronMenu.ElectronMenu, { | |
| 186 | const layerElectronMenu = Layer.succeed(ElectronMenu.ElectronMenu, { | |
| 187 | 187 | setApplicationMenu: () => Effect.void, |
| 188 | 188 | popupTemplate: () => Effect.void, |
| 189 | 189 | showContextMenu: () => Effect.succeedNone, |
| 190 | 190 | } satisfies ElectronMenu.ElectronMenu["Service"]); |
| 191 | 191 | |
| 192 | const electronThemeLayer = Layer.succeed(ElectronTheme.ElectronTheme, { | |
| 192 | const layerElectronTheme = Layer.succeed(ElectronTheme.ElectronTheme, { | |
| 193 | 193 | shouldUseDarkColors: Effect.succeed(false), |
| 194 | 194 | setSource: () => Effect.void, |
| 195 | 195 | onUpdated: () => Effect.void, |
| 196 | 196 | } satisfies ElectronTheme.ElectronTheme["Service"]); |
| 197 | 197 | |
| 198 | const desktopEnvironmentLayer = DesktopEnvironment.layer(environmentInput).pipe( | |
| 198 | const layerDesktopEnvironment = DesktopEnvironment.layer(environmentInput).pipe( | |
| 199 | 199 | Layer.provide( |
| 200 | 200 | Layer.mergeAll( |
| 201 | 201 | NodeServices.layer, |
| ... | ... | @@ -211,7 +211,7 @@ const desktopWindowBoundsEquivalence = Schema.toEquivalence( |
| 211 | 211 | DesktopAppSettings.DesktopWindowBoundsSchema, |
| 212 | 212 | ); |
| 213 | 213 | |
| 214 | function makeTestLayer(input: { | |
| 214 | function layerTest(input: { | |
| 215 | 215 | readonly window: Electron.BrowserWindow; |
| 216 | 216 | readonly createCount: Ref.Ref<number>; |
| 217 | 217 | readonly mainWindow: Ref.Ref<Option.Option<Electron.BrowserWindow>>; |
| ... | ... | @@ -229,7 +229,7 @@ function makeTestLayer(input: { |
| 229 | 229 | readonly onReveal?: (window: Electron.BrowserWindow) => void; |
| 230 | 230 | }) { |
| 231 | 231 | let desktopSettings = input.desktopSettings ?? DesktopAppSettings.DEFAULT_DESKTOP_SETTINGS; |
| 232 | const desktopAppSettingsLayer = Layer.succeed(DesktopAppSettings.DesktopAppSettings, { | |
| 232 | const layerDesktopAppSettings = Layer.succeed(DesktopAppSettings.DesktopAppSettings, { | |
| 233 | 233 | get: Effect.sync(() => desktopSettings), |
| 234 | 234 | load: Effect.sync(() => desktopSettings), |
| 235 | 235 | setMainWindowBounds: (bounds, isMaximized) => |
| ... | ... | @@ -263,7 +263,7 @@ function makeTestLayer(input: { |
| 263 | 263 | applyWslWindowsFallbackInMemory: Effect.die("unexpected WSL Windows fallback"), |
| 264 | 264 | } satisfies DesktopAppSettings.DesktopAppSettings["Service"]); |
| 265 | 265 | |
| 266 | const electronWindowLayer = Layer.succeed(ElectronWindow.ElectronWindow, { | |
| 266 | const layerElectronWindow = Layer.succeed(ElectronWindow.ElectronWindow, { | |
| 267 | 267 | create: (options) => |
| 268 | 268 | Effect.sync(() => { |
| 269 | 269 | input.createdWindowOptions?.push(options); |
| ... | ... | @@ -286,18 +286,18 @@ function makeTestLayer(input: { |
| 286 | 286 | return DesktopWindow.layer.pipe( |
| 287 | 287 | Layer.provide( |
| 288 | 288 | Layer.mergeAll( |
| 289 | desktopAssetsLayer, | |
| 289 | layerDesktopAssets, | |
| 290 | 290 | Layer.succeed(DesktopRendererHistory.DesktopRendererHistory, { |
| 291 | 291 | register: () => Effect.void, |
| 292 | 292 | recordMetrics: () => Effect.void, |
| 293 | 293 | shutdown: Effect.void, |
| 294 | 294 | }), |
| 295 | desktopEnvironmentLayer, | |
| 296 | desktopAppSettingsLayer, | |
| 297 | desktopClientSettingsLayer, | |
| 298 | desktopServerExposureLayer, | |
| 295 | layerDesktopEnvironment, | |
| 296 | layerDesktopAppSettings, | |
| 297 | layerDesktopClientSettings, | |
| 298 | layerDesktopServerExposure, | |
| 299 | 299 | DesktopState.layer, |
| 300 | electronAppLayer, | |
| 300 | layerElectronApp, | |
| 301 | 301 | Layer.succeed(ElectronMenu.ElectronMenu, { |
| 302 | 302 | setApplicationMenu: () => Effect.void, |
| 303 | 303 | showContextMenu: () => Effect.succeedNone, |
| ... | ... | @@ -315,8 +315,8 @@ function makeTestLayer(input: { |
| 315 | 315 | input.copiedTexts?.push(text); |
| 316 | 316 | }), |
| 317 | 317 | } satisfies ElectronShell.ElectronShell["Service"]), |
| 318 | electronThemeLayer, | |
| 319 | electronWindowLayer, | |
| 318 | layerElectronTheme, | |
| 319 | layerElectronWindow, | |
| 320 | 320 | Layer.mock(PreviewManager.PreviewManager)({ |
| 321 | 321 | getBrowserSession: () => Effect.succeed({} as Electron.Session), |
| 322 | 322 | setMainWindow: () => Effect.void, |
| ... | ... | @@ -406,24 +406,24 @@ const makeSplashScenario = (createOutcomes: readonly (Electron.BrowserWindow | n |
| 406 | 406 | const layer = DesktopWindow.layer.pipe( |
| 407 | 407 | Layer.provide( |
| 408 | 408 | Layer.mergeAll( |
| 409 | desktopAssetsLayer, | |
| 409 | layerDesktopAssets, | |
| 410 | 410 | Layer.succeed(DesktopRendererHistory.DesktopRendererHistory, { |
| 411 | 411 | register: () => Effect.void, |
| 412 | 412 | recordMetrics: () => Effect.void, |
| 413 | 413 | shutdown: Effect.void, |
| 414 | 414 | }), |
| 415 | desktopEnvironmentLayer, | |
| 415 | layerDesktopEnvironment, | |
| 416 | 416 | DesktopAppSettings.layerTest(), |
| 417 | desktopClientSettingsLayer, | |
| 418 | desktopServerExposureLayer, | |
| 419 | electronAppLayer, | |
| 420 | electronMenuLayer, | |
| 417 | layerDesktopClientSettings, | |
| 418 | layerDesktopServerExposure, | |
| 419 | layerElectronApp, | |
| 420 | layerElectronMenu, | |
| 421 | 421 | Layer.succeed(ElectronShell.ElectronShell, { |
| 422 | 422 | openExternal: () => Effect.succeed(true), |
| 423 | 423 | openSystemSettings: () => Effect.succeed(true), |
| 424 | 424 | copyText: () => Effect.void, |
| 425 | 425 | } satisfies ElectronShell.ElectronShell["Service"]), |
| 426 | electronThemeLayer, | |
| 426 | layerElectronTheme, | |
| 427 | 427 | Layer.succeed(ElectronWindow.ElectronWindow, electronWindowShape), |
| 428 | 428 | Layer.mock(PreviewManager.PreviewManager)({ |
| 429 | 429 | getBrowserSession: () => Effect.succeed({} as Electron.Session), |
| ... | ... | @@ -466,7 +466,7 @@ describe("DesktopWindow", () => { |
| 466 | 466 | focusedContents: unknown; |
| 467 | 467 | }>(); |
| 468 | 468 | const copiedTexts: string[] = []; |
| 469 | const layer = makeTestLayer({ | |
| 469 | const layer = layerTest({ | |
| 470 | 470 | window: host.window, |
| 471 | 471 | createCount: yield* Ref.make(0), |
| 472 | 472 | mainWindow: yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none()), |
| ... | ... | @@ -619,7 +619,7 @@ describe("DesktopWindow", () => { |
| 619 | 619 | const createCount = yield* Ref.make(0); |
| 620 | 620 | const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none()); |
| 621 | 621 | const createdWindowOptions: Electron.BrowserWindowConstructorOptions[] = []; |
| 622 | const layer = makeTestLayer({ | |
| 622 | const layer = layerTest({ | |
| 623 | 623 | window: fakeWindow.window, |
| 624 | 624 | createCount, |
| 625 | 625 | mainWindow, |
| ... | ... | @@ -653,7 +653,7 @@ describe("DesktopWindow", () => { |
| 653 | 653 | const fakeWindow = makeFakeBrowserWindow(); |
| 654 | 654 | const createCount = yield* Ref.make(0); |
| 655 | 655 | const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none()); |
| 656 | const layer = makeTestLayer({ | |
| 656 | const layer = layerTest({ | |
| 657 | 657 | window: fakeWindow.window, |
| 658 | 658 | createCount, |
| 659 | 659 | mainWindow, |
| ... | ... | @@ -682,7 +682,7 @@ describe("DesktopWindow", () => { |
| 682 | 682 | const fakeWindow = makeFakeBrowserWindow(); |
| 683 | 683 | const createCount = yield* Ref.make(0); |
| 684 | 684 | const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none()); |
| 685 | const layer = makeTestLayer({ | |
| 685 | const layer = layerTest({ | |
| 686 | 686 | window: fakeWindow.window, |
| 687 | 687 | createCount, |
| 688 | 688 | mainWindow, |
| ... | ... | @@ -728,7 +728,7 @@ describe("DesktopWindow", () => { |
| 728 | 728 | const send = vi.spyOn(fakeWindow.window.webContents, "send"); |
| 729 | 729 | const createCount = yield* Ref.make(0); |
| 730 | 730 | const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none()); |
| 731 | const layer = makeTestLayer({ window: fakeWindow.window, createCount, mainWindow }); | |
| 731 | const layer = layerTest({ window: fakeWindow.window, createCount, mainWindow }); | |
| 732 | 732 | |
| 733 | 733 | yield* Effect.gen(function* () { |
| 734 | 734 | const desktopWindow = yield* DesktopWindow.DesktopWindow; |
| ... | ... | @@ -755,7 +755,7 @@ describe("DesktopWindow", () => { |
| 755 | 755 | const createCount = yield* Ref.make(0); |
| 756 | 756 | const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none()); |
| 757 | 757 | const previewZoomReapplies: number[] = []; |
| 758 | const layer = makeTestLayer({ | |
| 758 | const layer = layerTest({ | |
| 759 | 759 | window: fakeWindow.window, |
| 760 | 760 | createCount, |
| 761 | 761 | mainWindow, |
| ... | ... | @@ -787,7 +787,7 @@ describe("DesktopWindow", () => { |
| 787 | 787 | const fakeWindow = makeFakeBrowserWindow(); |
| 788 | 788 | const createCount = yield* Ref.make(0); |
| 789 | 789 | const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none()); |
| 790 | const layer = makeTestLayer({ window: fakeWindow.window, createCount, mainWindow }); | |
| 790 | const layer = layerTest({ window: fakeWindow.window, createCount, mainWindow }); | |
| 791 | 791 | |
| 792 | 792 | yield* Effect.gen(function* () { |
| 793 | 793 | const desktopWindow = yield* DesktopWindow.DesktopWindow; |
| ... | ... | @@ -821,7 +821,7 @@ describe("DesktopWindow", () => { |
| 821 | 821 | const createCount = yield* Ref.make(0); |
| 822 | 822 | const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none()); |
| 823 | 823 | const createdWindowOptions: Electron.BrowserWindowConstructorOptions[] = []; |
| 824 | const layer = makeTestLayer({ | |
| 824 | const layer = layerTest({ | |
| 825 | 825 | window: fakeWindow.window, |
| 826 | 826 | createCount, |
| 827 | 827 | mainWindow, |
| ... | ... | @@ -849,7 +849,7 @@ describe("DesktopWindow", () => { |
| 849 | 849 | const fakeWindow = makeFakeBrowserWindow(); |
| 850 | 850 | const createCount = yield* Ref.make(0); |
| 851 | 851 | const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none()); |
| 852 | const layer = makeTestLayer({ | |
| 852 | const layer = layerTest({ | |
| 853 | 853 | window: fakeWindow.window, |
| 854 | 854 | createCount, |
| 855 | 855 | mainWindow, |
| ... | ... | @@ -883,7 +883,7 @@ describe("DesktopWindow", () => { |
| 883 | 883 | const fakeWindow = makeFakeBrowserWindow(); |
| 884 | 884 | const createCount = yield* Ref.make(0); |
| 885 | 885 | const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none()); |
| 886 | const layer = makeTestLayer({ | |
| 886 | const layer = layerTest({ | |
| 887 | 887 | window: fakeWindow.window, |
| 888 | 888 | createCount, |
| 889 | 889 | mainWindow, |
| ... | ... | @@ -910,7 +910,7 @@ describe("DesktopWindow", () => { |
| 910 | 910 | const createCount = yield* Ref.make(0); |
| 911 | 911 | const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none()); |
| 912 | 912 | const mainWindowBoundsUpdates: DesktopAppSettings.DesktopWindowBounds[] = []; |
| 913 | const layer = makeTestLayer({ | |
| 913 | const layer = layerTest({ | |
| 914 | 914 | window: fakeWindow.window, |
| 915 | 915 | createCount, |
| 916 | 916 | mainWindow, |
| ... | ... | @@ -953,7 +953,7 @@ describe("DesktopWindow", () => { |
| 953 | 953 | const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none()); |
| 954 | 954 | const mainWindowBoundsUpdates: DesktopAppSettings.DesktopWindowBounds[] = []; |
| 955 | 955 | const mainWindowMaximizedUpdates: boolean[] = []; |
| 956 | const layer = makeTestLayer({ | |
| 956 | const layer = layerTest({ | |
| 957 | 957 | window: fakeWindow.window, |
| 958 | 958 | createCount, |
| 959 | 959 | mainWindow, |
| ... | ... | @@ -987,7 +987,7 @@ describe("DesktopWindow", () => { |
| 987 | 987 | const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none()); |
| 988 | 988 | const mainWindowBoundsUpdates: DesktopAppSettings.DesktopWindowBounds[] = []; |
| 989 | 989 | const mainWindowMaximizedUpdates: boolean[] = []; |
| 990 | const layer = makeTestLayer({ | |
| 990 | const layer = layerTest({ | |
| 991 | 991 | window: fakeWindow.window, |
| 992 | 992 | createCount, |
| 993 | 993 | mainWindow, |
| ... | ... | @@ -1024,7 +1024,7 @@ describe("DesktopWindow", () => { |
| 1024 | 1024 | const createCount = yield* Ref.make(0); |
| 1025 | 1025 | const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none()); |
| 1026 | 1026 | const mainWindowBoundsUpdates: DesktopAppSettings.DesktopWindowBounds[] = []; |
| 1027 | const layer = makeTestLayer({ | |
| 1027 | const layer = layerTest({ | |
| 1028 | 1028 | window: fakeWindow.window, |
| 1029 | 1029 | createCount, |
| 1030 | 1030 | mainWindow, |
| ... | ... | @@ -1054,7 +1054,7 @@ describe("DesktopWindow", () => { |
| 1054 | 1054 | const createCount = yield* Ref.make(0); |
| 1055 | 1055 | const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none()); |
| 1056 | 1056 | const mainWindowBoundsUpdates: DesktopAppSettings.DesktopWindowBounds[] = []; |
| 1057 | const layer = makeTestLayer({ | |
| 1057 | const layer = layerTest({ | |
| 1058 | 1058 | window: fakeWindow.window, |
| 1059 | 1059 | createCount, |
| 1060 | 1060 | mainWindow, |
| ... | ... | @@ -1096,7 +1096,7 @@ describe("DesktopWindow", () => { |
| 1096 | 1096 | const createCount = yield* Ref.make(0); |
| 1097 | 1097 | const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none()); |
| 1098 | 1098 | const mainWindowBoundsUpdates: DesktopAppSettings.DesktopWindowBounds[] = []; |
| 1099 | const layer = makeTestLayer({ | |
| 1099 | const layer = layerTest({ | |
| 1100 | 1100 | window: fakeWindow.window, |
| 1101 | 1101 | createCount, |
| 1102 | 1102 | mainWindow, |
| ... | ... | @@ -1132,7 +1132,7 @@ describe("DesktopWindow", () => { |
| 1132 | 1132 | const createCount = yield* Ref.make(0); |
| 1133 | 1133 | const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none()); |
| 1134 | 1134 | const mainWindowBoundsUpdates: DesktopAppSettings.DesktopWindowBounds[] = []; |
| 1135 | const layer = makeTestLayer({ | |
| 1135 | const layer = layerTest({ | |
| 1136 | 1136 | window: fakeWindow.window, |
| 1137 | 1137 | createCount, |
| 1138 | 1138 | mainWindow, |
| ... | ... | @@ -1180,7 +1180,7 @@ describe("DesktopWindow", () => { |
| 1180 | 1180 | const createCount = yield* Ref.make(0); |
| 1181 | 1181 | const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none()); |
| 1182 | 1182 | const createdWindowOptions: Electron.BrowserWindowConstructorOptions[] = []; |
| 1183 | const layer = makeTestLayer({ | |
| 1183 | const layer = layerTest({ | |
| 1184 | 1184 | window: fakeWindow.window, |
| 1185 | 1185 | createCount, |
| 1186 | 1186 | mainWindow, |
| ... | ... | @@ -1218,7 +1218,7 @@ describe("DesktopWindow", () => { |
| 1218 | 1218 | const writeStarted = yield* Deferred.make<void>(); |
| 1219 | 1219 | const allowWrite = yield* Deferred.make<void>(); |
| 1220 | 1220 | const flushCompleted = yield* Deferred.make<void>(); |
| 1221 | const layer = makeTestLayer({ | |
| 1221 | const layer = layerTest({ | |
| 1222 | 1222 | window: fakeWindow.window, |
| 1223 | 1223 | createCount, |
| 1224 | 1224 | mainWindow, |
| ... | ... | @@ -1270,7 +1270,7 @@ describe("DesktopWindow", () => { |
| 1270 | 1270 | const fakeWindow = makeFakeBrowserWindow(); |
| 1271 | 1271 | const createCount = yield* Ref.make(0); |
| 1272 | 1272 | const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none()); |
| 1273 | const layer = makeTestLayer({ | |
| 1273 | const layer = layerTest({ | |
| 1274 | 1274 | window: fakeWindow.window, |
| 1275 | 1275 | createCount, |
| 1276 | 1276 | mainWindow, |
| ... | ... | @@ -1301,7 +1301,7 @@ describe("DesktopWindow", () => { |
| 1301 | 1301 | const fakeWindow = makeFakeBrowserWindow(); |
| 1302 | 1302 | const createCount = yield* Ref.make(0); |
| 1303 | 1303 | const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none()); |
| 1304 | const layer = makeTestLayer({ | |
| 1304 | const layer = layerTest({ | |
| 1305 | 1305 | window: fakeWindow.window, |
| 1306 | 1306 | createCount, |
| 1307 | 1307 | mainWindow, |
| ... | ... | @@ -1369,7 +1369,7 @@ describe("DesktopWindow", () => { |
| 1369 | 1369 | const createCount = yield* Ref.make(0); |
| 1370 | 1370 | const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none()); |
| 1371 | 1371 | const openedExternalUrls: unknown[] = []; |
| 1372 | const layer = makeTestLayer({ | |
| 1372 | const layer = layerTest({ | |
| 1373 | 1373 | window: fakeWindow.window, |
| 1374 | 1374 | createCount, |
| 1375 | 1375 | mainWindow, |
| ... | ... | @@ -1515,7 +1515,7 @@ describe("DesktopWindow", () => { |
| 1515 | 1515 | }); |
| 1516 | 1516 | const createCount = yield* Ref.make(0); |
| 1517 | 1517 | const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none()); |
| 1518 | const layer = makeTestLayer({ | |
| 1518 | const layer = layerTest({ | |
| 1519 | 1519 | window: fakeWindow.window, |
| 1520 | 1520 | createCount, |
| 1521 | 1521 | mainWindow, |
| ... | ... | @@ -1550,7 +1550,7 @@ describe("DesktopWindow", () => { |
| 1550 | 1550 | const fakeWindow = makeFakeBrowserWindow(); |
| 1551 | 1551 | const createCount = yield* Ref.make(0); |
| 1552 | 1552 | const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none()); |
| 1553 | const layer = makeTestLayer({ | |
| 1553 | const layer = layerTest({ | |
| 1554 | 1554 | window: fakeWindow.window, |
| 1555 | 1555 | createCount, |
| 1556 | 1556 | mainWindow, |
| ... | ... | @@ -1596,7 +1596,7 @@ describe("DesktopWindow", () => { |
| 1596 | 1596 | const createCount = yield* Ref.make(0); |
| 1597 | 1597 | const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none()); |
| 1598 | 1598 | const onReveal = vi.fn(); |
| 1599 | const layer = makeTestLayer({ window: fakeWindow.window, createCount, mainWindow, onReveal }); | |
| 1599 | const layer = layerTest({ window: fakeWindow.window, createCount, mainWindow, onReveal }); | |
| 1600 | 1600 | |
| 1601 | 1601 | yield* Effect.gen(function* () { |
| 1602 | 1602 | const desktopWindow = yield* DesktopWindow.DesktopWindow; |
| ... | ... | @@ -1620,7 +1620,7 @@ describe("DesktopWindow", () => { |
| 1620 | 1620 | Option.some(fakeWindow.window), |
| 1621 | 1621 | ); |
| 1622 | 1622 | const onReveal = vi.fn(); |
| 1623 | const layer = makeTestLayer({ window: fakeWindow.window, createCount, mainWindow, onReveal }); | |
| 1623 | const layer = layerTest({ window: fakeWindow.window, createCount, mainWindow, onReveal }); | |
| 1624 | 1624 | vi.mocked(fakeWindow.window.webContents.isLoadingMainFrame).mockReturnValue(true); |
| 1625 | 1625 | |
| 1626 | 1626 | yield* Effect.gen(function* () { |
apps/desktop/src/wsl/DesktopWslBackend.test.ts+14-14| ... | ... | @@ -50,7 +50,7 @@ const primarySnapshot: DesktopBackendSnapshot = { |
| 50 | 50 | restartScheduled: false, |
| 51 | 51 | }; |
| 52 | 52 | |
| 53 | const serverExposureLayer = Layer.succeed(DesktopServerExposure.DesktopServerExposure, { | |
| 53 | const layerServerExposure = Layer.succeed(DesktopServerExposure.DesktopServerExposure, { | |
| 54 | 54 | getState: Effect.die("unexpected getState"), |
| 55 | 55 | backendConfig: Effect.succeed({ |
| 56 | 56 | port: 3773, |
| ... | ... | @@ -65,7 +65,7 @@ const serverExposureLayer = Layer.succeed(DesktopServerExposure.DesktopServerExp |
| 65 | 65 | getAdvertisedEndpoints: Effect.succeed([]), |
| 66 | 66 | } satisfies DesktopServerExposure.DesktopServerExposure["Service"]); |
| 67 | 67 | |
| 68 | const backendConfigurationLayer = Layer.succeed( | |
| 68 | const layerBackendConfiguration = Layer.succeed( | |
| 69 | 69 | DesktopBackendConfiguration.DesktopBackendConfiguration, |
| 70 | 70 | { |
| 71 | 71 | resolvePrimary: Effect.die("unexpected resolvePrimary"), |
| ... | ... | @@ -74,7 +74,7 @@ const backendConfigurationLayer = Layer.succeed( |
| 74 | 74 | } satisfies DesktopBackendConfiguration.DesktopBackendConfiguration["Service"], |
| 75 | 75 | ); |
| 76 | 76 | |
| 77 | const netLayer = Layer.succeed(NetService.NetService, { | |
| 77 | const layerNet = Layer.succeed(NetService.NetService, { | |
| 78 | 78 | canListenOnHost: () => Effect.succeed(true), |
| 79 | 79 | isPortAvailableOnLoopback: () => Effect.succeed(true), |
| 80 | 80 | hasListenerOnHost: () => Effect.succeed(false), |
| ... | ... | @@ -91,9 +91,9 @@ describe("DesktopWslBackend", () => { |
| 91 | 91 | Effect.provide( |
| 92 | 92 | DesktopWslBackend.layer.pipe( |
| 93 | 93 | Layer.provide(Layer.mock(DesktopBackendPool.DesktopBackendPool, {})), |
| 94 | Layer.provide(backendConfigurationLayer), | |
| 95 | Layer.provide(serverExposureLayer), | |
| 96 | Layer.provide(netLayer), | |
| 94 | Layer.provide(layerBackendConfiguration), | |
| 95 | Layer.provide(layerServerExposure), | |
| 96 | Layer.provide(layerNet), | |
| 97 | 97 | Layer.provide(Layer.mock(DesktopWslEnvironment.DesktopWslEnvironment, {})), |
| 98 | 98 | Layer.provide( |
| 99 | 99 | DesktopAppSettings.layerTest({ |
| ... | ... | @@ -118,7 +118,7 @@ describe("DesktopWslBackend", () => { |
| 118 | 118 | label: "WSL (Ubuntu)", |
| 119 | 119 | snapshot: primarySnapshot, |
| 120 | 120 | }); |
| 121 | const poolLayer = Layer.succeed(DesktopBackendPool.DesktopBackendPool, { | |
| 121 | const layerPool = Layer.succeed(DesktopBackendPool.DesktopBackendPool, { | |
| 122 | 122 | get: (id) => |
| 123 | 123 | Effect.succeed( |
| 124 | 124 | id === DesktopBackendPool.PRIMARY_INSTANCE_ID |
| ... | ... | @@ -160,10 +160,10 @@ describe("DesktopWslBackend", () => { |
| 160 | 160 | }).pipe( |
| 161 | 161 | Effect.provide( |
| 162 | 162 | DesktopWslBackend.layer.pipe( |
| 163 | Layer.provideMerge(poolLayer), | |
| 164 | Layer.provideMerge(backendConfigurationLayer), | |
| 165 | Layer.provideMerge(serverExposureLayer), | |
| 166 | Layer.provideMerge(netLayer), | |
| 163 | Layer.provideMerge(layerPool), | |
| 164 | Layer.provideMerge(layerBackendConfiguration), | |
| 165 | Layer.provideMerge(layerServerExposure), | |
| 166 | Layer.provideMerge(layerNet), | |
| 167 | 167 | Layer.provideMerge( |
| 168 | 168 | DesktopAppSettings.layerTest({ |
| 169 | 169 | ...DesktopAppSettings.DEFAULT_DESKTOP_SETTINGS, |
| ... | ... | @@ -204,9 +204,9 @@ describe("DesktopWslBackend", () => { |
| 204 | 204 | Effect.provide( |
| 205 | 205 | DesktopWslBackend.layer.pipe( |
| 206 | 206 | Layer.provideMerge(DesktopBackendPool.layerTest([primary, wsl])), |
| 207 | Layer.provideMerge(backendConfigurationLayer), | |
| 208 | Layer.provideMerge(serverExposureLayer), | |
| 209 | Layer.provideMerge(netLayer), | |
| 207 | Layer.provideMerge(layerBackendConfiguration), | |
| 208 | Layer.provideMerge(layerServerExposure), | |
| 209 | Layer.provideMerge(layerNet), | |
| 210 | 210 | Layer.provideMerge( |
| 211 | 211 | DesktopAppSettings.layerTest({ |
| 212 | 212 | ...DesktopAppSettings.DEFAULT_DESKTOP_SETTINGS, |
apps/desktop/src/wsl/DesktopWslServerTree.test.ts+7-7| ... | ... | @@ -16,7 +16,7 @@ import * as DesktopWslServerTree from "./DesktopWslServerTree.ts"; |
| 16 | 16 | // Electron) fs, so a plain directory named server.asar exercises the full |
| 17 | 17 | // extraction path under plain Node. |
| 18 | 18 | |
| 19 | const environmentLayer = (input: { | |
| 19 | const layerEnvironment = (input: { | |
| 20 | 20 | readonly baseDir: string; |
| 21 | 21 | readonly resourcesPath: string; |
| 22 | 22 | readonly appVersion?: string; |
| ... | ... | @@ -69,7 +69,7 @@ const ensureWith = (input: { |
| 69 | 69 | const tree = yield* DesktopWslServerTree.DesktopWslServerTree; |
| 70 | 70 | return yield* tree.ensure; |
| 71 | 71 | }).pipe( |
| 72 | Effect.provide(DesktopWslServerTree.layer.pipe(Layer.provideMerge(environmentLayer(input)))), | |
| 72 | Effect.provide(DesktopWslServerTree.layer.pipe(Layer.provideMerge(layerEnvironment(input)))), | |
| 73 | 73 | ); |
| 74 | 74 | |
| 75 | 75 | describe("DesktopWslServerTree", () => { |
| ... | ... | @@ -182,7 +182,7 @@ describe("DesktopWslServerTree", () => { |
| 182 | 182 | }).pipe( |
| 183 | 183 | Effect.provide( |
| 184 | 184 | DesktopWslServerTree.layer.pipe( |
| 185 | Layer.provideMerge(environmentLayer({ baseDir: tempDir, resourcesPath })), | |
| 185 | Layer.provideMerge(layerEnvironment({ baseDir: tempDir, resourcesPath })), | |
| 186 | 186 | ), |
| 187 | 187 | ), |
| 188 | 188 | ); |
| ... | ... | @@ -313,7 +313,7 @@ describe("DesktopWslServerTree", () => { |
| 313 | 313 | }).pipe( |
| 314 | 314 | Effect.provide( |
| 315 | 315 | DesktopWslServerTree.layer.pipe( |
| 316 | Layer.provideMerge(environmentLayer({ baseDir: tempDir, resourcesPath })), | |
| 316 | Layer.provideMerge(layerEnvironment({ baseDir: tempDir, resourcesPath })), | |
| 317 | 317 | ), |
| 318 | 318 | ), |
| 319 | 319 | ); |
| ... | ... | @@ -340,7 +340,7 @@ describe("DesktopWslServerTree", () => { |
| 340 | 340 | const treeRoot = path.dirname(versionDir); |
| 341 | 341 | const extractedEntryPath = path.join(versionDir, "apps/server/dist/bin.mjs"); |
| 342 | 342 | let cleanupFailed = false; |
| 343 | const partialCleanupFileSystem = Layer.effect( | |
| 343 | const layerPartialCleanupFileSystem = Layer.effect( | |
| 344 | 344 | FileSystem.FileSystem, |
| 345 | 345 | Effect.gen(function* () { |
| 346 | 346 | const realFileSystem = yield* FileSystem.FileSystem; |
| ... | ... | @@ -371,8 +371,8 @@ describe("DesktopWslServerTree", () => { |
| 371 | 371 | }).pipe( |
| 372 | 372 | Effect.provide( |
| 373 | 373 | DesktopWslServerTree.layer.pipe( |
| 374 | Layer.provideMerge(environmentLayer({ baseDir: tempDir, resourcesPath })), | |
| 375 | Layer.provideMerge(partialCleanupFileSystem), | |
| 374 | Layer.provideMerge(layerEnvironment({ baseDir: tempDir, resourcesPath })), | |
| 375 | Layer.provideMerge(layerPartialCleanupFileSystem), | |
| 376 | 376 | ), |
| 377 | 377 | ), |
| 378 | 378 | ); |
apps/mobile/src/connection/background-activity.ts+2-2| ... | ... | @@ -36,14 +36,14 @@ function normalizeAppState( |
| 36 | 36 | return "unknown"; |
| 37 | 37 | } |
| 38 | 38 | |
| 39 | export const mobileBackgroundActivityObserverLayer = Layer.succeed( | |
| 39 | export const layerObserver = Layer.succeed( | |
| 40 | 40 | EnvironmentRpcSubscriptionObserver, |
| 41 | 41 | EnvironmentRpcSubscriptionObserver.of({ |
| 42 | 42 | observe: observeMobileBackgroundActivitySubscription, |
| 43 | 43 | }), |
| 44 | 44 | ); |
| 45 | 45 | |
| 46 | export const mobileBackgroundActivityReporterLayer = Layer.effectDiscard( | |
| 46 | export const layerReporter = Layer.effectDiscard( | |
| 47 | 47 | Effect.gen(function* () { |
| 48 | 48 | const registry = yield* EnvironmentRegistry.EnvironmentRegistry; |
| 49 | 49 | const storage = yield* MobileStorage.MobileStorage; |
apps/mobile/src/connection/platform.ts+23-27| ... | ... | @@ -29,7 +29,7 @@ import { clearThreadOutboxEnvironment } from "../state/thread-outbox-removal"; |
| 29 | 29 | import { clearComposerDraftsEnvironment } from "../state/use-composer-drafts"; |
| 30 | 30 | import { clearThreadComposerErrorsForEnvironment } from "../state/thread-composer-error"; |
| 31 | 31 | import { mobileApplicationActiveWakeup } from "./app-state-wakeups"; |
| 32 | import { connectionStorageLayer } from "./storage"; | |
| 32 | import * as ConnectionStorage from "./storage"; | |
| 33 | 33 | |
| 34 | 34 | function networkStatus(state: Network.NetworkState): "unknown" | "offline" | "online" { |
| 35 | 35 | if (state.isConnected === false) { |
| ... | ... | @@ -41,7 +41,7 @@ function networkStatus(state: Network.NetworkState): "unknown" | "offline" | "on |
| 41 | 41 | return "unknown"; |
| 42 | 42 | } |
| 43 | 43 | |
| 44 | const connectivityLayer = Connectivity.layer({ | |
| 44 | const layerConnectivity = Connectivity.layer({ | |
| 45 | 45 | status: Effect.tryPromise({ |
| 46 | 46 | try: () => Network.getNetworkStateAsync(), |
| 47 | 47 | catch: () => undefined, |
| ... | ... | @@ -122,7 +122,7 @@ const networkPathChanges = Stream.callback<"network-changed">((queue) => |
| 122 | 122 | ).pipe(Effect.asVoid), |
| 123 | 123 | ); |
| 124 | 124 | |
| 125 | const wakeupsLayer = Wakeups.layer({ | |
| 125 | const layerWakeups = Wakeups.layer({ | |
| 126 | 126 | changes: Stream.mergeAll( |
| 127 | 127 | [ |
| 128 | 128 | Stream.callback<"application-active-probe" | "application-active-reconnect">((queue) => |
| ... | ... | @@ -155,7 +155,7 @@ const wakeupsLayer = Wakeups.layer({ |
| 155 | 155 | ), |
| 156 | 156 | }); |
| 157 | 157 | |
| 158 | const capabilitiesLayer = Layer.effectContext( | |
| 158 | const layerCapabilities = Layer.effectContext( | |
| 159 | 159 | Effect.gen(function* () { |
| 160 | 160 | const storage = yield* MobileStorage.MobileStorage; |
| 161 | 161 | return Context.make( |
| ... | ... | @@ -243,21 +243,17 @@ const capabilitiesLayer = Layer.effectContext( |
| 243 | 243 | }), |
| 244 | 244 | ); |
| 245 | 245 | |
| 246 | const platformConnectionSourceLayer = Layer.succeed( | |
| 246 | const layerPlatformConnectionSource = Layer.succeed( | |
| 247 | 247 | PlatformConnectionSource.PlatformConnectionSource, |
| 248 | 248 | PlatformConnectionSource.PlatformConnectionSource.of({ |
| 249 | 249 | registrations: Stream.empty, |
| 250 | 250 | }), |
| 251 | 251 | ); |
| 252 | 252 | |
| 253 | const providedConnectionStorageLayer = connectionStorageLayer.pipe( | |
| 254 | Layer.provide(Runtime.runtimeContextLayer), | |
| 255 | ); | |
| 256 | const providedCapabilitiesLayer = capabilitiesLayer.pipe( | |
| 257 | Layer.provide(Runtime.runtimeContextLayer), | |
| 258 | ); | |
| 253 | const layerProvidedConnectionStorage = ConnectionStorage.layer.pipe(Layer.provide(Runtime.layer)); | |
| 254 | const layerProvidedCapabilities = layerCapabilities.pipe(Layer.provide(Runtime.layer)); | |
| 259 | 255 | |
| 260 | const environmentOwnedDataCleanupLayer = Layer.succeed( | |
| 256 | const layerEnvironmentOwnedDataCleanup = Layer.succeed( | |
| 261 | 257 | Persistence.EnvironmentOwnedDataCleanup, |
| 262 | 258 | Persistence.EnvironmentOwnedDataCleanup.of({ |
| 263 | 259 | clear: (environmentId) => |
| ... | ... | @@ -280,24 +276,24 @@ const environmentOwnedDataCleanupLayer = Layer.succeed( |
| 280 | 276 | ); |
| 281 | 277 | |
| 282 | 278 | type ConnectionPlatformLayerSource = |
| 283 | | typeof providedConnectionStorageLayer | |
| 284 | | typeof Runtime.runtimeContextLayer | |
| 285 | | typeof connectivityLayer | |
| 286 | | typeof wakeupsLayer | |
| 287 | | typeof providedCapabilitiesLayer | |
| 288 | | typeof platformConnectionSourceLayer | |
| 289 | | typeof environmentOwnedDataCleanupLayer; | |
| 279 | | typeof layerProvidedConnectionStorage | |
| 280 | | typeof Runtime.layer | |
| 281 | | typeof layerConnectivity | |
| 282 | | typeof layerWakeups | |
| 283 | | typeof layerProvidedCapabilities | |
| 284 | | typeof layerPlatformConnectionSource | |
| 285 | | typeof layerEnvironmentOwnedDataCleanup; | |
| 290 | 286 | |
| 291 | export const connectionPlatformLayer: Layer.Layer< | |
| 287 | export const layer: Layer.Layer< | |
| 292 | 288 | Layer.Success<ConnectionPlatformLayerSource>, |
| 293 | 289 | Layer.Error<ConnectionPlatformLayerSource>, |
| 294 | 290 | Layer.Services<ConnectionPlatformLayerSource> |
| 295 | 291 | > = Layer.mergeAll( |
| 296 | providedConnectionStorageLayer, | |
| 297 | Runtime.runtimeContextLayer, | |
| 298 | connectivityLayer, | |
| 299 | wakeupsLayer, | |
| 300 | providedCapabilitiesLayer, | |
| 301 | platformConnectionSourceLayer, | |
| 302 | environmentOwnedDataCleanupLayer, | |
| 292 | layerProvidedConnectionStorage, | |
| 293 | Runtime.layer, | |
| 294 | layerConnectivity, | |
| 295 | layerWakeups, | |
| 296 | layerProvidedCapabilities, | |
| 297 | layerPlatformConnectionSource, | |
| 298 | layerEnvironmentOwnedDataCleanup, | |
| 303 | 299 | ); |
apps/mobile/src/connection/runtime.ts+19-24| ... | ... | @@ -1,7 +1,7 @@ |
| 1 | 1 | import { Connection } from "@t3tools/client-runtime/connection"; |
| 2 | 2 | import { ShellSnapshotLoader } from "@t3tools/client-runtime/state/shell"; |
| 3 | 3 | import { |
| 4 | boundedThreadSnapshotLoaderLayer, | |
| 4 | BoundedThreadSnapshotLoader, | |
| 5 | 5 | ThreadHistoryController, |
| 6 | 6 | } from "@t3tools/client-runtime/state/threads"; |
| 7 | 7 | import * as Layer from "effect/Layer"; |
| ... | ... | @@ -9,49 +9,44 @@ import { Atom } from "effect/reactivity"; |
| 9 | 9 | |
| 10 | 10 | import type { FoundationHotModule } from "../lib/foundation-fast-refresh"; |
| 11 | 11 | import { hotSwappableAtomRuntime } from "../lib/hot-swappable-atom-runtime"; |
| 12 | import { runtimeContextLayer } from "../lib/runtime"; | |
| 12 | import * as Runtime from "../lib/runtime"; | |
| 13 | 13 | import { appAtomRegistry } from "../state/atom-registry"; |
| 14 | import { | |
| 15 | mobileBackgroundActivityObserverLayer, | |
| 16 | mobileBackgroundActivityReporterLayer, | |
| 17 | } from "./background-activity"; | |
| 18 | import { connectionPlatformLayer } from "./platform"; | |
| 14 | import * as BackgroundActivity from "./background-activity"; | |
| 15 | import * as ConnectionPlatform from "./platform"; | |
| 19 | 16 | |
| 20 | 17 | declare const module: { readonly hot?: FoundationHotModule } | undefined; |
| 21 | 18 | |
| 22 | const providedConnectionPlatformLayer = connectionPlatformLayer.pipe( | |
| 23 | Layer.provide(runtimeContextLayer), | |
| 24 | ); | |
| 19 | const layerProvidedConnectionPlatform = ConnectionPlatform.layer.pipe(Layer.provide(Runtime.layer)); | |
| 25 | 20 | |
| 26 | const snapshotLoaderLayer = Layer.mergeAll( | |
| 27 | boundedThreadSnapshotLoaderLayer, | |
| 21 | const layerSnapshotLoader = Layer.mergeAll( | |
| 22 | BoundedThreadSnapshotLoader.layer, | |
| 28 | 23 | ShellSnapshotLoader.layer, |
| 29 | 24 | ThreadHistoryController.layer, |
| 30 | 25 | ); |
| 31 | 26 | |
| 32 | 27 | type ConnectionLayerSource = |
| 33 | 28 | | typeof Connection.layer |
| 34 | | typeof snapshotLoaderLayer | |
| 35 | | typeof runtimeContextLayer | |
| 36 | | typeof connectionPlatformLayer | |
| 37 | | typeof mobileBackgroundActivityObserverLayer | |
| 38 | | typeof mobileBackgroundActivityReporterLayer; | |
| 29 | | typeof layerSnapshotLoader | |
| 30 | | typeof Runtime.layer | |
| 31 | | typeof ConnectionPlatform.layer | |
| 32 | | typeof BackgroundActivity.layerObserver | |
| 33 | | typeof BackgroundActivity.layerReporter; | |
| 39 | 34 | |
| 40 | const providedClientConnectionLayer = snapshotLoaderLayer.pipe( | |
| 35 | const layerProvidedClientConnection = layerSnapshotLoader.pipe( | |
| 41 | 36 | Layer.provideMerge( |
| 42 | 37 | Connection.layerWithOptions({ usageLimitSources: true, usageLimitsCommand: true }), |
| 43 | 38 | ), |
| 44 | 39 | Layer.provideMerge( |
| 45 | 40 | Layer.mergeAll( |
| 46 | runtimeContextLayer, | |
| 47 | providedConnectionPlatformLayer, | |
| 48 | mobileBackgroundActivityObserverLayer, | |
| 41 | Runtime.layer, | |
| 42 | layerProvidedConnectionPlatform, | |
| 43 | BackgroundActivity.layerObserver, | |
| 49 | 44 | ), |
| 50 | 45 | ), |
| 51 | 46 | ); |
| 52 | 47 | |
| 53 | const connectionLayer = mobileBackgroundActivityReporterLayer.pipe( | |
| 54 | Layer.provideMerge(providedClientConnectionLayer), | |
| 48 | const layerConnection = BackgroundActivity.layerReporter.pipe( | |
| 49 | Layer.provideMerge(layerProvidedClientConnection), | |
| 55 | 50 | ); |
| 56 | 51 | |
| 57 | 52 | export const connectionAtomRuntime: Atom.AtomRuntime< |
| ... | ... | @@ -61,5 +56,5 @@ export const connectionAtomRuntime: Atom.AtomRuntime< |
| 61 | 56 | id: "t3.mobile.connection-runtime", |
| 62 | 57 | hotModule: typeof module === "undefined" ? undefined : module.hot, |
| 63 | 58 | registry: appAtomRegistry, |
| 64 | layer: connectionLayer, | |
| 59 | layer: layerConnection, | |
| 65 | 60 | }); |
apps/mobile/src/connection/storage.ts+1-1| ... | ... | @@ -39,7 +39,7 @@ function targetPersistenceError( |
| 39 | 39 | }); |
| 40 | 40 | } |
| 41 | 41 | |
| 42 | export const connectionStorageLayer = Layer.effectContext( | |
| 42 | export const layer = Layer.effectContext( | |
| 43 | 43 | Effect.gen(function* () { |
| 44 | 44 | const catalog = yield* CatalogStore.make(); |
| 45 | 45 | const githubRoutingPermissions = yield* makeGitHubRoutingPermissions({ |
apps/mobile/src/features/agent-awareness/liveActivityPreferences.test.ts+6-6| ... | ... | @@ -43,7 +43,7 @@ const connection: SavedRemoteConnection = { |
| 43 | 43 | bearerToken: "local-bearer", |
| 44 | 44 | }; |
| 45 | 45 | |
| 46 | const testLayer = Layer.mergeAll( | |
| 46 | const layerTest = Layer.mergeAll( | |
| 47 | 47 | Layer.succeed(ManagedRelay.ManagedRelayClient, null as never), |
| 48 | 48 | Layer.succeed( |
| 49 | 49 | HttpClient.HttpClient, |
| ... | ... | @@ -88,7 +88,7 @@ describe("liveActivityPreferences", () => { |
| 88 | 88 | connection, |
| 89 | 89 | liveActivitiesEnabled: false, |
| 90 | 90 | }); |
| 91 | }).pipe(Effect.provide(testLayer)), | |
| 91 | }).pipe(Effect.provide(layerTest)), | |
| 92 | 92 | ); |
| 93 | 93 | |
| 94 | 94 | it.effect("pushes enabled Live Activity preferences to relay registrations", () => |
| ... | ... | @@ -108,7 +108,7 @@ describe("liveActivityPreferences", () => { |
| 108 | 108 | connection, |
| 109 | 109 | liveActivitiesEnabled: true, |
| 110 | 110 | }); |
| 111 | }).pipe(Effect.provide(testLayer)), | |
| 111 | }).pipe(Effect.provide(layerTest)), | |
| 112 | 112 | ); |
| 113 | 113 | |
| 114 | 114 | it.effect("keeps local preferences refreshable when signed out", () => |
| ... | ... | @@ -124,7 +124,7 @@ describe("liveActivityPreferences", () => { |
| 124 | 124 | liveActivitiesEnabled: false, |
| 125 | 125 | }); |
| 126 | 126 | expect(linkEnvironmentToCloudWithPreference).not.toHaveBeenCalled(); |
| 127 | }).pipe(Effect.provide(testLayer)), | |
| 127 | }).pipe(Effect.provide(layerTest)), | |
| 128 | 128 | ); |
| 129 | 129 | |
| 130 | 130 | it.effect("does not try to re-link managed relay connections without bearer credentials", () => { |
| ... | ... | @@ -147,7 +147,7 @@ describe("liveActivityPreferences", () => { |
| 147 | 147 | connection, |
| 148 | 148 | liveActivitiesEnabled: true, |
| 149 | 149 | }); |
| 150 | }).pipe(Effect.provide(testLayer)); | |
| 150 | }).pipe(Effect.provide(layerTest)); | |
| 151 | 151 | }); |
| 152 | 152 | |
| 153 | 153 | it.effect("restores relay preferences when an environment update fails", () => { |
| ... | ... | @@ -182,6 +182,6 @@ describe("liveActivityPreferences", () => { |
| 182 | 182 | connection, |
| 183 | 183 | liveActivitiesEnabled: true, |
| 184 | 184 | }); |
| 185 | }).pipe(Effect.provide(testLayer)); | |
| 185 | }).pipe(Effect.provide(layerTest)); | |
| 186 | 186 | }); |
| 187 | 187 | }); |
apps/mobile/src/features/agent-awareness/remoteRegistration.test.ts+27-27| ... | ... | @@ -15,8 +15,8 @@ import { ManagedRelay } from "@t3tools/client-runtime/relay"; |
| 15 | 15 | import type { EnvironmentId } from "@t3tools/contracts"; |
| 16 | 16 | import { verifyDpopProof } from "@t3tools/shared/dpop"; |
| 17 | 17 | import type { SavedRemoteConnection } from "../../lib/connection"; |
| 18 | import { cryptoLayer } from "../cloud/dpop"; | |
| 19 | import { managedRelayClientLayer } from "../cloud/managedRelayLayer"; | |
| 18 | import * as Dpop from "../cloud/dpop"; | |
| 19 | import * as ManagedRelayLayer from "../cloud/managedRelayLayer"; | |
| 20 | 20 | import { |
| 21 | 21 | clearAgentAwarenessRegistrationRecord, |
| 22 | 22 | loadAgentAwarenessRegistrationRecord, |
| ... | ... | @@ -220,8 +220,8 @@ function savedConnection(): SavedRemoteConnection { |
| 220 | 220 | }; |
| 221 | 221 | } |
| 222 | 222 | |
| 223 | const relayTestLayer = managedRelayClientLayer("https://relay.example.test").pipe( | |
| 224 | Layer.provide(Layer.mergeAll(FetchHttpClient.layer, cryptoLayer)), | |
| 223 | const layerRelayTest = ManagedRelayLayer.layer("https://relay.example.test").pipe( | |
| 224 | Layer.provide(Layer.mergeAll(FetchHttpClient.layer, Dpop.layer)), | |
| 225 | 225 | ); |
| 226 | 226 | |
| 227 | 227 | const runBackgroundOperations = Effect.fn("TestRemoteRegistration.runBackgroundOperations")( |
| ... | ... | @@ -405,7 +405,7 @@ describe("makeRelayDeviceRegistrationRequest", () => { |
| 405 | 405 | |
| 406 | 406 | expect(activity.getPushToken).toHaveBeenCalledTimes(2); |
| 407 | 407 | expect(addPushTokenListener).toHaveBeenCalledTimes(1); |
| 408 | }).pipe(Effect.provide(relayTestLayer)); | |
| 408 | }).pipe(Effect.provide(layerRelayTest)); | |
| 409 | 409 | }); |
| 410 | 410 | |
| 411 | 411 | it.effect("preserves Live Activity push-token lookup failures", () => { |
| ... | ... | @@ -427,7 +427,7 @@ describe("makeRelayDeviceRegistrationRequest", () => { |
| 427 | 427 | cause, |
| 428 | 428 | message: "Agent awareness operation read-live-activity-push-token failed.", |
| 429 | 429 | }); |
| 430 | }).pipe(Effect.provide(relayTestLayer)); | |
| 430 | }).pipe(Effect.provide(layerRelayTest)); | |
| 431 | 431 | }); |
| 432 | 432 | |
| 433 | 433 | it.effect( |
| ... | ... | @@ -441,7 +441,7 @@ describe("makeRelayDeviceRegistrationRequest", () => { |
| 441 | 441 | |
| 442 | 442 | return Effect.gen(function* () { |
| 443 | 443 | expect(yield* registerLiveActivityPushToken({ activity: activity as never })).toBe(false); |
| 444 | }).pipe(Effect.provide(relayTestLayer)); | |
| 444 | }).pipe(Effect.provide(layerRelayTest)); | |
| 445 | 445 | }, |
| 446 | 446 | ); |
| 447 | 447 | |
| ... | ... | @@ -465,7 +465,7 @@ describe("makeRelayDeviceRegistrationRequest", () => { |
| 465 | 465 | expect(activity.start).not.toHaveBeenCalled(); |
| 466 | 466 | expect(activity.update).not.toHaveBeenCalled(); |
| 467 | 467 | expect(activity.end).not.toHaveBeenCalled(); |
| 468 | }).pipe(Effect.provide(relayTestLayer)); | |
| 468 | }).pipe(Effect.provide(layerRelayTest)); | |
| 469 | 469 | }, |
| 470 | 470 | ); |
| 471 | 471 | |
| ... | ... | @@ -495,7 +495,7 @@ describe("makeRelayDeviceRegistrationRequest", () => { |
| 495 | 495 | } |
| 496 | 496 | yield* runBackgroundOperations(); |
| 497 | 497 | expect(activity.getPushToken).toHaveBeenCalled(); |
| 498 | }).pipe(Effect.provide(relayTestLayer)); | |
| 498 | }).pipe(Effect.provide(layerRelayTest)); | |
| 499 | 499 | }, |
| 500 | 500 | ); |
| 501 | 501 | |
| ... | ... | @@ -525,7 +525,7 @@ describe("makeRelayDeviceRegistrationRequest", () => { |
| 525 | 525 | yield* refreshAgentAwarenessRegistration(); |
| 526 | 526 | |
| 527 | 527 | expect(Notifications.getDevicePushTokenAsync).toHaveBeenCalledTimes(1); |
| 528 | }).pipe(Effect.provide(relayTestLayer)); | |
| 528 | }).pipe(Effect.provide(layerRelayTest)); | |
| 529 | 529 | }); |
| 530 | 530 | |
| 531 | 531 | it.effect("registers the APNs device when cloud auth becomes available", () => { |
| ... | ... | @@ -583,7 +583,7 @@ describe("makeRelayDeviceRegistrationRequest", () => { |
| 583 | 583 | }), |
| 584 | 584 | ).toMatchObject({ ok: true }); |
| 585 | 585 | expect(getAgentAwarenessRegistrationStatus()).toBe("registered"); |
| 586 | }).pipe(Effect.provide(relayTestLayer)); | |
| 586 | }).pipe(Effect.provide(layerRelayTest)); | |
| 587 | 587 | }); |
| 588 | 588 | |
| 589 | 589 | it.effect("marks registration failed when device registration cannot complete", () => { |
| ... | ... | @@ -604,7 +604,7 @@ describe("makeRelayDeviceRegistrationRequest", () => { |
| 604 | 604 | // read as enabled. |
| 605 | 605 | yield* refreshAgentAwarenessRegistration(); |
| 606 | 606 | expect(getAgentAwarenessRegistrationStatus()).toBe("failed"); |
| 607 | }).pipe(Effect.provide(relayTestLayer)); | |
| 607 | }).pipe(Effect.provide(layerRelayTest)); | |
| 608 | 608 | }); |
| 609 | 609 | |
| 610 | 610 | it("clears registration status on cloud sign-out", () => { |
| ... | ... | @@ -642,7 +642,7 @@ describe("makeRelayDeviceRegistrationRequest", () => { |
| 642 | 642 | return Effect.gen(function* () { |
| 643 | 643 | yield* runBackgroundOperations(); |
| 644 | 644 | expect(getAgentAwarenessRegistrationStatus()).toBe("unknown"); |
| 645 | }).pipe(Effect.provide(relayTestLayer)); | |
| 645 | }).pipe(Effect.provide(layerRelayTest)); | |
| 646 | 646 | }); |
| 647 | 647 | |
| 648 | 648 | it.effect("keeps a registered status when a later refresh fails", () => { |
| ... | ... | @@ -664,7 +664,7 @@ describe("makeRelayDeviceRegistrationRequest", () => { |
| 664 | 664 | ); |
| 665 | 665 | yield* refreshAgentAwarenessRegistration(); |
| 666 | 666 | expect(getAgentAwarenessRegistrationStatus()).toBe("registered"); |
| 667 | }).pipe(Effect.provide(relayTestLayer)); | |
| 667 | }).pipe(Effect.provide(layerRelayTest)); | |
| 668 | 668 | }); |
| 669 | 669 | |
| 670 | 670 | it.effect("does not re-register the same account when nothing has changed", () => { |
| ... | ... | @@ -687,7 +687,7 @@ describe("makeRelayDeviceRegistrationRequest", () => { |
| 687 | 687 | yield* refreshAgentAwarenessRegistration(); |
| 688 | 688 | expect(getAgentAwarenessRegistrationStatus()).toBe("registered"); |
| 689 | 689 | expect(saveAgentAwarenessRegistrationRecord).not.toHaveBeenCalled(); |
| 690 | }).pipe(Effect.provide(relayTestLayer)); | |
| 690 | }).pipe(Effect.provide(layerRelayTest)); | |
| 691 | 691 | }); |
| 692 | 692 | |
| 693 | 693 | it.effect("dedupes rapid activity-token re-registrations within the replay window", () => { |
| ... | ... | @@ -735,7 +735,7 @@ describe("makeRelayDeviceRegistrationRequest", () => { |
| 735 | 735 | vi.mocked(loadOrCreateAgentAwarenessDeviceId).mockClear(); |
| 736 | 736 | yield* refreshActiveLiveActivityRemoteRegistration(); |
| 737 | 737 | expect(loadOrCreateAgentAwarenessDeviceId).not.toHaveBeenCalled(); |
| 738 | }).pipe(Effect.provide(relayTestLayer)); | |
| 738 | }).pipe(Effect.provide(layerRelayTest)); | |
| 739 | 739 | }); |
| 740 | 740 | |
| 741 | 741 | it.effect("re-registers when the stored account identity differs", () => { |
| ... | ... | @@ -750,7 +750,7 @@ describe("makeRelayDeviceRegistrationRequest", () => { |
| 750 | 750 | return Effect.gen(function* () { |
| 751 | 751 | yield* refreshAgentAwarenessRegistration(); |
| 752 | 752 | expect(saveAgentAwarenessRegistrationRecord).toHaveBeenCalledTimes(1); |
| 753 | }).pipe(Effect.provide(relayTestLayer)); | |
| 753 | }).pipe(Effect.provide(layerRelayTest)); | |
| 754 | 754 | }); |
| 755 | 755 | |
| 756 | 756 | it.effect("coalesces simultaneous sign-in and environment connection registrations", () => { |
| ... | ... | @@ -784,7 +784,7 @@ describe("makeRelayDeviceRegistrationRequest", () => { |
| 784 | 784 | return Effect.gen(function* () { |
| 785 | 785 | yield* runBackgroundOperations(); |
| 786 | 786 | expect(Notifications.getPermissionsAsync).toHaveBeenCalledTimes(1); |
| 787 | }).pipe(Effect.provide(relayTestLayer)); | |
| 787 | }).pipe(Effect.provide(layerRelayTest)); | |
| 788 | 788 | }); |
| 789 | 789 | |
| 790 | 790 | it.effect("continues queued device registration after a failed auth lookup", () => { |
| ... | ... | @@ -808,7 +808,7 @@ describe("makeRelayDeviceRegistrationRequest", () => { |
| 808 | 808 | |
| 809 | 809 | expect(backgroundRuntime.pending).toHaveLength(0); |
| 810 | 810 | expect(tokenProvider).toHaveBeenCalledTimes(2); |
| 811 | }).pipe(Effect.provide(relayTestLayer)); | |
| 811 | }).pipe(Effect.provide(layerRelayTest)); | |
| 812 | 812 | }); |
| 813 | 813 | |
| 814 | 814 | it("only registers again when the authenticated identity changes", () => { |
| ... | ... | @@ -852,7 +852,7 @@ describe("makeRelayDeviceRegistrationRequest", () => { |
| 852 | 852 | return Effect.gen(function* () { |
| 853 | 853 | yield* runBackgroundOperations(); |
| 854 | 854 | expect(Notifications.getDevicePushTokenAsync).toHaveBeenCalledTimes(1); |
| 855 | }).pipe(Effect.provide(relayTestLayer)); | |
| 855 | }).pipe(Effect.provide(layerRelayTest)); | |
| 856 | 856 | }); |
| 857 | 857 | |
| 858 | 858 | it.effect( |
| ... | ... | @@ -890,7 +890,7 @@ describe("makeRelayDeviceRegistrationRequest", () => { |
| 890 | 890 | unregisterAgentAwarenessConnection(savedConnection().environmentId); |
| 891 | 891 | |
| 892 | 892 | expect(fetchMock).not.toHaveBeenCalled(); |
| 893 | }).pipe(Effect.provide(relayTestLayer)); | |
| 893 | }).pipe(Effect.provide(layerRelayTest)); | |
| 894 | 894 | }, |
| 895 | 895 | ); |
| 896 | 896 | |
| ... | ... | @@ -988,8 +988,8 @@ describe("makeRelayDeviceRegistrationRequest", () => { |
| 988 | 988 | }).pipe( |
| 989 | 989 | Effect.provideService(FetchHttpClient.Fetch, globalThis.fetch), |
| 990 | 990 | Effect.provide( |
| 991 | managedRelayClientLayer("https://permission-relay.example.test").pipe( | |
| 992 | Layer.provide(Layer.mergeAll(FetchHttpClient.layer, cryptoLayer)), | |
| 991 | ManagedRelayLayer.layer("https://permission-relay.example.test").pipe( | |
| 992 | Layer.provide(Layer.mergeAll(FetchHttpClient.layer, Dpop.layer)), | |
| 993 | 993 | ), |
| 994 | 994 | ), |
| 995 | 995 | ); |
| ... | ... | @@ -1039,7 +1039,7 @@ describe("makeRelayDeviceRegistrationRequest", () => { |
| 1039 | 1039 | } |
| 1040 | 1040 | expect(getAgentAwarenessRegistrationStatus()).toBe("failed"); |
| 1041 | 1041 | expect(saveAgentAwarenessRegistrationRecord).not.toHaveBeenCalled(); |
| 1042 | }).pipe(Effect.provide(relayTestLayer)); | |
| 1042 | }).pipe(Effect.provide(layerRelayTest)); | |
| 1043 | 1043 | }); |
| 1044 | 1044 | |
| 1045 | 1045 | it.effect("registers an Android FCM token without invoking Apple Live Activities", () => { |
| ... | ... | @@ -1082,7 +1082,7 @@ describe("makeRelayDeviceRegistrationRequest", () => { |
| 1082 | 1082 | vi.mocked(clearAndroidAgentNotifications).mockClear(); |
| 1083 | 1083 | setAgentAwarenessRelayTokenProvider(() => Promise.resolve("clerk-token-user-b"), "user-b"); |
| 1084 | 1084 | expect(clearAndroidAgentNotifications).toHaveBeenCalled(); |
| 1085 | }).pipe(Effect.provide(relayTestLayer)); | |
| 1085 | }).pipe(Effect.provide(layerRelayTest)); | |
| 1086 | 1086 | }); |
| 1087 | 1087 | it.effect( |
| 1088 | 1088 | "preserves same-account Android notifications and replays on remount and later foreground", |
| ... | ... | @@ -1120,7 +1120,7 @@ describe("makeRelayDeviceRegistrationRequest", () => { |
| 1120 | 1120 | setAgentAwarenessRelayTokenProvider(null); |
| 1121 | 1121 | expect(clearAndroidAgentNotifications).toHaveBeenCalled(); |
| 1122 | 1122 | expect(clearAgentAwarenessRegistrationRecord).toHaveBeenCalled(); |
| 1123 | }).pipe(Effect.provide(relayTestLayer)); | |
| 1123 | }).pipe(Effect.provide(layerRelayTest)); | |
| 1124 | 1124 | }, |
| 1125 | 1125 | ); |
| 1126 | 1126 | |
| ... | ... | @@ -1137,7 +1137,7 @@ describe("makeRelayDeviceRegistrationRequest", () => { |
| 1137 | 1137 | yield* runBackgroundOperations(); |
| 1138 | 1138 | expect(configureAndroidAgentNotifications).not.toHaveBeenCalled(); |
| 1139 | 1139 | expect(saveAgentAwarenessRegistrationRecord).not.toHaveBeenCalled(); |
| 1140 | }).pipe(Effect.provide(relayTestLayer)); | |
| 1140 | }).pipe(Effect.provide(layerRelayTest)); | |
| 1141 | 1141 | }, |
| 1142 | 1142 | ); |
| 1143 | 1143 | }); |
apps/mobile/src/features/cloud/dpop.test.ts+7-11| ... | ... | @@ -8,12 +8,8 @@ import * as Crypto from "effect/Crypto"; |
| 8 | 8 | import * as Effect from "effect/Effect"; |
| 9 | 9 | import { verifyDpopProof } from "@t3tools/shared/dpop"; |
| 10 | 10 | |
| 11 | import { | |
| 12 | createDpopProof, | |
| 13 | generateDpopProofKeyPair, | |
| 14 | loadOrCreateDpopProofKeyPair, | |
| 15 | cryptoLayer, | |
| 16 | } from "./dpop"; | |
| 11 | import { createDpopProof, generateDpopProofKeyPair, loadOrCreateDpopProofKeyPair } from "./dpop"; | |
| 12 | import * as Dpop from "./dpop"; | |
| 17 | 13 | |
| 18 | 14 | vi.mock("expo-crypto", () => ({ |
| 19 | 15 | CryptoDigestAlgorithm: { |
| ... | ... | @@ -75,7 +71,7 @@ describe("mobile DPoP", () => { |
| 75 | 71 | expect(Buffer.from(digest).toString("hex")).toBe( |
| 76 | 72 | NodeCrypto.createHash("sha256").update("typed-array").digest("hex"), |
| 77 | 73 | ); |
| 78 | }).pipe(Effect.provide(cryptoLayer)), | |
| 74 | }).pipe(Effect.provide(Dpop.layer)), | |
| 79 | 75 | ); |
| 80 | 76 | |
| 81 | 77 | it.effect("persists and reuses the installation proof key", () => |
| ... | ... | @@ -86,7 +82,7 @@ describe("mobile DPoP", () => { |
| 86 | 82 | |
| 87 | 83 | expect(second.thumbprint).toBe(first.thumbprint); |
| 88 | 84 | expect(second.privateJwk).toEqual(first.privateJwk); |
| 89 | }).pipe(Effect.provide(cryptoLayer)), | |
| 85 | }).pipe(Effect.provide(Dpop.layer)), | |
| 90 | 86 | ); |
| 91 | 87 | |
| 92 | 88 | it.effect("rejects malformed persisted proof keys", () => |
| ... | ... | @@ -96,7 +92,7 @@ describe("mobile DPoP", () => { |
| 96 | 92 | const error = yield* loadOrCreateDpopProofKeyPair().pipe(Effect.flip); |
| 97 | 93 | |
| 98 | 94 | expect(error.message).toBe("Stored DPoP proof key is invalid."); |
| 99 | }).pipe(Effect.provide(cryptoLayer)), | |
| 95 | }).pipe(Effect.provide(Dpop.layer)), | |
| 100 | 96 | ); |
| 101 | 97 | |
| 102 | 98 | it.effect("signs connect and bootstrap proofs with the same ephemeral proof key", () => |
| ... | ... | @@ -135,7 +131,7 @@ describe("mobile DPoP", () => { |
| 135 | 131 | nowEpochSeconds: proofIat(bootstrap.proof), |
| 136 | 132 | }), |
| 137 | 133 | ).toMatchObject({ ok: true, thumbprint: proofKey.thumbprint }); |
| 138 | }).pipe(Effect.provide(cryptoLayer)), | |
| 134 | }).pipe(Effect.provide(Dpop.layer)), | |
| 139 | 135 | ); |
| 140 | 136 | |
| 141 | 137 | it.effect("signs DPoP proofs with RFC 9449 htu normalization", () => |
| ... | ... | @@ -161,6 +157,6 @@ describe("mobile DPoP", () => { |
| 161 | 157 | nowEpochSeconds: proofIat(proof.proof), |
| 162 | 158 | }), |
| 163 | 159 | ).toMatchObject({ ok: true }); |
| 164 | }).pipe(Effect.provide(cryptoLayer)), | |
| 160 | }).pipe(Effect.provide(Dpop.layer)), | |
| 165 | 161 | ); |
| 166 | 162 | }); |
apps/mobile/src/features/cloud/dpop.ts+1-1| ... | ... | @@ -65,7 +65,7 @@ function toExpoDigestAlgorithm( |
| 65 | 65 | } |
| 66 | 66 | } |
| 67 | 67 | |
| 68 | export const cryptoLayer = Layer.succeed( | |
| 68 | export const layer = Layer.succeed( | |
| 69 | 69 | Crypto.Crypto, |
| 70 | 70 | Crypto.make({ |
| 71 | 71 | randomBytes: ExpoCrypto.getRandomBytes, |
apps/mobile/src/features/cloud/linkEnvironment.test.ts+7-7| ... | ... | @@ -5,7 +5,7 @@ import * as Layer from "effect/Layer"; |
| 5 | 5 | import { EnvironmentId } from "@t3tools/contracts"; |
| 6 | 6 | import { RelayMobileClientId } from "@t3tools/contracts/relay"; |
| 7 | 7 | import { ManagedRelay } from "@t3tools/client-runtime/relay"; |
| 8 | import { remoteHttpClientLayer } from "@t3tools/client-runtime/rpc"; | |
| 8 | import { layerRemoteHttpClient } from "@t3tools/client-runtime/rpc"; | |
| 9 | 9 | import { HttpClient } from "effect/http"; |
| 10 | 10 | |
| 11 | 11 | import * as MobileStorage from "../../persistence/mobile-storage"; |
| ... | ... | @@ -63,7 +63,7 @@ const createProofMock = vi.fn( |
| 63 | 63 | (input: { readonly method: string; readonly url: string; readonly accessToken?: string }) => |
| 64 | 64 | Effect.succeed(`dpop:${input.method}:${input.url}`), |
| 65 | 65 | ); |
| 66 | const testDpopSignerLayer = Layer.succeed( | |
| 66 | const layerTestDpopSigner = Layer.succeed( | |
| 67 | 67 | ManagedRelay.ManagedRelayDpopSigner, |
| 68 | 68 | ManagedRelay.ManagedRelayDpopSigner.of({ |
| 69 | 69 | thumbprint: Effect.succeed("client-proof-key-thumbprint"), |
| ... | ... | @@ -71,10 +71,10 @@ const testDpopSignerLayer = Layer.succeed( |
| 71 | 71 | }), |
| 72 | 72 | ); |
| 73 | 73 | |
| 74 | function cloudClientLayer() { | |
| 75 | const httpClientLayer = remoteHttpClientLayer((input, init) => globalThis.fetch(input, init)); | |
| 74 | function layerCloudClient() { | |
| 75 | const layerHttpClient = layerRemoteHttpClient((input, init) => globalThis.fetch(input, init)); | |
| 76 | 76 | return Layer.mergeAll( |
| 77 | httpClientLayer, | |
| 77 | layerHttpClient, | |
| 78 | 78 | Layer.succeed( |
| 79 | 79 | MobileStorage.MobileStorage, |
| 80 | 80 | MobileStorage.MobileStorage.of({ |
| ... | ... | @@ -93,7 +93,7 @@ function cloudClientLayer() { |
| 93 | 93 | ManagedRelay.layer({ |
| 94 | 94 | relayUrl: "https://relay.example.test", |
| 95 | 95 | clientId: RelayMobileClientId, |
| 96 | }).pipe(Layer.provideMerge(testDpopSignerLayer), Layer.provide(httpClientLayer)), | |
| 96 | }).pipe(Layer.provideMerge(layerTestDpopSigner), Layer.provide(layerHttpClient)), | |
| 97 | 97 | ); |
| 98 | 98 | } |
| 99 | 99 | |
| ... | ... | @@ -106,7 +106,7 @@ const withCloudServices = <A, E>( |
| 106 | 106 | | ManagedRelay.ManagedRelayDpopSigner |
| 107 | 107 | | MobileStorage.MobileStorage |
| 108 | 108 | >, |
| 109 | ) => effect.pipe(Effect.provide(cloudClientLayer())); | |
| 109 | ) => effect.pipe(Effect.provide(layerCloudClient())); | |
| 110 | 110 | |
| 111 | 111 | function validLinkProof() { |
| 112 | 112 | return "signed-environment-link-jwt"; |
apps/mobile/src/features/cloud/managedRelayLayer.test.ts+4-4| ... | ... | @@ -9,8 +9,8 @@ import * as Effect from "effect/Effect"; |
| 9 | 9 | import * as Layer from "effect/Layer"; |
| 10 | 10 | import { FetchHttpClient } from "effect/http"; |
| 11 | 11 | |
| 12 | import { cryptoLayer } from "./dpop"; | |
| 13 | import { managedRelayClientLayer } from "./managedRelayLayer"; | |
| 12 | import * as Dpop from "./dpop"; | |
| 13 | import * as ManagedRelayLayer from "./managedRelayLayer"; | |
| 14 | 14 | |
| 15 | 15 | vi.mock("expo-crypto", () => ({ |
| 16 | 16 | CryptoDigestAlgorithm: { SHA256: "SHA-256" }, |
| ... | ... | @@ -54,8 +54,8 @@ describe("managed relay DPoP signer", () => { |
| 54 | 54 | assert.equal(yield* signer.thumbprint, thumbprint); |
| 55 | 55 | }).pipe( |
| 56 | 56 | Effect.provide( |
| 57 | managedRelayClientLayer("https://relay.example.test").pipe( | |
| 58 | Layer.provide(Layer.mergeAll(FetchHttpClient.layer, cryptoLayer)), | |
| 57 | ManagedRelayLayer.layer("https://relay.example.test").pipe( | |
| 58 | Layer.provide(Layer.mergeAll(FetchHttpClient.layer, Dpop.layer)), | |
| 59 | 59 | ), |
| 60 | 60 | ), |
| 61 | 61 | ), |
apps/mobile/src/features/cloud/managedRelayLayer.ts+3-3| ... | ... | @@ -10,7 +10,7 @@ import * as Layer from "effect/Layer"; |
| 10 | 10 | import { createDpopProof, loadOrCreateDpopProofKeyPair } from "./dpop"; |
| 11 | 11 | import { managedRelayAccessTokenStore } from "./managedRelayTokenStore"; |
| 12 | 12 | |
| 13 | const relayDpopSignerLayer = Layer.effect( | |
| 13 | const layerRelayDpopSigner = Layer.effect( | |
| 14 | 14 | ManagedRelay.ManagedRelayDpopSigner, |
| 15 | 15 | Effect.gen(function* () { |
| 16 | 16 | const crypto = yield* Crypto.Crypto; |
| ... | ... | @@ -64,9 +64,9 @@ const relayDpopSignerLayer = Layer.effect( |
| 64 | 64 | }), |
| 65 | 65 | ); |
| 66 | 66 | |
| 67 | export const managedRelayClientLayer = (relayUrl: string) => | |
| 67 | export const layer = (relayUrl: string) => | |
| 68 | 68 | ManagedRelay.layer({ |
| 69 | 69 | relayUrl, |
| 70 | 70 | clientId: RelayMobileClientId, |
| 71 | 71 | accessTokenStore: managedRelayAccessTokenStore, |
| 72 | }).pipe(Layer.provideMerge(relayDpopSignerLayer)); | |
| 72 | }).pipe(Layer.provideMerge(layerRelayDpopSigner)); |
apps/mobile/src/features/cloud/managedRelayState.ts+2-2| ... | ... | @@ -14,11 +14,11 @@ import type { RelayClientEnvironmentRecord } from "@t3tools/contracts/relay"; |
| 14 | 14 | import { AsyncResult, Atom } from "effect/reactivity"; |
| 15 | 15 | import { useCallback, useEffect } from "react"; |
| 16 | 16 | |
| 17 | import { runtimeContextLayer } from "../../lib/runtime"; | |
| 17 | import * as Runtime from "../../lib/runtime"; | |
| 18 | 18 | import { appAtomRegistry } from "../../state/atom-registry"; |
| 19 | 19 | import { cloudDebugLog } from "./cloudDebugLog"; |
| 20 | 20 | |
| 21 | const managedRelayAtomRuntime = Atom.runtime(runtimeContextLayer); | |
| 21 | const managedRelayAtomRuntime = Atom.runtime(Runtime.layer); | |
| 22 | 22 | |
| 23 | 23 | export const managedRelayQueryManager = createManagedRelayQueryManager(managedRelayAtomRuntime, { |
| 24 | 24 | onQueryEvent: (event) => |
apps/mobile/src/features/observability/tracing.test.ts+12-12| ... | ... | @@ -4,10 +4,10 @@ import * as Effect from "effect/Effect"; |
| 4 | 4 | import * as Layer from "effect/Layer"; |
| 5 | 5 | import { vi } from "vite-plus/test"; |
| 6 | 6 | |
| 7 | import { remoteHttpClientLayer } from "@t3tools/client-runtime/rpc"; | |
| 7 | import { layerRemoteHttpClient } from "@t3tools/client-runtime/rpc"; | |
| 8 | 8 | import { withRelayClientTracing } from "@t3tools/shared/relayTracing"; |
| 9 | 9 | |
| 10 | import { makeTracingLayer } from "./tracing"; | |
| 10 | import * as Tracing from "./tracing"; | |
| 11 | 11 | |
| 12 | 12 | vi.mock("expo-constants", () => ({ |
| 13 | 13 | default: { |
| ... | ... | @@ -19,7 +19,7 @@ vi.mock("expo-constants", () => ({ |
| 19 | 19 | |
| 20 | 20 | it.effect("exports spans through the scoped mobile OTLP layer", () => { |
| 21 | 21 | const fetchFn = vi.fn<typeof fetch>(async () => new Response(null, { status: 202 })); |
| 22 | const tracingLayer = makeTracingLayer( | |
| 22 | const layerTracing = Tracing.layerFromConfig( | |
| 23 | 23 | { |
| 24 | 24 | tracesUrl: "https://api.axiom.test/v1/traces", |
| 25 | 25 | tracesDataset: "mobile-traces", |
| ... | ... | @@ -29,13 +29,13 @@ it.effect("exports spans through the scoped mobile OTLP layer", () => { |
| 29 | 29 | appVariant: "test", |
| 30 | 30 | serviceVersion: "1.2.3", |
| 31 | 31 | }, |
| 32 | ).pipe(Layer.provide(remoteHttpClientLayer(fetchFn))); | |
| 33 | const tracedApplication = Layer.effectDiscard( | |
| 32 | ).pipe(Layer.provide(layerRemoteHttpClient(fetchFn))); | |
| 33 | const layerTracedApplication = Layer.effectDiscard( | |
| 34 | 34 | Effect.void.pipe(Effect.withSpan("mobile.test.span"), withRelayClientTracing), |
| 35 | ).pipe(Layer.provide(tracingLayer)); | |
| 35 | ).pipe(Layer.provide(layerTracing)); | |
| 36 | 36 | |
| 37 | 37 | return Effect.gen(function* () { |
| 38 | yield* Layer.build(tracedApplication); | |
| 38 | yield* Layer.build(layerTracedApplication); | |
| 39 | 39 | |
| 40 | 40 | expect(fetchFn).not.toHaveBeenCalled(); |
| 41 | 41 | }).pipe( |
| ... | ... | @@ -55,7 +55,7 @@ it.effect("exports spans through the scoped mobile OTLP layer", () => { |
| 55 | 55 | |
| 56 | 56 | it.effect("does not let OTLP serialization failures alter application effects", () => { |
| 57 | 57 | const fetchFn = vi.fn<typeof fetch>(async () => new Response(null, { status: 202 })); |
| 58 | const tracingLayer = makeTracingLayer( | |
| 58 | const layerTracing = Tracing.layerFromConfig( | |
| 59 | 59 | { |
| 60 | 60 | tracesUrl: "https://api.axiom.test/v1/traces", |
| 61 | 61 | tracesDataset: "mobile-traces", |
| ... | ... | @@ -65,9 +65,9 @@ it.effect("does not let OTLP serialization failures alter application effects", |
| 65 | 65 | appVariant: "test", |
| 66 | 66 | serviceVersion: "1.2.3", |
| 67 | 67 | }, |
| 68 | ).pipe(Layer.provide(remoteHttpClientLayer(fetchFn))); | |
| 68 | ).pipe(Layer.provide(layerRemoteHttpClient(fetchFn))); | |
| 69 | 69 | const failure = { durationNanos: 1n }; |
| 70 | const tracedApplication = Layer.effectDiscard( | |
| 70 | const layerTracedApplication = Layer.effectDiscard( | |
| 71 | 71 | Effect.fail(failure).pipe( |
| 72 | 72 | Effect.withSpan("mobile.test.failed-span"), |
| 73 | 73 | withRelayClientTracing, |
| ... | ... | @@ -81,9 +81,9 @@ it.effect("does not let OTLP serialization failures alter application effects", |
| 81 | 81 | : Effect.die(new Error("Expected the original typed failure.")); |
| 82 | 82 | }), |
| 83 | 83 | ), |
| 84 | ).pipe(Layer.provide(tracingLayer)); | |
| 84 | ).pipe(Layer.provide(layerTracing)); | |
| 85 | 85 | |
| 86 | return Layer.build(tracedApplication).pipe( | |
| 86 | return Layer.build(layerTracedApplication).pipe( | |
| 87 | 87 | Effect.scoped, |
| 88 | 88 | Effect.andThen( |
| 89 | 89 | Effect.sync(() => { |
apps/mobile/src/features/observability/tracing.ts+4-4| ... | ... | @@ -1,5 +1,5 @@ |
| 1 | 1 | import Constants from "expo-constants"; |
| 2 | import { makeRelayClientTracingLayer } from "@t3tools/shared/relayTracing"; | |
| 2 | import * as RelayTracing from "@t3tools/shared/relayTracing"; | |
| 3 | 3 | |
| 4 | 4 | import { hasTracingPublicConfig, resolveCloudPublicConfig } from "../cloud/publicConfig"; |
| 5 | 5 | |
| ... | ... | @@ -23,8 +23,8 @@ export function resolveTracingConfig(): TracingConfig | null { |
| 23 | 23 | return { tracesUrl, tracesDataset, tracesToken }; |
| 24 | 24 | } |
| 25 | 25 | |
| 26 | export function makeTracingLayer(config: TracingConfig | null, resource: TracingResource) { | |
| 27 | return makeRelayClientTracingLayer(config, { | |
| 26 | export function layerFromConfig(config: TracingConfig | null, resource: TracingResource) { | |
| 27 | return RelayTracing.layer(config, { | |
| 28 | 28 | serviceName: "t3code-mobile", |
| 29 | 29 | serviceVersion: resource.serviceVersion, |
| 30 | 30 | runtime: "react-native", |
| ... | ... | @@ -32,7 +32,7 @@ export function makeTracingLayer(config: TracingConfig | null, resource: Tracing |
| 32 | 32 | }); |
| 33 | 33 | } |
| 34 | 34 | |
| 35 | export const tracingLayer = makeTracingLayer(resolveTracingConfig(), { | |
| 35 | export const layer = layerFromConfig(resolveTracingConfig(), { | |
| 36 | 36 | serviceVersion: Constants.expoConfig?.version, |
| 37 | 37 | appVariant: |
| 38 | 38 | typeof Constants.expoConfig?.extra?.appVariant === "string" |
apps/mobile/src/lib/hot-swappable-atom-runtime.test.ts+8-8| ... | ... | @@ -10,7 +10,7 @@ class RuntimeValue extends Context.Service<RuntimeValue, { readonly value: strin |
| 10 | 10 | "t3/mobile/test/RuntimeValue", |
| 11 | 11 | ) {} |
| 12 | 12 | |
| 13 | function runtimeLayer(value: string, events: string[]) { | |
| 13 | function layerRuntime(value: string, events: string[]) { | |
| 14 | 14 | return Layer.effect( |
| 15 | 15 | RuntimeValue, |
| 16 | 16 | Effect.acquireRelease( |
| ... | ... | @@ -26,7 +26,7 @@ function runtimeLayer(value: string, events: string[]) { |
| 26 | 26 | ); |
| 27 | 27 | } |
| 28 | 28 | |
| 29 | function runtimeLayerWithRelease( | |
| 29 | function layerRuntimeWithRelease( | |
| 30 | 30 | value: string, |
| 31 | 31 | events: string[], |
| 32 | 32 | release: Effect.Effect<void> = Effect.sync(() => { |
| ... | ... | @@ -56,7 +56,7 @@ describe("hotSwappableAtomRuntime", () => { |
| 56 | 56 | id, |
| 57 | 57 | hotModule: { accept }, |
| 58 | 58 | registry, |
| 59 | layer: runtimeLayer("first", events), | |
| 59 | layer: layerRuntime("first", events), | |
| 60 | 60 | }); |
| 61 | 61 | const valueAtom = runtime.atom(RuntimeValue.pipe(Effect.map((service) => service.value))); |
| 62 | 62 | const values: string[] = []; |
| ... | ... | @@ -81,13 +81,13 @@ describe("hotSwappableAtomRuntime", () => { |
| 81 | 81 | id, |
| 82 | 82 | hotModule: { accept }, |
| 83 | 83 | registry, |
| 84 | layer: runtimeLayer("second", events), | |
| 84 | layer: layerRuntime("second", events), | |
| 85 | 85 | }); |
| 86 | 86 | hotSwappableAtomRuntime({ |
| 87 | 87 | id, |
| 88 | 88 | hotModule: { accept }, |
| 89 | 89 | registry, |
| 90 | layer: runtimeLayer("third", events), | |
| 90 | layer: layerRuntime("third", events), | |
| 91 | 91 | }); |
| 92 | 92 | registry.set(unrelatedAtom, 8); |
| 93 | 93 | |
| ... | ... | @@ -120,7 +120,7 @@ describe("hotSwappableAtomRuntime", () => { |
| 120 | 120 | vi.stubGlobal("__DEV__", false); |
| 121 | 121 | const registry = AtomRegistry.make(); |
| 122 | 122 | const accept = vi.fn(); |
| 123 | const layer = runtimeLayer("production", []); | |
| 123 | const layer = layerRuntime("production", []); | |
| 124 | 124 | |
| 125 | 125 | const first = hotSwappableAtomRuntime({ |
| 126 | 126 | id: "production", |
| ... | ... | @@ -163,7 +163,7 @@ describe("hotSwappableAtomRuntime", () => { |
| 163 | 163 | id, |
| 164 | 164 | hotModule: { accept() {} }, |
| 165 | 165 | registry, |
| 166 | layer: runtimeLayerWithRelease("first", events, firstRelease), | |
| 166 | layer: layerRuntimeWithRelease("first", events, firstRelease), | |
| 167 | 167 | }); |
| 168 | 168 | const valueAtom = runtime.atom(RuntimeValue.pipe(Effect.map((service) => service.value))); |
| 169 | 169 | const values: string[] = []; |
| ... | ... | @@ -176,7 +176,7 @@ describe("hotSwappableAtomRuntime", () => { |
| 176 | 176 | id, |
| 177 | 177 | hotModule: { accept() {} }, |
| 178 | 178 | registry, |
| 179 | layer: runtimeLayer("second", events), | |
| 179 | layer: layerRuntime("second", events), | |
| 180 | 180 | }); |
| 181 | 181 | |
| 182 | 182 | expect(events).toEqual(["acquire:first", "release:start:first", "acquire:second"]); |
apps/mobile/src/lib/runtime.ts+16-16| ... | ... | @@ -2,12 +2,12 @@ import * as Layer from "effect/Layer"; |
| 2 | 2 | import * as ManagedRuntime from "effect/ManagedRuntime"; |
| 3 | 3 | import * as Socket from "effect/socket/Socket"; |
| 4 | 4 | |
| 5 | import { remoteHttpClientLayer } from "@t3tools/client-runtime/rpc"; | |
| 5 | import { layerRemoteHttpClient } from "@t3tools/client-runtime/rpc"; | |
| 6 | 6 | |
| 7 | import { cryptoLayer } from "../features/cloud/dpop"; | |
| 8 | import { managedRelayClientLayer } from "../features/cloud/managedRelayLayer"; | |
| 7 | import * as Dpop from "../features/cloud/dpop"; | |
| 8 | import * as ManagedRelayLayer from "../features/cloud/managedRelayLayer"; | |
| 9 | 9 | import { resolveCloudPublicConfig } from "../features/cloud/publicConfig"; |
| 10 | import { tracingLayer } from "../features/observability/tracing"; | |
| 10 | import * as Tracing from "../features/observability/tracing"; | |
| 11 | 11 | import * as Persistence from "../persistence/layer"; |
| 12 | 12 | import { disposeOnFoundationReplace, type FoundationHotModule } from "./foundation-fast-refresh"; |
| 13 | 13 | |
| ... | ... | @@ -17,32 +17,32 @@ function configuredRelayUrl(): string { |
| 17 | 17 | return resolveCloudPublicConfig().relay.url ?? "http://relay.invalid"; |
| 18 | 18 | } |
| 19 | 19 | |
| 20 | const httpClientLayer = remoteHttpClientLayer(fetch); | |
| 20 | const layerHttpClient = layerRemoteHttpClient(fetch); | |
| 21 | 21 | |
| 22 | 22 | type RuntimeLayerSource = |
| 23 | | ReturnType<typeof managedRelayClientLayer> | |
| 23 | | ReturnType<typeof ManagedRelayLayer.layer> | |
| 24 | 24 | | typeof Socket.layerWebSocketConstructorGlobal |
| 25 | | typeof cryptoLayer | |
| 26 | | typeof httpClientLayer | |
| 25 | | typeof Dpop.layer | |
| 26 | | typeof layerHttpClient | |
| 27 | 27 | | typeof Persistence.layer |
| 28 | | typeof tracingLayer; | |
| 28 | | typeof Tracing.layer; | |
| 29 | 29 | |
| 30 | const runtimeLayer = Layer.merge( | |
| 31 | managedRelayClientLayer(configuredRelayUrl()), | |
| 30 | const layerRuntime = Layer.merge( | |
| 31 | ManagedRelayLayer.layer(configuredRelayUrl()), | |
| 32 | 32 | Socket.layerWebSocketConstructorGlobal, |
| 33 | 33 | ).pipe( |
| 34 | Layer.provideMerge(cryptoLayer), | |
| 35 | Layer.provideMerge(httpClientLayer), | |
| 36 | Layer.provideMerge(tracingLayer.pipe(Layer.provide(httpClientLayer))), | |
| 34 | Layer.provideMerge(Dpop.layer), | |
| 35 | Layer.provideMerge(layerHttpClient), | |
| 36 | Layer.provideMerge(Tracing.layer.pipe(Layer.provide(layerHttpClient))), | |
| 37 | 37 | Layer.provideMerge(Persistence.layer), |
| 38 | 38 | ); |
| 39 | 39 | |
| 40 | 40 | export const runtime: ManagedRuntime.ManagedRuntime< |
| 41 | 41 | Layer.Success<RuntimeLayerSource>, |
| 42 | 42 | Layer.Error<RuntimeLayerSource> |
| 43 | > = ManagedRuntime.make(runtimeLayer); | |
| 43 | > = ManagedRuntime.make(layerRuntime); | |
| 44 | 44 | |
| 45 | export const runtimeContextLayer: Layer.Layer< | |
| 45 | export const layer: Layer.Layer< | |
| 46 | 46 | Layer.Success<RuntimeLayerSource>, |
| 47 | 47 | Layer.Error<RuntimeLayerSource> |
| 48 | 48 | > = Layer.effectContext(runtime.contextEffect); |
apps/mobile/src/persistence/layer.ts+4-4| ... | ... | @@ -6,11 +6,11 @@ import * as MobilePreferences from "./mobile-preferences"; |
| 6 | 6 | import * as MobileSecureStorage from "./mobile-secure-storage"; |
| 7 | 7 | import * as MobileStorage from "./mobile-storage"; |
| 8 | 8 | |
| 9 | const baseLayer = Layer.merge(MobileDatabase.layer, MobileSecureStorage.layer); | |
| 10 | const dependentLayer = Layer.mergeAll( | |
| 9 | const layerBase = Layer.merge(MobileDatabase.layer, MobileSecureStorage.layer); | |
| 10 | const layerDependent = Layer.mergeAll( | |
| 11 | 11 | MobilePreferences.layer, |
| 12 | 12 | MobileStorage.layer, |
| 13 | 13 | EnvironmentCacheStore.layer, |
| 14 | ).pipe(Layer.provide(baseLayer)); | |
| 14 | ).pipe(Layer.provide(layerBase)); | |
| 15 | 15 | |
| 16 | export const layer = Layer.merge(baseLayer, dependentLayer); | |
| 16 | export const layer = Layer.merge(layerBase, layerDependent); |
apps/mobile/src/state/client-cache-state.ts+1-1| ... | ... | @@ -59,7 +59,7 @@ function aggregateCacheSummary( |
| 59 | 59 | }; |
| 60 | 60 | } |
| 61 | 61 | |
| 62 | const clientCacheRuntime = Atom.runtime(Runtime.runtimeContextLayer); | |
| 62 | const clientCacheRuntime = Atom.runtime(Runtime.layer); | |
| 63 | 63 | |
| 64 | 64 | export const clientCacheSummaryAtom = clientCacheRuntime |
| 65 | 65 | .atom( |
apps/mobile/src/state/preferences.test.ts+1-1| ... | ... | @@ -22,7 +22,7 @@ vi.mock("../lib/runtime", async () => { |
| 22 | 22 | const Layer = await import("effect/Layer"); |
| 23 | 23 | return { |
| 24 | 24 | runtime: { runPromise: vi.fn() }, |
| 25 | runtimeContextLayer: Layer.empty, | |
| 25 | layer: Layer.empty, | |
| 26 | 26 | }; |
| 27 | 27 | }); |
| 28 | 28 |
apps/mobile/src/state/preferences.ts+1-1| ... | ... | @@ -136,7 +136,7 @@ export function createMobilePreferencesState( |
| 136 | 136 | return { preferencesAtom, updatePreferencesAtom } as const; |
| 137 | 137 | } |
| 138 | 138 | |
| 139 | const mobilePreferencesRuntime = Atom.runtime(Runtime.runtimeContextLayer); | |
| 139 | const mobilePreferencesRuntime = Atom.runtime(Runtime.layer); | |
| 140 | 140 | export const mobilePreferencesState = createMobilePreferencesState(mobilePreferencesRuntime); |
| 141 | 141 | |
| 142 | 142 | export const mobilePreferencesAtom = mobilePreferencesState.preferencesAtom; |
apps/server/integration/NetworkTransferMeasurement.integration.ts+4-4| ... | ... | @@ -165,19 +165,19 @@ function makeWebSocketTransferRecorder(): WebSocketTransferRecorder { |
| 165 | 165 | }; |
| 166 | 166 | } |
| 167 | 167 | |
| 168 | function countingWsRpcProtocolLayer(input: { | |
| 168 | function layerCountingWsRpcProtocol(input: { | |
| 169 | 169 | readonly url: string; |
| 170 | 170 | readonly cookie: string; |
| 171 | 171 | readonly recorder: WebSocketTransferRecorder; |
| 172 | 172 | }) { |
| 173 | 173 | // Socket.makeWebSocket only ever passes its `protocols` option here. |
| 174 | const webSocketConstructorLayer = Layer.succeed(Socket.WebSocketConstructor, (url, protocols) => | |
| 174 | const layerWebSocketConstructor = Layer.succeed(Socket.WebSocketConstructor, (url, protocols) => | |
| 175 | 175 | input.recorder.connect(url, protocols as string | string[] | undefined, input.cookie), |
| 176 | 176 | ); |
| 177 | 177 | return RpcClient.layerProtocolSocket().pipe( |
| 178 | 178 | Layer.provide( |
| 179 | 179 | Socket.layerWebSocket(input.url, { openTimeout: "10 seconds" }).pipe( |
| 180 | Layer.provide(webSocketConstructorLayer), | |
| 180 | Layer.provide(layerWebSocketConstructor), | |
| 181 | 181 | ), |
| 182 | 182 | ), |
| 183 | 183 | Layer.provide(RpcSerialization.layerJson), |
| ... | ... | @@ -207,7 +207,7 @@ export const openMeasuredWsClient = Effect.fn("TransferBudget.openMeasuredWsClie |
| 207 | 207 | const parent = yield* Effect.scope; |
| 208 | 208 | const scope = yield* Scope.fork(parent); |
| 209 | 209 | const protocol = yield* Layer.buildWithScope( |
| 210 | countingWsRpcProtocolLayer({ url: input.url, cookie: input.cookie, recorder }), | |
| 210 | layerCountingWsRpcProtocol({ url: input.url, cookie: input.cookie, recorder }), | |
| 211 | 211 | scope, |
| 212 | 212 | ); |
| 213 | 213 | const client = yield* makeCountingWsRpcClient.pipe( |
apps/server/integration/transferBudgetV2.integration.test.ts+22-22| ... | ... | @@ -35,8 +35,8 @@ import * as HttpApi from "effect/http-api/HttpApi"; |
| 35 | 35 | import * as HttpApiBuilder from "effect/http-api/HttpApiBuilder"; |
| 36 | 36 | import { HttpRouter, HttpServer } from "effect/http"; |
| 37 | 37 | import { Rpc, RpcGroup, RpcServer, RpcSerialization } from "effect/rpc"; |
| 38 | import { SqlitePersistenceMemory } from "../src/persistence/Layers/Sqlite.ts"; | |
| 39 | import { OrchestrationEventStoreLive } from "../src/persistence/Layers/OrchestrationEventStore.ts"; | |
| 38 | import * as SqlitePersistence from "../src/persistence/Layers/Sqlite.ts"; | |
| 39 | import * as OrchestrationEventStore from "../src/persistence/Layers/OrchestrationEventStore.ts"; | |
| 40 | 40 | import * as EventStore from "../src/orchestration-v2/EventStore.ts"; |
| 41 | 41 | import * as EventSink from "../src/orchestration-v2/EventSink.ts"; |
| 42 | 42 | import * as ProjectionStore from "../src/orchestration-v2/ProjectionStore.ts"; |
| ... | ... | @@ -44,8 +44,8 @@ import * as ThreadManagementService from "../src/orchestration-v2/ThreadManageme |
| 44 | 44 | import * as ProjectStore from "../src/orchestration-v2/ProjectStore.ts"; |
| 45 | 45 | import * as ProjectService from "../src/project/ProjectService.ts"; |
| 46 | 46 | import * as ProjectEnrichmentService from "../src/project/ProjectEnrichmentService.ts"; |
| 47 | import { orchestrationHttpApiLayer } from "../src/orchestration-v2/http.ts"; | |
| 48 | import { httpCompressionLayer } from "../src/http.ts"; | |
| 47 | import * as OrchestrationHttp from "../src/orchestration-v2/http.ts"; | |
| 48 | import * as ServerHttp from "../src/http.ts"; | |
| 49 | 49 | import { subscribeOrchestrationV2Thread, subscribeOrchestrationV2Shell } from "../src/ws.ts"; |
| 50 | 50 | import { |
| 51 | 51 | measureHttpGet, |
| ... | ... | @@ -69,15 +69,15 @@ const decodeShellSnapshot = Schema.decodeUnknownEffect( |
| 69 | 69 | Schema.fromJsonString(Schema.toCodecJson(OrchestrationV2ShellSnapshot)), |
| 70 | 70 | ); |
| 71 | 71 | |
| 72 | const persistence = EventSink.layer.pipe( | |
| 72 | const layerPersistence = EventSink.layer.pipe( | |
| 73 | 73 | Layer.provideMerge(EventStore.layerFromOrchestrationEventStore), |
| 74 | 74 | Layer.provideMerge(ProjectionStore.layer), |
| 75 | Layer.provideMerge(OrchestrationEventStoreLive), | |
| 76 | Layer.provideMerge(SqlitePersistenceMemory), | |
| 75 | Layer.provideMerge(OrchestrationEventStore.layer), | |
| 76 | Layer.provideMerge(SqlitePersistence.layerMemory), | |
| 77 | 77 | ); |
| 78 | 78 | // The facade's import/command paths are outside this measurement. Its reads and |
| 79 | 79 | // stream delegate to the real SQL projection and event sink, as the runtime does. |
| 80 | const management = Layer.unwrap( | |
| 80 | const layerManagement = Layer.unwrap( | |
| 81 | 81 | Effect.gen(function* () { |
| 82 | 82 | const projections = yield* ProjectionStore.ProjectionStoreV2; |
| 83 | 83 | const sink = yield* EventSink.EventSinkV2; |
| ... | ... | @@ -93,7 +93,7 @@ const management = Layer.unwrap( |
| 93 | 93 | }); |
| 94 | 94 | }), |
| 95 | 95 | ); |
| 96 | const enrichment = Layer.unwrap( | |
| 96 | const layerEnrichment = Layer.unwrap( | |
| 97 | 97 | Effect.gen(function* () { |
| 98 | 98 | const changes = yield* PubSub.unbounded<never>(); |
| 99 | 99 | return Layer.mock(ProjectEnrichmentService.ProjectEnrichmentService)({ |
| ... | ... | @@ -108,16 +108,16 @@ const enrichment = Layer.unwrap( |
| 108 | 108 | }), |
| 109 | 109 | ); |
| 110 | 110 | // The transfer history has no project events, so shell streams never read a project shell. |
| 111 | const services = management.pipe( | |
| 111 | const layerServices = layerManagement.pipe( | |
| 112 | 112 | Layer.provideMerge(ProjectStore.layer), |
| 113 | 113 | Layer.provideMerge(Layer.mock(ProjectService.ProjectService)({})), |
| 114 | Layer.provideMerge(enrichment), | |
| 115 | Layer.provideMerge(persistence), | |
| 114 | Layer.provideMerge(layerEnrichment), | |
| 115 | Layer.provideMerge(layerPersistence), | |
| 116 | 116 | ); |
| 117 | 117 | class TransferApi extends HttpApi.make("environment").add( |
| 118 | 118 | EnvironmentHttpApi.groups.orchestration, |
| 119 | 119 | ) {} |
| 120 | const auth = Layer.succeed(EnvironmentAuthenticatedAuth, (effect) => | |
| 120 | const layerAuth = Layer.succeed(EnvironmentAuthenticatedAuth, (effect) => | |
| 121 | 121 | effect.pipe( |
| 122 | 122 | Effect.provideService(EnvironmentAuthenticatedPrincipal, { |
| 123 | 123 | sessionId: AuthSessionId.make("transfer-session"), |
| ... | ... | @@ -141,22 +141,22 @@ const group = RpcGroup.make( |
| 141 | 141 | stream: true, |
| 142 | 142 | }), |
| 143 | 143 | ); |
| 144 | const handlers = group.toLayer({ | |
| 144 | const layerHandlers = group.toLayer({ | |
| 145 | 145 | [ORCHESTRATION_V2_WS_METHODS.subscribeThread]: (input) => |
| 146 | 146 | Stream.unwrap(subscribeOrchestrationV2Thread(input)), |
| 147 | 147 | [ORCHESTRATION_V2_WS_METHODS.subscribeShell]: (input) => |
| 148 | 148 | Stream.unwrap(subscribeOrchestrationV2Shell(input)), |
| 149 | 149 | }); |
| 150 | const routes = Layer.mergeAll( | |
| 150 | const layerRoutes = Layer.mergeAll( | |
| 151 | 151 | HttpApiBuilder.layer(TransferApi).pipe( |
| 152 | Layer.provide(orchestrationHttpApiLayer), | |
| 153 | Layer.provide(auth), | |
| 152 | Layer.provide(OrchestrationHttp.layer), | |
| 153 | Layer.provide(layerAuth), | |
| 154 | 154 | ), |
| 155 | 155 | RpcServer.layerHttp({ group, path: "/ws", protocol: "websocket" }).pipe( |
| 156 | Layer.provide(handlers), | |
| 156 | Layer.provide(layerHandlers), | |
| 157 | 157 | Layer.provide(RpcSerialization.layerJson), |
| 158 | 158 | ), |
| 159 | ).pipe(Layer.provide(httpCompressionLayer), Layer.provide(NodeHttpPlatform.layer)); | |
| 159 | ).pipe(Layer.provide(ServerHttp.layerHttpCompression), Layer.provide(NodeHttpPlatform.layer)); | |
| 160 | 160 | |
| 161 | 161 | function difference( |
| 162 | 162 | after: { wireBytes: number; decodedBytes: number; messages: number }, |
| ... | ... | @@ -229,9 +229,9 @@ it.live( |
| 229 | 229 | yield* sink.write({ events: [threadCreated(provider)] }); |
| 230 | 230 | for (let index = 0; index < TRANSFER_HISTORY_TURN_COUNT; index++) |
| 231 | 231 | yield* sink.write({ events: turnEvents(provider, index, false) }); |
| 232 | const context = yield* Effect.context<Layer.Success<typeof services>>(); | |
| 232 | const context = yield* Effect.context<Layer.Success<typeof layerServices>>(); | |
| 233 | 233 | const server = yield* Layer.build( |
| 234 | HttpRouter.serve(routes, { disableListenLog: true }).pipe( | |
| 234 | HttpRouter.serve(layerRoutes, { disableListenLog: true }).pipe( | |
| 235 | 235 | Layer.provide(Layer.succeedContext(context)), |
| 236 | 236 | Layer.provideMerge( |
| 237 | 237 | NodeHttpServer.layer(NodeHttp.createServer, { |
| ... | ... | @@ -334,7 +334,7 @@ it.live( |
| 334 | 334 | reconnectSqlStatements: counter.count() - reconnectSqlStart, |
| 335 | 335 | } satisfies TransferBudgetRun; |
| 336 | 336 | }).pipe( |
| 337 | Effect.provide(Layer.fresh(services).pipe(Layer.provideMerge(NodeServices.layer))), | |
| 337 | Effect.provide(Layer.fresh(layerServices).pipe(Layer.provideMerge(NodeServices.layer))), | |
| 338 | 338 | Effect.withTracer(counter.tracer), |
| 339 | 339 | ), |
| 340 | 340 | ); |
apps/server/scripts/measure-pr-preview.ts+4-4| ... | ... | @@ -22,7 +22,7 @@ if (!repository || numbers.length === 0 || numbers.some((number) => !/^\d+$/.tes |
| 22 | 22 | |
| 23 | 23 | type Read = { graphqlRequests: number; restRequests: number; cost: number; debug: string }; |
| 24 | 24 | const reads: Read[] = []; |
| 25 | const measuredProcess = Layer.effect( | |
| 25 | const layerMeasuredProcess = Layer.effect( | |
| 26 | 26 | VcsProcess.VcsProcess, |
| 27 | 27 | Effect.gen(function* () { |
| 28 | 28 | const vcs = yield* VcsProcess.VcsProcess; |
| ... | ... | @@ -47,9 +47,9 @@ const measuredProcess = Layer.effect( |
| 47 | 47 | }), |
| 48 | 48 | ).pipe(Layer.provide(VcsProcess.layer), Layer.provide(NodeServices.layer)); |
| 49 | 49 | |
| 50 | const services = GitHubPullRequestCli.layer.pipe( | |
| 50 | const layerServices = GitHubPullRequestCli.layer.pipe( | |
| 51 | 51 | Layer.provide(GitHubCli.layer), |
| 52 | Layer.provideMerge(measuredProcess), | |
| 52 | Layer.provideMerge(layerMeasuredProcess), | |
| 53 | 53 | ); |
| 54 | 54 | |
| 55 | 55 | const decodeJson = Schema.decodeSync(Schema.fromJsonString(Schema.Unknown)); |
| ... | ... | @@ -122,7 +122,7 @@ for (const mode of ["detail", "preview"] as const) { |
| 122 | 122 | }), |
| 123 | 123 | ); |
| 124 | 124 | }), |
| 125 | ).pipe(Effect.provide(services)), | |
| 125 | ).pipe(Effect.provide(layerServices)), | |
| 126 | 126 | ); |
| 127 | 127 | for (const row of rows) await Effect.runPromise(Console.log(encodeJson(row))); |
| 128 | 128 | } |
apps/server/scripts/record-codex-app-server-replay-fixture.ts+6-6| ... | ... | @@ -1145,7 +1145,7 @@ function makeRecorder({ |
| 1145 | 1145 | }); |
| 1146 | 1146 | } |
| 1147 | 1147 | |
| 1148 | function makeCodexLayer({ recorder }: { readonly recorder: Recorder }) { | |
| 1148 | function layerCodex({ recorder }: { readonly recorder: Recorder }) { | |
| 1149 | 1149 | const clientRequestMethodById = new Map<string, string>(); |
| 1150 | 1150 | const serverRequestMethodById = new Map<string, string>(); |
| 1151 | 1151 | const clientOptions: CodexClient.CodexAppServerClientOptions = { |
| ... | ... | @@ -1465,7 +1465,7 @@ function runReplaySession({ |
| 1465 | 1465 | return thread; |
| 1466 | 1466 | }).pipe( |
| 1467 | 1467 | Effect.provide( |
| 1468 | makeCodexLayer({ | |
| 1468 | layerCodex({ | |
| 1469 | 1469 | recorder, |
| 1470 | 1470 | }), |
| 1471 | 1471 | ), |
| ... | ... | @@ -1486,7 +1486,7 @@ function runReplaySession({ |
| 1486 | 1486 | yield* runTurnStep(client, thread.thread.id, secondStep); |
| 1487 | 1487 | }).pipe( |
| 1488 | 1488 | Effect.provide( |
| 1489 | makeCodexLayer({ | |
| 1489 | layerCodex({ | |
| 1490 | 1490 | recorder, |
| 1491 | 1491 | }), |
| 1492 | 1492 | ), |
| ... | ... | @@ -1519,7 +1519,7 @@ function runReplaySession({ |
| 1519 | 1519 | yield* runTurnStep(client, thread.thread.id, first); |
| 1520 | 1520 | yield* runTurnStep(client, thread.thread.id, second); |
| 1521 | 1521 | return thread.thread.id; |
| 1522 | }).pipe(Effect.provide(makeCodexLayer({ recorder }))); | |
| 1522 | }).pipe(Effect.provide(layerCodex({ recorder }))); | |
| 1523 | 1523 | |
| 1524 | 1524 | yield* recorder.writeRecord({ |
| 1525 | 1525 | type: "runtime_exit", |
| ... | ... | @@ -1536,7 +1536,7 @@ function runReplaySession({ |
| 1536 | 1536 | yield* revertCodexThread(client, threadId, rollback.numTurns); |
| 1537 | 1537 | yield* client.request("thread/resume", resumeParams); |
| 1538 | 1538 | yield* runTurnStep(client, threadId, after); |
| 1539 | }).pipe(Effect.provide(makeCodexLayer({ recorder }))); | |
| 1539 | }).pipe(Effect.provide(layerCodex({ recorder }))); | |
| 1540 | 1540 | return; |
| 1541 | 1541 | } |
| 1542 | 1542 | |
| ... | ... | @@ -1587,7 +1587,7 @@ function runReplaySession({ |
| 1587 | 1587 | } |
| 1588 | 1588 | }).pipe( |
| 1589 | 1589 | Effect.provide( |
| 1590 | makeCodexLayer({ | |
| 1590 | layerCodex({ | |
| 1591 | 1591 | recorder, |
| 1592 | 1592 | }), |
| 1593 | 1593 | ), |
apps/server/scripts/record-grok-acp-replay-fixture.ts+5-7| ... | ... | @@ -40,10 +40,8 @@ import { provideDeterministicTestRuntime } from "../src/orchestration-v2/testkit |
| 40 | 40 | import { ORCHESTRATOR_REPLAY_FIXTURES } from "../src/orchestration-v2/testkit/fixtures/index.ts"; |
| 41 | 41 | import { materializeFixtureInput } from "../src/orchestration-v2/testkit/fixtures/shared.ts"; |
| 42 | 42 | import { runOrchestratorV2Scenario } from "../src/orchestration-v2/testkit/OrchestratorScenario.ts"; |
| 43 | import { | |
| 44 | makeOrchestratorV2ReplayLayerWithRegistry, | |
| 45 | makeReplayServerConfig, | |
| 46 | } from "../src/orchestration-v2/testkit/ProviderReplayHarness.ts"; | |
| 43 | import { makeReplayServerConfig } from "../src/orchestration-v2/testkit/ProviderReplayHarness.ts"; | |
| 44 | import * as ProviderReplayHarness from "../src/orchestration-v2/testkit/ProviderReplayHarness.ts"; | |
| 47 | 45 | import { checkpointWorkspace } from "../src/orchestration-v2/testkit/ReplayFixtureWorkspace.ts"; |
| 48 | 46 | import { makeGrokAcpRuntime } from "../src/provider/acp/GrokAcpSupport.ts"; |
| 49 | 47 | import { buildRuntimeInstructions } from "../src/provider/RuntimeInstructions.ts"; |
| ... | ... | @@ -444,7 +442,7 @@ const recordScenario = Effect.fn("recordGrokScenario")(function* (fixtureName: s |
| 444 | 442 | |
| 445 | 443 | const tee = makeWireTee(); |
| 446 | 444 | const settings = { ...DEFAULT_GROK_SETTINGS, binaryPath: process.env.T3_GROK_BIN ?? "grok" }; |
| 447 | const registryLayer = ProviderAdapterRegistry.makeLayerEffect( | |
| 445 | const layerRegistry = ProviderAdapterRegistry.layerFromAdaptersEffect( | |
| 448 | 446 | Effect.gen(function* () { |
| 449 | 447 | const childProcessSpawner = yield* ChildProcessSpawner.ChildProcessSpawner; |
| 450 | 448 | const environment = yield* HostProcessEnvironment; |
| ... | ... | @@ -532,9 +530,9 @@ const recordScenario = Effect.fn("recordGrokScenario")(function* (fixtureName: s |
| 532 | 530 | ); |
| 533 | 531 | const result = yield* runOrchestratorV2Scenario(scenario, { afterSteps: waitForGrokIdle }).pipe( |
| 534 | 532 | Effect.provide( |
| 535 | makeOrchestratorV2ReplayLayerWithRegistry( | |
| 533 | ProviderReplayHarness.layerWithRegistry( | |
| 536 | 534 | scenario, |
| 537 | registryLayer, | |
| 535 | layerRegistry, | |
| 538 | 536 | variant.runContinuationWorker === true ? { runContinuationWorker: true } : {}, |
| 539 | 537 | ), |
| 540 | 538 | ), |
apps/server/scripts/record-pi-rpc-replay-fixture.ts+4-4| ... | ... | @@ -23,12 +23,12 @@ import { TestClock } from "effect/testing"; |
| 23 | 23 | import { ChildProcess, ChildProcessSpawner } from "effect/process"; |
| 24 | 24 | |
| 25 | 25 | import { |
| 26 | makePiProviderAdapterRegistryLayer, | |
| 27 | 26 | makePiRecordingSpawner, |
| 28 | 27 | PI_REPLAY_ANY, |
| 29 | 28 | PI_RPC_REPLAY_PROTOCOL, |
| 30 | 29 | PiOrchestratorReplayHarness, |
| 31 | 30 | } from "../src/orchestration-v2/Adapters/PiAdapterV2.testkit.ts"; |
| 31 | import * as PiAdapterV2Testkit from "../src/orchestration-v2/Adapters/PiAdapterV2.testkit.ts"; | |
| 32 | 32 | import { PI_PROVIDER } from "../src/orchestration-v2/Adapters/PiAdapterV2.ts"; |
| 33 | 33 | import * as IdAllocator from "../src/orchestration-v2/IdAllocator.ts"; |
| 34 | 34 | import { provideDeterministicTestRuntime } from "../src/orchestration-v2/testkit/DeterministicRuntime.ts"; |
| ... | ... | @@ -251,7 +251,7 @@ const record = Effect.gen(function* () { |
| 251 | 251 | scenario: fixture.name, |
| 252 | 252 | entries: [], |
| 253 | 253 | } satisfies ProviderReplayTranscript; |
| 254 | const recordingSpawner = Layer.effect( | |
| 254 | const layerRecordingSpawner = Layer.effect( | |
| 255 | 255 | ChildProcessSpawner.ChildProcessSpawner, |
| 256 | 256 | Effect.map(Effect.service(ChildProcessSpawner.ChildProcessSpawner), (live) => |
| 257 | 257 | makePiRecordingSpawner(live, entries), |
| ... | ... | @@ -283,7 +283,7 @@ const record = Effect.gen(function* () { |
| 283 | 283 | driver: PI_PROVIDER, |
| 284 | 284 | decodeTranscript: Effect.succeed, |
| 285 | 285 | makeProviderAdapterRegistryLayer: () => |
| 286 | makePiProviderAdapterRegistryLayer({ | |
| 286 | PiAdapterV2Testkit.layer({ | |
| 287 | 287 | scenario: fixture.name, |
| 288 | 288 | binaryPath: piBinary, |
| 289 | 289 | launchArgs, |
| ... | ... | @@ -291,7 +291,7 @@ const record = Effect.gen(function* () { |
| 291 | 291 | { name: "PI_CODING_AGENT_SESSION_DIR", value: sessionDir, sensitive: false }, |
| 292 | 292 | { name: "PI_SKIP_VERSION_CHECK", value: "1", sensitive: false }, |
| 293 | 293 | ], |
| 294 | spawner: recordingSpawner, | |
| 294 | spawner: layerRecordingSpawner, | |
| 295 | 295 | }), |
| 296 | 296 | }, |
| 297 | 297 | continuationOptions, |
apps/server/scripts/verify-background-live.ts+4-4| ... | ... | @@ -295,11 +295,11 @@ try { |
| 295 | 295 | const ticket = Schema.decodeUnknownSync(Schema.Struct({ ticket: Schema.String }))( |
| 296 | 296 | await ticketResponse.json(), |
| 297 | 297 | ); |
| 298 | const socket = Socket.layerWebSocket( | |
| 298 | const layerSocket = Socket.layerWebSocket( | |
| 299 | 299 | `${origin.replace("http:", "ws:")}/ws?orchestrationProtocol=${ORCHESTRATION_PROTOCOL_VERSION}&wsTicket=${encodeURIComponent(ticket.ticket)}`, |
| 300 | 300 | ).pipe(Layer.provide(Socket.layerWebSocketConstructorGlobal)); |
| 301 | const protocol = RpcClient.layerProtocolSocket().pipe( | |
| 302 | Layer.provide(socket), | |
| 301 | const layerProtocol = RpcClient.layerProtocolSocket().pipe( | |
| 302 | Layer.provide(layerSocket), | |
| 303 | 303 | Layer.provide(RpcSerialization.layerJson), |
| 304 | 304 | ); |
| 305 | 305 | const threadId = ThreadId.make(NodeCrypto.randomUUID()); |
| ... | ... | @@ -483,7 +483,7 @@ try { |
| 483 | 483 | .filter((message) => message.role === "assistant" && message.text.includes(secret)) |
| 484 | 484 | .map((message) => message.id), |
| 485 | 485 | }; |
| 486 | }).pipe(Effect.scoped, Effect.provide(protocol), Effect.timeout(timeoutMs)); | |
| 486 | }).pipe(Effect.scoped, Effect.provide(layerProtocol), Effect.timeout(timeoutMs)); | |
| 487 | 487 | const finished = await Effect.runPromise(program, { signal: abort.signal }); |
| 488 | 488 | verdict = finished; |
| 489 | 489 | // Check persistence through an actual clean server restart on the same SQLite DB. |
apps/server/src/assets/AssetAccess.test.ts+40-40| ... | ... | @@ -48,7 +48,7 @@ vi.mock("node:os", async (importOriginal) => { |
| 48 | 48 | return { ...actual, homedir: vi.fn(actual.homedir) }; |
| 49 | 49 | }); |
| 50 | 50 | |
| 51 | const configLayer = ServerConfig.ServerConfig.layerTest(process.cwd(), { | |
| 51 | const layerConfig = ServerConfig.ServerConfig.layerTest(process.cwd(), { | |
| 52 | 52 | prefix: "t3-asset-access-test-", |
| 53 | 53 | }); |
| 54 | 54 | // A PNG header is enough for the dimension read: signature, then IHDR width and height. |
| ... | ... | @@ -103,7 +103,7 @@ const oversizedScreenshotItem = { |
| 103 | 103 | }, |
| 104 | 104 | }; |
| 105 | 105 | |
| 106 | const testLayer = Layer.mergeAll( | |
| 106 | const layerTest = Layer.mergeAll( | |
| 107 | 107 | NodeHttpPlatform.layer, |
| 108 | 108 | Layer.mock(Orchestrator.OrchestratorV2)({ |
| 109 | 109 | getTurnItem: ({ itemId }) => |
| ... | ... | @@ -115,14 +115,14 @@ const testLayer = Layer.mergeAll( |
| 115 | 115 | : null, |
| 116 | 116 | ), |
| 117 | 117 | }), |
| 118 | configLayer, | |
| 118 | layerConfig, | |
| 119 | 119 | WorkspacePaths.layer, |
| 120 | 120 | ProjectFaviconResolver.layer.pipe( |
| 121 | 121 | Layer.provide(WorkspacePaths.layer), |
| 122 | 122 | Layer.provide(T3ProjectFileLoader.layer), |
| 123 | 123 | ), |
| 124 | NativeAppIconResolver.layer.pipe(Layer.provide(configLayer)), | |
| 125 | ServerSecretStore.layer.pipe(Layer.provide(configLayer)), | |
| 124 | NativeAppIconResolver.layer.pipe(Layer.provide(layerConfig)), | |
| 125 | ServerSecretStore.layer.pipe(Layer.provide(layerConfig)), | |
| 126 | 126 | ).pipe(Layer.provideMerge(NodeServices.layer)); |
| 127 | 127 | |
| 128 | 128 | describe("AssetAccess", () => { |
| ... | ... | @@ -198,7 +198,7 @@ describe("AssetAccess", () => { |
| 198 | 198 | resource: { ...resource, itemId: oversizedScreenshotItem.id }, |
| 199 | 199 | }).pipe(Effect.flip); |
| 200 | 200 | expect(oversized._tag).toBe("AssetWorkspaceAssetNotFoundError"); |
| 201 | }).pipe(Effect.provide(testLayer)), | |
| 201 | }).pipe(Effect.provide(layerTest)), | |
| 202 | 202 | ); |
| 203 | 203 | |
| 204 | 204 | it.effect("issues exact URLs for media and browser documents outside the workspace", () => |
| ... | ... | @@ -234,7 +234,7 @@ describe("AssetAccess", () => { |
| 234 | 234 | expect(yield* resolveAsset(token, `../${name}`)).toBeNull(); |
| 235 | 235 | expect(yield* resolveAsset(`${token}tampered`, name)).toBeNull(); |
| 236 | 236 | } |
| 237 | }).pipe(Effect.provide(testLayer)), | |
| 237 | }).pipe(Effect.provide(layerTest)), | |
| 238 | 238 | ); |
| 239 | 239 | |
| 240 | 240 | it.effect("reports pixel dimensions from an image header and nothing for other files", () => |
| ... | ... | @@ -258,7 +258,7 @@ describe("AssetAccess", () => { |
| 258 | 258 | expect((yield* issue("shot.png")).imageDimensions).toEqual({ width: 1600, height: 900 }); |
| 259 | 259 | expect((yield* issue("clip.mp4")).imageDimensions).toBeUndefined(); |
| 260 | 260 | expect((yield* issue("broken.png")).imageDimensions).toBeUndefined(); |
| 261 | }).pipe(Effect.provide(testLayer)), | |
| 261 | }).pipe(Effect.provide(layerTest)), | |
| 262 | 262 | ); |
| 263 | 263 | |
| 264 | 264 | it.effect("resolves relative media paths from the thread workspace, including outside it", () => |
| ... | ... | @@ -284,7 +284,7 @@ describe("AssetAccess", () => { |
| 284 | 284 | path: yield* fs.realPath(filePath), |
| 285 | 285 | }); |
| 286 | 286 | } |
| 287 | }).pipe(Effect.provide(testLayer)), | |
| 287 | }).pipe(Effect.provide(layerTest)), | |
| 288 | 288 | ); |
| 289 | 289 | |
| 290 | 290 | it.effect("resolves home-relative media paths independently of the workspace", () => |
| ... | ... | @@ -333,7 +333,7 @@ describe("AssetAccess", () => { |
| 333 | 333 | } finally { |
| 334 | 334 | homeSpy.mockRestore(); |
| 335 | 335 | } |
| 336 | }).pipe(Effect.provide(testLayer)), | |
| 336 | }).pipe(Effect.provide(layerTest)), | |
| 337 | 337 | ); |
| 338 | 338 | |
| 339 | 339 | it.effect.skipIf(!symlinksSupported)( |
| ... | ... | @@ -371,7 +371,7 @@ describe("AssetAccess", () => { |
| 371 | 371 | }, |
| 372 | 372 | }).pipe(Effect.flip); |
| 373 | 373 | expect(directoryError._tag).toBe("AssetWorkspaceAssetNotFoundError"); |
| 374 | }).pipe(Effect.provide(testLayer)), | |
| 374 | }).pipe(Effect.provide(layerTest)), | |
| 375 | 375 | ); |
| 376 | 376 | |
| 377 | 377 | it.effect.skipIf(!symlinksSupported)( |
| ... | ... | @@ -403,7 +403,7 @@ describe("AssetAccess", () => { |
| 403 | 403 | yield* fs.remove(filePath); |
| 404 | 404 | yield* fs.symlink(replacementPath, filePath); |
| 405 | 405 | expect(yield* resolveAsset(token, name)).toBeNull(); |
| 406 | }).pipe(Effect.provide(testLayer)), | |
| 406 | }).pipe(Effect.provide(layerTest)), | |
| 407 | 407 | ); |
| 408 | 408 | |
| 409 | 409 | it.effect.skipIf(!symlinksSupported)( |
| ... | ... | @@ -442,7 +442,7 @@ describe("AssetAccess", () => { |
| 442 | 442 | yield* fs.remove(filePath); |
| 443 | 443 | yield* fs.rename(savedPath, filePath); |
| 444 | 444 | } |
| 445 | }).pipe(Effect.provide(testLayer)), | |
| 445 | }).pipe(Effect.provide(layerTest)), | |
| 446 | 446 | ); |
| 447 | 447 | |
| 448 | 448 | it.effect.skipIf(!symlinksSupported)( |
| ... | ... | @@ -478,7 +478,7 @@ describe("AssetAccess", () => { |
| 478 | 478 | Effect.provideService(FileSystem.FileSystem, swappingFileSystem), |
| 479 | 479 | ), |
| 480 | 480 | ).toBeNull(); |
| 481 | }).pipe(Effect.provide(testLayer)), | |
| 481 | }).pipe(Effect.provide(layerTest)), | |
| 482 | 482 | ); |
| 483 | 483 | |
| 484 | 484 | it.effect.skipIf(!symlinksSupported)( |
| ... | ... | @@ -512,7 +512,7 @@ describe("AssetAccess", () => { |
| 512 | 512 | expect(yield* openMediaFile(canonicalPath)).toBeNull(); |
| 513 | 513 | expect(opened).toBeDefined(); |
| 514 | 514 | expect(opened?.fd).toBe(-1); |
| 515 | }).pipe(Effect.provide(testLayer)), | |
| 515 | }).pipe(Effect.provide(layerTest)), | |
| 516 | 516 | ); |
| 517 | 517 | |
| 518 | 518 | it.effect.skipIf(!symlinksSupported)( |
| ... | ... | @@ -570,7 +570,7 @@ describe("AssetAccess", () => { |
| 570 | 570 | Effect.provideService(FileSystem.FileSystem, swappingFileSystem), |
| 571 | 571 | ), |
| 572 | 572 | ).toBeNull(); |
| 573 | }).pipe(Effect.provide(testLayer)), | |
| 573 | }).pipe(Effect.provide(layerTest)), | |
| 574 | 574 | ); |
| 575 | 575 | |
| 576 | 576 | it.effect("keeps in-place edits readable but requires a new URL after atomic replacement", () => |
| ... | ... | @@ -620,7 +620,7 @@ describe("AssetAccess", () => { |
| 620 | 620 | renewedSuffix.slice(renewedSeparator + 1), |
| 621 | 621 | ), |
| 622 | 622 | ).toBeNull(); |
| 623 | }).pipe(Effect.provide(testLayer)), | |
| 623 | }).pipe(Effect.provide(layerTest)), | |
| 624 | 624 | ); |
| 625 | 625 | |
| 626 | 626 | it.effect("issues workspace URLs that resolve the entry file and sibling assets", () => |
| ... | ... | @@ -661,7 +661,7 @@ describe("AssetAccess", () => { |
| 661 | 661 | expect(yield* resolveAsset(token, "../secret.txt")).toBeNull(); |
| 662 | 662 | expect(yield* resolveAsset(token, ".env")).toBeNull(); |
| 663 | 663 | expect(yield* resolveAsset(`${token}tampered`, "report.html")).toBeNull(); |
| 664 | }).pipe(Effect.provide(testLayer)), | |
| 664 | }).pipe(Effect.provide(layerTest)), | |
| 665 | 665 | ); |
| 666 | 666 | |
| 667 | 667 | it.effect("rejects workspace files outside the authorized root", () => |
| ... | ... | @@ -695,7 +695,7 @@ describe("AssetAccess", () => { |
| 695 | 695 | }, |
| 696 | 696 | }); |
| 697 | 697 | expect(error.cause).toBeInstanceOf(WorkspacePaths.WorkspacePathOutsideRootError); |
| 698 | }).pipe(Effect.provide(testLayer)), | |
| 698 | }).pipe(Effect.provide(layerTest)), | |
| 699 | 699 | ); |
| 700 | 700 | |
| 701 | 701 | it.effect("issues draft workspace URLs without a thread", () => |
| ... | ... | @@ -729,7 +729,7 @@ describe("AssetAccess", () => { |
| 729 | 729 | path: canonicalCssPath, |
| 730 | 730 | }); |
| 731 | 731 | expect(yield* resolveAsset(token, "../secret.txt")).toBeNull(); |
| 732 | }).pipe(Effect.provide(testLayer)), | |
| 732 | }).pipe(Effect.provide(layerTest)), | |
| 733 | 733 | ); |
| 734 | 734 | |
| 735 | 735 | it.effect("serves absolute draft media files exactly, wherever they live", () => |
| ... | ... | @@ -760,7 +760,7 @@ describe("AssetAccess", () => { |
| 760 | 760 | mimeType: "video/mp4", |
| 761 | 761 | }); |
| 762 | 762 | expect(yield* resolveAsset(token, "other.mp4")).toBeNull(); |
| 763 | }).pipe(Effect.provide(testLayer)), | |
| 763 | }).pipe(Effect.provide(layerTest)), | |
| 764 | 764 | ); |
| 765 | 765 | |
| 766 | 766 | it.effect("falls back to the resource cwd for relative draft paths", () => |
| ... | ... | @@ -785,7 +785,7 @@ describe("AssetAccess", () => { |
| 785 | 785 | kind: "file", |
| 786 | 786 | path: canonicalHtmlPath, |
| 787 | 787 | }); |
| 788 | }).pipe(Effect.provide(testLayer)), | |
| 788 | }).pipe(Effect.provide(layerTest)), | |
| 789 | 789 | ); |
| 790 | 790 | |
| 791 | 791 | it.effect("preserves non-missing canonical path failures when issuing asset URLs", () => |
| ... | ... | @@ -827,7 +827,7 @@ describe("AssetAccess", () => { |
| 827 | 827 | }, |
| 828 | 828 | }); |
| 829 | 829 | expect(error.cause).toBe(cause); |
| 830 | }).pipe(Effect.provide(testLayer)), | |
| 830 | }).pipe(Effect.provide(layerTest)), | |
| 831 | 831 | ); |
| 832 | 832 | |
| 833 | 833 | it.effect("issues exact workspace URLs for image previews", () => |
| ... | ... | @@ -863,7 +863,7 @@ describe("AssetAccess", () => { |
| 863 | 863 | }); |
| 864 | 864 | expect(yield* resolveAsset(token, "other.png")).toBeNull(); |
| 865 | 865 | expect(yield* resolveAsset(token, "../icon.png")).toBeNull(); |
| 866 | }).pipe(Effect.provide(testLayer)), | |
| 866 | }).pipe(Effect.provide(layerTest)), | |
| 867 | 867 | ); |
| 868 | 868 | |
| 869 | 869 | it.effect("previews workspace files with literal fragment characters in their paths", () => |
| ... | ... | @@ -906,7 +906,7 @@ describe("AssetAccess", () => { |
| 906 | 906 | workspaceRoot: root, |
| 907 | 907 | }).pipe(Effect.flip); |
| 908 | 908 | expect(error).toBeInstanceOf(AssetPreviewTypeValidationError); |
| 909 | }).pipe(Effect.provide(testLayer)), | |
| 909 | }).pipe(Effect.provide(layerTest)), | |
| 910 | 910 | ); |
| 911 | 911 | |
| 912 | 912 | it.effect("issues exact attachment capabilities by attachment id", () => |
| ... | ... | @@ -930,7 +930,7 @@ describe("AssetAccess", () => { |
| 930 | 930 | kind: "file", |
| 931 | 931 | path: attachmentPath, |
| 932 | 932 | }); |
| 933 | }).pipe(Effect.provide(testLayer)), | |
| 933 | }).pipe(Effect.provide(layerTest)), | |
| 934 | 934 | ); |
| 935 | 935 | |
| 936 | 936 | it.effect("serves video attachments inline", () => |
| ... | ... | @@ -962,7 +962,7 @@ describe("AssetAccess", () => { |
| 962 | 962 | fileName: "demo.mp4", |
| 963 | 963 | mimeType: "video/mp4", |
| 964 | 964 | }); |
| 965 | }).pipe(Effect.provide(testLayer)), | |
| 965 | }).pipe(Effect.provide(layerTest)), | |
| 966 | 966 | ); |
| 967 | 967 | it.effect("issues signed native application icon capabilities", () => |
| 968 | 968 | Effect.gen(function* () { |
| ... | ... | @@ -977,7 +977,7 @@ describe("AssetAccess", () => { |
| 977 | 977 | new RegExp(`^${ASSET_ROUTE_PREFIX}/[^/]+/native-app-icon\\.png$`, "u"), |
| 978 | 978 | ); |
| 979 | 979 | expect(result.expiresAt).toBeGreaterThan(0); |
| 980 | }).pipe(Effect.provide(testLayer)), | |
| 980 | }).pipe(Effect.provide(layerTest)), | |
| 981 | 981 | ); |
| 982 | 982 | |
| 983 | 983 | it.effect("serves document attachments inline when a viewer requests it", () => |
| ... | ... | @@ -1010,7 +1010,7 @@ describe("AssetAccess", () => { |
| 1010 | 1010 | fileName: "report.pdf", |
| 1011 | 1011 | mimeType: "application/pdf", |
| 1012 | 1012 | }); |
| 1013 | }).pipe(Effect.provide(testLayer)), | |
| 1013 | }).pipe(Effect.provide(layerTest)), | |
| 1014 | 1014 | ); |
| 1015 | 1015 | |
| 1016 | 1016 | it.effect("serves audio previews with their stored format and keeps saving explicit", () => |
| ... | ... | @@ -1044,7 +1044,7 @@ describe("AssetAccess", () => { |
| 1044 | 1044 | ...(disposition === "attachment" ? { download: true } : {}), |
| 1045 | 1045 | }); |
| 1046 | 1046 | } |
| 1047 | }).pipe(Effect.provide(testLayer)), | |
| 1047 | }).pipe(Effect.provide(layerTest)), | |
| 1048 | 1048 | ); |
| 1049 | 1049 | |
| 1050 | 1050 | it.effect("keeps inline requests for other attachment types as downloads", () => |
| ... | ... | @@ -1072,7 +1072,7 @@ describe("AssetAccess", () => { |
| 1072 | 1072 | expect( |
| 1073 | 1073 | yield* resolveAsset(suffix.slice(0, separatorIndex), suffix.slice(separatorIndex + 1)), |
| 1074 | 1074 | ).toMatchObject({ kind: "file", path: attachmentPath, download: true }); |
| 1075 | }).pipe(Effect.provide(testLayer)), | |
| 1075 | }).pipe(Effect.provide(layerTest)), | |
| 1076 | 1076 | ); |
| 1077 | 1077 | it.effect("issues project favicon capabilities with a signed fallback", () => |
| 1078 | 1078 | Effect.gen(function* () { |
| ... | ... | @@ -1129,7 +1129,7 @@ describe("AssetAccess", () => { |
| 1129 | 1129 | fallbackSuffix.slice(fallbackSeparatorIndex + 1), |
| 1130 | 1130 | ), |
| 1131 | 1131 | ).toBeNull(); |
| 1132 | }).pipe(Effect.provide(testLayer)), | |
| 1132 | }).pipe(Effect.provide(layerTest)), | |
| 1133 | 1133 | ); |
| 1134 | 1134 | |
| 1135 | 1135 | it.effect("issues project favicon capabilities for a saved override", () => |
| ... | ... | @@ -1150,7 +1150,7 @@ describe("AssetAccess", () => { |
| 1150 | 1150 | |
| 1151 | 1151 | expect(result.sourcePath).toBe(path.join("brand", "custom.svg")); |
| 1152 | 1152 | expect(result.relativeUrl).toMatch(/\/v[0-9a-f]{64}-custom\.svg$/); |
| 1153 | }).pipe(Effect.provide(testLayer)), | |
| 1153 | }).pipe(Effect.provide(layerTest)), | |
| 1154 | 1154 | ); |
| 1155 | 1155 | |
| 1156 | 1156 | it.effect("issues an exact capability for a saved favicon outside the workspace", () => |
| ... | ... | @@ -1188,7 +1188,7 @@ describe("AssetAccess", () => { |
| 1188 | 1188 | ); |
| 1189 | 1189 | expect(tamperedSuffixResult).toEqual({ kind: "file", path: canonicalPath }); |
| 1190 | 1190 | expect(tamperedSuffixResult).not.toEqual({ kind: "file", path: canonicalSiblingPath }); |
| 1191 | }).pipe(Effect.provide(testLayer)), | |
| 1191 | }).pipe(Effect.provide(layerTest)), | |
| 1192 | 1192 | ); |
| 1193 | 1193 | |
| 1194 | 1194 | it.effect("ignores a client favicon path hint", () => |
| ... | ... | @@ -1209,7 +1209,7 @@ describe("AssetAccess", () => { |
| 1209 | 1209 | |
| 1210 | 1210 | expect(result.sourcePath).toBe(path.join("brand", "saved.svg")); |
| 1211 | 1211 | expect(result.relativeUrl).toMatch(/\/v[0-9a-f]{64}-saved\.svg$/); |
| 1212 | }).pipe(Effect.provide(testLayer)), | |
| 1212 | }).pipe(Effect.provide(layerTest)), | |
| 1213 | 1213 | ); |
| 1214 | 1214 | |
| 1215 | 1215 | it.effect("keeps automatic favicon resolution separate from a saved override", () => |
| ... | ... | @@ -1229,7 +1229,7 @@ describe("AssetAccess", () => { |
| 1229 | 1229 | |
| 1230 | 1230 | expect(result.sourcePath).toBe("favicon.svg"); |
| 1231 | 1231 | expect(result.relativeUrl).toMatch(/\/v[0-9a-f]{64}-favicon\.svg$/); |
| 1232 | }).pipe(Effect.provide(testLayer)), | |
| 1232 | }).pipe(Effect.provide(layerTest)), | |
| 1233 | 1233 | ); |
| 1234 | 1234 | |
| 1235 | 1235 | it.effect("rejects a resolved project favicon with a non-image extension", () => |
| ... | ... | @@ -1247,7 +1247,7 @@ describe("AssetAccess", () => { |
| 1247 | 1247 | }).pipe(Effect.flip); |
| 1248 | 1248 | |
| 1249 | 1249 | expect(error).toBeInstanceOf(AssetPreviewTypeValidationError); |
| 1250 | }).pipe(Effect.provide(testLayer)), | |
| 1250 | }).pipe(Effect.provide(layerTest)), | |
| 1251 | 1251 | ); |
| 1252 | 1252 | |
| 1253 | 1253 | it.effect("buckets project favicon expiry after content hashing", () => |
| ... | ... | @@ -1272,7 +1272,7 @@ describe("AssetAccess", () => { |
| 1272 | 1272 | }).pipe(Effect.provideService(Crypto.Crypto, crossingCrypto)); |
| 1273 | 1273 | |
| 1274 | 1274 | expect(result.expiresAt).toBe(3 * bucketMs); |
| 1275 | }).pipe(Effect.provide(testLayer)), | |
| 1275 | }).pipe(Effect.provide(layerTest)), | |
| 1276 | 1276 | ); |
| 1277 | 1277 | |
| 1278 | 1278 | it.effect("preserves structured project favicon resolution causes", () => |
| ... | ... | @@ -1306,7 +1306,7 @@ describe("AssetAccess", () => { |
| 1306 | 1306 | expect(error.message).toBe("Failed to resolve project favicon."); |
| 1307 | 1307 | expect(error._tag).toBe("AssetProjectFaviconResolutionError"); |
| 1308 | 1308 | expect(error.cause).toBe(resolutionCause); |
| 1309 | }).pipe(Effect.provide(testLayer)), | |
| 1309 | }).pipe(Effect.provide(layerTest)), | |
| 1310 | 1310 | ); |
| 1311 | 1311 | |
| 1312 | 1312 | it.effect("serves GitHub-hosted pull request media through the repository's credential", () => |
| ... | ... | @@ -1362,6 +1362,6 @@ describe("AssetAccess", () => { |
| 1362 | 1362 | const encoded = yield* Schema.encodeEffect(Schema.fromJsonString(AssetAccessError))(error); |
| 1363 | 1363 | expect(encoded).not.toContain(url); |
| 1364 | 1364 | } |
| 1365 | }).pipe(Effect.provide(testLayer)), | |
| 1365 | }).pipe(Effect.provide(layerTest)), | |
| 1366 | 1366 | ); |
| 1367 | 1367 | }); |
apps/server/src/assets/AttachmentUpload.test.ts+11-11| ... | ... | @@ -24,7 +24,7 @@ import { |
| 24 | 24 | validateAttachmentUploadToken, |
| 25 | 25 | } from "./AttachmentUpload.ts"; |
| 26 | 26 | |
| 27 | const testLayer = ServerSecretStore.layer.pipe( | |
| 27 | const layerTest = ServerSecretStore.layer.pipe( | |
| 28 | 28 | Layer.provideMerge(ServerConfig.layerTest(process.cwd(), { prefix: "t3-attachment-upload-" })), |
| 29 | 29 | Layer.provideMerge(NodeServices.layer), |
| 30 | 30 | ); |
| ... | ... | @@ -62,7 +62,7 @@ describe("AttachmentUpload", () => { |
| 62 | 62 | mimeType: "image/png", |
| 63 | 63 | sizeBytes: 6, |
| 64 | 64 | }); |
| 65 | }).pipe(Effect.provide(testLayer)), | |
| 65 | }).pipe(Effect.provide(layerTest)), | |
| 66 | 66 | ); |
| 67 | 67 | |
| 68 | 68 | it.effect("rejects tampered and malformed upload tokens", () => |
| ... | ... | @@ -74,7 +74,7 @@ describe("AttachmentUpload", () => { |
| 74 | 74 | expect(yield* validateAttachmentUploadToken(`${payload}x.${signature}`)).toBeNull(); |
| 75 | 75 | expect(yield* validateAttachmentUploadToken(`${token}.extra`)).toBeNull(); |
| 76 | 76 | expect(yield* validateAttachmentUploadToken("garbage")).toBeNull(); |
| 77 | }).pipe(Effect.provide(testLayer)), | |
| 77 | }).pipe(Effect.provide(layerTest)), | |
| 78 | 78 | ); |
| 79 | 79 | |
| 80 | 80 | it.effect("accepts unexpired image upload tokens issued before file support", () => |
| ... | ... | @@ -99,7 +99,7 @@ describe("AttachmentUpload", () => { |
| 99 | 99 | type: "image", |
| 100 | 100 | attachmentId: issued.attachmentId, |
| 101 | 101 | }); |
| 102 | }).pipe(Effect.provide(testLayer)), | |
| 102 | }).pipe(Effect.provide(layerTest)), | |
| 103 | 103 | ); |
| 104 | 104 | |
| 105 | 105 | it.effect("rejects expired upload tokens", () => |
| ... | ... | @@ -109,7 +109,7 @@ describe("AttachmentUpload", () => { |
| 109 | 109 | |
| 110 | 110 | yield* TestClock.adjust("11 minutes"); |
| 111 | 111 | expect(yield* validateAttachmentUploadToken(token)).toBeNull(); |
| 112 | }).pipe(Effect.provide(testLayer)), | |
| 112 | }).pipe(Effect.provide(layerTest)), | |
| 113 | 113 | ); |
| 114 | 114 | |
| 115 | 115 | it.effect("removes expired pending uploads while issuing a new upload URL", () => |
| ... | ... | @@ -124,7 +124,7 @@ describe("AttachmentUpload", () => { |
| 124 | 124 | yield* issueAttachmentUploadUrl(uploadInput); |
| 125 | 125 | |
| 126 | 126 | expect(NodeFS.existsSync(stalePath)).toBe(false); |
| 127 | }).pipe(Effect.provide(testLayer)), | |
| 127 | }).pipe(Effect.provide(layerTest)), | |
| 128 | 128 | ); |
| 129 | 129 | |
| 130 | 130 | it.effect("stores the expected bytes without leaving temporary files", () => |
| ... | ... | @@ -148,7 +148,7 @@ describe("AttachmentUpload", () => { |
| 148 | 148 | expect( |
| 149 | 149 | NodeFS.readdirSync(config.attachmentsDir).filter((entry) => entry.endsWith(".part")), |
| 150 | 150 | ).toEqual([]); |
| 151 | }).pipe(Effect.provide(testLayer)), | |
| 151 | }).pipe(Effect.provide(layerTest)), | |
| 152 | 152 | ); |
| 153 | 153 | |
| 154 | 154 | it.effect("streams generic files to a path with their original extension", () => |
| ... | ... | @@ -179,7 +179,7 @@ describe("AttachmentUpload", () => { |
| 179 | 179 | |
| 180 | 180 | yield* deletePendingAttachment(issued.attachmentId); |
| 181 | 181 | expect(NodeFS.readdirSync(config.attachmentsDir)).toEqual([]); |
| 182 | }).pipe(Effect.provide(testLayer)), | |
| 182 | }).pipe(Effect.provide(layerTest)), | |
| 183 | 183 | ); |
| 184 | 184 | |
| 185 | 185 | it.effect("removes partial streamed uploads that exceed their signed size", () => |
| ... | ... | @@ -197,7 +197,7 @@ describe("AttachmentUpload", () => { |
| 197 | 197 | status: 400, |
| 198 | 198 | }); |
| 199 | 199 | expect(NodeFS.readdirSync(config.attachmentsDir)).toEqual([]); |
| 200 | }).pipe(Effect.provide(testLayer)), | |
| 200 | }).pipe(Effect.provide(layerTest)), | |
| 201 | 201 | ); |
| 202 | 202 | |
| 203 | 203 | it.effect("removes partial streamed uploads when the upload is interrupted", () => |
| ... | ... | @@ -227,7 +227,7 @@ describe("AttachmentUpload", () => { |
| 227 | 227 | |
| 228 | 228 | yield* Fiber.interrupt(upload); |
| 229 | 229 | expect(NodeFS.readdirSync(config.attachmentsDir)).toEqual([]); |
| 230 | }).pipe(Effect.provide(testLayer)), | |
| 230 | }).pipe(Effect.provide(layerTest)), | |
| 231 | 231 | ); |
| 232 | 232 | |
| 233 | 233 | it.effect("deletes pending uploads without deleting thread-owned copies", () => |
| ... | ... | @@ -245,6 +245,6 @@ describe("AttachmentUpload", () => { |
| 245 | 245 | |
| 246 | 246 | expect(NodeFS.existsSync(pendingPath)).toBe(false); |
| 247 | 247 | expect(NodeFS.existsSync(claimedPath)).toBe(true); |
| 248 | }).pipe(Effect.provide(testLayer)), | |
| 248 | }).pipe(Effect.provide(layerTest)), | |
| 249 | 249 | ); |
| 250 | 250 | }); |
apps/server/src/assets/NativeAppIconResolver.test.ts+5-5| ... | ... | @@ -39,15 +39,15 @@ describe("resolveNativeAppIcon", () => { |
| 39 | 39 | return emptyProcessHandle(); |
| 40 | 40 | }), |
| 41 | 41 | ); |
| 42 | const configLayer = ServerConfig.ServerConfig.layerTest(process.cwd(), { | |
| 42 | const layerConfig = ServerConfig.ServerConfig.layerTest(process.cwd(), { | |
| 43 | 43 | prefix: "t3-native-app-icon-test-", |
| 44 | 44 | }); |
| 45 | const dependencies = Layer.mergeAll( | |
| 46 | configLayer, | |
| 45 | const layerDependencies = Layer.mergeAll( | |
| 46 | layerConfig, | |
| 47 | 47 | Layer.succeed(HostProcessPlatform, "darwin"), |
| 48 | 48 | Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner), |
| 49 | 49 | ).pipe(Layer.provideMerge(NodeServices.layer)); |
| 50 | const testLayer = NativeAppIconResolver.layer.pipe(Layer.provide(dependencies)); | |
| 50 | const layerTest = NativeAppIconResolver.layer.pipe(Layer.provide(layerDependencies)); | |
| 51 | 51 | const app = { _tag: "display-name", displayName: "Review * App" } as const; |
| 52 | 52 | |
| 53 | 53 | return Effect.gen(function* () { |
| ... | ... | @@ -70,6 +70,6 @@ describe("resolveNativeAppIcon", () => { |
| 70 | 70 | expect(commands).toHaveLength(257); |
| 71 | 71 | expect(yield* resolver.resolve(app)).toBeNull(); |
| 72 | 72 | expect(commands).toHaveLength(258); |
| 73 | }).pipe(Effect.provide(testLayer)); | |
| 73 | }).pipe(Effect.provide(layerTest)); | |
| 74 | 74 | }); |
| 75 | 75 | }); |
apps/server/src/auth/EnvironmentAuth.test.ts+20-20| ... | ... | @@ -10,7 +10,7 @@ import * as SqlClient from "effect/sql/SqlClient"; |
| 10 | 10 | import * as ServerConfig from "../config.ts"; |
| 11 | 11 | import * as ServerEnvironment from "../environment/ServerEnvironment.ts"; |
| 12 | 12 | import * as PersistenceErrors from "../persistence/Errors.ts"; |
| 13 | import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts"; | |
| 13 | import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts"; | |
| 14 | 14 | import * as PairingGrantStore from "./PairingGrantStore.ts"; |
| 15 | 15 | import * as EnvironmentAuth from "./EnvironmentAuth.ts"; |
| 16 | 16 | |
| ... | ... | @@ -22,7 +22,7 @@ const TEST_SERVER_PORT = 13_773; |
| 22 | 22 | const isPairingCredentialIssueError = Schema.is(PairingGrantStore.PairingCredentialIssueError); |
| 23 | 23 | const isPersistenceSqlError = Schema.is(PersistenceErrors.PersistenceSqlError); |
| 24 | 24 | |
| 25 | const makeServerConfigLayer = (overrides?: Partial<ServerConfig.ServerConfig["Service"]>) => | |
| 25 | const layerServerConfig = (overrides?: Partial<ServerConfig.ServerConfig["Service"]>) => | |
| 26 | 26 | Layer.effect( |
| 27 | 27 | ServerConfig.ServerConfig, |
| 28 | 28 | Effect.gen(function* () { |
| ... | ... | @@ -37,12 +37,12 @@ const makeServerConfigLayer = (overrides?: Partial<ServerConfig.ServerConfig["Se |
| 37 | 37 | }), |
| 38 | 38 | ).pipe(Layer.provide(ServerConfig.layerTest(process.cwd(), { prefix: "t3-auth-server-test-" }))); |
| 39 | 39 | |
| 40 | const makeEnvironmentAuthLayer = (overrides?: Partial<ServerConfig.ServerConfig["Service"]>) => | |
| 40 | const layerEnvironmentAuth = (overrides?: Partial<ServerConfig.ServerConfig["Service"]>) => | |
| 41 | 41 | EnvironmentAuth.layer.pipe( |
| 42 | Layer.provideMerge(SqlitePersistenceMemory), | |
| 42 | Layer.provideMerge(SqlitePersistence.layerMemory), | |
| 43 | 43 | Layer.provide(ServerSecretStore.layer), |
| 44 | Layer.provide(ServerEnvironment.identityLayer), | |
| 45 | Layer.provide(makeServerConfigLayer(overrides)), | |
| 44 | Layer.provide(ServerEnvironment.layerIdentity), | |
| 45 | Layer.provide(layerServerConfig(overrides)), | |
| 46 | 46 | ); |
| 47 | 47 | |
| 48 | 48 | const makeCookieRequest = ( |
| ... | ... | @@ -105,7 +105,7 @@ it.layer(NodeServices.layer)("EnvironmentAuth.layer", (it) => { |
| 105 | 105 | expect(authenticated.scopes).toEqual(["orchestration:read"]); |
| 106 | 106 | }).pipe( |
| 107 | 107 | Effect.provide( |
| 108 | makeEnvironmentAuthLayer({ | |
| 108 | layerEnvironmentAuth({ | |
| 109 | 109 | mode: "web", |
| 110 | 110 | devUrl: new URL("http://127.0.0.1:5173"), |
| 111 | 111 | devAuthToken: Redacted.make("reusable-dev-auth-token-that-is-long-enough"), |
| ... | ... | @@ -141,7 +141,7 @@ it.layer(NodeServices.layer)("EnvironmentAuth.layer", (it) => { |
| 141 | 141 | expect(error._tag).toBe("ServerAuthInvalidCredentialError"); |
| 142 | 142 | }).pipe( |
| 143 | 143 | Effect.provide( |
| 144 | makeEnvironmentAuthLayer({ | |
| 144 | layerEnvironmentAuth({ | |
| 145 | 145 | mode: "web", |
| 146 | 146 | devUrl: new URL("http://127.0.0.1:5173"), |
| 147 | 147 | devAuthToken: Redacted.make("reusable-dev-auth-token-that-is-long-enough"), |
| ... | ... | @@ -167,7 +167,7 @@ it.layer(NodeServices.layer)("EnvironmentAuth.layer", (it) => { |
| 167 | 167 | } |
| 168 | 168 | }).pipe( |
| 169 | 169 | Effect.provide( |
| 170 | makeEnvironmentAuthLayer({ | |
| 170 | layerEnvironmentAuth({ | |
| 171 | 171 | mode: "web", |
| 172 | 172 | devUrl: new URL("http://127.0.0.1:5173"), |
| 173 | 173 | devAuthToken: Redacted.make("reusable-dev-auth-token-that-is-long-enough"), |
| ... | ... | @@ -216,7 +216,7 @@ it.layer(NodeServices.layer)("EnvironmentAuth.layer", (it) => { |
| 216 | 216 | expect((yield* sessions.verify(token)).subject).toBe("reusable-dev-token"); |
| 217 | 217 | }).pipe( |
| 218 | 218 | Effect.provide( |
| 219 | makeEnvironmentAuthLayer({ | |
| 219 | layerEnvironmentAuth({ | |
| 220 | 220 | mode: "web", |
| 221 | 221 | devUrl: new URL("http://127.0.0.1:5173"), |
| 222 | 222 | devAuthToken: Redacted.make("reusable-dev-auth-token-that-is-long-enough"), |
| ... | ... | @@ -244,7 +244,7 @@ it.layer(NodeServices.layer)("EnvironmentAuth.layer", (it) => { |
| 244 | 244 | ).toMatchObject({ authenticated: true, scopes: AuthAdministrativeScopes }); |
| 245 | 245 | }).pipe( |
| 246 | 246 | Effect.provide( |
| 247 | makeEnvironmentAuthLayer({ | |
| 247 | layerEnvironmentAuth({ | |
| 248 | 248 | mode: "web", |
| 249 | 249 | devUrl: new URL("http://127.0.0.1:5173"), |
| 250 | 250 | devAuthToken: Redacted.make("reusable-dev-auth-token-that-is-long-enough"), |
| ... | ... | @@ -279,7 +279,7 @@ it.layer(NodeServices.layer)("EnvironmentAuth.layer", (it) => { |
| 279 | 279 | } |
| 280 | 280 | }).pipe( |
| 281 | 281 | Effect.provide( |
| 282 | makeEnvironmentAuthLayer({ | |
| 282 | layerEnvironmentAuth({ | |
| 283 | 283 | mode: "web", |
| 284 | 284 | devUrl: new URL("http://127.0.0.1:5173"), |
| 285 | 285 | devAuthToken: Redacted.make("reusable-dev-auth-token-that-is-long-enough"), |
| ... | ... | @@ -336,7 +336,7 @@ it.layer(NodeServices.layer)("EnvironmentAuth.layer", (it) => { |
| 336 | 336 | "relay:read", |
| 337 | 337 | ]); |
| 338 | 338 | expect(verified.subject).toBe("one-time-token"); |
| 339 | }).pipe(Effect.provide(makeEnvironmentAuthLayer())), | |
| 339 | }).pipe(Effect.provide(layerEnvironmentAuth())), | |
| 340 | 340 | ); |
| 341 | 341 | |
| 342 | 342 | it.effect("prefers a bearer token over a stale legacy cookie", () => |
| ... | ... | @@ -350,7 +350,7 @@ it.layer(NodeServices.layer)("EnvironmentAuth.layer", (it) => { |
| 350 | 350 | } as never); |
| 351 | 351 | |
| 352 | 352 | expect(verified.sessionId).toBe(bearer.sessionId); |
| 353 | }).pipe(Effect.provide(makeEnvironmentAuthLayer({ mode: "web", host: "192.168.1.50" }))), | |
| 353 | }).pipe(Effect.provide(layerEnvironmentAuth({ mode: "web", host: "192.168.1.50" }))), | |
| 354 | 354 | ); |
| 355 | 355 | |
| 356 | 356 | it.effect("does not exchange ordinary pairing grants for administrative access tokens", () => |
| ... | ... | @@ -367,7 +367,7 @@ it.layer(NodeServices.layer)("EnvironmentAuth.layer", (it) => { |
| 367 | 367 | .pipe(Effect.flip); |
| 368 | 368 | |
| 369 | 369 | expect(error._tag).toBe("ServerAuthScopeNotGrantedError"); |
| 370 | }).pipe(Effect.provide(makeEnvironmentAuthLayer())), | |
| 370 | }).pipe(Effect.provide(layerEnvironmentAuth())), | |
| 371 | 371 | ); |
| 372 | 372 | |
| 373 | 373 | it.effect("inherits a constrained pairing grant when token exchange omits scope", () => |
| ... | ... | @@ -384,7 +384,7 @@ it.layer(NodeServices.layer)("EnvironmentAuth.layer", (it) => { |
| 384 | 384 | ); |
| 385 | 385 | |
| 386 | 386 | expect(token.scope).toBe("orchestration:read"); |
| 387 | }).pipe(Effect.provide(makeEnvironmentAuthLayer())), | |
| 387 | }).pipe(Effect.provide(layerEnvironmentAuth())), | |
| 388 | 388 | ); |
| 389 | 389 | |
| 390 | 390 | it.effect("rotates desktop bearer sessions without accumulating authorized clients", () => |
| ... | ... | @@ -445,7 +445,7 @@ it.layer(NodeServices.layer)("EnvironmentAuth.layer", (it) => { |
| 445 | 445 | expect(active.map((entry) => entry.sessionId)).toContain(pairedSession.sessionId); |
| 446 | 446 | }).pipe( |
| 447 | 447 | Effect.provide( |
| 448 | makeEnvironmentAuthLayer({ | |
| 448 | layerEnvironmentAuth({ | |
| 449 | 449 | desktopBootstrapToken: "desktop-bootstrap-token", |
| 450 | 450 | }), |
| 451 | 451 | ), |
| ... | ... | @@ -463,7 +463,7 @@ it.layer(NodeServices.layer)("EnvironmentAuth.layer", (it) => { |
| 463 | 463 | expect( |
| 464 | 464 | listedPairingLinks.find((pairingLink) => pairingLink.id === pairingCredential.id)?.subject, |
| 465 | 465 | ).toBe("one-time-token"); |
| 466 | }).pipe(Effect.provide(makeEnvironmentAuthLayer())), | |
| 466 | }).pipe(Effect.provide(layerEnvironmentAuth())), | |
| 467 | 467 | ); |
| 468 | 468 | |
| 469 | 469 | it.effect("issues startup pairing URLs that bootstrap administrative sessions", () => |
| ... | ... | @@ -497,7 +497,7 @@ it.layer(NodeServices.layer)("EnvironmentAuth.layer", (it) => { |
| 497 | 497 | "relay:write", |
| 498 | 498 | ]); |
| 499 | 499 | expect(verified.subject).toBe("administrative-bootstrap"); |
| 500 | }).pipe(Effect.provide(makeEnvironmentAuthLayer())), | |
| 500 | }).pipe(Effect.provide(layerEnvironmentAuth())), | |
| 501 | 501 | ); |
| 502 | 502 | |
| 503 | 503 | it.effect( |
| ... | ... | @@ -566,7 +566,7 @@ it.layer(NodeServices.layer)("EnvironmentAuth.layer", (it) => { |
| 566 | 566 | expect(clientsAfterRevoke[0]?.sessionId).toBe(administrativeSession.sessionId); |
| 567 | 567 | }).pipe( |
| 568 | 568 | Effect.provide( |
| 569 | makeEnvironmentAuthLayer({ | |
| 569 | layerEnvironmentAuth({ | |
| 570 | 570 | desktopBootstrapToken: "desktop-bootstrap-token", |
| 571 | 571 | }), |
| 572 | 572 | ), |
apps/server/src/auth/EnvironmentAuth.ts+4-4| ... | ... | @@ -1125,9 +1125,9 @@ export const layer = Layer.effect(EnvironmentAuth, make).pipe( |
| 1125 | 1125 | Layer.provideMerge(EnvironmentAuthPolicy.layer), |
| 1126 | 1126 | ); |
| 1127 | 1127 | |
| 1128 | const storageLayer = Layer.mergeAll(ServerSecretStore.layer, SqlitePersistence.layerConfig); | |
| 1128 | const layerStorage = Layer.mergeAll(ServerSecretStore.layer, SqlitePersistence.layerConfig); | |
| 1129 | 1129 | |
| 1130 | export const runtimeLayer = layer.pipe( | |
| 1131 | Layer.provideMerge(storageLayer), | |
| 1132 | Layer.provideMerge(ServerEnvironment.identityLayer), | |
| 1130 | export const layerRuntime = layer.pipe( | |
| 1131 | Layer.provideMerge(layerStorage), | |
| 1132 | Layer.provideMerge(ServerEnvironment.layerIdentity), | |
| 1133 | 1133 | ); |
apps/server/src/auth/EnvironmentAuthAdmin.test.ts+9-9| ... | ... | @@ -5,12 +5,12 @@ import * as Layer from "effect/Layer"; |
| 5 | 5 | |
| 6 | 6 | import * as ServerConfig from "../config.ts"; |
| 7 | 7 | import * as ServerEnvironment from "../environment/ServerEnvironment.ts"; |
| 8 | import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts"; | |
| 8 | import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts"; | |
| 9 | 9 | import * as EnvironmentAuth from "./EnvironmentAuth.ts"; |
| 10 | 10 | import * as ServerSecretStore from "./ServerSecretStore.ts"; |
| 11 | 11 | import * as SessionStore from "./SessionStore.ts"; |
| 12 | 12 | |
| 13 | const makeServerConfigLayer = ( | |
| 13 | const layerServerConfig = ( | |
| 14 | 14 | overrides?: Partial<Pick<ServerConfig.ServerConfig["Service"], "desktopBootstrapToken">>, |
| 15 | 15 | ) => |
| 16 | 16 | Layer.effect( |
| ... | ... | @@ -30,14 +30,14 @@ const makeServerConfigLayer = ( |
| 30 | 30 | ), |
| 31 | 31 | ); |
| 32 | 32 | |
| 33 | const makeEnvironmentAuthLayer = ( | |
| 33 | const layerEnvironmentAuth = ( | |
| 34 | 34 | overrides?: Partial<Pick<ServerConfig.ServerConfig["Service"], "desktopBootstrapToken">>, |
| 35 | 35 | ) => |
| 36 | 36 | EnvironmentAuth.layer.pipe( |
| 37 | 37 | Layer.provideMerge(ServerSecretStore.layer), |
| 38 | Layer.provideMerge(SqlitePersistenceMemory), | |
| 39 | Layer.provide(ServerEnvironment.identityLayer), | |
| 40 | Layer.provide(makeServerConfigLayer(overrides)), | |
| 38 | Layer.provideMerge(SqlitePersistence.layerMemory), | |
| 39 | Layer.provide(ServerEnvironment.layerIdentity), | |
| 40 | Layer.provide(layerServerConfig(overrides)), | |
| 41 | 41 | ); |
| 42 | 42 | |
| 43 | 43 | it.layer(NodeServices.layer)("EnvironmentAuth administrative operations", (it) => { |
| ... | ... | @@ -62,7 +62,7 @@ it.layer(NodeServices.layer)("EnvironmentAuth administrative operations", (it) = |
| 62 | 62 | expect(listedBeforeRevoke[0]).not.toHaveProperty("credential"); |
| 63 | 63 | expect(revoked).toBe(true); |
| 64 | 64 | expect(listedAfterRevoke).toHaveLength(0); |
| 65 | }).pipe(Effect.provide(makeEnvironmentAuthLayer())), | |
| 65 | }).pipe(Effect.provide(layerEnvironmentAuth())), | |
| 66 | 66 | ); |
| 67 | 67 | |
| 68 | 68 | it.effect("issues bearer access token sessions without exposing raw tokens", () => |
| ... | ... | @@ -108,7 +108,7 @@ it.layer(NodeServices.layer)("EnvironmentAuth administrative operations", (it) = |
| 108 | 108 | expect("token" in (listedBeforeRevoke[0] ?? {})).toBe(false); |
| 109 | 109 | expect(revoked).toBe(true); |
| 110 | 110 | expect(listedAfterRevoke).toHaveLength(0); |
| 111 | }).pipe(Effect.provide(makeEnvironmentAuthLayer())), | |
| 111 | }).pipe(Effect.provide(layerEnvironmentAuth())), | |
| 112 | 112 | ); |
| 113 | 113 | |
| 114 | 114 | it.effect("surfaces lastConnectedAt through the listed session view", () => |
| ... | ... | @@ -125,6 +125,6 @@ it.layer(NodeServices.layer)("EnvironmentAuth administrative operations", (it) = |
| 125 | 125 | |
| 126 | 126 | expect(beforeConnect[0]?.lastConnectedAt).toBeNull(); |
| 127 | 127 | expect(afterConnect[0]?.lastConnectedAt).not.toBeNull(); |
| 128 | }).pipe(Effect.provide(makeEnvironmentAuthLayer())), | |
| 128 | }).pipe(Effect.provide(layerEnvironmentAuth())), | |
| 129 | 129 | ); |
| 130 | 130 | }); |
apps/server/src/auth/EnvironmentAuthPolicy.test.ts+9-11| ... | ... | @@ -7,11 +7,9 @@ import * as ServerConfig from "../config.ts"; |
| 7 | 7 | import * as ServerEnvironment from "../environment/ServerEnvironment.ts"; |
| 8 | 8 | import * as EnvironmentAuthPolicy from "./EnvironmentAuthPolicy.ts"; |
| 9 | 9 | |
| 10 | const makeEnvironmentAuthPolicyLayer = ( | |
| 11 | overrides?: Partial<ServerConfig.ServerConfig["Service"]>, | |
| 12 | ) => | |
| 10 | const layerEnvironmentAuthPolicy = (overrides?: Partial<ServerConfig.ServerConfig["Service"]>) => | |
| 13 | 11 | EnvironmentAuthPolicy.layer.pipe( |
| 14 | Layer.provide(ServerEnvironment.identityLayer), | |
| 12 | Layer.provide(ServerEnvironment.layerIdentity), | |
| 15 | 13 | Layer.provide( |
| 16 | 14 | Layer.effect( |
| 17 | 15 | ServerConfig.ServerConfig, |
| ... | ... | @@ -42,7 +40,7 @@ it.layer(NodeServices.layer)("EnvironmentAuthPolicy.layer", (it) => { |
| 42 | 40 | expect(descriptor.sessionCookieName).toBe("t3_session_3773"); |
| 43 | 41 | }).pipe( |
| 44 | 42 | Effect.provide( |
| 45 | makeEnvironmentAuthPolicyLayer({ | |
| 43 | layerEnvironmentAuthPolicy({ | |
| 46 | 44 | mode: "desktop", |
| 47 | 45 | port: 3773, |
| 48 | 46 | }), |
| ... | ... | @@ -58,7 +56,7 @@ it.layer(NodeServices.layer)("EnvironmentAuthPolicy.layer", (it) => { |
| 58 | 56 | expect(descriptor.sessionCookieName).toBe("t3_session_3774"); |
| 59 | 57 | }).pipe( |
| 60 | 58 | Effect.provide( |
| 61 | makeEnvironmentAuthPolicyLayer({ | |
| 59 | layerEnvironmentAuthPolicy({ | |
| 62 | 60 | mode: "desktop", |
| 63 | 61 | port: 3774, |
| 64 | 62 | }), |
| ... | ... | @@ -75,7 +73,7 @@ it.layer(NodeServices.layer)("EnvironmentAuthPolicy.layer", (it) => { |
| 75 | 73 | expect(descriptor.bootstrapMethods).toEqual(["desktop-bootstrap", "one-time-token"]); |
| 76 | 74 | }).pipe( |
| 77 | 75 | Effect.provide( |
| 78 | makeEnvironmentAuthPolicyLayer({ | |
| 76 | layerEnvironmentAuthPolicy({ | |
| 79 | 77 | mode: "desktop", |
| 80 | 78 | host: "0.0.0.0", |
| 81 | 79 | }), |
| ... | ... | @@ -93,7 +91,7 @@ it.layer(NodeServices.layer)("EnvironmentAuthPolicy.layer", (it) => { |
| 93 | 91 | expect(descriptor.sessionCookieName).toMatch(/^t3_session_3773_[a-f0-9]{12}$/); |
| 94 | 92 | }).pipe( |
| 95 | 93 | Effect.provide( |
| 96 | makeEnvironmentAuthPolicyLayer({ | |
| 94 | layerEnvironmentAuthPolicy({ | |
| 97 | 95 | mode: "web", |
| 98 | 96 | host: "127.0.0.1", |
| 99 | 97 | port: 3773, |
| ... | ... | @@ -112,7 +110,7 @@ it.layer(NodeServices.layer)("EnvironmentAuthPolicy.layer", (it) => { |
| 112 | 110 | expect(descriptor.sessionCookieName).toMatch(/^t3_session_[a-f0-9]{12}$/); |
| 113 | 111 | }).pipe( |
| 114 | 112 | Effect.provide( |
| 115 | makeEnvironmentAuthPolicyLayer({ | |
| 113 | layerEnvironmentAuthPolicy({ | |
| 116 | 114 | mode: "web", |
| 117 | 115 | host: "0.0.0.0", |
| 118 | 116 | }), |
| ... | ... | @@ -129,7 +127,7 @@ it.layer(NodeServices.layer)("EnvironmentAuthPolicy.layer", (it) => { |
| 129 | 127 | expect(descriptor.sessionCookieName).toMatch(/^t3_session_5775_[a-f0-9]{12}$/); |
| 130 | 128 | }).pipe( |
| 131 | 129 | Effect.provide( |
| 132 | makeEnvironmentAuthPolicyLayer({ | |
| 130 | layerEnvironmentAuthPolicy({ | |
| 133 | 131 | mode: "web", |
| 134 | 132 | host: "0.0.0.0", |
| 135 | 133 | port: 5775, |
| ... | ... | @@ -148,7 +146,7 @@ it.layer(NodeServices.layer)("EnvironmentAuthPolicy.layer", (it) => { |
| 148 | 146 | expect(descriptor.sessionCookieName).toMatch(/^t3_session_[a-f0-9]{12}$/); |
| 149 | 147 | }).pipe( |
| 150 | 148 | Effect.provide( |
| 151 | makeEnvironmentAuthPolicyLayer({ | |
| 149 | layerEnvironmentAuthPolicy({ | |
| 152 | 150 | mode: "web", |
| 153 | 151 | host: "192.168.1.50", |
| 154 | 152 | }), |
apps/server/src/auth/PairingGrantStore.test.ts+16-16| ... | ... | @@ -10,10 +10,10 @@ import * as TestClock from "effect/testing/TestClock"; |
| 10 | 10 | import * as ServerConfig from "../config.ts"; |
| 11 | 11 | import * as AuthPairingLinks from "../persistence/AuthPairingLinks.ts"; |
| 12 | 12 | import { PersistenceSqlError } from "../persistence/Errors.ts"; |
| 13 | import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts"; | |
| 13 | import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts"; | |
| 14 | 14 | import * as PairingGrantStore from "./PairingGrantStore.ts"; |
| 15 | 15 | |
| 16 | const makeServerConfigLayer = ( | |
| 16 | const layerServerConfig = ( | |
| 17 | 17 | overrides?: Partial<Pick<ServerConfig.ServerConfig["Service"], "desktopBootstrapToken">>, |
| 18 | 18 | ) => |
| 19 | 19 | Layer.effect( |
| ... | ... | @@ -29,15 +29,15 @@ const makeServerConfigLayer = ( |
| 29 | 29 | Layer.provide(ServerConfig.layerTest(process.cwd(), { prefix: "t3-auth-bootstrap-test-" })), |
| 30 | 30 | ); |
| 31 | 31 | |
| 32 | const makePairingGrantStoreLayer = ( | |
| 32 | const layerPairingGrantStore = ( | |
| 33 | 33 | overrides?: Partial<Pick<ServerConfig.ServerConfig["Service"], "desktopBootstrapToken">>, |
| 34 | 34 | ) => |
| 35 | 35 | PairingGrantStore.layer.pipe( |
| 36 | Layer.provide(SqlitePersistenceMemory), | |
| 37 | Layer.provide(makeServerConfigLayer(overrides)), | |
| 36 | Layer.provide(SqlitePersistence.layerMemory), | |
| 37 | Layer.provide(layerServerConfig(overrides)), | |
| 38 | 38 | ); |
| 39 | 39 | |
| 40 | const makePairingGrantStoreTestLayer = ( | |
| 40 | const layerPairingGrantStoreTest = ( | |
| 41 | 41 | overrides: Partial<AuthPairingLinks.AuthPairingLinkRepository["Service"]>, |
| 42 | 42 | ) => |
| 43 | 43 | Layer.effect(PairingGrantStore.PairingGrantStore, PairingGrantStore.make).pipe( |
| ... | ... | @@ -54,7 +54,7 @@ const makePairingGrantStoreTestLayer = ( |
| 54 | 54 | }), |
| 55 | 55 | ), |
| 56 | 56 | ), |
| 57 | Layer.provide(makeServerConfigLayer()), | |
| 57 | Layer.provide(layerServerConfig()), | |
| 58 | 58 | ); |
| 59 | 59 | |
| 60 | 60 | it.layer(NodeServices.layer)("PairingGrantStore.layer", (it) => { |
| ... | ... | @@ -64,7 +64,7 @@ it.layer(NodeServices.layer)("PairingGrantStore.layer", (it) => { |
| 64 | 64 | const issued = yield* bootstrapCredentials.issueOneTimeToken(); |
| 65 | 65 | |
| 66 | 66 | expect(issued.credential).toMatch(/^[23456789ABCDEFGHJKLMNPQRSTUVWXYZ]{12}$/); |
| 67 | }).pipe(Effect.provide(makePairingGrantStoreLayer())), | |
| 67 | }).pipe(Effect.provide(layerPairingGrantStore())), | |
| 68 | 68 | ); |
| 69 | 69 | |
| 70 | 70 | it.effect("issues one-time bootstrap tokens that can only be consumed once", () => |
| ... | ... | @@ -87,7 +87,7 @@ it.layer(NodeServices.layer)("PairingGrantStore.layer", (it) => { |
| 87 | 87 | expect(issued.label).toBe("Julius iPhone"); |
| 88 | 88 | expect(second._tag).toBe("UnknownBootstrapCredentialError"); |
| 89 | 89 | expect(second.message).toContain("Unknown bootstrap credential"); |
| 90 | }).pipe(Effect.provide(makePairingGrantStoreLayer())), | |
| 90 | }).pipe(Effect.provide(layerPairingGrantStore())), | |
| 91 | 91 | ); |
| 92 | 92 | |
| 93 | 93 | it.effect("atomically consumes a one-time token when multiple requests race", () => |
| ... | ... | @@ -112,7 +112,7 @@ it.layer(NodeServices.layer)("PairingGrantStore.layer", (it) => { |
| 112 | 112 | expect(failure.failure._tag).toBe("UnknownBootstrapCredentialError"); |
| 113 | 113 | expect(failure.failure.message).toContain("Unknown bootstrap credential"); |
| 114 | 114 | } |
| 115 | }).pipe(Effect.provide(makePairingGrantStoreLayer())), | |
| 115 | }).pipe(Effect.provide(layerPairingGrantStore())), | |
| 116 | 116 | ); |
| 117 | 117 | |
| 118 | 118 | it.effect("requires the bound proof key thumbprint when present", () => |
| ... | ... | @@ -135,7 +135,7 @@ it.layer(NodeServices.layer)("PairingGrantStore.layer", (it) => { |
| 135 | 135 | expect(missing.message).toContain("proof key mismatch"); |
| 136 | 136 | expect(wrong.message).toContain("proof key mismatch"); |
| 137 | 137 | expect(consumed.proofKeyThumbprint).toBe("client-proof-key-thumbprint"); |
| 138 | }).pipe(Effect.provide(makePairingGrantStoreLayer())), | |
| 138 | }).pipe(Effect.provide(layerPairingGrantStore())), | |
| 139 | 139 | ); |
| 140 | 140 | |
| 141 | 141 | it.effect("seeds the desktop bootstrap credential as a reusable grant", () => |
| ... | ... | @@ -161,7 +161,7 @@ it.layer(NodeServices.layer)("PairingGrantStore.layer", (it) => { |
| 161 | 161 | expect(third.method).toBe("desktop-bootstrap"); |
| 162 | 162 | }).pipe( |
| 163 | 163 | Effect.provide( |
| 164 | makePairingGrantStoreLayer({ | |
| 164 | layerPairingGrantStore({ | |
| 165 | 165 | desktopBootstrapToken: "desktop-bootstrap-token", |
| 166 | 166 | }), |
| 167 | 167 | ), |
| ... | ... | @@ -186,7 +186,7 @@ it.layer(NodeServices.layer)("PairingGrantStore.layer", (it) => { |
| 186 | 186 | }).pipe( |
| 187 | 187 | Effect.provide( |
| 188 | 188 | Layer.merge( |
| 189 | makePairingGrantStoreLayer({ | |
| 189 | layerPairingGrantStore({ | |
| 190 | 190 | desktopBootstrapToken: "desktop-bootstrap-token", |
| 191 | 191 | }), |
| 192 | 192 | TestClock.layer(), |
| ... | ... | @@ -217,7 +217,7 @@ it.layer(NodeServices.layer)("PairingGrantStore.layer", (it) => { |
| 217 | 217 | expect(consumed.scopes).toEqual(change.pairingLink.scopes); |
| 218 | 218 | expect(yield* Queue.take(changes)).toEqual({ type: "pairingLinkRemoved", id: issued.id }); |
| 219 | 219 | } |
| 220 | }).pipe(Effect.scoped, Effect.provide(makePairingGrantStoreLayer())), | |
| 220 | }).pipe(Effect.scoped, Effect.provide(layerPairingGrantStore())), | |
| 221 | 221 | ); |
| 222 | 222 | |
| 223 | 223 | it.effect("lists and revokes active pairing links", () => |
| ... | ... | @@ -244,7 +244,7 @@ it.layer(NodeServices.layer)("PairingGrantStore.layer", (it) => { |
| 244 | 244 | expect(activeAfterRevoke.map((entry) => entry.id)).toContain(second.id); |
| 245 | 245 | expect(revokedConsume.message).toContain("no longer available"); |
| 246 | 246 | expect(revokedConsume._tag).toBe("UnavailableBootstrapCredentialError"); |
| 247 | }).pipe(Effect.provide(makePairingGrantStoreLayer())), | |
| 247 | }).pipe(Effect.provide(layerPairingGrantStore())), | |
| 248 | 248 | ); |
| 249 | 249 | |
| 250 | 250 | it.effect("identifies consume-available failures and preserves their cause", () => { |
| ... | ... | @@ -264,7 +264,7 @@ it.layer(NodeServices.layer)("PairingGrantStore.layer", (it) => { |
| 264 | 264 | expect(error.cause).toBe(repositoryFailure); |
| 265 | 265 | }).pipe( |
| 266 | 266 | Effect.provide( |
| 267 | makePairingGrantStoreTestLayer({ | |
| 267 | layerPairingGrantStoreTest({ | |
| 268 | 268 | consumeAvailable: () => Effect.fail(repositoryFailure), |
| 269 | 269 | }), |
| 270 | 270 | ), |
apps/server/src/auth/RpcAuthorization.test.ts+2-2| ... | ... | @@ -15,8 +15,8 @@ import { |
| 15 | 15 | RPC_REQUIRED_SCOPES, |
| 16 | 16 | requiredScopeForRpcMethod, |
| 17 | 17 | requiredScopeForDeviceList, |
| 18 | rpcScopeAuthorizationLayer, | |
| 19 | 18 | } from "./RpcAuthorization.ts"; |
| 19 | import * as RpcAuthorization from "./RpcAuthorization.ts"; | |
| 20 | 20 | |
| 21 | 21 | describe("RPC authorization scopes", () => { |
| 22 | 22 | it("declares exactly one scope for every RPC in the server group", () => { |
| ... | ... | @@ -150,7 +150,7 @@ describe("RPC scope middleware", () => { |
| 150 | 150 | group.toLayerHandler(WS_METHODS.serverRetryResourceTelemetry, () => |
| 151 | 151 | Effect.sync(() => handled.push("retry")).pipe(Effect.andThen(Effect.never)), |
| 152 | 152 | ), |
| 153 | rpcScopeAuthorizationLayer([AuthOrchestrationReadScope]), | |
| 153 | RpcAuthorization.layer([AuthOrchestrationReadScope]), | |
| 154 | 154 | ), |
| 155 | 155 | ), |
| 156 | 156 | ); |
apps/server/src/auth/RpcAuthorization.ts+1-1| ... | ... | @@ -229,7 +229,7 @@ export const rpcAuthorizationError = (requiredScope: AuthEnvironmentScope) => |
| 229 | 229 | }); |
| 230 | 230 | |
| 231 | 231 | /** Authorizes every RPC on one connection against that connection's session scopes. */ |
| 232 | export const rpcScopeAuthorizationLayer = (scopes: ReadonlyArray<AuthEnvironmentScope>) => | |
| 232 | export const layer = (scopes: ReadonlyArray<AuthEnvironmentScope>) => | |
| 233 | 233 | Layer.succeed(RpcScopeAuthorization)((effect, { rpc }) => { |
| 234 | 234 | const requiredScope = requiredScopeForRpcMethod(rpc._tag); |
| 235 | 235 | return scopes.includes(requiredScope) |
apps/server/src/auth/ServerSecretStore.test.ts+27-28| ... | ... | @@ -12,13 +12,12 @@ import * as PlatformError from "effect/PlatformError"; |
| 12 | 12 | import * as ServerConfig from "../config.ts"; |
| 13 | 13 | import * as ServerSecretStore from "./ServerSecretStore.ts"; |
| 14 | 14 | |
| 15 | const makeServerConfigLayer = () => | |
| 15 | const layerServerConfig = () => | |
| 16 | 16 | ServerConfig.layerTest(process.cwd(), { prefix: "t3-secret-store-test-" }); |
| 17 | 17 | |
| 18 | const makeServerSecretStoreLayer = () => | |
| 19 | Layer.provide(ServerSecretStore.layer, makeServerConfigLayer()); | |
| 18 | const layerServerSecretStore = () => Layer.provide(ServerSecretStore.layer, layerServerConfig()); | |
| 20 | 19 | |
| 21 | const PermissionDeniedFileSystemLayer = Layer.effect( | |
| 20 | const layerPermissionDeniedFileSystem = Layer.effect( | |
| 22 | 21 | FileSystem.FileSystem, |
| 23 | 22 | Effect.gen(function* () { |
| 24 | 23 | const fileSystem = yield* FileSystem.FileSystem; |
| ... | ... | @@ -39,13 +38,13 @@ const PermissionDeniedFileSystemLayer = Layer.effect( |
| 39 | 38 | }), |
| 40 | 39 | ).pipe(Layer.provide(NodeServices.layer)); |
| 41 | 40 | |
| 42 | const makePermissionDeniedSecretStoreLayer = () => | |
| 41 | const layerPermissionDeniedSecretStore = () => | |
| 43 | 42 | ServerSecretStore.layer.pipe( |
| 44 | Layer.provide(makeServerConfigLayer()), | |
| 45 | Layer.provideMerge(PermissionDeniedFileSystemLayer), | |
| 43 | Layer.provide(layerServerConfig()), | |
| 44 | Layer.provideMerge(layerPermissionDeniedFileSystem), | |
| 46 | 45 | ); |
| 47 | 46 | |
| 48 | const RenameFailureFileSystemLayer = Layer.effect( | |
| 47 | const layerRenameFailureFileSystem = Layer.effect( | |
| 49 | 48 | FileSystem.FileSystem, |
| 50 | 49 | Effect.gen(function* () { |
| 51 | 50 | const fileSystem = yield* FileSystem.FileSystem; |
| ... | ... | @@ -66,13 +65,13 @@ const RenameFailureFileSystemLayer = Layer.effect( |
| 66 | 65 | }), |
| 67 | 66 | ).pipe(Layer.provide(NodeServices.layer)); |
| 68 | 67 | |
| 69 | const makeRenameFailureSecretStoreLayer = () => | |
| 68 | const layerRenameFailureSecretStore = () => | |
| 70 | 69 | ServerSecretStore.layer.pipe( |
| 71 | Layer.provide(makeServerConfigLayer()), | |
| 72 | Layer.provideMerge(RenameFailureFileSystemLayer), | |
| 70 | Layer.provide(layerServerConfig()), | |
| 71 | Layer.provideMerge(layerRenameFailureFileSystem), | |
| 73 | 72 | ); |
| 74 | 73 | |
| 75 | const RemoveFailureFileSystemLayer = Layer.effect( | |
| 74 | const layerRemoveFailureFileSystem = Layer.effect( | |
| 76 | 75 | FileSystem.FileSystem, |
| 77 | 76 | Effect.gen(function* () { |
| 78 | 77 | const fileSystem = yield* FileSystem.FileSystem; |
| ... | ... | @@ -93,13 +92,13 @@ const RemoveFailureFileSystemLayer = Layer.effect( |
| 93 | 92 | }), |
| 94 | 93 | ).pipe(Layer.provide(NodeServices.layer)); |
| 95 | 94 | |
| 96 | const makeRemoveFailureSecretStoreLayer = () => | |
| 95 | const layerRemoveFailureSecretStore = () => | |
| 97 | 96 | ServerSecretStore.layer.pipe( |
| 98 | Layer.provide(makeServerConfigLayer()), | |
| 99 | Layer.provideMerge(RemoveFailureFileSystemLayer), | |
| 97 | Layer.provide(layerServerConfig()), | |
| 98 | Layer.provideMerge(layerRemoveFailureFileSystem), | |
| 100 | 99 | ); |
| 101 | 100 | |
| 102 | const ConcurrentReadMissFileSystemLayer = Layer.effect( | |
| 101 | const layerConcurrentReadMissFileSystem = Layer.effect( | |
| 103 | 102 | FileSystem.FileSystem, |
| 104 | 103 | Effect.gen(function* () { |
| 105 | 104 | const fileSystem = yield* FileSystem.FileSystem; |
| ... | ... | @@ -139,10 +138,10 @@ const ConcurrentReadMissFileSystemLayer = Layer.effect( |
| 139 | 138 | }), |
| 140 | 139 | ).pipe(Layer.provide(NodeServices.layer)); |
| 141 | 140 | |
| 142 | const makeConcurrentCreateSecretStoreLayer = () => | |
| 141 | const layerConcurrentCreateSecretStore = () => | |
| 143 | 142 | ServerSecretStore.layer.pipe( |
| 144 | Layer.provide(makeServerConfigLayer()), | |
| 145 | Layer.provideMerge(ConcurrentReadMissFileSystemLayer), | |
| 143 | Layer.provide(layerServerConfig()), | |
| 144 | Layer.provideMerge(layerConcurrentReadMissFileSystem), | |
| 146 | 145 | ); |
| 147 | 146 | |
| 148 | 147 | it.layer(NodeServices.layer)("ServerSecretStore.layer", (it) => { |
| ... | ... | @@ -153,7 +152,7 @@ it.layer(NodeServices.layer)("ServerSecretStore.layer", (it) => { |
| 153 | 152 | const secret = yield* secretStore.get("missing-secret"); |
| 154 | 153 | |
| 155 | 154 | assert.isTrue(Option.isNone(secret)); |
| 156 | }).pipe(Effect.provide(makeServerSecretStoreLayer())), | |
| 155 | }).pipe(Effect.provide(layerServerSecretStore())), | |
| 157 | 156 | ); |
| 158 | 157 | |
| 159 | 158 | it.effect("reuses an existing secret instead of regenerating it", () => |
| ... | ... | @@ -164,7 +163,7 @@ it.layer(NodeServices.layer)("ServerSecretStore.layer", (it) => { |
| 164 | 163 | const second = yield* secretStore.getOrCreateRandom("session-signing-key", 32); |
| 165 | 164 | |
| 166 | 165 | assert.deepEqual(Array.from(second), Array.from(first)); |
| 167 | }).pipe(Effect.provide(makeServerSecretStoreLayer())), | |
| 166 | }).pipe(Effect.provide(layerServerSecretStore())), | |
| 168 | 167 | ); |
| 169 | 168 | |
| 170 | 169 | it.effect("returns the persisted secret when concurrent creators race", () => |
| ... | ... | @@ -183,13 +182,13 @@ it.layer(NodeServices.layer)("ServerSecretStore.layer", (it) => { |
| 183 | 182 | |
| 184 | 183 | assert.deepEqual(Array.from(first), Array.from(persistedBytes)); |
| 185 | 184 | assert.deepEqual(Array.from(second), Array.from(persistedBytes)); |
| 186 | }).pipe(Effect.provide(makeConcurrentCreateSecretStoreLayer())), | |
| 185 | }).pipe(Effect.provide(layerConcurrentCreateSecretStore())), | |
| 187 | 186 | ); |
| 188 | 187 | |
| 189 | 188 | it.effect("uses restrictive permissions for the secret directory and files", () => |
| 190 | 189 | Effect.gen(function* () { |
| 191 | 190 | const chmodCalls: Array<{ readonly path: string; readonly mode: number }> = []; |
| 192 | const recordingFileSystemLayer = Layer.effect( | |
| 191 | const layerRecordingFileSystem = Layer.effect( | |
| 193 | 192 | FileSystem.FileSystem, |
| 194 | 193 | Effect.gen(function* () { |
| 195 | 194 | const fileSystem = yield* FileSystem.FileSystem; |
| ... | ... | @@ -210,8 +209,8 @@ it.layer(NodeServices.layer)("ServerSecretStore.layer", (it) => { |
| 210 | 209 | const secretStore = yield* Effect.service(ServerSecretStore.ServerSecretStore).pipe( |
| 211 | 210 | Effect.provide( |
| 212 | 211 | ServerSecretStore.layer.pipe( |
| 213 | Layer.provide(makeServerConfigLayer()), | |
| 214 | Layer.provideMerge(recordingFileSystemLayer), | |
| 212 | Layer.provide(layerServerConfig()), | |
| 213 | Layer.provideMerge(layerRecordingFileSystem), | |
| 215 | 214 | ), |
| 216 | 215 | ), |
| 217 | 216 | ); |
| ... | ... | @@ -235,7 +234,7 @@ it.layer(NodeServices.layer)("ServerSecretStore.layer", (it) => { |
| 235 | 234 | assert.include(error.message, "Failed to read secret session-signing-key."); |
| 236 | 235 | assert.instanceOf(error.cause, PlatformError.PlatformError); |
| 237 | 236 | assert.equal((error.cause as PlatformError.PlatformError).reason._tag, "PermissionDenied"); |
| 238 | }).pipe(Effect.provide(makePermissionDeniedSecretStoreLayer())), | |
| 237 | }).pipe(Effect.provide(layerPermissionDeniedSecretStore())), | |
| 239 | 238 | ); |
| 240 | 239 | |
| 241 | 240 | it.effect("propagates write failures instead of treating them as success", () => |
| ... | ... | @@ -250,7 +249,7 @@ it.layer(NodeServices.layer)("ServerSecretStore.layer", (it) => { |
| 250 | 249 | assert.include(error.message, "Failed to persist secret session-signing-key."); |
| 251 | 250 | assert.instanceOf(error.cause, PlatformError.PlatformError); |
| 252 | 251 | assert.equal((error.cause as PlatformError.PlatformError).reason._tag, "PermissionDenied"); |
| 253 | }).pipe(Effect.provide(makeRenameFailureSecretStoreLayer())), | |
| 252 | }).pipe(Effect.provide(layerRenameFailureSecretStore())), | |
| 254 | 253 | ); |
| 255 | 254 | |
| 256 | 255 | it.effect("propagates remove failures other than missing-file errors", () => |
| ... | ... | @@ -263,6 +262,6 @@ it.layer(NodeServices.layer)("ServerSecretStore.layer", (it) => { |
| 263 | 262 | assert.include(error.message, "Failed to remove secret session-signing-key."); |
| 264 | 263 | assert.instanceOf(error.cause, PlatformError.PlatformError); |
| 265 | 264 | assert.equal((error.cause as PlatformError.PlatformError).reason._tag, "PermissionDenied"); |
| 266 | }).pipe(Effect.provide(makeRemoveFailureSecretStoreLayer())), | |
| 265 | }).pipe(Effect.provide(layerRemoveFailureSecretStore())), | |
| 267 | 266 | ); |
| 268 | 267 | }); |
apps/server/src/auth/SessionStore.test.ts+44-52| ... | ... | @@ -15,15 +15,13 @@ import * as SqlClient from "effect/sql/SqlClient"; |
| 15 | 15 | import * as ServerConfig from "../config.ts"; |
| 16 | 16 | import * as ServerEnvironment from "../environment/ServerEnvironment.ts"; |
| 17 | 17 | import { PersistenceSqlError } from "../persistence/Errors.ts"; |
| 18 | import { | |
| 19 | makeSqlitePersistenceLive, | |
| 20 | SqlitePersistenceMemory, | |
| 21 | } from "../persistence/Layers/Sqlite.ts"; | |
| 18 | ||
| 19 | import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts"; | |
| 22 | 20 | import * as AuthSessions from "../persistence/AuthSessions.ts"; |
| 23 | 21 | import * as SessionStore from "./SessionStore.ts"; |
| 24 | 22 | import * as ServerSecretStore from "./ServerSecretStore.ts"; |
| 25 | 23 | |
| 26 | const makeServerConfigLayer = (overrides?: Partial<ServerConfig.ServerConfig["Service"]>) => | |
| 24 | const layerServerConfig = (overrides?: Partial<ServerConfig.ServerConfig["Service"]>) => | |
| 27 | 25 | Layer.effect( |
| 28 | 26 | ServerConfig.ServerConfig, |
| 29 | 27 | Effect.gen(function* () { |
| ... | ... | @@ -35,20 +33,20 @@ const makeServerConfigLayer = (overrides?: Partial<ServerConfig.ServerConfig["Se |
| 35 | 33 | }), |
| 36 | 34 | ).pipe(Layer.provide(ServerConfig.layerTest(process.cwd(), { prefix: "t3-auth-session-test-" }))); |
| 37 | 35 | |
| 38 | const makeServerEnvironmentLayer = (environmentId: EnvironmentId) => | |
| 36 | const layerServerEnvironment = (environmentId: EnvironmentId) => | |
| 39 | 37 | Layer.succeed(ServerEnvironment.ServerEnvironmentIdentity, { |
| 40 | 38 | getEnvironmentId: Effect.succeed(environmentId), |
| 41 | 39 | }); |
| 42 | 40 | |
| 43 | const makeSessionStoreLayer = ( | |
| 41 | const layerSessionStore = ( | |
| 44 | 42 | overrides?: Partial<ServerConfig.ServerConfig["Service"]>, |
| 45 | 43 | environmentId = EnvironmentId.make("test-environment"), |
| 46 | 44 | ) => |
| 47 | 45 | SessionStore.layer.pipe( |
| 48 | Layer.provide(SqlitePersistenceMemory), | |
| 46 | Layer.provide(SqlitePersistence.layerMemory), | |
| 49 | 47 | Layer.provide(ServerSecretStore.layer), |
| 50 | Layer.provide(makeServerEnvironmentLayer(environmentId)), | |
| 51 | Layer.provide(makeServerConfigLayer(overrides)), | |
| 48 | Layer.provide(layerServerEnvironment(environmentId)), | |
| 49 | Layer.provide(layerServerConfig(overrides)), | |
| 52 | 50 | ); |
| 53 | 51 | |
| 54 | 52 | const relaySessionInput = { |
| ... | ... | @@ -68,13 +66,13 @@ const makeDiskSessionStoreLayer = Effect.fn("makeDiskSessionStoreLayer")(functio |
| 68 | 66 | baseDirIsExplicit: true, |
| 69 | 67 | }); |
| 70 | 68 | yield* ServerConfig.ensureServerDirectories(paths); |
| 71 | const persistence = makeSqlitePersistenceLive(paths.dbPath); | |
| 69 | const layerPersistence = SqlitePersistence.layerFromPath(paths.dbPath); | |
| 72 | 70 | return SessionStore.layer.pipe( |
| 73 | Layer.provide(persistence), | |
| 71 | Layer.provide(layerPersistence), | |
| 74 | 72 | Layer.provide(ServerSecretStore.layer), |
| 75 | Layer.provide(makeServerEnvironmentLayer(EnvironmentId.make(baseDir))), | |
| 73 | Layer.provide(layerServerEnvironment(EnvironmentId.make(baseDir))), | |
| 76 | 74 | Layer.provide( |
| 77 | makeServerConfigLayer({ | |
| 75 | layerServerConfig({ | |
| 78 | 76 | ...paths, |
| 79 | 77 | baseDir, |
| 80 | 78 | mode: "web", |
| ... | ... | @@ -90,7 +88,7 @@ const repositoryFailure = new PersistenceSqlError({ |
| 90 | 88 | detail: "sqlite is unavailable", |
| 91 | 89 | }); |
| 92 | 90 | |
| 93 | const failingSessionLookupRepositoryLayer = Layer.succeed(AuthSessions.AuthSessionRepository, { | |
| 91 | const layerFailingSessionLookupRepository = Layer.succeed(AuthSessions.AuthSessionRepository, { | |
| 94 | 92 | create: () => Effect.void, |
| 95 | 93 | createReplacingActive: () => Effect.succeed([]), |
| 96 | 94 | createIfAbsent: () => Effect.void, |
| ... | ... | @@ -102,15 +100,15 @@ const failingSessionLookupRepositoryLayer = Layer.succeed(AuthSessions.AuthSessi |
| 102 | 100 | setClientConnection: () => Effect.void, |
| 103 | 101 | }); |
| 104 | 102 | |
| 105 | const failingSessionLookupCredentialLayer = Layer.effect( | |
| 103 | const layerFailingSessionLookupCredential = Layer.effect( | |
| 106 | 104 | SessionStore.SessionStore, |
| 107 | 105 | SessionStore.make, |
| 108 | 106 | ).pipe( |
| 109 | Layer.provide(failingSessionLookupRepositoryLayer), | |
| 107 | Layer.provide(layerFailingSessionLookupRepository), | |
| 110 | 108 | Layer.provide(ServerSecretStore.layer), |
| 111 | Layer.provide(SqlitePersistenceMemory), | |
| 112 | Layer.provide(makeServerEnvironmentLayer(EnvironmentId.make("test-environment"))), | |
| 113 | Layer.provide(makeServerConfigLayer()), | |
| 109 | Layer.provide(SqlitePersistence.layerMemory), | |
| 110 | Layer.provide(layerServerEnvironment(EnvironmentId.make("test-environment"))), | |
| 111 | Layer.provide(layerServerConfig()), | |
| 114 | 112 | ); |
| 115 | 113 | |
| 116 | 114 | it.layer(NodeServices.layer)("SessionStore.layer", (it) => { |
| ... | ... | @@ -122,7 +120,7 @@ it.layer(NodeServices.layer)("SessionStore.layer", (it) => { |
| 122 | 120 | return sessions.cookieName; |
| 123 | 121 | }).pipe( |
| 124 | 122 | Effect.provide( |
| 125 | makeSessionStoreLayer({ mode: "web", host: "192.168.1.50", stateDir }, environmentId), | |
| 123 | layerSessionStore({ mode: "web", host: "192.168.1.50", stateDir }, environmentId), | |
| 126 | 124 | ), |
| 127 | 125 | ); |
| 128 | 126 | |
| ... | ... | @@ -164,20 +162,20 @@ it.layer(NodeServices.layer)("SessionStore.layer", (it) => { |
| 164 | 162 | ); |
| 165 | 163 | }).pipe(Effect.provide(layerB), Effect.scoped); |
| 166 | 164 | |
| 167 | const reopenedB = yield* makeDiskSessionStoreLayer(baseB, token); | |
| 165 | const layerReopenedB = yield* makeDiskSessionStoreLayer(baseB, token); | |
| 168 | 166 | yield* Effect.gen(function* () { |
| 169 | 167 | const sessions = yield* SessionStore.SessionStore; |
| 170 | 168 | const dev = yield* sessions.verify(token); |
| 171 | 169 | expect(dev.sessionId).toBe(fromA.dev.sessionId); |
| 172 | 170 | const ticket = yield* sessions.issueWebSocketToken(dev.sessionId); |
| 173 | 171 | expect((yield* sessions.verifyWebSocketToken(ticket.token)).sessionId).toBe(dev.sessionId); |
| 174 | }).pipe(Effect.provide(reopenedB), Effect.scoped); | |
| 172 | }).pipe(Effect.provide(layerReopenedB), Effect.scoped); | |
| 175 | 173 | |
| 176 | const reopenedA = yield* makeDiskSessionStoreLayer(baseA, token); | |
| 174 | const layerReopenedA = yield* makeDiskSessionStoreLayer(baseA, token); | |
| 177 | 175 | yield* Effect.gen(function* () { |
| 178 | 176 | const sessions = yield* SessionStore.SessionStore; |
| 179 | 177 | expect((yield* Effect.flip(sessions.verify(token)))._tag).toBe("SessionTokenRevokedError"); |
| 180 | }).pipe(Effect.provide(reopenedA), Effect.scoped); | |
| 178 | }).pipe(Effect.provide(layerReopenedA), Effect.scoped); | |
| 181 | 179 | }), |
| 182 | 180 | ); |
| 183 | 181 | |
| ... | ... | @@ -187,15 +185,15 @@ it.layer(NodeServices.layer)("SessionStore.layer", (it) => { |
| 187 | 185 | const baseDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-dev-auth-rotation-" }); |
| 188 | 186 | const oldToken = "old-reusable-dev-auth-token-that-is-long-enough"; |
| 189 | 187 | const newToken = "new-reusable-dev-auth-token-that-is-long-enough"; |
| 190 | const initialLayer = yield* makeDiskSessionStoreLayer(baseDir, oldToken); | |
| 188 | const layerInitial = yield* makeDiskSessionStoreLayer(baseDir, oldToken); | |
| 191 | 189 | const old = yield* Effect.gen(function* () { |
| 192 | 190 | const sessions = yield* SessionStore.SessionStore; |
| 193 | 191 | const dev = yield* sessions.verify(oldToken); |
| 194 | 192 | const ticket = yield* sessions.issueWebSocketToken(dev.sessionId); |
| 195 | 193 | return { dev, ticket }; |
| 196 | }).pipe(Effect.provide(initialLayer), Effect.scoped); | |
| 194 | }).pipe(Effect.provide(layerInitial), Effect.scoped); | |
| 197 | 195 | |
| 198 | const rotatedLayer = yield* makeDiskSessionStoreLayer(baseDir, newToken); | |
| 196 | const layerRotated = yield* makeDiskSessionStoreLayer(baseDir, newToken); | |
| 199 | 197 | const rotated = yield* Effect.gen(function* () { |
| 200 | 198 | const sessions = yield* SessionStore.SessionStore; |
| 201 | 199 | expect((yield* Effect.flip(sessions.verify(oldToken)))._tag).toBe( |
| ... | ... | @@ -210,9 +208,9 @@ it.layer(NodeServices.layer)("SessionStore.layer", (it) => { |
| 210 | 208 | const dev = yield* sessions.verify(newToken); |
| 211 | 209 | const ticket = yield* sessions.issueWebSocketToken(dev.sessionId); |
| 212 | 210 | return { dev, ticket }; |
| 213 | }).pipe(Effect.provide(rotatedLayer), Effect.scoped); | |
| 211 | }).pipe(Effect.provide(layerRotated), Effect.scoped); | |
| 214 | 212 | |
| 215 | const removedLayer = yield* makeDiskSessionStoreLayer(baseDir); | |
| 213 | const layerRemoved = yield* makeDiskSessionStoreLayer(baseDir); | |
| 216 | 214 | yield* Effect.gen(function* () { |
| 217 | 215 | const sessions = yield* SessionStore.SessionStore; |
| 218 | 216 | expect((yield* Effect.flip(sessions.verify(newToken)))._tag).toBe( |
| ... | ... | @@ -224,7 +222,7 @@ it.layer(NodeServices.layer)("SessionStore.layer", (it) => { |
| 224 | 222 | expect( |
| 225 | 223 | (yield* sessions.listActive()).some((row) => row.sessionId === rotated.dev.sessionId), |
| 226 | 224 | ).toBe(true); |
| 227 | }).pipe(Effect.provide(removedLayer), Effect.scoped); | |
| 225 | }).pipe(Effect.provide(layerRemoved), Effect.scoped); | |
| 228 | 226 | }), |
| 229 | 227 | ); |
| 230 | 228 | |
| ... | ... | @@ -243,7 +241,7 @@ it.layer(NodeServices.layer)("SessionStore.layer", (it) => { |
| 243 | 241 | }).pipe( |
| 244 | 242 | Effect.provide( |
| 245 | 243 | Layer.merge( |
| 246 | makeSessionStoreLayer({ | |
| 244 | layerSessionStore({ | |
| 247 | 245 | mode: "web", |
| 248 | 246 | devUrl: new URL("http://127.0.0.1:5173"), |
| 249 | 247 | devAuthToken: Redacted.make("reusable-dev-auth-token-that-is-long-enough"), |
| ... | ... | @@ -276,7 +274,7 @@ it.layer(NodeServices.layer)("SessionStore.layer", (it) => { |
| 276 | 274 | expect(verified.client.label).toBe("Desktop app"); |
| 277 | 275 | expect(verified.client.browser).toBe("Electron"); |
| 278 | 276 | expect(verified.expiresAt?.toString()).toBe(issued.expiresAt.toString()); |
| 279 | }).pipe(Effect.provide(makeSessionStoreLayer())), | |
| 277 | }).pipe(Effect.provide(layerSessionStore())), | |
| 280 | 278 | ); |
| 281 | 279 | it.effect("rejects malformed session tokens", () => |
| 282 | 280 | Effect.gen(function* () { |
| ... | ... | @@ -285,7 +283,7 @@ it.layer(NodeServices.layer)("SessionStore.layer", (it) => { |
| 285 | 283 | |
| 286 | 284 | expect(error._tag).toBe("MalformedSessionTokenError"); |
| 287 | 285 | expect(error.message).toContain("Malformed session token"); |
| 288 | }).pipe(Effect.provide(makeSessionStoreLayer())), | |
| 286 | }).pipe(Effect.provide(layerSessionStore())), | |
| 289 | 287 | ); |
| 290 | 288 | it.effect("preserves repository failures while verifying session and websocket credentials", () => |
| 291 | 289 | Effect.gen(function* () { |
| ... | ... | @@ -321,7 +319,7 @@ it.layer(NodeServices.layer)("SessionStore.layer", (it) => { |
| 321 | 319 | currentSessionId: issued.sessionId, |
| 322 | 320 | cause: repositoryFailure, |
| 323 | 321 | }); |
| 324 | }).pipe(Effect.provide(failingSessionLookupCredentialLayer)), | |
| 322 | }).pipe(Effect.provide(layerFailingSessionLookupCredential)), | |
| 325 | 323 | ); |
| 326 | 324 | it.effect("verifies session tokens against the Effect clock", () => |
| 327 | 325 | Effect.gen(function* () { |
| ... | ... | @@ -341,7 +339,7 @@ it.layer(NodeServices.layer)("SessionStore.layer", (it) => { |
| 341 | 339 | "review:write", |
| 342 | 340 | "relay:read", |
| 343 | 341 | ]); |
| 344 | }).pipe(Effect.provide(Layer.merge(makeSessionStoreLayer(), TestClock.layer()))), | |
| 342 | }).pipe(Effect.provide(Layer.merge(layerSessionStore(), TestClock.layer()))), | |
| 345 | 343 | ); |
| 346 | 344 | |
| 347 | 345 | it.effect("atomically replaces active sessions with the same subject and method", () => |
| ... | ... | @@ -382,7 +380,7 @@ it.layer(NodeServices.layer)("SessionStore.layer", (it) => { |
| 382 | 380 | ), |
| 383 | 381 | ).toHaveLength(1); |
| 384 | 382 | expect(bearerVerification.filter(Option.isSome)).toHaveLength(1); |
| 385 | }).pipe(Effect.provide(makeSessionStoreLayer())), | |
| 383 | }).pipe(Effect.provide(layerSessionStore())), | |
| 386 | 384 | ); |
| 387 | 385 | |
| 388 | 386 | it.effect("keeps the previous desktop session valid when replacement fails", () => |
| ... | ... | @@ -413,7 +411,7 @@ it.layer(NodeServices.layer)("SessionStore.layer", (it) => { |
| 413 | 411 | expect((yield* sessions.listActive()).map((session) => session.sessionId)).toEqual([ |
| 414 | 412 | previous.sessionId, |
| 415 | 413 | ]); |
| 416 | }).pipe(Effect.provide(Layer.mergeAll(makeSessionStoreLayer(), SqlitePersistenceMemory))), | |
| 414 | }).pipe(Effect.provide(Layer.mergeAll(layerSessionStore(), SqlitePersistence.layerMemory))), | |
| 417 | 415 | ); |
| 418 | 416 | |
| 419 | 417 | it.effect("rejects websocket tokens once the parent session has expired", () => |
| ... | ... | @@ -437,7 +435,7 @@ it.layer(NodeServices.layer)("SessionStore.layer", (it) => { |
| 437 | 435 | error.expiresAt.epochMilliseconds, |
| 438 | 436 | ); |
| 439 | 437 | } |
| 440 | }).pipe(Effect.provide(Layer.merge(makeSessionStoreLayer(), TestClock.layer()))), | |
| 438 | }).pipe(Effect.provide(Layer.merge(layerSessionStore(), TestClock.layer()))), | |
| 441 | 439 | ); |
| 442 | 440 | |
| 443 | 441 | it.effect("includes expiry context when session and websocket tokens expire", () => |
| ... | ... | @@ -475,7 +473,7 @@ it.layer(NodeServices.layer)("SessionStore.layer", (it) => { |
| 475 | 473 | websocketError.expiresAt.epochMilliseconds, |
| 476 | 474 | ); |
| 477 | 475 | } |
| 478 | }).pipe(Effect.provide(Layer.merge(makeSessionStoreLayer(), TestClock.layer()))), | |
| 476 | }).pipe(Effect.provide(Layer.merge(layerSessionStore(), TestClock.layer()))), | |
| 479 | 477 | ); |
| 480 | 478 | |
| 481 | 479 | it.effect("lists active sessions, tracks connectivity, and revokes other sessions", () => |
| ... | ... | @@ -537,7 +535,7 @@ it.layer(NodeServices.layer)("SessionStore.layer", (it) => { |
| 537 | 535 | expect(revokedClientWebSocket.sessionId).toBe(client.sessionId); |
| 538 | 536 | expect(revokedClientWebSocket.revokedAt.epochMilliseconds).toBeGreaterThanOrEqual(0); |
| 539 | 537 | } |
| 540 | }).pipe(Effect.provide(makeSessionStoreLayer())), | |
| 538 | }).pipe(Effect.provide(layerSessionStore())), | |
| 541 | 539 | ); |
| 542 | 540 | |
| 543 | 541 | it.effect("persists lastConnectedAt on first connect and updates it after reconnect", () => |
| ... | ... | @@ -579,7 +577,7 @@ it.layer(NodeServices.layer)("SessionStore.layer", (it) => { |
| 579 | 577 | expect(afterReconnect[0]?.connected).toBe(true); |
| 580 | 578 | expect(afterReconnect[0]?.lastConnectedAt).not.toBeNull(); |
| 581 | 579 | expect(afterReconnect[0]?.lastConnectedAt?.toString()).not.toBe(firstConnectedAt?.toString()); |
| 582 | }).pipe(Effect.provide(Layer.merge(makeSessionStoreLayer(), TestClock.layer()))), | |
| 580 | }).pipe(Effect.provide(Layer.merge(layerSessionStore(), TestClock.layer()))), | |
| 583 | 581 | ); |
| 584 | 582 | |
| 585 | 583 | it.effect("keeps connected relay sessions visible through expiry and HTTP renewal", () => |
| ... | ... | @@ -619,7 +617,7 @@ it.layer(NodeServices.layer)("SessionStore.layer", (it) => { |
| 619 | 617 | }), |
| 620 | 618 | ]), |
| 621 | 619 | ); |
| 622 | }).pipe(Effect.provide(Layer.merge(makeSessionStoreLayer(), TestClock.layer()))), | |
| 620 | }).pipe(Effect.provide(Layer.merge(layerSessionStore(), TestClock.layer()))), | |
| 623 | 621 | ); |
| 624 | 622 | |
| 625 | 623 | it.effect.each([1, 2])( |
| ... | ... | @@ -659,10 +657,7 @@ it.layer(NodeServices.layer)("SessionStore.layer", (it) => { |
| 659 | 657 | expect(listed).toEqual([]); |
| 660 | 658 | } |
| 661 | 659 | } |
| 662 | }).pipe( | |
| 663 | Effect.scoped, | |
| 664 | Effect.provide(Layer.merge(makeSessionStoreLayer(), TestClock.layer())), | |
| 665 | ), | |
| 660 | }).pipe(Effect.scoped, Effect.provide(Layer.merge(layerSessionStore(), TestClock.layer()))), | |
| 666 | 661 | ); |
| 667 | 662 | |
| 668 | 663 | it.effect.each(["revoke", "revokeAllExcept"] as const)( |
| ... | ... | @@ -693,10 +688,7 @@ it.layer(NodeServices.layer)("SessionStore.layer", (it) => { |
| 693 | 688 | const listed = yield* sessions.listActive(); |
| 694 | 689 | expect(listed).toHaveLength(1); |
| 695 | 690 | expect(listed[0]?.sessionId).toBe(administrative.sessionId); |
| 696 | }).pipe( | |
| 697 | Effect.scoped, | |
| 698 | Effect.provide(Layer.merge(makeSessionStoreLayer(), TestClock.layer())), | |
| 699 | ), | |
| 691 | }).pipe(Effect.scoped, Effect.provide(Layer.merge(layerSessionStore(), TestClock.layer()))), | |
| 700 | 692 | ); |
| 701 | 693 | |
| 702 | 694 | it.effect("records client connection metadata without clearing prior values", () => |
| ... | ... | @@ -728,6 +720,6 @@ it.layer(NodeServices.layer)("SessionStore.layer", (it) => { |
| 728 | 720 | |
| 729 | 721 | yield* sessions.recordClientConnection(issued.sessionId, {}); |
| 730 | 722 | expect((yield* readRow)[0]).toEqual({ surface: "mobile", appVersion: "1.3.0" }); |
| 731 | }).pipe(Effect.provide(Layer.mergeAll(makeSessionStoreLayer(), SqlitePersistenceMemory))), | |
| 723 | }).pipe(Effect.provide(Layer.mergeAll(layerSessionStore(), SqlitePersistence.layerMemory))), | |
| 732 | 724 | ); |
| 733 | 725 | }); |
apps/server/src/auth/http.test.ts+14-14| ... | ... | @@ -15,15 +15,15 @@ import * as HttpRouter from "effect/http/HttpRouter"; |
| 15 | 15 | |
| 16 | 16 | import * as ServerConfig from "../config.ts"; |
| 17 | 17 | import * as ServerEnvironment from "../environment/ServerEnvironment.ts"; |
| 18 | import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts"; | |
| 18 | import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts"; | |
| 19 | 19 | import * as EnvironmentAuth from "./EnvironmentAuth.ts"; |
| 20 | 20 | import * as ServerSecretStore from "./ServerSecretStore.ts"; |
| 21 | import { authHttpApiLayer, environmentAuthenticatedAuthLayer } from "./http.ts"; | |
| 21 | import * as AuthHttp from "./http.ts"; | |
| 22 | 22 | |
| 23 | 23 | const DEV_TOKEN = "reusable-dev-auth-token-that-is-long-enough"; |
| 24 | 24 | class AuthTestApi extends HttpApi.make("environment").add(EnvironmentHttpApi.groups.auth) {} |
| 25 | 25 | |
| 26 | const configLayer = Layer.effect( | |
| 26 | const layerConfig = Layer.effect( | |
| 27 | 27 | ServerConfig.ServerConfig, |
| 28 | 28 | Effect.gen(function* () { |
| 29 | 29 | const config = yield* ServerConfig.ServerConfig; |
| ... | ... | @@ -36,17 +36,17 @@ const configLayer = Layer.effect( |
| 36 | 36 | }), |
| 37 | 37 | ).pipe(Layer.provide(ServerConfig.layerTest(process.cwd(), { prefix: "t3-auth-http-test-" }))); |
| 38 | 38 | |
| 39 | const environmentAuthLayer = EnvironmentAuth.layer.pipe( | |
| 40 | Layer.provide(SqlitePersistenceMemory), | |
| 39 | const layerEnvironmentAuth = EnvironmentAuth.layer.pipe( | |
| 40 | Layer.provide(SqlitePersistence.layerMemory), | |
| 41 | 41 | Layer.provide(ServerSecretStore.layer), |
| 42 | Layer.provide(ServerEnvironment.identityLayer), | |
| 43 | Layer.provide(configLayer), | |
| 42 | Layer.provide(ServerEnvironment.layerIdentity), | |
| 43 | Layer.provide(layerConfig), | |
| 44 | 44 | ); |
| 45 | const routesLayer = HttpApiBuilder.layer(AuthTestApi).pipe( | |
| 46 | Layer.provide(authHttpApiLayer), | |
| 47 | Layer.provide(environmentAuthenticatedAuthLayer), | |
| 48 | Layer.provideMerge(environmentAuthLayer), | |
| 49 | Layer.provide(configLayer), | |
| 45 | const layerRoutes = HttpApiBuilder.layer(AuthTestApi).pipe( | |
| 46 | Layer.provide(AuthHttp.layer), | |
| 47 | Layer.provide(AuthHttp.layerAuthenticatedAuth), | |
| 48 | Layer.provideMerge(layerEnvironmentAuth), | |
| 49 | Layer.provide(layerConfig), | |
| 50 | 50 | Layer.provideMerge( |
| 51 | 51 | HttpPlatform.layer.pipe( |
| 52 | 52 | Layer.provideMerge(NodeServices.layer), |
| ... | ... | @@ -81,8 +81,8 @@ it.effect("sets the selected browser session cookies through the HTTP route", () |
| 81 | 81 | Effect.sync( |
| 82 | 82 | () => |
| 83 | 83 | [ |
| 84 | HttpRouter.toWebHandler(routesLayer, { disableLogger: true }), | |
| 85 | HttpRouter.toWebHandler(routesLayer, { disableLogger: true }), | |
| 84 | HttpRouter.toWebHandler(layerRoutes, { disableLogger: true }), | |
| 85 | HttpRouter.toWebHandler(layerRoutes, { disableLogger: true }), | |
| 86 | 86 | ] as const, |
| 87 | 87 | ), |
| 88 | 88 | ([environmentA, environmentB]) => |
apps/server/src/auth/http.ts+2-2| ... | ... | @@ -198,7 +198,7 @@ export const requireEnvironmentScope = Effect.fn("environment.auth.requireScope" |
| 198 | 198 | return session; |
| 199 | 199 | }); |
| 200 | 200 | |
| 201 | export const environmentAuthenticatedAuthLayer = Layer.effect( | |
| 201 | export const layerAuthenticatedAuth = Layer.effect( | |
| 202 | 202 | EnvironmentAuthenticatedAuth, |
| 203 | 203 | Effect.gen(function* () { |
| 204 | 204 | const serverAuth = yield* EnvironmentAuth.EnvironmentAuth; |
| ... | ... | @@ -227,7 +227,7 @@ export const environmentAuthenticatedAuthLayer = Layer.effect( |
| 227 | 227 | }), |
| 228 | 228 | ); |
| 229 | 229 | |
| 230 | export const authHttpApiLayer = HttpApiBuilder.group( | |
| 230 | export const layer = HttpApiBuilder.group( | |
| 231 | 231 | EnvironmentHttpApi, |
| 232 | 232 | "auth", |
| 233 | 233 | Effect.fnUntraced(function* (handlers) { |
apps/server/src/background/BackgroundPolicy.test.ts+17-17| ... | ... | @@ -53,11 +53,11 @@ function makeReport(overrides: Partial<ClientActivityReportInput> = {}): ClientA |
| 53 | 53 | }; |
| 54 | 54 | } |
| 55 | 55 | |
| 56 | function makeLayer( | |
| 56 | function layerFor( | |
| 57 | 57 | hostPower: HostPowerSnapshot, |
| 58 | 58 | settingsOverrides: Parameters<typeof ServerSettings.layerTest>[0] = {}, |
| 59 | 59 | ) { |
| 60 | const hostLayer = Layer.effect( | |
| 60 | const layerHost = Layer.effect( | |
| 61 | 61 | HostPowerMonitor.HostPowerMonitor, |
| 62 | 62 | Effect.gen(function* () { |
| 63 | 63 | const changes = yield* PubSub.sliding<HostPowerSnapshot>(1); |
| ... | ... | @@ -73,7 +73,7 @@ function makeLayer( |
| 73 | 73 | }), |
| 74 | 74 | ); |
| 75 | 75 | return BackgroundPolicy.layer.pipe( |
| 76 | Layer.provide(Layer.merge(hostLayer, ServerSettings.layerTest(settingsOverrides))), | |
| 76 | Layer.provide(Layer.merge(layerHost, ServerSettings.layerTest(settingsOverrides))), | |
| 77 | 77 | ); |
| 78 | 78 | } |
| 79 | 79 | |
| ... | ... | @@ -95,7 +95,7 @@ describe("BackgroundPolicy", () => { |
| 95 | 95 | assert.equal(yield* policy.hasDemand({ type: "vcs-status", cwd: "/other" }), false); |
| 96 | 96 | assert.equal(yield* policy.shouldRunScopeWork({ type: "vcs-status", cwd: "/repo" }), true); |
| 97 | 97 | assert.equal(yield* policy.shouldRunScopeWork({ type: "vcs-status", cwd: "/other" }), false); |
| 98 | }).pipe(Effect.provide(makeLayer(nominalHostPower))), | |
| 98 | }).pipe(Effect.provide(layerFor(nominalHostPower))), | |
| 99 | 99 | ); |
| 100 | 100 | |
| 101 | 101 | it.effect("removes all leases for a disconnected websocket connection", () => |
| ... | ... | @@ -112,7 +112,7 @@ describe("BackgroundPolicy", () => { |
| 112 | 112 | assert.equal(snapshot.activeForegroundLeaseCount, 0); |
| 113 | 113 | assert.deepStrictEqual(snapshot.activeScopeKeys, []); |
| 114 | 114 | assert.equal(snapshot.shouldRunOpportunisticWork, false); |
| 115 | }).pipe(Effect.provide(makeLayer(nominalHostPower))), | |
| 115 | }).pipe(Effect.provide(layerFor(nominalHostPower))), | |
| 116 | 116 | ); |
| 117 | 117 | |
| 118 | 118 | it.effect("keeps leases from another session when rpc client ids are reused", () => |
| ... | ... | @@ -136,7 +136,7 @@ describe("BackgroundPolicy", () => { |
| 136 | 136 | assert.equal(snapshot.activeForegroundLeaseCount, 1); |
| 137 | 137 | assert.equal(snapshot.leases[0]?.sessionId, AuthSessionId.make("session-2")); |
| 138 | 138 | assert.equal(snapshot.leases[0]?.clientId, "client-2"); |
| 139 | }).pipe(Effect.provide(makeLayer(nominalHostPower))), | |
| 139 | }).pipe(Effect.provide(layerFor(nominalHostPower))), | |
| 140 | 140 | ); |
| 141 | 141 | |
| 142 | 142 | it.effect("serializes lease mutation publications", () => |
| ... | ... | @@ -144,7 +144,7 @@ describe("BackgroundPolicy", () => { |
| 144 | 144 | const firstSnapshotStarted = yield* Deferred.make<void>(); |
| 145 | 145 | const releaseFirstSnapshot = yield* Deferred.make<void>(); |
| 146 | 146 | const snapshotReads = yield* Ref.make(0); |
| 147 | const hostLayer = Layer.succeed( | |
| 147 | const layerHost = Layer.succeed( | |
| 148 | 148 | HostPowerMonitor.HostPowerMonitor, |
| 149 | 149 | HostPowerMonitor.HostPowerMonitor.of({ |
| 150 | 150 | snapshot: Ref.updateAndGet(snapshotReads, (count) => count + 1).pipe( |
| ... | ... | @@ -162,7 +162,7 @@ describe("BackgroundPolicy", () => { |
| 162 | 162 | }), |
| 163 | 163 | ); |
| 164 | 164 | const layer = BackgroundPolicy.layer.pipe( |
| 165 | Layer.provide(Layer.merge(hostLayer, ServerSettings.layerTest())), | |
| 165 | Layer.provide(Layer.merge(layerHost, ServerSettings.layerTest())), | |
| 166 | 166 | ); |
| 167 | 167 | |
| 168 | 168 | yield* Effect.gen(function* () { |
| ... | ... | @@ -221,7 +221,7 @@ describe("BackgroundPolicy", () => { |
| 221 | 221 | `client-${BackgroundPolicy.MAX_CLIENT_ACTIVITY_LEASES_PER_RPC_CLIENT}`, |
| 222 | 222 | ), |
| 223 | 223 | ); |
| 224 | }).pipe(Effect.provide(makeLayer(nominalHostPower))), | |
| 224 | }).pipe(Effect.provide(layerFor(nominalHostPower))), | |
| 225 | 225 | ); |
| 226 | 226 | |
| 227 | 227 | it.effect("host low power mode disables opportunistic work without dropping scoped demand", () => |
| ... | ... | @@ -239,7 +239,7 @@ describe("BackgroundPolicy", () => { |
| 239 | 239 | assert.equal(snapshot.shouldRunOpportunisticWork, false); |
| 240 | 240 | assert.equal(yield* policy.hasDemand({ type: "vcs-status", cwd: "/repo" }), true); |
| 241 | 241 | assert.equal(yield* policy.shouldRunScopeWork({ type: "vcs-status", cwd: "/repo" }), false); |
| 242 | }).pipe(Effect.provide(makeLayer(constrainedHostPower))), | |
| 242 | }).pipe(Effect.provide(layerFor(constrainedHostPower))), | |
| 243 | 243 | ); |
| 244 | 244 | |
| 245 | 245 | it.effect("host suspension disables scoped and opportunistic work", () => |
| ... | ... | @@ -258,7 +258,7 @@ describe("BackgroundPolicy", () => { |
| 258 | 258 | assert.equal(yield* policy.shouldRunScopeWork({ type: "vcs-status", cwd: "/repo" }), false); |
| 259 | 259 | }).pipe( |
| 260 | 260 | Effect.provide( |
| 261 | makeLayer({ | |
| 261 | layerFor({ | |
| 262 | 262 | ...nominalHostPower, |
| 263 | 263 | suspended: true, |
| 264 | 264 | stale: false, |
| ... | ... | @@ -281,7 +281,7 @@ describe("BackgroundPolicy", () => { |
| 281 | 281 | assert.deepStrictEqual(snapshot.activeScopeKeys, ["vcs-status:/repo"]); |
| 282 | 282 | assert.equal(yield* policy.hasDemand({ type: "vcs-status", cwd: "/repo" }), true); |
| 283 | 283 | assert.equal(yield* policy.shouldRunScopeWork({ type: "vcs-status", cwd: "/repo" }), false); |
| 284 | }).pipe(Effect.provide(makeLayer(nominalHostPower))), | |
| 284 | }).pipe(Effect.provide(layerFor(nominalHostPower))), | |
| 285 | 285 | ); |
| 286 | 286 | |
| 287 | 287 | it.effect("keeps scoped work active after a recently used visible window loses focus", () => |
| ... | ... | @@ -297,7 +297,7 @@ describe("BackgroundPolicy", () => { |
| 297 | 297 | assert.equal(snapshot.activeForegroundLeaseCount, 1); |
| 298 | 298 | assert.equal(snapshot.shouldRunOpportunisticWork, true); |
| 299 | 299 | assert.equal(yield* policy.shouldRunScopeWork({ type: "vcs-status", cwd: "/repo" }), true); |
| 300 | }).pipe(Effect.provide(makeLayer(nominalHostPower))), | |
| 300 | }).pipe(Effect.provide(layerFor(nominalHostPower))), | |
| 301 | 301 | ); |
| 302 | 302 | |
| 303 | 303 | it.effect("pauses scoped work for a visible unfocused client without recent interaction", () => |
| ... | ... | @@ -313,7 +313,7 @@ describe("BackgroundPolicy", () => { |
| 313 | 313 | assert.equal(snapshot.activeForegroundLeaseCount, 0); |
| 314 | 314 | assert.equal(snapshot.shouldRunOpportunisticWork, false); |
| 315 | 315 | assert.equal(yield* policy.shouldRunScopeWork({ type: "vcs-status", cwd: "/repo" }), false); |
| 316 | }).pipe(Effect.provide(makeLayer(nominalHostPower))), | |
| 316 | }).pipe(Effect.provide(layerFor(nominalHostPower))), | |
| 317 | 317 | ); |
| 318 | 318 | |
| 319 | 319 | it.effect( |
| ... | ... | @@ -329,7 +329,7 @@ describe("BackgroundPolicy", () => { |
| 329 | 329 | |
| 330 | 330 | assert.equal(yield* policy.shouldRunScopeWork({ type: "vcs-status", cwd: "/repo" }), true); |
| 331 | 331 | }).pipe( |
| 332 | Effect.provide(makeLayer(nominalHostPower, { backgroundActivityProfile: "performance" })), | |
| 332 | Effect.provide(layerFor(nominalHostPower, { backgroundActivityProfile: "performance" })), | |
| 333 | 333 | ), |
| 334 | 334 | ); |
| 335 | 335 | |
| ... | ... | @@ -345,7 +345,7 @@ describe("BackgroundPolicy", () => { |
| 345 | 345 | assert.equal(yield* policy.shouldRunScopeWork({ type: "vcs-status", cwd: "/repo" }), false); |
| 346 | 346 | }).pipe( |
| 347 | 347 | Effect.provide( |
| 348 | makeLayer( | |
| 348 | layerFor( | |
| 349 | 349 | { |
| 350 | 350 | ...nominalHostPower, |
| 351 | 351 | onBattery: "true", |
| ... | ... | @@ -369,7 +369,7 @@ describe("BackgroundPolicy", () => { |
| 369 | 369 | assert.equal(yield* policy.shouldRunScopeWork({ type: "vcs-status", cwd: "/repo" }), true); |
| 370 | 370 | }).pipe( |
| 371 | 371 | Effect.provide( |
| 372 | makeLayer( | |
| 372 | layerFor( | |
| 373 | 373 | { |
| 374 | 374 | ...nominalHostPower, |
| 375 | 375 | locked: "true", |
apps/server/src/background/HostPowerMonitor.test.ts+4-4| ... | ... | @@ -84,11 +84,11 @@ describe("HostPowerMonitor", () => { |
| 84 | 84 | const sampledAt = DateTime.makeUnsafe("2026-06-17T12:00:00.000Z"); |
| 85 | 85 | const desktopChanges = yield* PubSub.sliding<DesktopHostTelemetrySnapshot>(1); |
| 86 | 86 | const diagnosticsDemandWrites = yield* Ref.make(0); |
| 87 | const receiverLayer = DesktopTelemetryReceiver.layerTest({ | |
| 87 | const layerReceiver = DesktopTelemetryReceiver.layerTest({ | |
| 88 | 88 | changes: Stream.fromPubSub(desktopChanges), |
| 89 | 89 | setDiagnosticsDemand: () => Ref.update(diagnosticsDemandWrites, (count) => count + 1), |
| 90 | 90 | }); |
| 91 | const layer = HostPowerMonitor.layer.pipe(Layer.provide(receiverLayer)); | |
| 91 | const layer = HostPowerMonitor.layer.pipe(Layer.provide(layerReceiver)); | |
| 92 | 92 | |
| 93 | 93 | yield* Effect.gen(function* () { |
| 94 | 94 | const monitor = yield* HostPowerMonitor.HostPowerMonitor; |
| ... | ... | @@ -158,7 +158,7 @@ describe("HostPowerMonitor", () => { |
| 158 | 158 | }, |
| 159 | 159 | }; |
| 160 | 160 | const desktopChanges = yield* PubSub.sliding<DesktopHostTelemetrySnapshot>(1); |
| 161 | const receiverLayer = DesktopTelemetryReceiver.layerTest({ | |
| 161 | const layerReceiver = DesktopTelemetryReceiver.layerTest({ | |
| 162 | 162 | latest: Effect.succeedSome(initial), |
| 163 | 163 | changes: Stream.empty, |
| 164 | 164 | subscribe: Effect.gen(function* () { |
| ... | ... | @@ -170,7 +170,7 @@ describe("HostPowerMonitor", () => { |
| 170 | 170 | }; |
| 171 | 171 | }), |
| 172 | 172 | }); |
| 173 | const layer = HostPowerMonitor.layer.pipe(Layer.provide(receiverLayer)); | |
| 173 | const layer = HostPowerMonitor.layer.pipe(Layer.provide(layerReceiver)); | |
| 174 | 174 | |
| 175 | 175 | yield* Effect.gen(function* () { |
| 176 | 176 | const monitor = yield* HostPowerMonitor.HostPowerMonitor; |
apps/server/src/binCli.ts+2-2| ... | ... | @@ -27,7 +27,7 @@ import { themeCommand } from "./cli/theme.ts"; |
| 27 | 27 | import { traceCommand } from "./cli/trace.ts"; |
| 28 | 28 | import { triageCommand } from "./cli/triage.ts"; |
| 29 | 29 | |
| 30 | const CliRuntimeLayer = Layer.mergeAll(NodeServices.layer, NetService.layer); | |
| 30 | const layerCliRuntime = Layer.mergeAll(NodeServices.layer, NetService.layer); | |
| 31 | 31 | |
| 32 | 32 | const connectPublicConfigMissingMessage = |
| 33 | 33 | "T3 Connect commands are unavailable: this build is missing T3 Connect public configuration."; |
| ... | ... | @@ -92,7 +92,7 @@ export const cli = makeCli(); |
| 92 | 92 | export function runCli() { |
| 93 | 93 | Command.run(cli, { version: packageJson.version }).pipe( |
| 94 | 94 | Effect.scoped, |
| 95 | Effect.provide(CliRuntimeLayer), | |
| 95 | Effect.provide(layerCliRuntime), | |
| 96 | 96 | NodeRuntime.runMain, |
| 97 | 97 | ); |
| 98 | 98 | } |
apps/server/src/checkpointing/CheckpointDiffQuery.test.ts+6-6| ... | ... | @@ -44,7 +44,7 @@ function makeProjection(): ProjectionCheckpointContext { |
| 44 | 44 | }; |
| 45 | 45 | } |
| 46 | 46 | |
| 47 | function makeLayer(input: { | |
| 47 | function layerFor(input: { | |
| 48 | 48 | readonly projection: Effect.Effect<ProjectionCheckpointContext, OrchestratorProjectionError>; |
| 49 | 49 | readonly diffCheckpoints?: CheckpointStore.CheckpointStore["Service"]["diffCheckpoints"]; |
| 50 | 50 | }) { |
| ... | ... | @@ -66,7 +66,7 @@ it.effect("computes V2 run diffs from projected checkpoint scopes", () => { |
| 66 | 66 | const diffCheckpoints = vi.fn((_input: CheckpointStore.DiffCheckpointsInput) => |
| 67 | 67 | Effect.succeed("diff --git a/file b/file"), |
| 68 | 68 | ); |
| 69 | const layer = makeLayer({ projection: Effect.succeed(makeProjection()), diffCheckpoints }); | |
| 69 | const layer = layerFor({ projection: Effect.succeed(makeProjection()), diffCheckpoints }); | |
| 70 | 70 | |
| 71 | 71 | return Effect.gen(function* () { |
| 72 | 72 | const query = yield* CheckpointDiffQuery.CheckpointDiffQuery; |
| ... | ... | @@ -92,7 +92,7 @@ it.effect("computes V2 run diffs from projected checkpoint scopes", () => { |
| 92 | 92 | }); |
| 93 | 93 | |
| 94 | 94 | it.effect("preserves the typed missing-thread error contract", () => { |
| 95 | const layer = makeLayer({ | |
| 95 | const layer = layerFor({ | |
| 96 | 96 | projection: Effect.fail(new OrchestratorProjectionError({ threadId })), |
| 97 | 97 | }); |
| 98 | 98 | |
| ... | ... | @@ -111,7 +111,7 @@ it.effect("preserves the typed missing-thread error contract", () => { |
| 111 | 111 | }); |
| 112 | 112 | |
| 113 | 113 | it.effect("preserves the typed unavailable-range error contract", () => { |
| 114 | const layer = makeLayer({ projection: Effect.succeed(makeProjection()) }); | |
| 114 | const layer = layerFor({ projection: Effect.succeed(makeProjection()) }); | |
| 115 | 115 | |
| 116 | 116 | return Effect.gen(function* () { |
| 117 | 117 | const query = yield* CheckpointDiffQuery.CheckpointDiffQuery; |
| ... | ... | @@ -132,7 +132,7 @@ it.effect("preserves the typed unavailable-range error contract", () => { |
| 132 | 132 | |
| 133 | 133 | it.effect("excludes ready checkpoints from rolled-back runs", () => { |
| 134 | 134 | const projection = makeProjection(); |
| 135 | const layer = makeLayer({ | |
| 135 | const layer = layerFor({ | |
| 136 | 136 | projection: Effect.succeed({ |
| 137 | 137 | ...projection, |
| 138 | 138 | runs: projection.runs.map((run) => |
| ... | ... | @@ -170,7 +170,7 @@ it.effect("excludes ready checkpoints from rolled-back runs", () => { |
| 170 | 170 | |
| 171 | 171 | it.effect("preserves the typed missing-baseline-ref error contract", () => { |
| 172 | 172 | const projection = makeProjection(); |
| 173 | const layer = makeLayer({ | |
| 173 | const layer = layerFor({ | |
| 174 | 174 | projection: Effect.succeed({ |
| 175 | 175 | ...projection, |
| 176 | 176 | checkpointScopes: projection.checkpointScopes.map((scope) => ({ |
apps/server/src/checkpointing/CheckpointStore.test.ts+10-10| ... | ... | @@ -19,19 +19,19 @@ import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts"; |
| 19 | 19 | import * as VcsProcess from "../vcs/VcsProcess.ts"; |
| 20 | 20 | import * as ServerConfig from "../config.ts"; |
| 21 | 21 | |
| 22 | const ServerConfigLayer = ServerConfig.ServerConfig.layerTest(process.cwd(), { | |
| 22 | const layerServerConfig = ServerConfig.ServerConfig.layerTest(process.cwd(), { | |
| 23 | 23 | prefix: "t3-checkpoint-store-test-", |
| 24 | 24 | }); |
| 25 | const VcsProcessTestLayer = VcsProcess.layer.pipe(Layer.provide(NodeServices.layer)); | |
| 26 | const VcsDriverTestLayer = VcsDriverRegistry.layer.pipe(Layer.provide(VcsProcessTestLayer)); | |
| 27 | const CheckpointStoreTestLayer = CheckpointStore.layer.pipe( | |
| 28 | Layer.provideMerge(VcsDriverTestLayer), | |
| 25 | const layerVcsProcessTest = VcsProcess.layer.pipe(Layer.provide(NodeServices.layer)); | |
| 26 | const layerVcsDriverTest = VcsDriverRegistry.layer.pipe(Layer.provide(layerVcsProcessTest)); | |
| 27 | const layerCheckpointStoreTest = CheckpointStore.layer.pipe( | |
| 28 | Layer.provideMerge(layerVcsDriverTest), | |
| 29 | 29 | Layer.provideMerge(NodeServices.layer), |
| 30 | 30 | ); |
| 31 | const TestLayer = CheckpointStoreTestLayer.pipe( | |
| 32 | Layer.provideMerge(VcsProcessTestLayer), | |
| 33 | Layer.provideMerge(VcsDriverTestLayer), | |
| 34 | Layer.provideMerge(ServerConfigLayer), | |
| 31 | const layerTest = layerCheckpointStoreTest.pipe( | |
| 32 | Layer.provideMerge(layerVcsProcessTest), | |
| 33 | Layer.provideMerge(layerVcsDriverTest), | |
| 34 | Layer.provideMerge(layerServerConfig), | |
| 35 | 35 | Layer.provideMerge(NodeServices.layer), |
| 36 | 36 | ); |
| 37 | 37 | |
| ... | ... | @@ -94,7 +94,7 @@ function buildLargeText(lineCount = 5_000): string { |
| 94 | 94 | .concat("\n"); |
| 95 | 95 | } |
| 96 | 96 | |
| 97 | it.layer(TestLayer)("CheckpointStore.layer", (it) => { | |
| 97 | it.layer(layerTest)("CheckpointStore.layer", (it) => { | |
| 98 | 98 | describe("isGitRepository", () => { |
| 99 | 99 | it.effect("returns false when no Git repository is detected", () => |
| 100 | 100 | Effect.gen(function* () { |
apps/server/src/cli/auth.ts+1-1| ... | ... | @@ -42,7 +42,7 @@ const runWithEnvironmentAuth = <A, E>( |
| 42 | 42 | return yield* run(environmentAuth); |
| 43 | 43 | }).pipe( |
| 44 | 44 | Effect.provide( |
| 45 | Layer.mergeAll(EnvironmentAuth.runtimeLayer).pipe( | |
| 45 | Layer.mergeAll(EnvironmentAuth.layerRuntime).pipe( | |
| 46 | 46 | Layer.provide(ServerConfig.layer(config)), |
| 47 | 47 | Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)), |
| 48 | 48 | ), |
apps/server/src/cli/config.test.ts+7-7| ... | ... | @@ -146,7 +146,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => { |
| 146 | 146 | tailscaleServeEnabled: Option.none<boolean>(), |
| 147 | 147 | tailscaleServePort: Option.none<number>(), |
| 148 | 148 | }; |
| 149 | const configLayer = ConfigProvider.layer( | |
| 149 | const layerConfig = ConfigProvider.layer( | |
| 150 | 150 | ConfigProvider.fromEnv({ |
| 151 | 151 | env: { |
| 152 | 152 | T3CODE_DEV_AUTH_TOKEN: " reusable-dev-auth-token-that-is-long-enough ", |
| ... | ... | @@ -154,12 +154,12 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => { |
| 154 | 154 | }), |
| 155 | 155 | ); |
| 156 | 156 | const web = yield* resolveServerConfig(flags, Option.none()).pipe( |
| 157 | Effect.provide(Layer.mergeAll(configLayer, NetService.layer)), | |
| 157 | Effect.provide(Layer.mergeAll(layerConfig, NetService.layer)), | |
| 158 | 158 | ); |
| 159 | 159 | const desktop = yield* resolveServerConfig( |
| 160 | 160 | { ...flags, mode: Option.some("desktop" as const) }, |
| 161 | 161 | Option.none(), |
| 162 | ).pipe(Effect.provide(Layer.mergeAll(configLayer, NetService.layer))); | |
| 162 | ).pipe(Effect.provide(Layer.mergeAll(layerConfig, NetService.layer))); | |
| 163 | 163 | |
| 164 | 164 | expect(web.devAuthToken).toBeDefined(); |
| 165 | 165 | if (web.devAuthToken === undefined) { |
| ... | ... | @@ -190,21 +190,21 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => { |
| 190 | 190 | tailscaleServeEnabled: Option.none<boolean>(), |
| 191 | 191 | tailscaleServePort: Option.none<number>(), |
| 192 | 192 | }; |
| 193 | const configLayer = ConfigProvider.layer( | |
| 193 | const layerConfig = ConfigProvider.layer( | |
| 194 | 194 | ConfigProvider.fromEnv({ env: { T3CODE_DEV_AUTH_TOKEN: secret } }), |
| 195 | 195 | ); |
| 196 | 196 | const error = yield* resolveServerConfig(flags, Option.none()).pipe( |
| 197 | Effect.provide(Layer.mergeAll(configLayer, NetService.layer)), | |
| 197 | Effect.provide(Layer.mergeAll(layerConfig, NetService.layer)), | |
| 198 | 198 | Effect.flip, |
| 199 | 199 | ); |
| 200 | 200 | const desktop = yield* resolveServerConfig( |
| 201 | 201 | { ...flags, mode: Option.some("desktop" as const) }, |
| 202 | 202 | Option.none(), |
| 203 | ).pipe(Effect.provide(Layer.mergeAll(configLayer, NetService.layer))); | |
| 203 | ).pipe(Effect.provide(Layer.mergeAll(layerConfig, NetService.layer))); | |
| 204 | 204 | const staticWeb = yield* resolveServerConfig( |
| 205 | 205 | { ...flags, devUrl: Option.none() }, |
| 206 | 206 | Option.none(), |
| 207 | ).pipe(Effect.provide(Layer.mergeAll(configLayer, NetService.layer))); | |
| 207 | ).pipe(Effect.provide(Layer.mergeAll(layerConfig, NetService.layer))); | |
| 208 | 208 | |
| 209 | 209 | expect(String(error)).not.toContain(secret); |
| 210 | 210 | const serialized = yield* encodeUnknownJson(error); |
apps/server/src/cli/connect.ts+8-11| ... | ... | @@ -37,18 +37,15 @@ import { |
| 37 | 37 | RELAY_URL_SECRET, |
| 38 | 38 | } from "../cloud/config.ts"; |
| 39 | 39 | import { relayUrlConfig } from "../cloud/publicConfig.ts"; |
| 40 | import { headlessRelayClientTracingLayer } from "../cloud/relayTracing.ts"; | |
| 40 | import * as RelayTracing from "../cloud/relayTracing.ts"; | |
| 41 | 41 | import * as ServerConfig from "../config.ts"; |
| 42 | 42 | import * as ServerEnvironment from "../environment/ServerEnvironment.ts"; |
| 43 | 43 | import * as ExternalLauncher from "../process/externalLauncher.ts"; |
| 44 | 44 | import { readPersistedServerRuntimeState } from "../serverRuntimeState.ts"; |
| 45 | 45 | import { projectLocationFlags, resolveCliAuthConfig } from "./config.ts"; |
| 46 | 46 | import { resolveCliCommand } from "./invocation.ts"; |
| 47 | import { | |
| 48 | bootServiceLayer, | |
| 49 | offerServiceDuringOnboarding, | |
| 50 | recoverServiceOnboardingOffer, | |
| 51 | } from "./service.ts"; | |
| 47 | import { offerServiceDuringOnboarding, recoverServiceOnboardingOffer } from "./service.ts"; | |
| 48 | import * as CliService from "./service.ts"; | |
| 52 | 49 | |
| 53 | 50 | const jsonFlag = Flag.Boolean("json").pipe( |
| 54 | 51 | Flag.withDescription("Emit JSON instead of human-readable output."), |
| ... | ... | @@ -434,22 +431,22 @@ const runCloudCommand = Effect.fn("cloud.cli.run_cloud_command")(function* <A, E |
| 434 | 431 | const logLevel = yield* GlobalFlag.LogLevel; |
| 435 | 432 | const config = yield* resolveCliAuthConfig(flags, logLevel); |
| 436 | 433 | const minimumLogLevel = options?.quietLogs ? "Error" : config.logLevel; |
| 437 | const runtimeLayer = Layer.mergeAll( | |
| 434 | const layerRuntime = Layer.mergeAll( | |
| 438 | 435 | ServerSecretStore.layer, |
| 439 | 436 | CliTokenManager.layer.pipe( |
| 440 | 437 | Layer.provide(ServerSecretStore.layer), |
| 441 | 438 | Layer.provide(ExternalLauncher.layer), |
| 442 | 439 | ), |
| 443 | 440 | RelayClient.layerCloudflared({ baseDir: config.baseDir }), |
| 444 | EnvironmentAuth.runtimeLayer, | |
| 445 | bootServiceLayer(config), | |
| 446 | headlessRelayClientTracingLayer, | |
| 441 | EnvironmentAuth.layerRuntime, | |
| 442 | CliService.layer(config), | |
| 443 | RelayTracing.layerHeadlessRelayClient, | |
| 447 | 444 | ).pipe( |
| 448 | 445 | Layer.provideMerge(FetchHttpClient.layer), |
| 449 | 446 | Layer.provideMerge(ServerConfig.layer(config)), |
| 450 | 447 | Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)), |
| 451 | 448 | ); |
| 452 | return yield* run.pipe(Effect.provide(runtimeLayer)); | |
| 449 | return yield* run.pipe(Effect.provide(layerRuntime)); | |
| 453 | 450 | }); |
| 454 | 451 | |
| 455 | 452 | const connectedAs = (identity: string | null): string => (identity ? ` as ${identity}` : ""); |
apps/server/src/cli/pair.test.ts+2-2| ... | ... | @@ -32,7 +32,7 @@ import { |
| 32 | 32 | |
| 33 | 33 | import packageJson from "../../package.json" with { type: "json" }; |
| 34 | 34 | |
| 35 | const CliRuntimeLayer = Layer.mergeAll(NodeServices.layer, NetService.layer); | |
| 35 | const layerCliRuntime = Layer.mergeAll(NodeServices.layer, NetService.layer); | |
| 36 | 36 | |
| 37 | 37 | const baseState = { |
| 38 | 38 | version: 1, |
| ... | ... | @@ -94,7 +94,7 @@ describe("pair tailscale local target", () => { |
| 94 | 94 | const runCli = (args: ReadonlyArray<string>) => Command.runWith(cli, { version: "0.0.0" })(args); |
| 95 | 95 | |
| 96 | 96 | const provideCliTestLayers = <A, E, R>(effect: Effect.Effect<A, E, R>) => |
| 97 | Effect.provide(effect, Layer.mergeAll(CliRuntimeLayer, TestConsole.layer)); | |
| 97 | Effect.provide(effect, Layer.mergeAll(layerCliRuntime, TestConsole.layer)); | |
| 98 | 98 | |
| 99 | 99 | // Console output accumulates across CLI runs within a test, and each |
| 100 | 100 | // Console.log call is one entry — so the latest command's output is the last |
apps/server/src/cli/pair.ts+1-1| ... | ... | @@ -437,7 +437,7 @@ const mintPairingLink = Effect.fn("pair.mintPairingLink")(function* (input: { |
| 437 | 437 | }); |
| 438 | 438 | }).pipe( |
| 439 | 439 | Effect.provide( |
| 440 | EnvironmentAuth.runtimeLayer.pipe( | |
| 440 | EnvironmentAuth.layerRuntime.pipe( | |
| 441 | 441 | Layer.provide(ServerConfig.layer(input.config)), |
| 442 | 442 | Layer.provide(Layer.succeed(References.MinimumLogLevel, input.config.logLevel)), |
| 443 | 443 | ), |
apps/server/src/cli/project.test.ts+5-8| ... | ... | @@ -30,10 +30,7 @@ import * as ServerConfig from "../config.ts"; |
| 30 | 30 | import * as EventSink from "../orchestration-v2/EventSink.ts"; |
| 31 | 31 | import * as EventStore from "../orchestration-v2/EventStore.ts"; |
| 32 | 32 | import * as ProjectionStore from "../orchestration-v2/ProjectionStore.ts"; |
| 33 | import { | |
| 34 | OrchestrationV2EventSinkLayerLive, | |
| 35 | ProjectServiceLayerLive, | |
| 36 | } from "../orchestration-v2/runtimeLayer.ts"; | |
| 33 | import * as RuntimeLayer from "../orchestration-v2/runtimeLayer.ts"; | |
| 37 | 34 | import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts"; |
| 38 | 35 | import * as ProjectEnrichmentService from "../project/ProjectEnrichmentService.ts"; |
| 39 | 36 | import * as ProjectFaviconResolver from "../project/ProjectFaviconResolver.ts"; |
| ... | ... | @@ -47,9 +44,9 @@ import { |
| 47 | 44 | projectCommandErrorFromLiveServerRequest, |
| 48 | 45 | } from "./project.ts"; |
| 49 | 46 | |
| 50 | const CliRuntimeLayer = Layer.mergeAll(NodeServices.layer, NetService.layer); | |
| 47 | const layerCliRuntime = Layer.mergeAll(NodeServices.layer, NetService.layer); | |
| 51 | 48 | const runCli = (args: ReadonlyArray<string>) => |
| 52 | Command.runWith(cli, { version: "0.0.0" })(args).pipe(Effect.provide(CliRuntimeLayer)); | |
| 49 | Command.runWith(cli, { version: "0.0.0" })(args).pipe(Effect.provide(layerCliRuntime)); | |
| 53 | 50 | |
| 54 | 51 | const makeConfig = (baseDir: string) => |
| 55 | 52 | Effect.gen(function* () { |
| ... | ... | @@ -90,7 +87,7 @@ const makeConfig = (baseDir: string) => |
| 90 | 87 | const readProjects = (baseDir: string) => |
| 91 | 88 | Effect.gen(function* () { |
| 92 | 89 | const config = yield* makeConfig(baseDir); |
| 93 | const layer = ProjectServiceLayerLive.pipe( | |
| 90 | const layer = RuntimeLayer.layerProjectService.pipe( | |
| 94 | 91 | Layer.provideMerge(ProjectEnrichmentService.layer), |
| 95 | 92 | Layer.provideMerge(RepositoryIdentityResolver.layer), |
| 96 | 93 | Layer.provideMerge(ProjectFaviconResolver.layer), |
| ... | ... | @@ -171,7 +168,7 @@ const makeThreadPersistenceLayer = Effect.fn("ProjectCliTest.makeThreadPersisten |
| 171 | 168 | function* (baseDir: string) { |
| 172 | 169 | const config = yield* makeConfig(baseDir); |
| 173 | 170 | return Layer.mergeAll( |
| 174 | OrchestrationV2EventSinkLayerLive, | |
| 171 | RuntimeLayer.layerEventSink, | |
| 175 | 172 | ProjectionStore.layer, |
| 176 | 173 | EventStore.layer, |
| 177 | 174 | ).pipe( |
apps/server/src/cli/project.ts+5-5| ... | ... | @@ -25,7 +25,7 @@ import * as EnvironmentAuth from "../auth/EnvironmentAuth.ts"; |
| 25 | 25 | |
| 26 | 26 | import * as ServerConfig from "../config.ts"; |
| 27 | 27 | import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts"; |
| 28 | import { ProjectServiceLayerLive } from "../orchestration-v2/runtimeLayer.ts"; | |
| 28 | import * as RuntimeLayer from "../orchestration-v2/runtimeLayer.ts"; | |
| 29 | 29 | import * as ProjectEnrichmentService from "../project/ProjectEnrichmentService.ts"; |
| 30 | 30 | import * as ProjectFaviconResolver from "../project/ProjectFaviconResolver.ts"; |
| 31 | 31 | import * as RepositoryIdentityResolver from "../project/RepositoryIdentityResolver.ts"; |
| ... | ... | @@ -196,7 +196,7 @@ const projectCommandUuid = Crypto.Crypto.pipe( |
| 196 | 196 | ), |
| 197 | 197 | ); |
| 198 | 198 | |
| 199 | const ProjectCliRuntimeLive = ProjectServiceLayerLive.pipe( | |
| 199 | const layerProjectCliRuntime = RuntimeLayer.layerProjectService.pipe( | |
| 200 | 200 | Layer.provideMerge(ProjectEnrichmentService.layer), |
| 201 | 201 | Layer.provideMerge(RepositoryIdentityResolver.layer), |
| 202 | 202 | Layer.provideMerge( |
| ... | ... | @@ -416,7 +416,7 @@ const runProjectMutation = Effect.fn("runProjectMutation")(function* ( |
| 416 | 416 | ); |
| 417 | 417 | } |
| 418 | 418 | |
| 419 | const offlineRuntimeLayer = ProjectCliRuntimeLive.pipe( | |
| 419 | const layerOfflineRuntime = layerProjectCliRuntime.pipe( | |
| 420 | 420 | Layer.provide(ServerConfig.layer(config)), |
| 421 | 421 | Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)), |
| 422 | 422 | ); |
| ... | ... | @@ -430,10 +430,10 @@ const runProjectMutation = Effect.fn("runProjectMutation")(function* ( |
| 430 | 430 | mode: "offline", |
| 431 | 431 | }); |
| 432 | 432 | yield* Console.log(output); |
| 433 | }).pipe(Effect.provide(offlineRuntimeLayer)); | |
| 433 | }).pipe(Effect.provide(layerOfflineRuntime)); | |
| 434 | 434 | }).pipe( |
| 435 | 435 | Effect.provide( |
| 436 | Layer.mergeAll(EnvironmentAuth.runtimeLayer, WorkspacePaths.layer).pipe( | |
| 436 | Layer.mergeAll(EnvironmentAuth.layerRuntime, WorkspacePaths.layer).pipe( | |
| 437 | 437 | Layer.provideMerge(FetchHttpClient.layer), |
| 438 | 438 | Layer.provide(ServerConfig.layer(config)), |
| 439 | 439 | Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)), |
apps/server/src/cli/service.ts+2-2| ... | ... | @@ -13,7 +13,7 @@ import type * as ServerConfig from "../config.ts"; |
| 13 | 13 | import * as ProcessRunner from "../processRunner.ts"; |
| 14 | 14 | import { projectLocationFlags, resolveCliAuthConfig } from "./config.ts"; |
| 15 | 15 | |
| 16 | export const bootServiceLayer = (config: ServerConfig.ServerConfig["Service"]) => | |
| 16 | export const layer = (config: ServerConfig.ServerConfig["Service"]) => | |
| 17 | 17 | BootService.layer({ |
| 18 | 18 | baseDir: config.baseDir, |
| 19 | 19 | logsDir: config.logsDir, |
| ... | ... | @@ -107,7 +107,7 @@ const runServiceCommand = Effect.fn("cli.service.run")(function* <A, E>( |
| 107 | 107 | ) { |
| 108 | 108 | const logLevel = yield* GlobalFlag.LogLevel; |
| 109 | 109 | const config = yield* resolveCliAuthConfig(flags, logLevel); |
| 110 | return yield* run.pipe(Effect.provide(bootServiceLayer(config))); | |
| 110 | return yield* run.pipe(Effect.provide(layer(config))); | |
| 111 | 111 | }); |
| 112 | 112 | |
| 113 | 113 | const serviceReconcileFlags = { |
apps/server/src/cli/uninstall.ts+2-2| ... | ... | @@ -20,7 +20,7 @@ import { Command, Flag, GlobalFlag, Prompt } from "effect/cli"; |
| 20 | 20 | import * as BootService from "../cloud/bootService.ts"; |
| 21 | 21 | import { pinnedRuntimeVersionsDir } from "../cloud/pinnedRuntime.ts"; |
| 22 | 22 | import { projectLocationFlags, resolveCliAuthConfig } from "./config.ts"; |
| 23 | import { bootServiceLayer } from "./service.ts"; | |
| 23 | import * as CliService from "./service.ts"; | |
| 24 | 24 | import { findWindowsShim, launcherOwnsVersionsDir, resolveLauncherPath } from "./update.ts"; |
| 25 | 25 | |
| 26 | 26 | export class CliUninstallError extends Schema.TaggedError<CliUninstallError>()( |
| ... | ... | @@ -117,7 +117,7 @@ export const uninstallCommand = Command.make("uninstall", { |
| 117 | 117 | const logLevel = yield* GlobalFlag.LogLevel; |
| 118 | 118 | const config = yield* resolveCliAuthConfig(flags, logLevel); |
| 119 | 119 | return yield* runUninstall({ baseDir: config.baseDir, assumeYes: flags.yes }).pipe( |
| 120 | Effect.provide(bootServiceLayer(config)), | |
| 120 | Effect.provide(CliService.layer(config)), | |
| 121 | 121 | ); |
| 122 | 122 | }), |
| 123 | 123 | ), |
apps/server/src/cli/update.ts+2-2| ... | ... | @@ -38,7 +38,7 @@ import * as ProcessRunner from "../processRunner.ts"; |
| 38 | 38 | import { isProcessAlive, readPersistedServerRuntimeState } from "../serverRuntimeState.ts"; |
| 39 | 39 | import { projectLocationFlags, resolveCliAuthConfig } from "./config.ts"; |
| 40 | 40 | import { createUpdateProgress } from "./updateProgress.ts"; |
| 41 | import { bootServiceLayer } from "./service.ts"; | |
| 41 | import * as CliService from "./service.ts"; | |
| 42 | 42 | |
| 43 | 43 | export class CliUpdateError extends Schema.TaggedError<CliUpdateError>()("CliUpdateError", { |
| 44 | 44 | reason: Schema.String, |
| ... | ... | @@ -267,7 +267,7 @@ export const updateCommand = Command.make("update", { |
| 267 | 267 | assumeYes: flags.yes, |
| 268 | 268 | }).pipe( |
| 269 | 269 | Effect.provide( |
| 270 | Layer.mergeAll(bootServiceLayer(config), ProcessRunner.layer, FetchHttpClient.layer), | |
| 270 | Layer.mergeAll(CliService.layer(config), ProcessRunner.layer, FetchHttpClient.layer), | |
| 271 | 271 | ), |
| 272 | 272 | ); |
| 273 | 273 | }), |
apps/server/src/cloud/CliState.test.ts+3-3| ... | ... | @@ -29,7 +29,7 @@ const persistedCloudLinkSecrets = [ |
| 29 | 29 | PUBLISH_AGENT_ACTIVITY_SECRET, |
| 30 | 30 | ] as const; |
| 31 | 31 | |
| 32 | const makeTestLayer = () => | |
| 32 | const layerTest = () => | |
| 33 | 33 | ServerSecretStore.layer.pipe( |
| 34 | 34 | Layer.provide( |
| 35 | 35 | ServerConfig.layerTest(process.cwd(), { |
| ... | ... | @@ -56,7 +56,7 @@ it.layer(NodeServices.layer)("CliState", (it) => { |
| 56 | 56 | for (const name of persistedCloudLinkSecrets) { |
| 57 | 57 | assert.isTrue(Option.isNone(yield* secrets.get(name))); |
| 58 | 58 | } |
| 59 | }).pipe(Effect.provide(makeTestLayer())), | |
| 59 | }).pipe(Effect.provide(layerTest())), | |
| 60 | 60 | ); |
| 61 | 61 | |
| 62 | 62 | it.effect("round-trips the desired link mode and defaults legacy links to managed", () => |
| ... | ... | @@ -79,6 +79,6 @@ it.layer(NodeServices.layer)("CliState", (it) => { |
| 79 | 79 | |
| 80 | 80 | yield* CliState.setCliDesiredCloudLink(false); |
| 81 | 81 | assert.equal(yield* CliState.readCliDesiredLinkMode, "managed"); |
| 82 | }).pipe(Effect.provide(makeTestLayer())), | |
| 82 | }).pipe(Effect.provide(layerTest())), | |
| 83 | 83 | ); |
| 84 | 84 | }); |
apps/server/src/cloud/CliTokenManager.test.ts+7-7| ... | ... | @@ -137,7 +137,7 @@ const tokenGranted = { |
| 137 | 137 | }), |
| 138 | 138 | }; |
| 139 | 139 | |
| 140 | const makeDeviceFlowLayer = (server: DeviceFlowServer) => | |
| 140 | const layerDeviceFlow = (server: DeviceFlowServer) => | |
| 141 | 141 | Layer.succeed( |
| 142 | 142 | HttpClient.HttpClient, |
| 143 | 143 | HttpClient.make((request) => |
| ... | ... | @@ -177,7 +177,7 @@ it.layer(NodeServices.layer)("CliTokenManager.deviceAuthorizationLogin", (it) => |
| 177 | 177 | Effect.sync(() => { |
| 178 | 178 | prompts.push(prompt); |
| 179 | 179 | }), |
| 180 | ).pipe(Effect.provide(makeDeviceFlowLayer(server)), provideTestEnv, Effect.forkChild); | |
| 180 | ).pipe(Effect.provide(layerDeviceFlow(server)), provideTestEnv, Effect.forkChild); | |
| 181 | 181 | |
| 182 | 182 | yield* TestClock.adjust(Duration.seconds(10)); |
| 183 | 183 | const { token, identity } = yield* Fiber.join(fiber); |
| ... | ... | @@ -219,7 +219,7 @@ it.layer(NodeServices.layer)("CliTokenManager.deviceAuthorizationLogin", (it) => |
| 219 | 219 | }; |
| 220 | 220 | |
| 221 | 221 | const fiber = yield* CliTokenManager.deviceAuthorizationLogin(() => Effect.void).pipe( |
| 222 | Effect.provide(makeDeviceFlowLayer(server)), | |
| 222 | Effect.provide(layerDeviceFlow(server)), | |
| 223 | 223 | provideTestEnv, |
| 224 | 224 | Effect.forkChild, |
| 225 | 225 | ); |
| ... | ... | @@ -245,7 +245,7 @@ it.layer(NodeServices.layer)("CliTokenManager.deviceAuthorizationLogin", (it) => |
| 245 | 245 | }; |
| 246 | 246 | |
| 247 | 247 | const fiber = yield* CliTokenManager.deviceAuthorizationLogin(() => Effect.void).pipe( |
| 248 | Effect.provide(makeDeviceFlowLayer(server)), | |
| 248 | Effect.provide(layerDeviceFlow(server)), | |
| 249 | 249 | provideTestEnv, |
| 250 | 250 | Effect.forkChild, |
| 251 | 251 | ); |
| ... | ... | @@ -270,7 +270,7 @@ it.layer(NodeServices.layer)("CliTokenManager.deviceAuthorizationLogin", (it) => |
| 270 | 270 | }; |
| 271 | 271 | |
| 272 | 272 | const fiber = yield* CliTokenManager.deviceAuthorizationLogin(() => Effect.void).pipe( |
| 273 | Effect.provide(makeDeviceFlowLayer(server)), | |
| 273 | Effect.provide(layerDeviceFlow(server)), | |
| 274 | 274 | provideTestEnv, |
| 275 | 275 | Effect.flip, |
| 276 | 276 | Effect.forkChild, |
| ... | ... | @@ -291,7 +291,7 @@ it.layer(NodeServices.layer)("CliTokenManager.deviceAuthorizationLogin", (it) => |
| 291 | 291 | }; |
| 292 | 292 | |
| 293 | 293 | const fiber = yield* CliTokenManager.deviceAuthorizationLogin(() => Effect.void).pipe( |
| 294 | Effect.provide(makeDeviceFlowLayer(server)), | |
| 294 | Effect.provide(layerDeviceFlow(server)), | |
| 295 | 295 | provideTestEnv, |
| 296 | 296 | Effect.flip, |
| 297 | 297 | Effect.forkChild, |
| ... | ... | @@ -311,7 +311,7 @@ it.layer(NodeServices.layer)("CliTokenManager.deviceAuthorizationLogin", (it) => |
| 311 | 311 | }; |
| 312 | 312 | |
| 313 | 313 | const fiber = yield* CliTokenManager.deviceAuthorizationLogin(() => Effect.void).pipe( |
| 314 | Effect.provide(makeDeviceFlowLayer(server)), | |
| 314 | Effect.provide(layerDeviceFlow(server)), | |
| 315 | 315 | provideTestEnv, |
| 316 | 316 | Effect.flip, |
| 317 | 317 | Effect.forkChild, |
apps/server/src/cloud/CliTokenManager.ts+2-2| ... | ... | @@ -461,7 +461,7 @@ export const make = Effect.gen(function* () { |
| 461 | 461 | const hostedAppUrl = yield* hostedAppUrlConfig; |
| 462 | 462 | const { verifier, challenge, state } = yield* makePkceRequest; |
| 463 | 463 | const callback = yield* Deferred.make<string>(); |
| 464 | const callbackRoute = HttpRouter.add( | |
| 464 | const layerCallbackRoute = HttpRouter.add( | |
| 465 | 465 | "GET", |
| 466 | 466 | "/callback", |
| 467 | 467 | Effect.gen(function* () { |
| ... | ... | @@ -477,7 +477,7 @@ export const make = Effect.gen(function* () { |
| 477 | 477 | return HttpServerResponse.html(renderLoopbackAuthorizationCompleteHtml()); |
| 478 | 478 | }), |
| 479 | 479 | ); |
| 480 | yield* HttpRouter.serve(callbackRoute, { | |
| 480 | yield* HttpRouter.serve(layerCallbackRoute, { | |
| 481 | 481 | disableListenLog: true, |
| 482 | 482 | disableLogger: true, |
| 483 | 483 | }).pipe( |
apps/server/src/cloud/CloudPreferences.test.ts+2-2| ... | ... | @@ -61,7 +61,7 @@ const withService = <A, E>( |
| 61 | 61 | }, |
| 62 | 62 | { preconnect: () => {} }, |
| 63 | 63 | ); |
| 64 | const dependencies = Layer.mergeAll( | |
| 64 | const layerDependencies = Layer.mergeAll( | |
| 65 | 65 | Layer.mock(ServerSecretStore.ServerSecretStore)({ |
| 66 | 66 | get: (name) => |
| 67 | 67 | (options.failActivityRead && name === PUBLISH_AGENT_ACTIVITY_SECRET) || |
| ... | ... | @@ -93,7 +93,7 @@ const withService = <A, E>( |
| 93 | 93 | const preferences = yield* CloudPreferences.CloudPreferences; |
| 94 | 94 | return yield* body({ preferences, stored, relayCalls }); |
| 95 | 95 | }).pipe( |
| 96 | Effect.provide(CloudPreferences.layer.pipe(Layer.provide(dependencies))), | |
| 96 | Effect.provide(CloudPreferences.layer.pipe(Layer.provide(layerDependencies))), | |
| 97 | 97 | Effect.provideService(FetchHttpClient.Fetch, fetch), |
| 98 | 98 | ); |
| 99 | 99 | }); |
apps/server/src/cloud/ManagedEndpointRuntime.test.ts+3-3| ... | ... | @@ -18,7 +18,7 @@ import * as RelayClient from "@t3tools/shared/relayClient"; |
| 18 | 18 | import * as ServerSecretStore from "../auth/ServerSecretStore.ts"; |
| 19 | 19 | import * as ManagedEndpointRuntime from "./ManagedEndpointRuntime.ts"; |
| 20 | 20 | |
| 21 | const relayClientAvailableLayer = Layer.succeed( | |
| 21 | const layerRelayClientAvailable = Layer.succeed( | |
| 22 | 22 | RelayClient.RelayClient, |
| 23 | 23 | RelayClient.RelayClient.of({ |
| 24 | 24 | resolve: Effect.succeed({ |
| ... | ... | @@ -34,7 +34,7 @@ const relayClientAvailableLayer = Layer.succeed( |
| 34 | 34 | |
| 35 | 35 | const runtimeDependencies = ( |
| 36 | 36 | spawner: ReturnType<typeof ChildProcessSpawner.make>, |
| 37 | relayClientLayer = relayClientAvailableLayer, | |
| 37 | relayClientLayer = layerRelayClientAvailable, | |
| 38 | 38 | ) => |
| 39 | 39 | Layer.mergeAll( |
| 40 | 40 | Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner), |
| ... | ... | @@ -46,7 +46,7 @@ const runtimeDependencies = ( |
| 46 | 46 | |
| 47 | 47 | const buildCloudManagedEndpointRuntime = ( |
| 48 | 48 | spawner: ReturnType<typeof ChildProcessSpawner.make>, |
| 49 | relayClientLayer = relayClientAvailableLayer, | |
| 49 | relayClientLayer = layerRelayClientAvailable, | |
| 50 | 50 | ) => |
| 51 | 51 | Effect.gen(function* () { |
| 52 | 52 | const context = yield* Layer.build( |
apps/server/src/cloud/environmentKeys.test.ts+3-3| ... | ... | @@ -9,7 +9,7 @@ import * as ServerSecretStore from "../auth/ServerSecretStore.ts"; |
| 9 | 9 | import * as ServerConfig from "../config.ts"; |
| 10 | 10 | import { getOrCreateEnvironmentKeyPairFromSecretStore } from "./environmentKeys.ts"; |
| 11 | 11 | |
| 12 | const makeServerSecretStoreLayer = () => | |
| 12 | const layerServerSecretStore = () => | |
| 13 | 13 | ServerSecretStore.layer.pipe( |
| 14 | 14 | Layer.provide(ServerConfig.layerTest(process.cwd(), { prefix: "t3-environment-keys-test-" })), |
| 15 | 15 | ); |
| ... | ... | @@ -28,7 +28,7 @@ it.layer(NodeServices.layer)("getOrCreateEnvironmentKeyPairFromSecretStore", (it |
| 28 | 28 | assert.isTrue(Option.isSome(yield* secretStore.get("cloud-link-ed25519-key-pair"))); |
| 29 | 29 | assert.isTrue(Option.isNone(yield* secretStore.get("cloud-link-ed25519-private-key"))); |
| 30 | 30 | assert.isTrue(Option.isNone(yield* secretStore.get("cloud-link-ed25519-public-key"))); |
| 31 | }).pipe(Effect.provide(makeServerSecretStoreLayer())), | |
| 31 | }).pipe(Effect.provide(layerServerSecretStore())), | |
| 32 | 32 | ); |
| 33 | 33 | |
| 34 | 34 | it.effect("migrates a legacy keypair into the atomic secret", () => |
| ... | ... | @@ -42,7 +42,7 @@ it.layer(NodeServices.layer)("getOrCreateEnvironmentKeyPairFromSecretStore", (it |
| 42 | 42 | publicKey: "public", |
| 43 | 43 | }); |
| 44 | 44 | assert.isTrue(Option.isSome(yield* secretStore.get("cloud-link-ed25519-key-pair"))); |
| 45 | }).pipe(Effect.provide(makeServerSecretStoreLayer())), | |
| 45 | }).pipe(Effect.provide(layerServerSecretStore())), | |
| 46 | 46 | ); |
| 47 | 47 | |
| 48 | 48 | it.effect("uses the persisted keypair when a concurrent creator wins", () => |
apps/server/src/cloud/http.ts+1-1| ... | ... | @@ -1594,7 +1594,7 @@ const cloudMintCredentialHandler = Effect.fn("environment.cloud.mintCredential") |
| 1594 | 1594 | ), |
| 1595 | 1595 | ); |
| 1596 | 1596 | |
| 1597 | export const connectHttpApiLayer = HttpApiBuilder.group( | |
| 1597 | export const layer = HttpApiBuilder.group( | |
| 1598 | 1598 | EnvironmentHttpApi, |
| 1599 | 1599 | "connect", |
| 1600 | 1600 | Effect.fnUntraced(function* (handlers) { |
apps/server/src/cloud/relayTracing.ts+7-10| ... | ... | @@ -1,19 +1,16 @@ |
| 1 | import { makeRelayClientTracingLayer } from "@t3tools/shared/relayTracing"; | |
| 1 | import * as RelayTracing from "@t3tools/shared/relayTracing"; | |
| 2 | 2 | |
| 3 | 3 | import { resolveRelayClientTracingConfig } from "./publicConfig.ts"; |
| 4 | 4 | |
| 5 | 5 | const relayClientTracingConfig = resolveRelayClientTracingConfig(); |
| 6 | 6 | |
| 7 | export const headlessRelayClientTracingLayer = makeRelayClientTracingLayer( | |
| 8 | relayClientTracingConfig, | |
| 9 | { | |
| 10 | serviceName: "t3code-server", | |
| 11 | runtime: "node", | |
| 12 | client: "headless-cli", | |
| 13 | }, | |
| 14 | ); | |
| 7 | export const layerHeadlessRelayClient = RelayTracing.layer(relayClientTracingConfig, { | |
| 8 | serviceName: "t3code-server", | |
| 9 | runtime: "node", | |
| 10 | client: "headless-cli", | |
| 11 | }); | |
| 15 | 12 | |
| 16 | export const serverRelayBrokerTracingLayer = makeRelayClientTracingLayer(relayClientTracingConfig, { | |
| 13 | export const layerServerRelayBroker = RelayTracing.layer(relayClientTracingConfig, { | |
| 17 | 14 | serviceName: "t3code-server", |
| 18 | 15 | runtime: "node", |
| 19 | 16 | client: "environment-server", |
apps/server/src/device/DeviceHubProxy.test.ts+2-2| ... | ... | @@ -11,7 +11,7 @@ import * as Layer from "effect/Layer"; |
| 11 | 11 | import { HttpClient, HttpClientResponse, HttpRouter } from "effect/http"; |
| 12 | 12 | import * as EnvironmentAuth from "../auth/EnvironmentAuth.ts"; |
| 13 | 13 | import * as DeviceService from "./DeviceService.ts"; |
| 14 | import { deviceHubProxyRouteLayer } from "./DeviceHubProxy.ts"; | |
| 14 | import * as DeviceHubProxy from "./DeviceHubProxy.ts"; | |
| 15 | 15 | |
| 16 | 16 | const disposers: Array<() => Promise<void>> = []; |
| 17 | 17 | afterEach(async () => { |
| ... | ... | @@ -36,7 +36,7 @@ const fixture = ( |
| 36 | 36 | }), |
| 37 | 37 | ); |
| 38 | 38 | const { handler, dispose } = HttpRouter.toWebHandler( |
| 39 | deviceHubProxyRouteLayer.pipe( | |
| 39 | DeviceHubProxy.layer.pipe( | |
| 40 | 40 | Layer.provideMerge( |
| 41 | 41 | Layer.succeed(EnvironmentAuth.EnvironmentAuth, { |
| 42 | 42 | authenticateWebSocketUpgrade: () => |
apps/server/src/device/DeviceHubProxy.ts+1-5| ... | ... | @@ -230,8 +230,4 @@ const handler = Effect.gen(function* () { |
| 230 | 230 | return yield* proxyHttp(request, `${ready.hub.origin}${upstreamPath}`, ready.hub.origin); |
| 231 | 231 | }); |
| 232 | 232 | |
| 233 | export const deviceHubProxyRouteLayer = HttpRouter.add( | |
| 234 | "*", | |
| 235 | `${DeviceService.DEVICE_HUB_ROUTE_PREFIX}/*`, | |
| 236 | handler, | |
| 237 | ); | |
| 233 | export const layer = HttpRouter.add("*", `${DeviceService.DEVICE_HUB_ROUTE_PREFIX}/*`, handler); |
apps/server/src/diagnostics/ProcessDiagnostics.test.ts+13-13| ... | ... | @@ -31,11 +31,11 @@ function makeNativeSnapshot( |
| 31 | 31 | }; |
| 32 | 32 | } |
| 33 | 33 | |
| 34 | function makeTelemetryLayer( | |
| 34 | function layerTelemetryFor( | |
| 35 | 35 | snapshot: ResourceMonitorSnapshotEvent, |
| 36 | 36 | desktopSnapshot?: DesktopHostTelemetrySnapshot, |
| 37 | 37 | ) { |
| 38 | const nativeLayer = NativeTelemetryClient.layerTest({ | |
| 38 | const layerNative = NativeTelemetryClient.layerTest({ | |
| 39 | 39 | sampleNow: Effect.succeed({ generation: 0, snapshot }), |
| 40 | 40 | health: Effect.succeed({ |
| 41 | 41 | status: "healthy", |
| ... | ... | @@ -46,7 +46,7 @@ function makeTelemetryLayer( |
| 46 | 46 | sampleIntervalMs: 1_000, |
| 47 | 47 | }), |
| 48 | 48 | }); |
| 49 | const desktopLayer = desktopSnapshot | |
| 49 | const layerDesktop = desktopSnapshot | |
| 50 | 50 | ? DesktopTelemetryReceiver.layerTest({ |
| 51 | 51 | latest: Effect.succeedSome(desktopSnapshot), |
| 52 | 52 | health: Effect.succeed({ |
| ... | ... | @@ -57,7 +57,7 @@ function makeTelemetryLayer( |
| 57 | 57 | }) |
| 58 | 58 | : DesktopTelemetryReceiver.layerTest(); |
| 59 | 59 | return ResourceTelemetry.layer.pipe( |
| 60 | Layer.provide(Layer.mergeAll(nativeLayer, desktopLayer, ResourceAttribution.layer)), | |
| 60 | Layer.provide(Layer.mergeAll(layerNative, layerDesktop, ResourceAttribution.layer)), | |
| 61 | 61 | ); |
| 62 | 62 | } |
| 63 | 63 | |
| ... | ... | @@ -98,8 +98,8 @@ describe("ProcessDiagnostics", () => { |
| 98 | 98 | ioSemantics: "storage", |
| 99 | 99 | }, |
| 100 | 100 | ]); |
| 101 | const telemetryLayer = makeTelemetryLayer(snapshot); | |
| 102 | const layer = ProcessDiagnostics.layer.pipe(Layer.provideMerge(telemetryLayer)); | |
| 101 | const layerTelemetry = layerTelemetryFor(snapshot); | |
| 102 | const layer = ProcessDiagnostics.layer.pipe(Layer.provideMerge(layerTelemetry)); | |
| 103 | 103 | |
| 104 | 104 | const diagnostics = yield* Effect.gen(function* () { |
| 105 | 105 | const processDiagnostics = yield* ProcessDiagnostics.ProcessDiagnostics; |
| ... | ... | @@ -116,8 +116,8 @@ describe("ProcessDiagnostics", () => { |
| 116 | 116 | it.effect("rejects stale process identities before signaling", () => |
| 117 | 117 | Effect.gen(function* () { |
| 118 | 118 | const snapshot = makeNativeSnapshot([]); |
| 119 | const telemetryLayer = makeTelemetryLayer(snapshot); | |
| 120 | const layer = ProcessDiagnostics.layer.pipe(Layer.provide(telemetryLayer)); | |
| 119 | const layerTelemetry = layerTelemetryFor(snapshot); | |
| 120 | const layer = ProcessDiagnostics.layer.pipe(Layer.provide(layerTelemetry)); | |
| 121 | 121 | |
| 122 | 122 | const result = yield* Effect.service(ProcessDiagnostics.ProcessDiagnostics).pipe( |
| 123 | 123 | Effect.flatMap((processDiagnostics) => |
| ... | ... | @@ -161,9 +161,9 @@ describe("ProcessDiagnostics", () => { |
| 161 | 161 | ]); |
| 162 | 162 | const staleTelemetry = yield* Effect.service(ResourceTelemetry.ResourceTelemetry).pipe( |
| 163 | 163 | Effect.flatMap((telemetry) => telemetry.latest), |
| 164 | Effect.provide(makeTelemetryLayer(snapshot)), | |
| 164 | Effect.provide(layerTelemetryFor(snapshot)), | |
| 165 | 165 | ); |
| 166 | const telemetryLayer = Layer.succeed( | |
| 166 | const layerTelemetry = Layer.succeed( | |
| 167 | 167 | ResourceTelemetry.ResourceTelemetry, |
| 168 | 168 | ResourceTelemetry.ResourceTelemetry.of({ |
| 169 | 169 | latest: Effect.succeed(staleTelemetry), |
| ... | ... | @@ -180,7 +180,7 @@ describe("ProcessDiagnostics", () => { |
| 180 | 180 | retry: Effect.die("unused"), |
| 181 | 181 | }), |
| 182 | 182 | ); |
| 183 | const layer = ProcessDiagnostics.layer.pipe(Layer.provide(telemetryLayer)); | |
| 183 | const layer = ProcessDiagnostics.layer.pipe(Layer.provide(layerTelemetry)); | |
| 184 | 184 | |
| 185 | 185 | const result = yield* Effect.service(ProcessDiagnostics.ProcessDiagnostics).pipe( |
| 186 | 186 | Effect.flatMap((processDiagnostics) => |
| ... | ... | @@ -228,7 +228,7 @@ describe("ProcessDiagnostics", () => { |
| 228 | 228 | }, |
| 229 | 229 | ]); |
| 230 | 230 | const sampledAt = DateTime.makeUnsafe(sampledAtUnixMs); |
| 231 | const telemetryLayer = makeTelemetryLayer(snapshot, { | |
| 231 | const layerTelemetry = layerTelemetryFor(snapshot, { | |
| 232 | 232 | version: 1, |
| 233 | 233 | type: "desktopTelemetry", |
| 234 | 234 | sequence: 1, |
| ... | ... | @@ -260,7 +260,7 @@ describe("ProcessDiagnostics", () => { |
| 260 | 260 | }, |
| 261 | 261 | ], |
| 262 | 262 | }); |
| 263 | const layer = ProcessDiagnostics.layer.pipe(Layer.provide(telemetryLayer)); | |
| 263 | const layer = ProcessDiagnostics.layer.pipe(Layer.provide(layerTelemetry)); | |
| 264 | 264 | |
| 265 | 265 | const result = yield* Effect.service(ProcessDiagnostics.ProcessDiagnostics).pipe( |
| 266 | 266 | Effect.flatMap((processDiagnostics) => |
apps/server/src/environment/DirectEndpoints.test.ts+4-4| ... | ... | @@ -135,7 +135,7 @@ const TAILSCALE_STATUS_JSON = JSON.stringify({ |
| 135 | 135 | }); |
| 136 | 136 | |
| 137 | 137 | /** `tailscale status --json` reporting a MagicDNS name. */ |
| 138 | const tailscaleUpLayer = Layer.succeed( | |
| 138 | const layerTailscaleUp = Layer.succeed( | |
| 139 | 139 | ChildProcessSpawner.ChildProcessSpawner, |
| 140 | 140 | ChildProcessSpawner.make(() => |
| 141 | 141 | Effect.succeed( |
| ... | ... | @@ -157,7 +157,7 @@ const tailscaleUpLayer = Layer.succeed( |
| 157 | 157 | ); |
| 158 | 158 | |
| 159 | 159 | /** Answers the Serve probe with `status`. */ |
| 160 | const serveProbeLayer = (status: number) => | |
| 160 | const layerServeProbe = (status: number) => | |
| 161 | 161 | Layer.succeed( |
| 162 | 162 | HttpClient.HttpClient, |
| 163 | 163 | HttpClient.make((request) => |
| ... | ... | @@ -166,7 +166,7 @@ const serveProbeLayer = (status: number) => |
| 166 | 166 | ); |
| 167 | 167 | |
| 168 | 168 | /** A loopback-only server with Tailscale Serve on, so only the Serve name can be listed. */ |
| 169 | const serveConfigLayer = Layer.effect( | |
| 169 | const layerServeConfig = Layer.effect( | |
| 170 | 170 | ServerConfig.ServerConfig, |
| 171 | 171 | Effect.map(ServerConfig.ServerConfig, (config) => ({ |
| 172 | 172 | ...config, |
| ... | ... | @@ -184,7 +184,7 @@ const resolveWithServe = (probeStatus: number) => |
| 184 | 184 | Effect.provide( |
| 185 | 185 | DirectEndpoints.layer.pipe( |
| 186 | 186 | Layer.provide( |
| 187 | Layer.mergeAll(serveConfigLayer, tailscaleUpLayer, serveProbeLayer(probeStatus)), | |
| 187 | Layer.mergeAll(layerServeConfig, layerTailscaleUp, layerServeProbe(probeStatus)), | |
| 188 | 188 | ), |
| 189 | 189 | ), |
| 190 | 190 | ), |
apps/server/src/environment/RemoteOpenTargets.test.ts+3-3| ... | ... | @@ -17,7 +17,7 @@ const TAILSCALE_STATUS_JSON = JSON.stringify({ |
| 17 | 17 | }); |
| 18 | 18 | |
| 19 | 19 | /** Spawner whose `tailscale status --json` exits with the given output. */ |
| 20 | const spawnerLayer = (input: { readonly exitCode: number; readonly stdout: string }) => | |
| 20 | const layerSpawner = (input: { readonly exitCode: number; readonly stdout: string }) => | |
| 21 | 21 | Layer.succeed( |
| 22 | 22 | ChildProcessSpawner.ChildProcessSpawner, |
| 23 | 23 | ChildProcessSpawner.make(() => |
| ... | ... | @@ -39,7 +39,7 @@ const spawnerLayer = (input: { readonly exitCode: number; readonly stdout: strin |
| 39 | 39 | ), |
| 40 | 40 | ); |
| 41 | 41 | |
| 42 | const netLayer = (input: { readonly ipv4: boolean; readonly ipv6: boolean }) => | |
| 42 | const layerNet = (input: { readonly ipv4: boolean; readonly ipv6: boolean }) => | |
| 43 | 43 | Layer.succeed(NetService.NetService, { |
| 44 | 44 | canListenOnHost: () => Effect.succeed(true), |
| 45 | 45 | isPortAvailableOnLoopback: () => Effect.succeed(true), |
| ... | ... | @@ -57,7 +57,7 @@ const resolveTargets = (input: { |
| 57 | 57 | Effect.provideService(HostProcessHostname, input.hostname), |
| 58 | 58 | Effect.provide( |
| 59 | 59 | RemoteOpenTargets.layer.pipe( |
| 60 | Layer.provide(Layer.mergeAll(netLayer(input.sshd), spawnerLayer(input.tailscale))), | |
| 60 | Layer.provide(Layer.mergeAll(layerNet(input.sshd), layerSpawner(input.tailscale))), | |
| 61 | 61 | ), |
| 62 | 62 | ), |
| 63 | 63 | ); |
apps/server/src/environment/ServerEnvironment.test.ts+11-11| ... | ... | @@ -31,13 +31,13 @@ const isServerEnvironmentIdPersistenceError = Schema.is( |
| 31 | 31 | ServerEnvironment.ServerEnvironmentIdPersistenceError, |
| 32 | 32 | ); |
| 33 | 33 | |
| 34 | const makeServerEnvironmentLayer = (baseDir: string) => | |
| 34 | const layerServerEnvironment = (baseDir: string) => | |
| 35 | 35 | ServerEnvironment.layer.pipe( |
| 36 | 36 | Layer.provide(ServerSecretStore.layer), |
| 37 | 37 | Layer.provide(ServerConfig.layerTest(process.cwd(), baseDir)), |
| 38 | 38 | ); |
| 39 | 39 | |
| 40 | const emptySecretStoreLayer = Layer.succeed( | |
| 40 | const layerEmptySecretStore = Layer.succeed( | |
| 41 | 41 | ServerSecretStore.ServerSecretStore, |
| 42 | 42 | ServerSecretStore.ServerSecretStore.of({ |
| 43 | 43 | get: () => Effect.succeedNone, |
| ... | ... | @@ -108,7 +108,7 @@ it.layer(NodeServices.layer)("ServerEnvironmentLive", (it) => { |
| 108 | 108 | }).pipe( |
| 109 | 109 | Effect.provide( |
| 110 | 110 | ServerEnvironment.layer.pipe( |
| 111 | Layer.provide(emptySecretStoreLayer), | |
| 111 | Layer.provide(layerEmptySecretStore), | |
| 112 | 112 | Layer.provide(ServerConfig.layer({ ...config, mode })), |
| 113 | 113 | ), |
| 114 | 114 | ), |
| ... | ... | @@ -152,7 +152,7 @@ it.layer(NodeServices.layer)("ServerEnvironmentLive", (it) => { |
| 152 | 152 | return yield* identity.getEnvironmentId; |
| 153 | 153 | }).pipe( |
| 154 | 154 | Effect.tap(() => Deferred.succeed(firstInitialized, undefined)), |
| 155 | Effect.provide(Layer.fresh(ServerEnvironment.identityLayer)), | |
| 155 | Effect.provide(Layer.fresh(ServerEnvironment.layerIdentity)), | |
| 156 | 156 | Effect.provideService(ServerConfig.ServerConfig, serverConfig), |
| 157 | 157 | Effect.provideService(FileSystem.FileSystem, { |
| 158 | 158 | ...fileSystem, |
| ... | ... | @@ -207,11 +207,11 @@ it.layer(NodeServices.layer)("ServerEnvironmentLive", (it) => { |
| 207 | 207 | const first = yield* Effect.gen(function* () { |
| 208 | 208 | const serverEnvironment = yield* ServerEnvironment.ServerEnvironment; |
| 209 | 209 | return yield* serverEnvironment.getDescriptor; |
| 210 | }).pipe(Effect.provide(makeServerEnvironmentLayer(baseDir))); | |
| 210 | }).pipe(Effect.provide(layerServerEnvironment(baseDir))); | |
| 211 | 211 | const second = yield* Effect.gen(function* () { |
| 212 | 212 | const serverEnvironment = yield* ServerEnvironment.ServerEnvironment; |
| 213 | 213 | return yield* serverEnvironment.getDescriptor; |
| 214 | }).pipe(Effect.provide(makeServerEnvironmentLayer(baseDir))); | |
| 214 | }).pipe(Effect.provide(layerServerEnvironment(baseDir))); | |
| 215 | 215 | |
| 216 | 216 | expect(first.environmentId).toBe(second.environmentId); |
| 217 | 217 | expect(first.orchestrationProtocolVersion).toBe(ORCHESTRATION_PROTOCOL_VERSION); |
| ... | ... | @@ -237,7 +237,7 @@ it.layer(NodeServices.layer)("ServerEnvironmentLive", (it) => { |
| 237 | 237 | const baseDir = yield* fileSystem.makeTempDirectoryScoped({ |
| 238 | 238 | prefix: "t3-server-environment-publish-test-", |
| 239 | 239 | }); |
| 240 | const testLayer = Layer.mergeAll( | |
| 240 | const layerTest = Layer.mergeAll( | |
| 241 | 241 | ServerEnvironment.layer.pipe(Layer.provide(ServerSecretStore.layer)), |
| 242 | 242 | ServerSecretStore.layer, |
| 243 | 243 | ).pipe(Layer.provide(ServerConfig.layerTest(process.cwd(), baseDir))); |
| ... | ... | @@ -273,7 +273,7 @@ it.layer(NodeServices.layer)("ServerEnvironmentLive", (it) => { |
| 273 | 273 | yield* secrets.set(PUBLISH_AGENT_ACTIVITY_SECRET, encode("false")); |
| 274 | 274 | const disabled = yield* serverEnvironment.getDescriptor; |
| 275 | 275 | expect(disabled.capabilities.agentActivityPublishing).toBe(false); |
| 276 | }).pipe(Effect.provide(testLayer)); | |
| 276 | }).pipe(Effect.provide(layerTest)); | |
| 277 | 277 | }), |
| 278 | 278 | ); |
| 279 | 279 | |
| ... | ... | @@ -342,7 +342,7 @@ it.layer(NodeServices.layer)("ServerEnvironmentLive", (it) => { |
| 342 | 342 | description: "permission denied", |
| 343 | 343 | pathOrDescriptor: environmentIdPath, |
| 344 | 344 | }); |
| 345 | const failingFileSystemLayer = FileSystem.layerNoop({ | |
| 345 | const layerFailingFileSystem = FileSystem.layerNoop({ | |
| 346 | 346 | exists: () => |
| 347 | 347 | operation === "check" ? Effect.fail(cause) : Effect.succeed(operation === "read"), |
| 348 | 348 | readFileString: () => Effect.fail(cause), |
| ... | ... | @@ -359,8 +359,8 @@ it.layer(NodeServices.layer)("ServerEnvironmentLive", (it) => { |
| 359 | 359 | }).pipe( |
| 360 | 360 | Effect.provide( |
| 361 | 361 | ServerEnvironment.layer.pipe( |
| 362 | Layer.provide(emptySecretStoreLayer), | |
| 363 | Layer.provide(Layer.merge(ServerConfig.layer(serverConfig), failingFileSystemLayer)), | |
| 362 | Layer.provide(layerEmptySecretStore), | |
| 363 | Layer.provide(Layer.merge(ServerConfig.layer(serverConfig), layerFailingFileSystem)), | |
| 364 | 364 | ), |
| 365 | 365 | ), |
| 366 | 366 | Effect.flip, |
apps/server/src/environment/ServerEnvironment.ts+2-2| ... | ... | @@ -275,7 +275,7 @@ export const make = Effect.gen(function* () { |
| 275 | 275 | }); |
| 276 | 276 | }); |
| 277 | 277 | |
| 278 | export const identityLayer = Layer.effect(ServerEnvironmentIdentity, makeIdentity); | |
| 278 | export const layerIdentity = Layer.effect(ServerEnvironmentIdentity, makeIdentity); | |
| 279 | 279 | |
| 280 | 280 | /** |
| 281 | 281 | * ServerEnvironment is acquired from persisted filesystem and host-process |
| ... | ... | @@ -284,6 +284,6 @@ export const identityLayer = Layer.effect(ServerEnvironmentIdentity, makeIdentit |
| 284 | 284 | * ServerSecretStore backing the descriptor's publishing capability. |
| 285 | 285 | */ |
| 286 | 286 | export const layer = Layer.effect(ServerEnvironment, make).pipe( |
| 287 | Layer.provideMerge(identityLayer), | |
| 287 | Layer.provideMerge(layerIdentity), | |
| 288 | 288 | Layer.provide(ProcessRunner.layer), |
| 289 | 289 | ); |
apps/server/src/environment/ServerEnvironmentLabel.test.ts+18-14| ... | ... | @@ -27,16 +27,16 @@ interface CapturedLog { |
| 27 | 27 | |
| 28 | 28 | const runMock = vi.fn<ProcessRunner.ProcessRunner["Service"]["run"]>(); |
| 29 | 29 | |
| 30 | const ProcessRunnerTest = Layer.succeed( | |
| 30 | const layerProcessRunnerTest = Layer.succeed( | |
| 31 | 31 | ProcessRunner.ProcessRunner, |
| 32 | 32 | ProcessRunner.ProcessRunner.of({ |
| 33 | 33 | run: (input) => runMock(input), |
| 34 | 34 | }), |
| 35 | 35 | ); |
| 36 | const NoopFileSystemLayer = FileSystem.layerNoop({}); | |
| 37 | const TestLayer = Layer.merge(NoopFileSystemLayer, ProcessRunnerTest); | |
| 38 | const LinuxMachineInfoLayer = Layer.merge( | |
| 39 | ProcessRunnerTest, | |
| 36 | const layerNoopFileSystem = FileSystem.layerNoop({}); | |
| 37 | const layerTest = Layer.merge(layerNoopFileSystem, layerProcessRunnerTest); | |
| 38 | const layerLinuxMachineInfo = Layer.merge( | |
| 39 | layerProcessRunnerTest, | |
| 40 | 40 | FileSystem.layerNoop({ |
| 41 | 41 | exists: (path) => Effect.succeed(path === "/etc/machine-info"), |
| 42 | 42 | readFileString: (path) => |
| ... | ... | @@ -65,7 +65,7 @@ describe("resolveServerEnvironmentLabel", () => { |
| 65 | 65 | Effect.gen(function* () { |
| 66 | 66 | const result = yield* ServerEnvironmentLabel.resolveServerEnvironmentLabel({ |
| 67 | 67 | cwdBaseName: "t3code", |
| 68 | }).pipe(Effect.provide(withHostPlatform(TestLayer, "win32", "macbook-pro"))); | |
| 68 | }).pipe(Effect.provide(withHostPlatform(layerTest, "win32", "macbook-pro"))); | |
| 69 | 69 | |
| 70 | 70 | expect(result).toBe("macbook-pro"); |
| 71 | 71 | }), |
| ... | ... | @@ -88,7 +88,7 @@ describe("resolveServerEnvironmentLabel", () => { |
| 88 | 88 | |
| 89 | 89 | const result = yield* ServerEnvironmentLabel.resolveServerEnvironmentLabel({ |
| 90 | 90 | cwdBaseName: "t3code", |
| 91 | }).pipe(Effect.provide(withHostPlatform(TestLayer, "darwin", "macbook-pro"))); | |
| 91 | }).pipe(Effect.provide(withHostPlatform(layerTest, "darwin", "macbook-pro"))); | |
| 92 | 92 | |
| 93 | 93 | expect(result).toBe("Julius's MacBook Pro"); |
| 94 | 94 | expect(runMock).toHaveBeenCalledWith( |
| ... | ... | @@ -105,7 +105,7 @@ describe("resolveServerEnvironmentLabel", () => { |
| 105 | 105 | Effect.gen(function* () { |
| 106 | 106 | const result = yield* ServerEnvironmentLabel.resolveServerEnvironmentLabel({ |
| 107 | 107 | cwdBaseName: "t3code", |
| 108 | }).pipe(Effect.provide(withHostPlatform(LinuxMachineInfoLayer, "linux", "buildbox"))); | |
| 108 | }).pipe(Effect.provide(withHostPlatform(layerLinuxMachineInfo, "linux", "buildbox"))); | |
| 109 | 109 | |
| 110 | 110 | expect(result).toBe("Build Agent 01"); |
| 111 | 111 | expect(runMock).not.toHaveBeenCalled(); |
| ... | ... | @@ -129,7 +129,7 @@ describe("resolveServerEnvironmentLabel", () => { |
| 129 | 129 | |
| 130 | 130 | const result = yield* ServerEnvironmentLabel.resolveServerEnvironmentLabel({ |
| 131 | 131 | cwdBaseName: "t3code", |
| 132 | }).pipe(Effect.provide(withHostPlatform(TestLayer, "linux", "runner-01"))); | |
| 132 | }).pipe(Effect.provide(withHostPlatform(layerTest, "linux", "runner-01"))); | |
| 133 | 133 | |
| 134 | 134 | expect(result).toBe("CI Runner"); |
| 135 | 135 | expect(runMock).toHaveBeenCalledWith( |
| ... | ... | @@ -146,7 +146,7 @@ describe("resolveServerEnvironmentLabel", () => { |
| 146 | 146 | Effect.gen(function* () { |
| 147 | 147 | const result = yield* ServerEnvironmentLabel.resolveServerEnvironmentLabel({ |
| 148 | 148 | cwdBaseName: "t3code", |
| 149 | }).pipe(Effect.provide(withHostPlatform(TestLayer, "win32", "JULIUS-LAPTOP"))); | |
| 149 | }).pipe(Effect.provide(withHostPlatform(layerTest, "win32", "JULIUS-LAPTOP"))); | |
| 150 | 150 | |
| 151 | 151 | expect(result).toBe("JULIUS-LAPTOP"); |
| 152 | 152 | }), |
| ... | ... | @@ -192,7 +192,7 @@ describe("resolveServerEnvironmentLabel", () => { |
| 192 | 192 | }).pipe( |
| 193 | 193 | Effect.provide( |
| 194 | 194 | Layer.mergeAll( |
| 195 | withHostPlatform(TestLayer, "darwin", "macbook-pro"), | |
| 195 | withHostPlatform(layerTest, "darwin", "macbook-pro"), | |
| 196 | 196 | Logger.layer([logger], { mergeWithExisting: false }), |
| 197 | 197 | Layer.succeed(References.MinimumLogLevel, "Debug"), |
| 198 | 198 | ), |
| ... | ... | @@ -216,7 +216,7 @@ describe("resolveServerEnvironmentLabel", () => { |
| 216 | 216 | pathOrDescriptor: "/etc/machine-info", |
| 217 | 217 | cause: fileCause, |
| 218 | 218 | }); |
| 219 | const fileSystemLayer = FileSystem.layerNoop({ | |
| 219 | const layerFileSystem = FileSystem.layerNoop({ | |
| 220 | 220 | exists: () => Effect.fail(platformError), |
| 221 | 221 | }); |
| 222 | 222 | runMock.mockReturnValueOnce( |
| ... | ... | @@ -252,7 +252,11 @@ describe("resolveServerEnvironmentLabel", () => { |
| 252 | 252 | }).pipe( |
| 253 | 253 | Effect.provide( |
| 254 | 254 | Layer.mergeAll( |
| 255 | withHostPlatform(Layer.merge(ProcessRunnerTest, fileSystemLayer), "linux", "buildbox"), | |
| 255 | withHostPlatform( | |
| 256 | Layer.merge(layerProcessRunnerTest, layerFileSystem), | |
| 257 | "linux", | |
| 258 | "buildbox", | |
| 259 | ), | |
| 256 | 260 | Logger.layer([logger], { mergeWithExisting: false }), |
| 257 | 261 | Layer.succeed(References.MinimumLogLevel, "Debug"), |
| 258 | 262 | ), |
| ... | ... | @@ -277,7 +281,7 @@ describe("resolveServerEnvironmentLabel", () => { |
| 277 | 281 | |
| 278 | 282 | const result = yield* ServerEnvironmentLabel.resolveServerEnvironmentLabel({ |
| 279 | 283 | cwdBaseName: "t3code", |
| 280 | }).pipe(Effect.provide(withHostPlatform(TestLayer, "linux", " "))); | |
| 284 | }).pipe(Effect.provide(withHostPlatform(layerTest, "linux", " "))); | |
| 281 | 285 | |
| 282 | 286 | expect(result).toBe("t3code"); |
| 283 | 287 | }), |
apps/server/src/environment/ServerEnvironmentMachine.test.ts+6-6| ... | ... | @@ -16,7 +16,7 @@ import { |
| 16 | 16 | |
| 17 | 17 | const runMock = vi.fn<ProcessRunner.ProcessRunner["Service"]["run"]>(); |
| 18 | 18 | |
| 19 | const ProcessRunnerTest = Layer.succeed( | |
| 19 | const layerProcessRunnerTest = Layer.succeed( | |
| 20 | 20 | ProcessRunner.ProcessRunner, |
| 21 | 21 | ProcessRunner.ProcessRunner.of({ run: (input) => runMock(input) }), |
| 22 | 22 | ); |
| ... | ... | @@ -33,7 +33,7 @@ const processOutput = (stdout: string, code = 0) => |
| 33 | 33 | stderrInvalidUtf8: false, |
| 34 | 34 | }); |
| 35 | 35 | |
| 36 | const dmiFileSystem = (files: Readonly<Record<string, string>>) => | |
| 36 | const layerDmiFileSystem = (files: Readonly<Record<string, string>>) => | |
| 37 | 37 | FileSystem.layerNoop({ |
| 38 | 38 | readFileString: (path) => { |
| 39 | 39 | const name = path.slice(path.lastIndexOf("/") + 1); |
| ... | ... | @@ -52,7 +52,7 @@ const dmiFileSystem = (files: Readonly<Record<string, string>>) => |
| 52 | 52 | }); |
| 53 | 53 | |
| 54 | 54 | const withPlatform = (platform: NodeJS.Platform, fileSystem = FileSystem.layerNoop({})) => |
| 55 | Layer.mergeAll(ProcessRunnerTest, fileSystem, Layer.succeed(HostProcessPlatform, platform)); | |
| 55 | Layer.mergeAll(layerProcessRunnerTest, fileSystem, Layer.succeed(HostProcessPlatform, platform)); | |
| 56 | 56 | |
| 57 | 57 | afterEach(() => { |
| 58 | 58 | runMock.mockReset(); |
| ... | ... | @@ -188,7 +188,7 @@ describe("detectServerEnvironmentMachineKind", () => { |
| 188 | 188 | Effect.provide( |
| 189 | 189 | withPlatform( |
| 190 | 190 | "linux", |
| 191 | dmiFileSystem({ | |
| 191 | layerDmiFileSystem({ | |
| 192 | 192 | chassis_type: "3\n", |
| 193 | 193 | sys_vendor: "GMKtec\n", |
| 194 | 194 | product_name: "NucBox K8 Plus\n", |
| ... | ... | @@ -208,7 +208,7 @@ describe("detectServerEnvironmentMachineKind", () => { |
| 208 | 208 | Effect.provide( |
| 209 | 209 | withPlatform( |
| 210 | 210 | "linux", |
| 211 | dmiFileSystem({ | |
| 211 | layerDmiFileSystem({ | |
| 212 | 212 | osrelease: "5.15.153.1-microsoft-standard-WSL2\n", |
| 213 | 213 | chassis_type: "3\n", |
| 214 | 214 | sys_vendor: "Microsoft Corporation\n", |
| ... | ... | @@ -225,7 +225,7 @@ describe("detectServerEnvironmentMachineKind", () => { |
| 225 | 225 | it.effect("returns null on Linux without DMI (containers, ARM boards)", () => |
| 226 | 226 | Effect.gen(function* () { |
| 227 | 227 | const result = yield* detectServerEnvironmentMachineKind().pipe( |
| 228 | Effect.provide(withPlatform("linux", dmiFileSystem({}))), | |
| 228 | Effect.provide(withPlatform("linux", layerDmiFileSystem({}))), | |
| 229 | 229 | ); |
| 230 | 230 | |
| 231 | 231 | expect(result).toBeNull(); |
apps/server/src/git/GitManager.test.ts+17-17| ... | ... | @@ -53,7 +53,7 @@ import * as SourceControlProviderRegistry from "../sourceControl/SourceControlPr |
| 53 | 53 | import * as ServerConfig from "../config.ts"; |
| 54 | 54 | import * as ProjectionStore from "../orchestration-v2/ProjectionStore.ts"; |
| 55 | 55 | import * as ProjectStore from "../orchestration-v2/ProjectStore.ts"; |
| 56 | import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts"; | |
| 56 | import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts"; | |
| 57 | 57 | import * as ProjectSetupScriptRunner from "../project/ProjectSetupScriptRunner.ts"; |
| 58 | 58 | import * as ProviderRegistry from "../provider/Services/ProviderRegistry.ts"; |
| 59 | 59 | import * as ServerSettings from "../serverSettings.ts"; |
| ... | ... | @@ -679,13 +679,13 @@ function makeManager(input?: { |
| 679 | 679 | }) { |
| 680 | 680 | const { service: gitHubCli, ghCalls } = createGitHubCliWithFakeGh(input?.ghScenario); |
| 681 | 681 | const textGeneration = createTextGeneration(input?.textGeneration); |
| 682 | const serverConfigLayer = ServerConfig.layerTest(process.cwd(), { | |
| 682 | const layerServerConfig = ServerConfig.layerTest(process.cwd(), { | |
| 683 | 683 | prefix: "t3-git-manager-test-", |
| 684 | 684 | }); |
| 685 | 685 | |
| 686 | const serverSettingsLayer = ServerSettings.ServerSettingsService.layerTest(input?.serverSettings); | |
| 686 | const layerServerSettings = ServerSettings.ServerSettingsService.layerTest(input?.serverSettings); | |
| 687 | 687 | |
| 688 | const vcsDriverLayer = input?.gitConfigReads | |
| 688 | const layerVcsDriver = input?.gitConfigReads | |
| 689 | 689 | ? Layer.effect( |
| 690 | 690 | GitVcsDriver.GitVcsDriver, |
| 691 | 691 | GitVcsDriver.make.pipe( |
| ... | ... | @@ -702,14 +702,14 @@ function makeManager(input?: { |
| 702 | 702 | ).pipe( |
| 703 | 703 | Layer.provideMerge(VcsProcess.layer), |
| 704 | 704 | Layer.provideMerge(NodeServices.layer), |
| 705 | Layer.provideMerge(serverConfigLayer), | |
| 705 | Layer.provideMerge(layerServerConfig), | |
| 706 | 706 | ) |
| 707 | 707 | : GitVcsDriver.layer.pipe( |
| 708 | 708 | Layer.provideMerge(VcsProcess.layer), |
| 709 | 709 | Layer.provideMerge(NodeServices.layer), |
| 710 | Layer.provideMerge(serverConfigLayer), | |
| 710 | Layer.provideMerge(layerServerConfig), | |
| 711 | 711 | ); |
| 712 | const sourceControlRegistryLayer = Layer.effect( | |
| 712 | const layerSourceControlRegistry = Layer.effect( | |
| 713 | 713 | SourceControlProviderRegistry.SourceControlProviderRegistry, |
| 714 | 714 | (input?.sourceControlProvider === undefined |
| 715 | 715 | ? GitHubSourceControlProvider.make |
| ... | ... | @@ -728,7 +728,7 @@ function makeManager(input?: { |
| 728 | 728 | ), |
| 729 | 729 | ); |
| 730 | 730 | |
| 731 | const managerLayer = Layer.mergeAll( | |
| 731 | const layerManager = Layer.mergeAll( | |
| 732 | 732 | Layer.succeed(TextGeneration.TextGeneration, textGeneration), |
| 733 | 733 | Layer.mock(ProviderRegistry.ProviderRegistry)({ |
| 734 | 734 | getProviders: Effect.succeed([]), |
| ... | ... | @@ -739,21 +739,21 @@ function makeManager(input?: { |
| 739 | 739 | runForThread: () => Effect.succeed({ status: "no-script" as const }), |
| 740 | 740 | }, |
| 741 | 741 | ), |
| 742 | vcsDriverLayer, | |
| 743 | serverSettingsLayer, | |
| 744 | ).pipe(Layer.provideMerge(sourceControlRegistryLayer), Layer.provideMerge(NodeServices.layer)); | |
| 742 | layerVcsDriver, | |
| 743 | layerServerSettings, | |
| 744 | ).pipe(Layer.provideMerge(layerSourceControlRegistry), Layer.provideMerge(NodeServices.layer)); | |
| 745 | 745 | // Built into the test's scope: the manager reads these stores after this returns. |
| 746 | const storesLayer = Layer.merge(ProjectionStore.layer, ProjectStore.layer).pipe( | |
| 747 | Layer.provideMerge(SqlitePersistenceMemory), | |
| 746 | const layerStores = Layer.merge(ProjectionStore.layer, ProjectStore.layer).pipe( | |
| 747 | Layer.provideMerge(SqlitePersistence.layerMemory), | |
| 748 | 748 | ); |
| 749 | 749 | |
| 750 | 750 | return Effect.gen(function* () { |
| 751 | const stores = yield* Layer.build(storesLayer); | |
| 751 | const stores = yield* Layer.build(layerStores); | |
| 752 | 752 | if (input?.seed !== undefined) { |
| 753 | 753 | yield* input.seed.pipe(Effect.provideContext(stores), Effect.orDie); |
| 754 | 754 | } |
| 755 | 755 | const manager = yield* GitManager.make.pipe( |
| 756 | Effect.provide(managerLayer), | |
| 756 | Effect.provide(layerManager), | |
| 757 | 757 | Effect.provideContext(stores), |
| 758 | 758 | ); |
| 759 | 759 | return { manager, ghCalls }; |
| ... | ... | @@ -762,13 +762,13 @@ function makeManager(input?: { |
| 762 | 762 | |
| 763 | 763 | const asThreadId = (threadId: string) => threadId as ThreadId; |
| 764 | 764 | |
| 765 | const GitManagerTestLayer = GitVcsDriver.layer.pipe( | |
| 765 | const layerGitManagerTest = GitVcsDriver.layer.pipe( | |
| 766 | 766 | Layer.provide(ServerConfig.layerTest(process.cwd(), { prefix: "t3-git-manager-test-" })), |
| 767 | 767 | Layer.provideMerge(VcsProcess.layer), |
| 768 | 768 | Layer.provideMerge(NodeServices.layer), |
| 769 | 769 | ); |
| 770 | 770 | |
| 771 | it.layer(GitManagerTestLayer)("GitManager", (it) => { | |
| 771 | it.layer(layerGitManagerTest)("GitManager", (it) => { | |
| 772 | 772 | it.effect("status includes draft PR metadata when branch already has a draft PR", () => |
| 773 | 773 | Effect.gen(function* () { |
| 774 | 774 | const repoDir = yield* makeTempDir("t3code-git-manager-"); |
apps/server/src/git/GitWorkflowService.test.ts+9-9| ... | ... | @@ -11,7 +11,7 @@ import * as GitWorkflowService from "./GitWorkflowService.ts"; |
| 11 | 11 | import * as GitVcsDriver from "../vcs/GitVcsDriver.ts"; |
| 12 | 12 | import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts"; |
| 13 | 13 | |
| 14 | function makeLayer(input: { | |
| 14 | function layer(input: { | |
| 15 | 15 | readonly detect: VcsDriverRegistry.VcsDriverRegistry["Service"]["detect"]; |
| 16 | 16 | }) { |
| 17 | 17 | return GitWorkflowService.layer.pipe( |
| ... | ... | @@ -34,7 +34,7 @@ describe("GitWorkflowService", () => { |
| 34 | 34 | assert.equal(isRepository, false); |
| 35 | 35 | }).pipe( |
| 36 | 36 | Effect.provide( |
| 37 | makeLayer({ | |
| 37 | layer({ | |
| 38 | 38 | detect: () => |
| 39 | 39 | Effect.succeed({ |
| 40 | 40 | kind: "jj", |
| ... | ... | @@ -74,7 +74,7 @@ describe("GitWorkflowService", () => { |
| 74 | 74 | }); |
| 75 | 75 | }).pipe( |
| 76 | 76 | Effect.provide( |
| 77 | makeLayer({ | |
| 77 | layer({ | |
| 78 | 78 | detect: () => Effect.succeed(null), |
| 79 | 79 | }), |
| 80 | 80 | ), |
| ... | ... | @@ -105,7 +105,7 @@ describe("GitWorkflowService", () => { |
| 105 | 105 | }); |
| 106 | 106 | }).pipe( |
| 107 | 107 | Effect.provide( |
| 108 | makeLayer({ | |
| 108 | layer({ | |
| 109 | 109 | detect: () => Effect.succeed(null), |
| 110 | 110 | }), |
| 111 | 111 | ), |
| ... | ... | @@ -117,7 +117,7 @@ describe("GitWorkflowService", () => { |
| 117 | 117 | const remoteStatus = vi.fn(); |
| 118 | 118 | const status = vi.fn(); |
| 119 | 119 | |
| 120 | const testLayer = GitWorkflowService.layer.pipe( | |
| 120 | const layerTest = GitWorkflowService.layer.pipe( | |
| 121 | 121 | Layer.provide( |
| 122 | 122 | Layer.mock(VcsDriverRegistry.VcsDriverRegistry)({ |
| 123 | 123 | detect: () => Effect.succeed(null), |
| ... | ... | @@ -142,7 +142,7 @@ describe("GitWorkflowService", () => { |
| 142 | 142 | assert.equal(localStatus.mock.calls.length, 0); |
| 143 | 143 | assert.equal(remoteStatus.mock.calls.length, 0); |
| 144 | 144 | assert.equal(status.mock.calls.length, 0); |
| 145 | }).pipe(Effect.provide(testLayer)); | |
| 145 | }).pipe(Effect.provide(layerTest)); | |
| 146 | 146 | }); |
| 147 | 147 | |
| 148 | 148 | it.effect("returns an empty ref list when no VCS repository is detected", () => |
| ... | ... | @@ -159,7 +159,7 @@ describe("GitWorkflowService", () => { |
| 159 | 159 | }); |
| 160 | 160 | }).pipe( |
| 161 | 161 | Effect.provide( |
| 162 | makeLayer({ | |
| 162 | layer({ | |
| 163 | 163 | detect: () => Effect.succeed(null), |
| 164 | 164 | }), |
| 165 | 165 | ), |
| ... | ... | @@ -186,7 +186,7 @@ describe("GitWorkflowService", () => { |
| 186 | 186 | expect(error.message).not.toContain(cause.detail); |
| 187 | 187 | }).pipe( |
| 188 | 188 | Effect.provide( |
| 189 | makeLayer({ | |
| 189 | layer({ | |
| 190 | 190 | detect: () => Effect.fail(cause), |
| 191 | 191 | }), |
| 192 | 192 | ), |
| ... | ... | @@ -214,7 +214,7 @@ describe("GitWorkflowService", () => { |
| 214 | 214 | expect(error.message).not.toContain(cause.detail); |
| 215 | 215 | }).pipe( |
| 216 | 216 | Effect.provide( |
| 217 | makeLayer({ | |
| 217 | layer({ | |
| 218 | 218 | detect: () => Effect.fail(cause), |
| 219 | 219 | }), |
| 220 | 220 | ), |
apps/server/src/git/linkCreatedPullRequest.test.ts+14-14| ... | ... | @@ -71,7 +71,7 @@ function prResult(pr: GitRunStackedActionResult["pr"]): Pick<GitRunStackedAction |
| 71 | 71 | return { pr }; |
| 72 | 72 | } |
| 73 | 73 | |
| 74 | const makeDependencies = ( | |
| 74 | const layerDependenciesFor = ( | |
| 75 | 75 | dispatch: Orchestrator.OrchestratorV2Shape["dispatch"], |
| 76 | 76 | threadShell: PullRequestTestThread | null = thread, |
| 77 | 77 | ) => |
| ... | ... | @@ -159,7 +159,7 @@ describe("linkCreatedPullRequest", () => { |
| 159 | 159 | url: "https://github.com/t3tools/t3code/pull/42", |
| 160 | 160 | }), |
| 161 | 161 | commandId, |
| 162 | }).pipe(Effect.provide(makeDependencies(dispatch))); | |
| 162 | }).pipe(Effect.provide(layerDependenciesFor(dispatch))); | |
| 163 | 163 | |
| 164 | 164 | expect(yield* Ref.get(commands)).toEqual([ |
| 165 | 165 | { |
| ... | ... | @@ -179,17 +179,17 @@ describe("linkCreatedPullRequest", () => { |
| 179 | 179 | it.effect("dispatches nothing when the action produced no pull request", () => |
| 180 | 180 | Effect.gen(function* () { |
| 181 | 181 | const { commands, dispatch } = yield* recordingDispatch(); |
| 182 | const dependencies = makeDependencies(dispatch); | |
| 182 | const layerDependencies = layerDependenciesFor(dispatch); | |
| 183 | 183 | yield* linkCreatedPullRequest({ |
| 184 | 184 | threadId: THREAD_ID, |
| 185 | 185 | result: prResult({ status: "skipped_not_requested" }), |
| 186 | 186 | commandId, |
| 187 | }).pipe(Effect.provide(dependencies)); | |
| 187 | }).pipe(Effect.provide(layerDependencies)); | |
| 188 | 188 | yield* linkCreatedPullRequest({ |
| 189 | 189 | threadId: THREAD_ID, |
| 190 | 190 | result: prResult({ status: "created", url: "https://github.com/t3tools/t3code/pull/42" }), |
| 191 | 191 | commandId, |
| 192 | }).pipe(Effect.provide(dependencies)); | |
| 192 | }).pipe(Effect.provide(layerDependencies)); | |
| 193 | 193 | |
| 194 | 194 | expect(yield* Ref.get(commands)).toEqual([]); |
| 195 | 195 | }), |
| ... | ... | @@ -211,14 +211,14 @@ describe("linkCreatedPullRequest", () => { |
| 211 | 211 | url: "https://github.com/t3tools/t3code/pull/7", |
| 212 | 212 | }); |
| 213 | 213 | yield* linkCreatedPullRequest({ threadId: THREAD_ID, result, commandId }).pipe( |
| 214 | Effect.provide(makeDependencies(rejecting)), | |
| 214 | Effect.provide(layerDependenciesFor(rejecting)), | |
| 215 | 215 | ); |
| 216 | 216 | yield* linkCreatedPullRequest({ threadId: THREAD_ID, result, commandId }).pipe( |
| 217 | Effect.provide(makeDependencies(() => Effect.die(new Error("engine down")))), | |
| 217 | Effect.provide(layerDependenciesFor(() => Effect.die(new Error("engine down")))), | |
| 218 | 218 | ); |
| 219 | 219 | // A thread that vanished between the action and the link is not an error either. |
| 220 | 220 | yield* linkCreatedPullRequest({ threadId: THREAD_ID, result, commandId }).pipe( |
| 221 | Effect.provide(makeDependencies(() => Effect.die(new Error("unreachable")), null)), | |
| 221 | Effect.provide(layerDependenciesFor(() => Effect.die(new Error("unreachable")), null)), | |
| 222 | 222 | ); |
| 223 | 223 | }), |
| 224 | 224 | ); |
| ... | ... | @@ -229,7 +229,7 @@ it.effect( |
| 229 | 229 | () => |
| 230 | 230 | Effect.gen(function* () { |
| 231 | 231 | const refreshed: string[] = []; |
| 232 | const dependencies = Layer.mergeAll( | |
| 232 | const layerDependencies = Layer.mergeAll( | |
| 233 | 233 | Layer.mock(Orchestrator.OrchestratorV2)({ |
| 234 | 234 | getThreadShell: () => Effect.succeed(v2PullRequestThread(thread)), |
| 235 | 235 | }), |
| ... | ... | @@ -246,22 +246,22 @@ it.effect( |
| 246 | 246 | yield* refreshPushedPullRequests( |
| 247 | 247 | { cwd: "/worktree", threadId: THREAD_ID }, |
| 248 | 248 | { push: { status: "pushed" } }, |
| 249 | ).pipe(Effect.provide(dependencies)); | |
| 249 | ).pipe(Effect.provide(layerDependencies)); | |
| 250 | 250 | yield* refreshPushedPullRequests( |
| 251 | 251 | { cwd: project.workspaceRoot }, |
| 252 | 252 | { push: { status: "pushed" } }, |
| 253 | ).pipe(Effect.provide(dependencies)); | |
| 253 | ).pipe(Effect.provide(layerDependencies)); | |
| 254 | 254 | yield* refreshPushedPullRequests({ cwd: "/unrelated" }, { push: { status: "pushed" } }).pipe( |
| 255 | Effect.provide(dependencies), | |
| 255 | Effect.provide(layerDependencies), | |
| 256 | 256 | ); |
| 257 | 257 | yield* refreshPushedPullRequests( |
| 258 | 258 | { cwd: project.workspaceRoot, threadId: THREAD_ID }, |
| 259 | 259 | { push: { status: "skipped_not_requested" } }, |
| 260 | ).pipe(Effect.provide(dependencies)); | |
| 260 | ).pipe(Effect.provide(layerDependencies)); | |
| 261 | 261 | yield* refreshPushedPullRequests( |
| 262 | 262 | { cwd: "/draft-worktree", projectId: PROJECT_ID }, |
| 263 | 263 | { push: { status: "pushed" } }, |
| 264 | ).pipe(Effect.provide(dependencies)); | |
| 264 | ).pipe(Effect.provide(layerDependencies)); | |
| 265 | 265 | expect(refreshed).toEqual([PROJECT_ID, PROJECT_ID, PROJECT_ID]); |
| 266 | 266 | }), |
| 267 | 267 | ); |
apps/server/src/htmlRender/HtmlRender.test.ts+11-11| ... | ... | @@ -30,7 +30,7 @@ import * as PreviewBrowser from "./PreviewBrowser.ts"; |
| 30 | 30 | // example one T3 installed under <T3 home>/tools/chrome-headless-shell. |
| 31 | 31 | const TEST_BROWSER_ENV = "T3CODE_TEST_HEADLESS_SHELL"; |
| 32 | 32 | |
| 33 | const htmlRenderLayer = ( | |
| 33 | const layerHtmlRender = ( | |
| 34 | 34 | executable?: string, |
| 35 | 35 | installed: Effect.Effect<Option.Option<string>> = Effect.succeed( |
| 36 | 36 | Option.fromUndefinedOr(executable), |
| ... | ... | @@ -52,7 +52,7 @@ const htmlRenderLayer = ( |
| 52 | 52 | Layer.provideMerge(ServerConfig.layerTest(process.cwd(), { prefix: "t3-html-render-" })), |
| 53 | 53 | Layer.provideMerge(NodeServices.layer), |
| 54 | 54 | ); |
| 55 | const testLayer = htmlRenderLayer(); | |
| 55 | const layerTest = layerHtmlRender(); | |
| 56 | 56 | |
| 57 | 57 | const PNG_BYTES = new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]); |
| 58 | 58 | |
| ... | ... | @@ -94,7 +94,7 @@ describe("HtmlRender", () => { |
| 94 | 94 | // The theme bootstrap opens the head, ahead of the page's own markup. |
| 95 | 95 | expect(prepared.indexOf("<head>")).toBeLessThan(prepared.indexOf('<style id="t3-theme">')); |
| 96 | 96 | expect(prepared.indexOf('<style id="t3-theme">')).toBeLessThan(prepared.indexOf("<title>")); |
| 97 | }).pipe(Effect.provide(testLayer)), | |
| 97 | }).pipe(Effect.provide(layerTest)), | |
| 98 | 98 | ); |
| 99 | 99 | |
| 100 | 100 | it.effect("inlines an SVG behind processing instructions and a doctype subset", () => |
| ... | ... | @@ -115,7 +115,7 @@ describe("HtmlRender", () => { |
| 115 | 115 | const prepared = yield* htmlRender.prepare(`<img src="${svg}">`); |
| 116 | 116 | |
| 117 | 117 | expect(prepared).toContain(`data:image/svg+xml;base64,${Base64.encode(source)}`); |
| 118 | }).pipe(Effect.provide(testLayer)), | |
| 118 | }).pipe(Effect.provide(layerTest)), | |
| 119 | 119 | ); |
| 120 | 120 | |
| 121 | 121 | it.effect("lists every local image it cannot read", () => |
| ... | ... | @@ -167,7 +167,7 @@ describe("HtmlRender", () => { |
| 167 | 167 | upper, |
| 168 | 168 | longer, |
| 169 | 169 | ]); |
| 170 | }).pipe(Effect.provide(testLayer)), | |
| 170 | }).pipe(Effect.provide(layerTest)), | |
| 171 | 171 | ); |
| 172 | 172 | |
| 173 | 173 | it.effect("publishes the prepared page as an html thread attachment", () => |
| ... | ... | @@ -197,7 +197,7 @@ describe("HtmlRender", () => { |
| 197 | 197 | const html = yield* fileSystem.readFileString(stored ?? ""); |
| 198 | 198 | expect(html).toContain('<style id="t3-theme">'); |
| 199 | 199 | expect(html).toContain("<p>Quarterly revenue</p>"); |
| 200 | }).pipe(Effect.provide(testLayer)), | |
| 200 | }).pipe(Effect.provide(layerTest)), | |
| 201 | 201 | ); |
| 202 | 202 | |
| 203 | 203 | it.effect("removes the page when publishing is interrupted", () => |
| ... | ... | @@ -225,7 +225,7 @@ describe("HtmlRender", () => { |
| 225 | 225 | expect(yield* storedPages).toEqual([]); |
| 226 | 226 | }).pipe( |
| 227 | 227 | Effect.provide( |
| 228 | htmlRenderLayer( | |
| 228 | layerHtmlRender( | |
| 229 | 229 | undefined, |
| 230 | 230 | Deferred.succeed(measuring, undefined).pipe(Effect.andThen(Effect.never)), |
| 231 | 231 | ), |
| ... | ... | @@ -283,7 +283,7 @@ describe("HtmlRender", () => { |
| 283 | 283 | }, |
| 284 | 284 | ]), |
| 285 | 285 | ); |
| 286 | }).pipe(Effect.provide(htmlRenderLayer(executable))); | |
| 286 | }).pipe(Effect.provide(layerHtmlRender(executable))); | |
| 287 | 287 | }), |
| 288 | 288 | 30_000, |
| 289 | 289 | ); |
| ... | ... | @@ -314,7 +314,7 @@ describe("HtmlRender", () => { |
| 314 | 314 | const texts = preview.consoleMessages.map((message) => message.text); |
| 315 | 315 | expect(texts).toContain("secret: none"); |
| 316 | 316 | expect(texts.join(" ")).not.toContain("abc123"); |
| 317 | }).pipe(Effect.scoped, Effect.provide(htmlRenderLayer(executable))); | |
| 317 | }).pipe(Effect.scoped, Effect.provide(layerHtmlRender(executable))); | |
| 318 | 318 | }), |
| 319 | 319 | 30_000, |
| 320 | 320 | ); |
| ... | ... | @@ -370,7 +370,7 @@ describe("HtmlRender", () => { |
| 370 | 370 | `window.open("${origin}/popup"); location.href = "${origin}/navigate";</script>`, |
| 371 | 371 | ].join(""), |
| 372 | 372 | }); |
| 373 | }).pipe(Effect.provide(htmlRenderLayer(executable))); | |
| 373 | }).pipe(Effect.provide(layerHtmlRender(executable))); | |
| 374 | 374 | expect(requests).toEqual([]); |
| 375 | 375 | expect(datagrams).toEqual([]); |
| 376 | 376 | }).pipe(Effect.scoped), |
| ... | ... | @@ -396,7 +396,7 @@ describe("HtmlRender", () => { |
| 396 | 396 | expect(reference.heights).toEqual( |
| 397 | 397 | HTML_RENDER_MEASURE_WIDTHS.map((width) => [width, Math.ceil(width / 2)]), |
| 398 | 398 | ); |
| 399 | }).pipe(Effect.provide(htmlRenderLayer(executable))); | |
| 399 | }).pipe(Effect.provide(layerHtmlRender(executable))); | |
| 400 | 400 | }), |
| 401 | 401 | 30_000, |
| 402 | 402 | ); |
apps/server/src/http.test.ts+20-21| ... | ... | @@ -22,21 +22,19 @@ import * as ServerConfig from "./config.ts"; |
| 22 | 22 | |
| 23 | 23 | import { |
| 24 | 24 | assetResponseHeaders, |
| 25 | browserApiCorsLayer, | |
| 26 | 25 | assetFileResponse, |
| 27 | 26 | downloadContentDisposition, |
| 28 | httpCompressionLayer, | |
| 29 | 27 | isLoopbackHostname, |
| 30 | 28 | resolveDevRedirectUrl, |
| 31 | staticAndDevRouteLayer, | |
| 32 | 29 | withUntracedRequests, |
| 33 | 30 | } from "./http.ts"; |
| 31 | import * as ServerHttp from "./http.ts"; | |
| 34 | 32 | |
| 35 | 33 | describe("untraced requests", () => { |
| 36 | 34 | it.effect("drops the HTTP server span for browser trace exports, query string included", () => { |
| 37 | 35 | const spanNames: Array<string> = []; |
| 38 | 36 | return Effect.gen(function* () { |
| 39 | const routes = Layer.effectDiscard( | |
| 37 | const layerRoutes = Layer.effectDiscard( | |
| 40 | 38 | Effect.gen(function* () { |
| 41 | 39 | const router = yield* HttpRouter.HttpRouter; |
| 42 | 40 | yield* router.add("POST", "/api/observability/v1/traces", HttpServerResponse.empty()); |
| ... | ... | @@ -44,7 +42,7 @@ describe("untraced requests", () => { |
| 44 | 42 | }), |
| 45 | 43 | ); |
| 46 | 44 | const services = yield* Layer.build( |
| 47 | withUntracedRequests(HttpRouter.serve(routes, { disableListenLog: true })).pipe( | |
| 45 | withUntracedRequests(HttpRouter.serve(layerRoutes, { disableListenLog: true })).pipe( | |
| 48 | 46 | Layer.provideMerge(NodeHttpServer.layerTest), |
| 49 | 47 | ), |
| 50 | 48 | ); |
| ... | ... | @@ -73,17 +71,17 @@ describe("untraced requests", () => { |
| 73 | 71 | |
| 74 | 72 | describe("browser API CORS", () => { |
| 75 | 73 | it("accepts protocol negotiation with authenticated browser headers", async () => { |
| 76 | const routeLayer = Layer.effectDiscard( | |
| 74 | const layerRoute = Layer.effectDiscard( | |
| 77 | 75 | Effect.gen(function* () { |
| 78 | 76 | const router = yield* HttpRouter.HttpRouter; |
| 79 | 77 | yield* router.add("GET", "/api/environment", HttpServerResponse.empty()); |
| 80 | 78 | }), |
| 81 | 79 | ); |
| 82 | const appLayer = Layer.merge(routeLayer, browserApiCorsLayer).pipe( | |
| 80 | const layerApp = Layer.merge(layerRoute, ServerHttp.layerBrowserApiCors).pipe( | |
| 83 | 81 | Layer.provide(ServerConfig.layerTest(process.cwd(), { prefix: "http-cors-test-" })), |
| 84 | 82 | Layer.provide(NodeServices.layer), |
| 85 | 83 | ); |
| 86 | const { handler, dispose } = HttpRouter.toWebHandler(appLayer, { disableLogger: true }); | |
| 84 | const { handler, dispose } = HttpRouter.toWebHandler(layerApp, { disableLogger: true }); | |
| 87 | 85 | |
| 88 | 86 | try { |
| 89 | 87 | const response = await handler( |
| ... | ... | @@ -117,19 +115,20 @@ describe("browser API CORS", () => { |
| 117 | 115 | }); |
| 118 | 116 | }); |
| 119 | 117 | |
| 120 | const fileResponseLayer = Layer.mergeAll(NodeHttpPlatform.layer, NodeServices.layer); | |
| 118 | const layerFileResponse = Layer.mergeAll(NodeHttpPlatform.layer, NodeServices.layer); | |
| 121 | 119 | |
| 122 | 120 | const makeStaticRequest = Effect.fn("HttpTest.makeStaticRequest")(function* (staticDir: string) { |
| 123 | 121 | const config = yield* ServerConfig.ServerConfig; |
| 124 | 122 | const fileSystem = yield* FileSystem.FileSystem; |
| 125 | const appLayer = Layer.merge(staticAndDevRouteLayer, httpCompressionLayer).pipe( | |
| 126 | Layer.provideMerge(NodeHttpPlatform.layer), | |
| 127 | ); | |
| 123 | const layerApp = Layer.merge( | |
| 124 | ServerHttp.layerStaticAndDevRoute, | |
| 125 | ServerHttp.layerHttpCompression, | |
| 126 | ).pipe(Layer.provideMerge(NodeHttpPlatform.layer)); | |
| 128 | 127 | // HttpRouter.serve reuses services from the layers around it before the |
| 129 | 128 | // app's own, and NodeHttpServer.layerTest brings a real FileSystem, so the |
| 130 | 129 | // caller's FileSystem and static directory go between the two. |
| 131 | 130 | const services = yield* Layer.build( |
| 132 | HttpRouter.serve(appLayer, { disableListenLog: true }).pipe( | |
| 131 | HttpRouter.serve(layerApp, { disableListenLog: true }).pipe( | |
| 133 | 132 | Layer.provideMerge(ServerConfig.layer({ ...config, staticDir })), |
| 134 | 133 | Layer.provideMerge(Layer.succeed(FileSystem.FileSystem, fileSystem)), |
| 135 | 134 | Layer.provideMerge(NodeHttpServer.layerTest), |
| ... | ... | @@ -351,7 +350,7 @@ describe("video asset byte ranges", () => { |
| 351 | 350 | } |
| 352 | 351 | expect(yield* Effect.promise(() => response.text())).toBe(expected); |
| 353 | 352 | } |
| 354 | }).pipe(Effect.provide(fileResponseLayer)), | |
| 353 | }).pipe(Effect.provide(layerFileResponse)), | |
| 355 | 354 | ); |
| 356 | 355 | |
| 357 | 356 | it.effect( |
| ... | ... | @@ -396,7 +395,7 @@ describe("video asset byte ranges", () => { |
| 396 | 395 | ); |
| 397 | 396 | } |
| 398 | 397 | } |
| 399 | }).pipe(Effect.provide(fileResponseLayer)), | |
| 398 | }).pipe(Effect.provide(layerFileResponse)), | |
| 400 | 399 | ); |
| 401 | 400 | |
| 402 | 401 | it.effect("streams guarded file ranges, including suffixes and conditional requests", () => |
| ... | ... | @@ -436,7 +435,7 @@ describe("video asset byte ranges", () => { |
| 436 | 435 | expect(response.headers.get("content-length")).toBe(String(expected.length)); |
| 437 | 436 | expect(yield* Effect.promise(() => response.text())).toBe(expected); |
| 438 | 437 | } |
| 439 | }).pipe(Effect.provide(fileResponseLayer)), | |
| 438 | }).pipe(Effect.provide(layerFileResponse)), | |
| 440 | 439 | ); |
| 441 | 440 | |
| 442 | 441 | it.effect("keeps attachment media out of the cache once its signed URL expires", () => |
| ... | ... | @@ -454,7 +453,7 @@ describe("video asset byte ranges", () => { |
| 454 | 453 | ); |
| 455 | 454 | expect(response.headers.get("cache-control")).toBe("private, no-store"); |
| 456 | 455 | expect(response.headers.get("accept-ranges")).toBe("bytes"); |
| 457 | }).pipe(Effect.provide(fileResponseLayer)), | |
| 456 | }).pipe(Effect.provide(layerFileResponse)), | |
| 458 | 457 | ); |
| 459 | 458 | |
| 460 | 459 | it.effect("closes guarded descriptors after full, HEAD, rejected, and cancelled responses", () => |
| ... | ... | @@ -501,7 +500,7 @@ describe("video asset byte ranges", () => { |
| 501 | 500 | ); |
| 502 | 501 | expect(file.handle.fd).toBe(-1); |
| 503 | 502 | } |
| 504 | }).pipe(Effect.provide(fileResponseLayer)), | |
| 503 | }).pipe(Effect.provide(layerFileResponse)), | |
| 505 | 504 | ); |
| 506 | 505 | |
| 507 | 506 | it.effect("streams exactly the requested bytes and leaves full downloads intact", () => |
| ... | ... | @@ -554,7 +553,7 @@ describe("video asset byte ranges", () => { |
| 554 | 553 | expect(image.status).toBe(200); |
| 555 | 554 | expect(image.headers.has("accept-ranges")).toBe(false); |
| 556 | 555 | expect(yield* Effect.promise(() => image.text())).toBe("0123456789"); |
| 557 | }).pipe(Effect.provide(fileResponseLayer)), | |
| 556 | }).pipe(Effect.provide(layerFileResponse)), | |
| 558 | 557 | ); |
| 559 | 558 | |
| 560 | 559 | it.effect( |
| ... | ... | @@ -584,7 +583,7 @@ describe("video asset byte ranges", () => { |
| 584 | 583 | expect(download.status).toBe(200); |
| 585 | 584 | expect(download.headers.get("content-disposition")).toContain("attachment;"); |
| 586 | 585 | expect(yield* Effect.promise(() => download.text())).toBe("0123456789"); |
| 587 | }).pipe(Effect.provide(fileResponseLayer)), | |
| 586 | }).pipe(Effect.provide(layerFileResponse)), | |
| 588 | 587 | ); |
| 589 | 588 | |
| 590 | 589 | it.effect("rejects ranges outside the file, including empty files", () => |
| ... | ... | @@ -608,7 +607,7 @@ describe("video asset byte ranges", () => { |
| 608 | 607 | ); |
| 609 | 608 | expect(empty.status).toBe(416); |
| 610 | 609 | expect(empty.headers.get("content-range")).toBe("bytes */0"); |
| 611 | }).pipe(Effect.provide(fileResponseLayer)), | |
| 610 | }).pipe(Effect.provide(layerFileResponse)), | |
| 612 | 611 | ); |
| 613 | 612 | }); |
| 614 | 613 |
apps/server/src/http.ts+9-9| ... | ... | @@ -230,11 +230,11 @@ export const assetFileResponse = Effect.fn("assetFileResponse")(function* ( |
| 230 | 230 | return yield* HttpServerResponse.file(asset.path, { status, offset, bytesToRead, headers }); |
| 231 | 231 | }); |
| 232 | 232 | |
| 233 | export const httpCompressionLayer = HttpRouter.middleware(HttpMiddleware.compression(), { | |
| 233 | export const layerHttpCompression = HttpRouter.middleware(HttpMiddleware.compression(), { | |
| 234 | 234 | global: true, |
| 235 | 235 | }); |
| 236 | 236 | |
| 237 | export const browserApiCorsLayer = Layer.unwrap( | |
| 237 | export const layerBrowserApiCors = Layer.unwrap( | |
| 238 | 238 | Effect.gen(function* () { |
| 239 | 239 | const config = yield* ServerConfig.ServerConfig; |
| 240 | 240 | const devOrigin = config.devUrl?.origin; |
| ... | ... | @@ -297,7 +297,7 @@ const authenticateRawRouteWithScope = ( |
| 297 | 297 | } |
| 298 | 298 | }); |
| 299 | 299 | |
| 300 | export const serverEnvironmentHttpApiLayer = HttpApiBuilder.group( | |
| 300 | export const layerServerEnvironmentHttpApi = HttpApiBuilder.group( | |
| 301 | 301 | EnvironmentHttpApi, |
| 302 | 302 | "metadata", |
| 303 | 303 | Effect.fnUntraced(function* (handlers) { |
| ... | ... | @@ -320,7 +320,7 @@ class DecodeOtlpTraceRecordsError extends Data.TaggedError("DecodeOtlpTraceRecor |
| 320 | 320 | // tracing this proxy would add more server spans than it forwards. |
| 321 | 321 | // withTracerEnabled(false) drops the handler's spans, including the forward. |
| 322 | 322 | // untracedRequestsLayer drops the HTTP server span. |
| 323 | export const otlpTracesProxyRouteLayer = HttpRouter.add( | |
| 323 | export const layerOtlpTracesProxyRoute = HttpRouter.add( | |
| 324 | 324 | "POST", |
| 325 | 325 | OTLP_TRACES_PROXY_PATH, |
| 326 | 326 | Effect.gen(function* () { |
| ... | ... | @@ -382,16 +382,16 @@ const UNTRACED_REQUEST_PATHS: ReadonlySet<string> = new Set([OTLP_TRACES_PROXY_P |
| 382 | 382 | // privately, so it is provided to the served layer, never merged into the |
| 383 | 383 | // routes. Add paths here instead of providing it again. The query string is |
| 384 | 384 | // ignored, as in routing. |
| 385 | const untracedRequestsLayer = Layer.succeed(HttpMiddleware.TracerDisabledWhen)((request) => { | |
| 385 | const layerUntracedRequests = Layer.succeed(HttpMiddleware.TracerDisabledWhen)((request) => { | |
| 386 | 386 | const queryIndex = request.url.indexOf("?"); |
| 387 | 387 | const path = queryIndex === -1 ? request.url : request.url.slice(0, queryIndex); |
| 388 | 388 | // Webhook URLs carry their secret token in the path, so they never reach a trace. |
| 389 | 389 | return UNTRACED_REQUEST_PATHS.has(path) || path.startsWith(`${WEBHOOK_ROUTE_PREFIX}/`); |
| 390 | 390 | }); |
| 391 | 391 | |
| 392 | export const withUntracedRequests = Layer.provide(untracedRequestsLayer); | |
| 392 | export const withUntracedRequests = Layer.provide(layerUntracedRequests); | |
| 393 | 393 | |
| 394 | export const assetRouteLayer = HttpRouter.add( | |
| 394 | export const layerAssetRoute = HttpRouter.add( | |
| 395 | 395 | "GET", |
| 396 | 396 | `${ASSET_ROUTE_PREFIX}/*`, |
| 397 | 397 | Effect.gen(function* () { |
| ... | ... | @@ -444,7 +444,7 @@ export const assetRouteLayer = HttpRouter.add( |
| 444 | 444 | }), |
| 445 | 445 | ); |
| 446 | 446 | |
| 447 | export const attachmentUploadRouteLayer = HttpRouter.add( | |
| 447 | export const layerAttachmentUploadRoute = HttpRouter.add( | |
| 448 | 448 | "POST", |
| 449 | 449 | `${ATTACHMENT_UPLOAD_ROUTE_PREFIX}/*`, |
| 450 | 450 | Effect.gen(function* () { |
| ... | ... | @@ -672,7 +672,7 @@ const handleStaticAndDevRequest = Effect.fn("handleStaticAndDevRequest")( |
| 672 | 672 | ); |
| 673 | 673 | |
| 674 | 674 | // Read the installed build's manifest once. Unknown files use revalidation. |
| 675 | export const staticAndDevRouteLayer = Layer.unwrap( | |
| 675 | export const layerStaticAndDevRoute = Layer.unwrap( | |
| 676 | 676 | loadImmutableBuildAssets.pipe( |
| 677 | 677 | Effect.map((assets) => HttpRouter.add("GET", "*", handleStaticAndDevRequest(assets))), |
| 678 | 678 | ), |
apps/server/src/keybindings.test.ts+20-23| ... | ... | @@ -23,7 +23,7 @@ const encodeResolvedKeybindingFromConfig = Schema.encodeEffect( |
| 23 | 23 | const decodeResolvedKeybindingFromConfigExit = Schema.decodeUnknownExit( |
| 24 | 24 | Keybindings.ResolvedKeybindingFromConfig, |
| 25 | 25 | ); |
| 26 | const makeKeybindingsLayer = () => { | |
| 26 | const layerKeybindings = () => { | |
| 27 | 27 | return Keybindings.layer.pipe( |
| 28 | 28 | Layer.provideMerge( |
| 29 | 29 | Layer.fresh( |
| ... | ... | @@ -185,7 +185,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => { |
| 185 | 185 | |
| 186 | 186 | const persisted = yield* readKeybindingsConfig(keybindingsConfigPath); |
| 187 | 187 | assert.deepEqual(persisted, Keybindings.DEFAULT_KEYBINDINGS); |
| 188 | }).pipe(Effect.provide(makeKeybindingsLayer())), | |
| 188 | }).pipe(Effect.provide(layerKeybindings())), | |
| 189 | 189 | ); |
| 190 | 190 | |
| 191 | 191 | it.effect("uses defaults in runtime when config is malformed without overriding file", () => |
| ... | ... | @@ -210,7 +210,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => { |
| 210 | 210 | }, |
| 211 | 211 | ]); |
| 212 | 212 | assert.equal(yield* fs.readFileString(keybindingsConfigPath), "{ not-json"); |
| 213 | }).pipe(Effect.provide(makeKeybindingsLayer())), | |
| 213 | }).pipe(Effect.provide(layerKeybindings())), | |
| 214 | 214 | ); |
| 215 | 215 | |
| 216 | 216 | it.effect("ignores invalid entries in runtime and reports them as issues", () => |
| ... | ... | @@ -248,7 +248,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => { |
| 248 | 248 | message: configState.issues[1]?.message ?? "", |
| 249 | 249 | }, |
| 250 | 250 | ]); |
| 251 | }).pipe(Effect.provide(makeKeybindingsLayer())), | |
| 251 | }).pipe(Effect.provide(layerKeybindings())), | |
| 252 | 252 | ); |
| 253 | 253 | |
| 254 | 254 | it.effect( |
| ... | ... | @@ -280,7 +280,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => { |
| 280 | 280 | assert.isTrue(byCommand.has(defaultRule.command), `expected ${defaultRule.command}`); |
| 281 | 281 | } |
| 282 | 282 | assert.isTrue(byCommand.has("script.run-tests.run")); |
| 283 | }).pipe(Effect.provide(makeKeybindingsLayer())), | |
| 283 | }).pipe(Effect.provide(layerKeybindings())), | |
| 284 | 284 | ); |
| 285 | 285 | |
| 286 | 286 | it.effect("adds a late default to an existing command once", () => |
| ... | ... | @@ -304,7 +304,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => { |
| 304 | 304 | yield* writeKeybindingsConfig(keybindingsConfigPath, [existing]); |
| 305 | 305 | yield* keybindings.syncDefaultKeybindingsOnStartup; |
| 306 | 306 | assert.deepStrictEqual(yield* backgroundRules, [existing]); |
| 307 | }).pipe(Effect.provide(makeKeybindingsLayer())), | |
| 307 | }).pipe(Effect.provide(layerKeybindings())), | |
| 308 | 308 | ); |
| 309 | 309 | |
| 310 | 310 | it.effect("leaves a customized command without the late default", () => |
| ... | ... | @@ -324,7 +324,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => { |
| 324 | 324 | persisted.filter((entry) => entry.command === "composer.sendBackground"), |
| 325 | 325 | [custom], |
| 326 | 326 | ); |
| 327 | }).pipe(Effect.provide(makeKeybindingsLayer())), | |
| 327 | }).pipe(Effect.provide(layerKeybindings())), | |
| 328 | 328 | ); |
| 329 | 329 | |
| 330 | 330 | it.effect("keeps a late default pending while the config is full", () => |
| ... | ... | @@ -350,7 +350,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => { |
| 350 | 350 | yield* writeKeybindingsConfig(keybindingsConfigPath, [existing]); |
| 351 | 351 | yield* keybindings.syncDefaultKeybindingsOnStartup; |
| 352 | 352 | assert.isTrue(yield* hasModEnter); |
| 353 | }).pipe(Effect.provide(makeKeybindingsLayer())), | |
| 353 | }).pipe(Effect.provide(layerKeybindings())), | |
| 354 | 354 | ); |
| 355 | 355 | |
| 356 | 356 | it.effect("skips conflicting default keybindings on startup and logs a detailed warning", () => { |
| ... | ... | @@ -381,10 +381,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => { |
| 381 | 381 | ); |
| 382 | 382 | }).pipe( |
| 383 | 383 | Effect.provide( |
| 384 | Layer.mergeAll( | |
| 385 | makeKeybindingsLayer(), | |
| 386 | Logger.layer([logger], { mergeWithExisting: false }), | |
| 387 | ), | |
| 384 | Layer.mergeAll(layerKeybindings(), Logger.layer([logger], { mergeWithExisting: false })), | |
| 388 | 385 | ), |
| 389 | 386 | ); |
| 390 | 387 | }); |
| ... | ... | @@ -412,7 +409,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => { |
| 412 | 409 | { key: "mod+shift+r", command: "script.run-tests.run" }, |
| 413 | 410 | ]); |
| 414 | 411 | assert.isTrue(resolved.some((entry) => entry.command === "script.run-tests.run")); |
| 415 | }).pipe(Effect.provide(makeKeybindingsLayer())), | |
| 412 | }).pipe(Effect.provide(layerKeybindings())), | |
| 416 | 413 | ); |
| 417 | 414 | |
| 418 | 415 | it.effect("appends additional custom keybindings for the same command", () => |
| ... | ... | @@ -435,7 +432,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => { |
| 435 | 432 | { key: "mod+r", command: "script.run-tests.run" }, |
| 436 | 433 | { key: "mod+shift+r", command: "script.run-tests.run" }, |
| 437 | 434 | ]); |
| 438 | }).pipe(Effect.provide(makeKeybindingsLayer())), | |
| 435 | }).pipe(Effect.provide(layerKeybindings())), | |
| 439 | 436 | ); |
| 440 | 437 | |
| 441 | 438 | it.effect("replaces only the targeted custom keybinding", () => |
| ... | ... | @@ -460,7 +457,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => { |
| 460 | 457 | { key: "mod+shift+r", command: "script.run-tests.run" }, |
| 461 | 458 | { key: "mod+alt+r", command: "script.run-tests.run" }, |
| 462 | 459 | ]); |
| 463 | }).pipe(Effect.provide(makeKeybindingsLayer())), | |
| 460 | }).pipe(Effect.provide(layerKeybindings())), | |
| 464 | 461 | ); |
| 465 | 462 | |
| 466 | 463 | it.effect("replacing with a rule that already exists elsewhere does not duplicate it", () => |
| ... | ... | @@ -482,7 +479,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => { |
| 482 | 479 | const persisted = yield* readKeybindingsConfig(keybindingsConfigPath); |
| 483 | 480 | const persistedView = persisted.map(({ key, command }) => ({ key, command })); |
| 484 | 481 | assert.deepEqual(persistedView, [{ key: "mod+alt+r", command: "script.run-tests.run" }]); |
| 485 | }).pipe(Effect.provide(makeKeybindingsLayer())), | |
| 482 | }).pipe(Effect.provide(layerKeybindings())), | |
| 486 | 483 | ); |
| 487 | 484 | |
| 488 | 485 | it.effect("removes only the targeted custom keybinding", () => |
| ... | ... | @@ -503,7 +500,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => { |
| 503 | 500 | const persisted = yield* readKeybindingsConfig(keybindingsConfigPath); |
| 504 | 501 | const persistedView = persisted.map(({ key, command }) => ({ key, command })); |
| 505 | 502 | assert.deepEqual(persistedView, [{ key: "mod+shift+r", command: "script.run-tests.run" }]); |
| 506 | }).pipe(Effect.provide(makeKeybindingsLayer())), | |
| 503 | }).pipe(Effect.provide(layerKeybindings())), | |
| 507 | 504 | ); |
| 508 | 505 | |
| 509 | 506 | it.effect("refuses to overwrite malformed keybindings config", () => |
| ... | ... | @@ -523,7 +520,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => { |
| 523 | 520 | |
| 524 | 521 | const persistedRaw = yield* fs.readFileString(keybindingsConfigPath); |
| 525 | 522 | assert.equal(persistedRaw, "{ not-json"); |
| 526 | }).pipe(Effect.provide(makeKeybindingsLayer())), | |
| 523 | }).pipe(Effect.provide(layerKeybindings())), | |
| 527 | 524 | ); |
| 528 | 525 | |
| 529 | 526 | it.effect("reports non-array config parse errors without duplicate prefix", () => |
| ... | ... | @@ -552,7 +549,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => { |
| 552 | 549 | }); |
| 553 | 550 | }).pipe(toDetailResult); |
| 554 | 551 | assertFailure(secondResult, "expected JSON array"); |
| 555 | }).pipe(Effect.provide(makeKeybindingsLayer())), | |
| 552 | }).pipe(Effect.provide(layerKeybindings())), | |
| 556 | 553 | ); |
| 557 | 554 | |
| 558 | 555 | // chmod cannot make a directory unwritable on Windows, so the write succeeds. |
| ... | ... | @@ -582,7 +579,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => { |
| 582 | 579 | const persisted = yield* readKeybindingsConfig(keybindingsConfigPath); |
| 583 | 580 | const persistedView = persisted.map(({ key, command }) => ({ key, command })); |
| 584 | 581 | assert.deepEqual(persistedView, [{ key: "mod+j", command: "terminal.toggle" }]); |
| 585 | }).pipe(Effect.provide(makeKeybindingsLayer())), | |
| 582 | }).pipe(Effect.provide(layerKeybindings())), | |
| 586 | 583 | ); |
| 587 | 584 | |
| 588 | 585 | it.effect("caches loaded resolved config across repeated reads", () => |
| ... | ... | @@ -601,7 +598,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => { |
| 601 | 598 | |
| 602 | 599 | assert.deepEqual(first, second); |
| 603 | 600 | assert.isTrue(second.some((entry) => entry.command === "terminal.toggle")); |
| 604 | }).pipe(Effect.provide(makeKeybindingsLayer())), | |
| 601 | }).pipe(Effect.provide(layerKeybindings())), | |
| 605 | 602 | ); |
| 606 | 603 | |
| 607 | 604 | it.effect("updates cached resolved config after upsert", () => |
| ... | ... | @@ -623,7 +620,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => { |
| 623 | 620 | |
| 624 | 621 | assert.isTrue(loadedAfterUpsert.some((entry) => entry.command === "script.run-tests.run")); |
| 625 | 622 | assert.isTrue(loadedAfterUpsert.some((entry) => entry.command === "terminal.toggle")); |
| 626 | }).pipe(Effect.provide(makeKeybindingsLayer())), | |
| 623 | }).pipe(Effect.provide(layerKeybindings())), | |
| 627 | 624 | ); |
| 628 | 625 | |
| 629 | 626 | it.effect("serializes concurrent upserts to avoid lost updates", () => |
| ... | ... | @@ -653,6 +650,6 @@ it.layer(NodeServices.layer)("keybindings", (it) => { |
| 653 | 650 | for (const command of commands) { |
| 654 | 651 | assert.isTrue(persistedCommands.has(command), `expected persisted command ${command}`); |
| 655 | 652 | } |
| 656 | }).pipe(Effect.provide(makeKeybindingsLayer())), | |
| 653 | }).pipe(Effect.provide(layerKeybindings())), | |
| 657 | 654 | ); |
| 658 | 655 | }); |
apps/server/src/mcp/McpDeviceToolkit.test.ts+7-7| ... | ... | @@ -81,7 +81,7 @@ new DataView(png.buffer).setUint32(12, 0x49484452); |
| 81 | 81 | new DataView(png.buffer).setUint32(16, 1206); |
| 82 | 82 | new DataView(png.buffer).setUint32(20, 2622); |
| 83 | 83 | |
| 84 | const DeviceServiceMock = Layer.mock(DeviceService.DeviceService)({ | |
| 84 | const layerDeviceServiceMock = Layer.mock(DeviceService.DeviceService)({ | |
| 85 | 85 | state: Effect.succeed(state), |
| 86 | 86 | list: Effect.succeed(state), |
| 87 | 87 | open: (input) => |
| ... | ... | @@ -100,9 +100,9 @@ const DeviceServiceMock = Layer.mock(DeviceService.DeviceService)({ |
| 100 | 100 | agentTarget: () => Effect.succeed(["--config", "/host.json", "--session", "thread-device"]), |
| 101 | 101 | }); |
| 102 | 102 | |
| 103 | const TestLayer = McpHttpServer.DeviceToolkitRegistrationLive.pipe( | |
| 103 | const layerTest = McpHttpServer.layerDeviceToolkit.pipe( | |
| 104 | 104 | Layer.provideMerge(McpServer.McpServer.layer), |
| 105 | Layer.provideMerge(DeviceServiceMock), | |
| 105 | Layer.provideMerge(layerDeviceServiceMock), | |
| 106 | 106 | Layer.provide(ServerConfig.layerTest(process.cwd(), { prefix: "t3-mcp-device-toolkit-test-" })), |
| 107 | 107 | Layer.provide(NodeServices.layer), |
| 108 | 108 | ); |
| ... | ... | @@ -138,11 +138,11 @@ it.effect("registers the device tools and returns the screenshot as image conten |
| 138 | 138 | .pipe(callWith(["preview"]), Effect.provideService(McpSchema.McpServerClient, client)); |
| 139 | 139 | expect(denied.isError).toBe(true); |
| 140 | 140 | }), |
| 141 | ).pipe(Effect.provide(TestLayer)), | |
| 141 | ).pipe(Effect.provide(layerTest)), | |
| 142 | 142 | ); |
| 143 | 143 | |
| 144 | 144 | it.effect("rejects unavailable agent access before booting or opening a device", () => { |
| 145 | const unavailable = Layer.mock(DeviceService.DeviceService)({ | |
| 145 | const layerUnavailable = Layer.mock(DeviceService.DeviceService)({ | |
| 146 | 146 | list: Effect.succeed(state), |
| 147 | 147 | agentTarget: () => |
| 148 | 148 | Effect.fail( |
| ... | ... | @@ -170,9 +170,9 @@ it.effect("rejects unavailable agent access before booting or opening a device", |
| 170 | 170 | }).pipe( |
| 171 | 171 | Effect.scoped, |
| 172 | 172 | Effect.provide( |
| 173 | McpHttpServer.DeviceToolkitRegistrationLive.pipe( | |
| 173 | McpHttpServer.layerDeviceToolkit.pipe( | |
| 174 | 174 | Layer.provideMerge(McpServer.McpServer.layer), |
| 175 | Layer.provide(unavailable), | |
| 175 | Layer.provide(layerUnavailable), | |
| 176 | 176 | Layer.provide(NodeServices.layer), |
| 177 | 177 | ), |
| 178 | 178 | ), |
apps/server/src/mcp/McpHttpServer.test.ts+17-17| ... | ... | @@ -49,13 +49,13 @@ const client = McpSchema.McpServerClient.of({ |
| 49 | 49 | }, |
| 50 | 50 | getClient: Effect.die("unused"), |
| 51 | 51 | }); |
| 52 | const TestLayer = McpHttpServer.PreviewToolkitRegistrationLive.pipe( | |
| 52 | const layerTest = McpHttpServer.layerPreviewToolkit.pipe( | |
| 53 | 53 | Layer.provideMerge(McpServer.McpServer.layer), |
| 54 | 54 | Layer.provideMerge(PreviewAutomationBroker.layer), |
| 55 | 55 | Layer.provideMerge(ServerConfig.layerTest(process.cwd(), { prefix: "t3-mcp-http-server-test-" })), |
| 56 | 56 | Layer.provideMerge(NodeServices.layer), |
| 57 | 57 | ); |
| 58 | const PullRequestsTestLayer = McpHttpServer.PullRequestsToolkitRegistrationLive.pipe( | |
| 58 | const layerPullRequestsTest = McpHttpServer.layerPullRequestsToolkit.pipe( | |
| 59 | 59 | Layer.provideMerge(McpServer.McpServer.layer), |
| 60 | 60 | Layer.provide( |
| 61 | 61 | Layer.mergeAll( |
| ... | ... | @@ -179,7 +179,7 @@ it.effect.each([{}, { includeImage: false }])( |
| 179 | 179 | }, |
| 180 | 180 | }); |
| 181 | 181 | }), |
| 182 | ).pipe(Effect.provide(TestLayer)), | |
| 182 | ).pipe(Effect.provide(layerTest)), | |
| 183 | 183 | ); |
| 184 | 184 | |
| 185 | 185 | it.effect.each([ |
| ... | ... | @@ -214,7 +214,7 @@ it.effect.each([ |
| 214 | 214 | { type: "text", text: `Preview snapshot failed: ${advice}` }, |
| 215 | 215 | ]); |
| 216 | 216 | }), |
| 217 | ).pipe(Effect.provide(TestLayer)), | |
| 217 | ).pipe(Effect.provide(layerTest)), | |
| 218 | 218 | ); |
| 219 | 219 | |
| 220 | 220 | it.effect("tells the agent how to fall back when no desktop app can run the snapshot", () => |
| ... | ... | @@ -229,7 +229,7 @@ it.effect("tells the agent how to fall back when no desktop app can run the snap |
| 229 | 229 | expect(snapshot.structuredContent).toMatchObject({ |
| 230 | 230 | error: { _tag: "PreviewAutomationNoAvailableHostError" }, |
| 231 | 231 | }); |
| 232 | }).pipe(Effect.provide(TestLayer)), | |
| 232 | }).pipe(Effect.provide(layerTest)), | |
| 233 | 233 | ); |
| 234 | 234 | |
| 235 | 235 | it.effect.each([ |
| ... | ... | @@ -350,7 +350,7 @@ it.effect.each([ |
| 350 | 350 | expect(nextDefault.structuredContent).not.toHaveProperty("accessibilityTree"); |
| 351 | 351 | expect(requests).toBe(7); |
| 352 | 352 | }), |
| 353 | ).pipe(Effect.provide(TestLayer)), | |
| 353 | ).pipe(Effect.provide(layerTest)), | |
| 354 | 354 | ); |
| 355 | 355 | |
| 356 | 356 | it.effect("rejects non-boolean snapshot image options before selecting a browser host", () => |
| ... | ... | @@ -372,7 +372,7 @@ it.effect("rejects non-boolean snapshot image options before selecting a browser |
| 372 | 372 | error: { _tag: "AiError", operation: "snapshot", failureCount: 1 }, |
| 373 | 373 | }); |
| 374 | 374 | } |
| 375 | }).pipe(Effect.provide(TestLayer)), | |
| 375 | }).pipe(Effect.provide(layerTest)), | |
| 376 | 376 | ); |
| 377 | 377 | |
| 378 | 378 | it.effect("saves the snapshot PNG on request and reports its path", () => |
| ... | ... | @@ -411,7 +411,7 @@ it.effect("saves the snapshot PNG on request and reports its path", () => |
| 411 | 411 | expect(others).toEqual([]); |
| 412 | 412 | expect(only?.type === "text" ? decodeJsonText(only.text) : null).toEqual(saved); |
| 413 | 413 | }), |
| 414 | ).pipe(Effect.provide(TestLayer)), | |
| 414 | ).pipe(Effect.provide(layerTest)), | |
| 415 | 415 | ); |
| 416 | 416 | |
| 417 | 417 | it.effect("reports a tagged error when the screenshot cannot be saved", () => |
| ... | ... | @@ -433,7 +433,7 @@ it.effect("reports a tagged error when the screenshot cannot be saved", () => |
| 433 | 433 | error: { _tag: "PreviewScreenshotSaveError", operation: "snapshot", failureCount: 1 }, |
| 434 | 434 | }); |
| 435 | 435 | }), |
| 436 | ).pipe(Effect.provide(TestLayer)), | |
| 436 | ).pipe(Effect.provide(layerTest)), | |
| 437 | 437 | ); |
| 438 | 438 | |
| 439 | 439 | it.effect( |
| ... | ... | @@ -465,7 +465,7 @@ it.effect( |
| 465 | 465 | expect(denied.content).toEqual([ |
| 466 | 466 | { type: "text", text: "MCP credential does not grant the pull-requests capability." }, |
| 467 | 467 | ]); |
| 468 | }).pipe(Effect.provide(PullRequestsTestLayer)), | |
| 468 | }).pipe(Effect.provide(layerPullRequestsTest)), | |
| 469 | 469 | ); |
| 470 | 470 | |
| 471 | 471 | it.effect("keeps the snapshot text under the agent's output ceiling", () => |
| ... | ... | @@ -535,7 +535,7 @@ it.effect("keeps the snapshot text under the agent's output ceiling", () => |
| 535 | 535 | omitted: expect.arrayContaining(["60 older console entries"]), |
| 536 | 536 | }); |
| 537 | 537 | }), |
| 538 | ).pipe(Effect.provide(TestLayer)), | |
| 538 | ).pipe(Effect.provide(layerTest)), | |
| 539 | 539 | ); |
| 540 | 540 | |
| 541 | 541 | it.effect("bounds the snapshot text even when nothing but logs and the title are large", () => |
| ... | ... | @@ -567,7 +567,7 @@ it.effect("bounds the snapshot text even when nothing but logs and the title are |
| 567 | 567 | expect(noticeText).toContain("url or title after 2048 characters"); |
| 568 | 568 | expect(noticeText).toContain("console entries text after 500 characters"); |
| 569 | 569 | }), |
| 570 | ).pipe(Effect.provide(TestLayer)), | |
| 570 | ).pipe(Effect.provide(layerTest)), | |
| 571 | 571 | ); |
| 572 | 572 | |
| 573 | 573 | it.effect("bounds page text made of wide characters before dropping locators", () => |
| ... | ... | @@ -606,7 +606,7 @@ it.effect("bounds page text made of wide characters before dropping locators", ( |
| 606 | 606 | "visibleText after 4000 characters", |
| 607 | 607 | ); |
| 608 | 608 | }), |
| 609 | ).pipe(Effect.provide(TestLayer)), | |
| 609 | ).pipe(Effect.provide(layerTest)), | |
| 610 | 610 | ); |
| 611 | 611 | |
| 612 | 612 | it.effect("sheds log entries before locators when every list is full", () => |
| ... | ... | @@ -672,19 +672,19 @@ it.effect("sheds log entries before locators when every list is full", () => |
| 672 | 672 | expect(noticeText).toContain("40 of 40 actionTimeline"); |
| 673 | 673 | expect(noticeText).not.toMatch(/\d+ of \d+ interactiveElements/); |
| 674 | 674 | }), |
| 675 | ).pipe(Effect.provide(TestLayer)), | |
| 675 | ).pipe(Effect.provide(layerTest)), | |
| 676 | 676 | ); |
| 677 | 677 | |
| 678 | 678 | it.effect("terminates HTTP MCP sessions with DELETE", () => |
| 679 | 679 | Effect.scoped( |
| 680 | 680 | Effect.gen(function* () { |
| 681 | const serverLayer = McpServer.layerHttp({ | |
| 681 | const layerServer = McpServer.layerHttp({ | |
| 682 | 682 | name: "MCP termination test", |
| 683 | 683 | version: "1.0.0", |
| 684 | 684 | path: "/mcp", |
| 685 | 685 | protocols: [McpProtocol.v2025_06_18], |
| 686 | 686 | }); |
| 687 | yield* HttpRouter.serve(serverLayer, { | |
| 687 | yield* HttpRouter.serve(layerServer, { | |
| 688 | 688 | disableListenLog: true, |
| 689 | 689 | disableLogger: true, |
| 690 | 690 | }).pipe(Layer.build); |
| ... | ... | @@ -872,5 +872,5 @@ it.effect("registers annotated tools and preserves authenticated request context |
| 872 | 872 | expect(text?.type === "text" ? decodeJsonText(text.text) : null).toEqual({ toolIcon }); |
| 873 | 873 | } |
| 874 | 874 | }), |
| 875 | ).pipe(Effect.provide(TestLayer)), | |
| 875 | ).pipe(Effect.provide(layerTest)), | |
| 876 | 876 | ); |
apps/server/src/mcp/McpHttpServer.ts+59-68| ... | ... | @@ -23,47 +23,38 @@ import * as PreviewBrowser from "../htmlRender/PreviewBrowser.ts"; |
| 23 | 23 | import * as McpInvocationContext from "./McpInvocationContext.ts"; |
| 24 | 24 | import * as OrchestratorMcpService from "./OrchestratorMcpService.ts"; |
| 25 | 25 | import { PreviewControlsToolkit } from "./toolkits/previewControls/tools.ts"; |
| 26 | import { PreviewControlsHandlersLive } from "./toolkits/previewControls/handlers.ts"; | |
| 26 | import * as PreviewControlsHandlers from "./toolkits/previewControls/handlers.ts"; | |
| 27 | 27 | import { EnvironmentToolkit } from "./toolkits/environment/tools.ts"; |
| 28 | import { EnvironmentHandlersLive } from "./toolkits/environment/handlers.ts"; | |
| 28 | import * as EnvironmentHandlers from "./toolkits/environment/handlers.ts"; | |
| 29 | 29 | import { ProjectToolkit } from "./toolkits/project/tools.ts"; |
| 30 | import { ProjectHandlersLive } from "./toolkits/project/handlers.ts"; | |
| 30 | import * as ProjectHandlers from "./toolkits/project/handlers.ts"; | |
| 31 | 31 | import { AttachmentToolkit } from "./toolkits/attachment/tools.ts"; |
| 32 | import { AttachmentHandlersLive } from "./toolkits/attachment/handlers.ts"; | |
| 32 | import * as AttachmentHandlers from "./toolkits/attachment/handlers.ts"; | |
| 33 | 33 | import { ThreadToolkit } from "./toolkits/thread/tools.ts"; |
| 34 | import { ThreadToolkitHandlersLive } from "./toolkits/thread/handlers.ts"; | |
| 34 | import * as ThreadHandlers from "./toolkits/thread/handlers.ts"; | |
| 35 | 35 | import * as ThreadMetadataMcpService from "./ThreadMetadataMcpService.ts"; |
| 36 | 36 | import * as McpSessionRegistry from "./McpSessionRegistry.ts"; |
| 37 | 37 | import * as PreviewAutomationBroker from "./PreviewAutomationBroker.ts"; |
| 38 | import { OrchestratorToolkitHandlersLive } from "./toolkits/orchestrator/handlers.ts"; | |
| 38 | import * as OrchestratorHandlers from "./toolkits/orchestrator/handlers.ts"; | |
| 39 | 39 | import { OrchestratorToolkit } from "./toolkits/orchestrator/tools.ts"; |
| 40 | import { | |
| 41 | PreviewSnapshotToolkitHandlersLive, | |
| 42 | PreviewStandardToolkitHandlersLive, | |
| 43 | } from "./toolkits/preview/handlers.ts"; | |
| 40 | import * as PreviewHandlers from "./toolkits/preview/handlers.ts"; | |
| 44 | 41 | import { |
| 45 | 42 | PreviewSnapshotTool, |
| 46 | 43 | PreviewSnapshotToolkit, |
| 47 | 44 | PreviewStandardToolkit, |
| 48 | 45 | } from "./toolkits/preview/tools.ts"; |
| 49 | import { WorktreeToolkitHandlersLive } from "./toolkits/worktree/handlers.ts"; | |
| 46 | import * as WorktreeHandlers from "./toolkits/worktree/handlers.ts"; | |
| 50 | 47 | import { WorktreeToolkit } from "./toolkits/worktree/tools.ts"; |
| 51 | 48 | import * as WorktreeMcpService from "./WorktreeMcpService.ts"; |
| 52 | import { PullRequestsToolkitHandlersLive } from "./toolkits/pullRequests/handlers.ts"; | |
| 49 | import * as PullRequestsHandlers from "./toolkits/pullRequests/handlers.ts"; | |
| 53 | 50 | import { PullRequestsToolkit } from "./toolkits/pullRequests/tools.ts"; |
| 54 | import { | |
| 55 | DeviceScreenshotToolkitHandlersLive, | |
| 56 | DeviceStandardToolkitHandlersLive, | |
| 57 | } from "./toolkits/device/handlers.ts"; | |
| 51 | import * as DeviceHandlers from "./toolkits/device/handlers.ts"; | |
| 58 | 52 | import { |
| 59 | 53 | DeviceScreenshotTool, |
| 60 | 54 | DeviceScreenshotToolkit, |
| 61 | 55 | DeviceStandardToolkit, |
| 62 | 56 | } from "./toolkits/device/tools.ts"; |
| 63 | import { | |
| 64 | HtmlPreviewToolkitHandlersLive, | |
| 65 | HtmlRenderToolkitHandlersLive, | |
| 66 | } from "./toolkits/html/handlers.ts"; | |
| 57 | import * as HtmlHandlers from "./toolkits/html/handlers.ts"; | |
| 67 | 58 | import { HtmlPreviewTool, HtmlPreviewToolkit, HtmlRenderToolkit } from "./toolkits/html/tools.ts"; |
| 68 | 59 | |
| 69 | 60 | const unauthorized = HttpServerResponse.jsonUnsafe( |
| ... | ... | @@ -134,7 +125,7 @@ const makeMcpAuthMiddleware = McpSessionRegistry.McpSessionRegistry.pipe( |
| 134 | 125 | Effect.withSpan("McpHttpServer.makeAuthMiddleware"), |
| 135 | 126 | ); |
| 136 | 127 | |
| 137 | const McpAuthMiddlewareLive = HttpRouter.middleware<{ | |
| 128 | const layerMcpAuthMiddleware = HttpRouter.middleware<{ | |
| 138 | 129 | provides: McpInvocationContext.McpInvocationContext; |
| 139 | 130 | }>()(makeMcpAuthMiddleware).layer; |
| 140 | 131 | |
| ... | ... | @@ -687,89 +678,89 @@ const registerHtmlPreview = Effect.fn("McpHttpServer.registerHtmlPreview")(funct |
| 687 | 678 | ); |
| 688 | 679 | }); |
| 689 | 680 | |
| 690 | export const HtmlToolkitRegistrationLive = Layer.mergeAll( | |
| 691 | McpServer.toolkit(HtmlRenderToolkit).pipe(Layer.provide(HtmlRenderToolkitHandlersLive)), | |
| 692 | Layer.effectDiscard(registerHtmlPreview()).pipe(Layer.provide(HtmlPreviewToolkitHandlersLive)), | |
| 681 | export const layerHtmlToolkit = Layer.mergeAll( | |
| 682 | McpServer.toolkit(HtmlRenderToolkit).pipe(Layer.provide(HtmlHandlers.layerRender)), | |
| 683 | Layer.effectDiscard(registerHtmlPreview()).pipe(Layer.provide(HtmlHandlers.layerPreview)), | |
| 693 | 684 | ).pipe(Layer.provide(HtmlRender.layer), Layer.provide(PreviewBrowser.layer)); |
| 694 | 685 | |
| 695 | const PreviewStandardToolkitRegistrationLive = McpServer.toolkit(PreviewStandardToolkit).pipe( | |
| 696 | Layer.provide(PreviewStandardToolkitHandlersLive), | |
| 686 | const layerPreviewStandardToolkitRegistration = McpServer.toolkit(PreviewStandardToolkit).pipe( | |
| 687 | Layer.provide(PreviewHandlers.layerStandard), | |
| 697 | 688 | ); |
| 698 | 689 | |
| 699 | const PreviewSnapshotRegistrationLive = Layer.effectDiscard(registerPreviewSnapshot()).pipe( | |
| 700 | Layer.provide(PreviewSnapshotToolkitHandlersLive), | |
| 690 | const layerPreviewSnapshotRegistration = Layer.effectDiscard(registerPreviewSnapshot()).pipe( | |
| 691 | Layer.provide(PreviewHandlers.layerSnapshot), | |
| 701 | 692 | ); |
| 702 | 693 | |
| 703 | export const PreviewToolkitRegistrationLive = Layer.mergeAll( | |
| 704 | PreviewStandardToolkitRegistrationLive, | |
| 705 | PreviewSnapshotRegistrationLive, | |
| 694 | export const layerPreviewToolkit = Layer.mergeAll( | |
| 695 | layerPreviewStandardToolkitRegistration, | |
| 696 | layerPreviewSnapshotRegistration, | |
| 706 | 697 | ); |
| 707 | 698 | |
| 708 | export const OrchestratorToolkitRegistrationLive = McpServer.toolkit(OrchestratorToolkit).pipe( | |
| 709 | Layer.provide(OrchestratorToolkitHandlersLive), | |
| 699 | export const layerOrchestratorToolkit = McpServer.toolkit(OrchestratorToolkit).pipe( | |
| 700 | Layer.provide(OrchestratorHandlers.layer), | |
| 710 | 701 | Layer.provide(OrchestratorMcpService.layer), |
| 711 | 702 | Layer.provide(ThreadMetadataMcpService.layer), |
| 712 | 703 | ); |
| 713 | 704 | |
| 714 | export const ThreadToolkitRegistrationLive = McpServer.toolkit(ThreadToolkit).pipe( | |
| 715 | Layer.provide(ThreadToolkitHandlersLive), | |
| 705 | export const layerThreadToolkit = McpServer.toolkit(ThreadToolkit).pipe( | |
| 706 | Layer.provide(ThreadHandlers.layer), | |
| 716 | 707 | ); |
| 717 | 708 | |
| 718 | const WorktreeToolkitRegistrationLive = McpServer.toolkit(WorktreeToolkit).pipe( | |
| 719 | Layer.provide(WorktreeToolkitHandlersLive), | |
| 709 | const layerWorktreeToolkitRegistration = McpServer.toolkit(WorktreeToolkit).pipe( | |
| 710 | Layer.provide(WorktreeHandlers.layer), | |
| 720 | 711 | Layer.provide(WorktreeMcpService.layer), |
| 721 | 712 | ); |
| 722 | 713 | |
| 723 | const PreviewControlsRegistrationLive = McpServer.toolkit(PreviewControlsToolkit).pipe( | |
| 724 | Layer.provide(PreviewControlsHandlersLive), | |
| 714 | const layerPreviewControlsRegistration = McpServer.toolkit(PreviewControlsToolkit).pipe( | |
| 715 | Layer.provide(PreviewControlsHandlers.layer), | |
| 725 | 716 | ); |
| 726 | 717 | |
| 727 | const EnvironmentRegistrationLive = McpServer.toolkit(EnvironmentToolkit).pipe( | |
| 728 | Layer.provide(EnvironmentHandlersLive), | |
| 718 | const layerEnvironmentRegistration = McpServer.toolkit(EnvironmentToolkit).pipe( | |
| 719 | Layer.provide(EnvironmentHandlers.layer), | |
| 729 | 720 | ); |
| 730 | 721 | |
| 731 | const ProjectRegistrationLive = McpServer.toolkit(ProjectToolkit).pipe( | |
| 732 | Layer.provide(ProjectHandlersLive), | |
| 722 | const layerProjectRegistration = McpServer.toolkit(ProjectToolkit).pipe( | |
| 723 | Layer.provide(ProjectHandlers.layer), | |
| 733 | 724 | ); |
| 734 | 725 | |
| 735 | const AttachmentRegistrationLive = McpServer.toolkit(AttachmentToolkit).pipe( | |
| 736 | Layer.provide(AttachmentHandlersLive), | |
| 726 | const layerAttachmentRegistration = McpServer.toolkit(AttachmentToolkit).pipe( | |
| 727 | Layer.provide(AttachmentHandlers.layer), | |
| 737 | 728 | ); |
| 738 | 729 | |
| 739 | export const PullRequestsToolkitRegistrationLive = McpServer.toolkit(PullRequestsToolkit).pipe( | |
| 740 | Layer.provide(PullRequestsToolkitHandlersLive), | |
| 730 | export const layerPullRequestsToolkit = McpServer.toolkit(PullRequestsToolkit).pipe( | |
| 731 | Layer.provide(PullRequestsHandlers.layer), | |
| 741 | 732 | ); |
| 742 | 733 | |
| 743 | const DeviceStandardToolkitRegistrationLive = McpServer.toolkit(DeviceStandardToolkit).pipe( | |
| 744 | Layer.provide(DeviceStandardToolkitHandlersLive), | |
| 734 | const layerDeviceStandardToolkitRegistration = McpServer.toolkit(DeviceStandardToolkit).pipe( | |
| 735 | Layer.provide(DeviceHandlers.layerStandard), | |
| 745 | 736 | ); |
| 746 | 737 | |
| 747 | const DeviceScreenshotRegistrationLive = Layer.effectDiscard(registerDeviceScreenshot()).pipe( | |
| 748 | Layer.provide(DeviceScreenshotToolkitHandlersLive), | |
| 738 | const layerDeviceScreenshotRegistration = Layer.effectDiscard(registerDeviceScreenshot()).pipe( | |
| 739 | Layer.provide(DeviceHandlers.layerScreenshot), | |
| 749 | 740 | ); |
| 750 | 741 | |
| 751 | export const DeviceToolkitRegistrationLive = Layer.mergeAll( | |
| 752 | DeviceStandardToolkitRegistrationLive, | |
| 753 | DeviceScreenshotRegistrationLive, | |
| 742 | export const layerDeviceToolkit = Layer.mergeAll( | |
| 743 | layerDeviceStandardToolkitRegistration, | |
| 744 | layerDeviceScreenshotRegistration, | |
| 754 | 745 | ); |
| 755 | 746 | |
| 756 | const McpTransportLive = McpServer.layerHttp({ | |
| 747 | const layerMcpTransport = McpServer.layerHttp({ | |
| 757 | 748 | name: "T3 Code", |
| 758 | 749 | version: packageJson.version, |
| 759 | 750 | path: "/mcp", |
| 760 | 751 | protocols: [McpProtocol.v2025_06_18], |
| 761 | }).pipe(Layer.provide(McpAuthMiddlewareLive)); | |
| 752 | }).pipe(Layer.provide(layerMcpAuthMiddleware)); | |
| 762 | 753 | |
| 763 | 754 | export const layer = Layer.mergeAll( |
| 764 | PreviewToolkitRegistrationLive, | |
| 765 | OrchestratorToolkitRegistrationLive, | |
| 766 | ThreadToolkitRegistrationLive, | |
| 767 | AttachmentRegistrationLive, | |
| 768 | ProjectRegistrationLive, | |
| 769 | EnvironmentRegistrationLive, | |
| 770 | PreviewControlsRegistrationLive, | |
| 771 | WorktreeToolkitRegistrationLive, | |
| 772 | PullRequestsToolkitRegistrationLive, | |
| 773 | DeviceToolkitRegistrationLive, | |
| 774 | HtmlToolkitRegistrationLive, | |
| 775 | ).pipe(Layer.provideMerge(McpTransportLive)); | |
| 755 | layerPreviewToolkit, | |
| 756 | layerOrchestratorToolkit, | |
| 757 | layerThreadToolkit, | |
| 758 | layerAttachmentRegistration, | |
| 759 | layerProjectRegistration, | |
| 760 | layerEnvironmentRegistration, | |
| 761 | layerPreviewControlsRegistration, | |
| 762 | layerWorktreeToolkitRegistration, | |
| 763 | layerPullRequestsToolkit, | |
| 764 | layerDeviceToolkit, | |
| 765 | layerHtmlToolkit, | |
| 766 | ).pipe(Layer.provideMerge(layerMcpTransport)); |
apps/server/src/mcp/OrchestratorMcpService.test.ts+28-22| ... | ... | @@ -78,7 +78,7 @@ describe("OrchestratorMcpService", () => { |
| 78 | 78 | providerThreads: [], |
| 79 | 79 | } as unknown as OrchestrationV2ThreadProjection; |
| 80 | 80 | let hasNestedWork = true; |
| 81 | const dependencies = Layer.mergeAll( | |
| 81 | const layerDependencies = Layer.mergeAll( | |
| 82 | 82 | NodeServices.layer, |
| 83 | 83 | Layer.mock(ThreadManagementService.ThreadManagementService)({ |
| 84 | 84 | getThreadRecords: (threadId) => |
| ... | ... | @@ -156,7 +156,7 @@ describe("OrchestratorMcpService", () => { |
| 156 | 156 | const commandIds = yield* Ref.get(acknowledgementCommandIds); |
| 157 | 157 | assert.equal(commandIds.length, 2); |
| 158 | 158 | assert.notEqual(commandIds[0], commandIds[1]); |
| 159 | }).pipe(Effect.provide(OrchestratorMcpService.layer.pipe(Layer.provide(dependencies)))); | |
| 159 | }).pipe(Effect.provide(OrchestratorMcpService.layer.pipe(Layer.provide(layerDependencies)))); | |
| 160 | 160 | }), |
| 161 | 161 | ); |
| 162 | 162 | |
| ... | ... | @@ -195,7 +195,7 @@ describe("OrchestratorMcpService", () => { |
| 195 | 195 | providerThreads: [], |
| 196 | 196 | turnItems: [], |
| 197 | 197 | } as unknown as OrchestrationV2ThreadProjection; |
| 198 | const dependencies = Layer.mergeAll( | |
| 198 | const layerDependencies = Layer.mergeAll( | |
| 199 | 199 | NodeServices.layer, |
| 200 | 200 | Layer.mock(ThreadManagementService.ThreadManagementService)({ |
| 201 | 201 | getThreadRecords: (threadId) => |
| ... | ... | @@ -243,7 +243,7 @@ describe("OrchestratorMcpService", () => { |
| 243 | 243 | const settled = yield* service.taskStatus(scope, taskId); |
| 244 | 244 | assert.equal(settled.status, "cancelled"); |
| 245 | 245 | assert.equal(yield* Ref.get(dispatched), 1); |
| 246 | }).pipe(Effect.provide(OrchestratorMcpService.layer.pipe(Layer.provide(dependencies)))); | |
| 246 | }).pipe(Effect.provide(OrchestratorMcpService.layer.pipe(Layer.provide(layerDependencies)))); | |
| 247 | 247 | }), |
| 248 | 248 | ); |
| 249 | 249 | |
| ... | ... | @@ -278,7 +278,7 @@ describe("OrchestratorMcpService", () => { |
| 278 | 278 | subagents: [], |
| 279 | 279 | providerThreads: [], |
| 280 | 280 | } as unknown as OrchestrationV2ThreadProjection; |
| 281 | const dependencies = Layer.mergeAll( | |
| 281 | const layerDependencies = Layer.mergeAll( | |
| 282 | 282 | NodeServices.layer, |
| 283 | 283 | Layer.mock(ThreadManagementService.ThreadManagementService)({ |
| 284 | 284 | getThreadRecords: (threadId) => |
| ... | ... | @@ -316,7 +316,7 @@ describe("OrchestratorMcpService", () => { |
| 316 | 316 | .pipe(Effect.flip); |
| 317 | 317 | assert.equal(error.code, "task_not_cancellable"); |
| 318 | 318 | assert.deepEqual(yield* Ref.get(dispatched), []); |
| 319 | }).pipe(Effect.provide(OrchestratorMcpService.layer.pipe(Layer.provide(dependencies)))); | |
| 319 | }).pipe(Effect.provide(OrchestratorMcpService.layer.pipe(Layer.provide(layerDependencies)))); | |
| 320 | 320 | }), |
| 321 | 321 | ); |
| 322 | 322 | |
| ... | ... | @@ -352,7 +352,7 @@ describe("OrchestratorMcpService", () => { |
| 352 | 352 | subagents: [], |
| 353 | 353 | providerThreads: [], |
| 354 | 354 | } as unknown as OrchestrationV2ThreadProjection; |
| 355 | const dependencies = Layer.mergeAll( | |
| 355 | const layerDependencies = Layer.mergeAll( | |
| 356 | 356 | NodeServices.layer, |
| 357 | 357 | Layer.mock(ThreadManagementService.ThreadManagementService)({ |
| 358 | 358 | getThreadRecords: (threadId) => |
| ... | ... | @@ -393,7 +393,7 @@ describe("OrchestratorMcpService", () => { |
| 393 | 393 | (yield* Ref.get(dispatched)).map((command) => (command as { type: string }).type), |
| 394 | 394 | ["thread.stop"], |
| 395 | 395 | ); |
| 396 | }).pipe(Effect.provide(OrchestratorMcpService.layer.pipe(Layer.provide(dependencies)))); | |
| 396 | }).pipe(Effect.provide(OrchestratorMcpService.layer.pipe(Layer.provide(layerDependencies)))); | |
| 397 | 397 | }), |
| 398 | 398 | ); |
| 399 | 399 | |
| ... | ... | @@ -429,7 +429,7 @@ describe("OrchestratorMcpService", () => { |
| 429 | 429 | subagents: [], |
| 430 | 430 | providerThreads: [], |
| 431 | 431 | } as unknown as OrchestrationV2ThreadProjection; |
| 432 | const dependencies = Layer.mergeAll( | |
| 432 | const layerDependencies = Layer.mergeAll( | |
| 433 | 433 | NodeServices.layer, |
| 434 | 434 | Layer.mock(ThreadManagementService.ThreadManagementService)({ |
| 435 | 435 | getThreadRecords: (threadId) => |
| ... | ... | @@ -476,7 +476,7 @@ describe("OrchestratorMcpService", () => { |
| 476 | 476 | (yield* Ref.get(dispatched)).map((command) => (command as { type: string }).type), |
| 477 | 477 | ["thread.stop", "delegated_task.completion-delivery.dispose"], |
| 478 | 478 | ); |
| 479 | }).pipe(Effect.provide(OrchestratorMcpService.layer.pipe(Layer.provide(dependencies)))); | |
| 479 | }).pipe(Effect.provide(OrchestratorMcpService.layer.pipe(Layer.provide(layerDependencies)))); | |
| 480 | 480 | }), |
| 481 | 481 | ); |
| 482 | 482 | }); |
| ... | ... | @@ -641,7 +641,7 @@ describe("OrchestratorMcpService provider resolution", () => { |
| 641 | 641 | }), |
| 642 | 642 | forkShadow, |
| 643 | 643 | ]; |
| 644 | const dependencies = Layer.mergeAll( | |
| 644 | const layerDependencies = Layer.mergeAll( | |
| 645 | 645 | NodeServices.layer, |
| 646 | 646 | Layer.mock(ThreadManagementService.ThreadManagementService)({ |
| 647 | 647 | getThreadRecords: () => Effect.succeed(parentProjection([])), |
| ... | ... | @@ -729,7 +729,9 @@ describe("OrchestratorMcpService provider resolution", () => { |
| 729 | 729 | assert.isTrue( |
| 730 | 730 | fork!.constraints.includes("Driver 'forkOnly' is not registered in this build."), |
| 731 | 731 | ); |
| 732 | }).pipe(Effect.provide(OrchestratorMcpService.layer.pipe(Layer.provide(dependencies)))); | |
| 732 | }).pipe( | |
| 733 | Effect.provide(OrchestratorMcpService.layer.pipe(Layer.provide(layerDependencies))), | |
| 734 | ); | |
| 733 | 735 | }), |
| 734 | 736 | ); |
| 735 | 737 | |
| ... | ... | @@ -759,7 +761,7 @@ describe("OrchestratorMcpService provider resolution", () => { |
| 759 | 761 | completedAt: null, |
| 760 | 762 | }; |
| 761 | 763 | const dispatched = yield* Ref.make<ReadonlyArray<unknown>>([]); |
| 762 | const dependencies = Layer.mergeAll( | |
| 764 | const layerDependencies = Layer.mergeAll( | |
| 763 | 765 | NodeServices.layer, |
| 764 | 766 | Layer.mock(ThreadManagementService.ThreadManagementService)({ |
| 765 | 767 | getThreadRecords: (threadId) => |
| ... | ... | @@ -826,7 +828,9 @@ describe("OrchestratorMcpService provider resolution", () => { |
| 826 | 828 | assert.equal(request.type, "delegated_task.request"); |
| 827 | 829 | assert.equal(request.modelSelection.instanceId, antigravityInstanceId); |
| 828 | 830 | assert.equal(request.modelSelection.model, "ant-model"); |
| 829 | }).pipe(Effect.provide(OrchestratorMcpService.layer.pipe(Layer.provide(dependencies)))); | |
| 831 | }).pipe( | |
| 832 | Effect.provide(OrchestratorMcpService.layer.pipe(Layer.provide(layerDependencies))), | |
| 833 | ); | |
| 830 | 834 | }), |
| 831 | 835 | ); |
| 832 | 836 | |
| ... | ... | @@ -854,7 +858,7 @@ describe("OrchestratorMcpService provider resolution", () => { |
| 854 | 858 | completedAt: null, |
| 855 | 859 | }; |
| 856 | 860 | let delegated = false; |
| 857 | const dependencies = Layer.mergeAll( | |
| 861 | const layerDependencies = Layer.mergeAll( | |
| 858 | 862 | NodeServices.layer, |
| 859 | 863 | Layer.mock(ThreadManagementService.ThreadManagementService)({ |
| 860 | 864 | getThreadRecords: (threadId) => |
| ... | ... | @@ -918,7 +922,7 @@ describe("OrchestratorMcpService provider resolution", () => { |
| 918 | 922 | }; |
| 919 | 923 | assert.equal(request.modelSelection.instanceId, antigravityInstanceId); |
| 920 | 924 | assert.equal(request.modelSelection.model, "ant-model"); |
| 921 | }).pipe(Effect.provide(OrchestratorMcpService.layer.pipe(Layer.provide(dependencies)))); | |
| 925 | }).pipe(Effect.provide(OrchestratorMcpService.layer.pipe(Layer.provide(layerDependencies)))); | |
| 922 | 926 | }), |
| 923 | 927 | ); |
| 924 | 928 | |
| ... | ... | @@ -931,7 +935,7 @@ describe("OrchestratorMcpService provider resolution", () => { |
| 931 | 935 | reason: "Driver 'forkOnly' is not registered in this build.", |
| 932 | 936 | checkedAt: "2026-09-13T00:00:00.000Z", |
| 933 | 937 | }); |
| 934 | const dependencies = Layer.mergeAll( | |
| 938 | const layerDependencies = Layer.mergeAll( | |
| 935 | 939 | NodeServices.layer, |
| 936 | 940 | Layer.mock(ThreadManagementService.ThreadManagementService)({ |
| 937 | 941 | getThreadRecords: () => Effect.succeed(parentProjection([])), |
| ... | ... | @@ -975,7 +979,7 @@ describe("OrchestratorMcpService provider resolution", () => { |
| 975 | 979 | assert.isTrue( |
| 976 | 980 | byDriver.message.includes("No V2 provider adapter is registered for driver forkOnly."), |
| 977 | 981 | ); |
| 978 | }).pipe(Effect.provide(OrchestratorMcpService.layer.pipe(Layer.provide(dependencies)))); | |
| 982 | }).pipe(Effect.provide(OrchestratorMcpService.layer.pipe(Layer.provide(layerDependencies)))); | |
| 979 | 983 | }), |
| 980 | 984 | ); |
| 981 | 985 | |
| ... | ... | @@ -1023,7 +1027,7 @@ describe("OrchestratorMcpService provider resolution", () => { |
| 1023 | 1027 | let cliInstalled = false; |
| 1024 | 1028 | const probes = yield* Ref.make(0); |
| 1025 | 1029 | const dispatched = yield* Ref.make(0); |
| 1026 | const dependencies = Layer.mergeAll( | |
| 1030 | const layerDependencies = Layer.mergeAll( | |
| 1027 | 1031 | NodeServices.layer, |
| 1028 | 1032 | Layer.mock(ThreadManagementService.ThreadManagementService)({ |
| 1029 | 1033 | getThreadRecords: (threadId) => |
| ... | ... | @@ -1077,7 +1081,7 @@ describe("OrchestratorMcpService provider resolution", () => { |
| 1077 | 1081 | assert.equal(result.providerInstanceId, claudeInstanceId); |
| 1078 | 1082 | assert.equal(yield* Ref.get(probes), 2); |
| 1079 | 1083 | assert.equal(yield* Ref.get(dispatched), 1); |
| 1080 | }).pipe(Effect.provide(OrchestratorMcpService.layer.pipe(Layer.provide(dependencies)))); | |
| 1084 | }).pipe(Effect.provide(OrchestratorMcpService.layer.pipe(Layer.provide(layerDependencies)))); | |
| 1081 | 1085 | }), |
| 1082 | 1086 | ); |
| 1083 | 1087 | |
| ... | ... | @@ -1167,7 +1171,7 @@ describe("OrchestratorMcpService provider resolution", () => { |
| 1167 | 1171 | for (const testCase of cases) { |
| 1168 | 1172 | const dispatched = yield* Ref.make<ReadonlyArray<unknown>>([]); |
| 1169 | 1173 | let delegated = false; |
| 1170 | const dependencies = Layer.mergeAll( | |
| 1174 | const layerDependencies = Layer.mergeAll( | |
| 1171 | 1175 | NodeServices.layer, |
| 1172 | 1176 | Layer.mock(ThreadManagementService.ThreadManagementService)({ |
| 1173 | 1177 | getThreadRecords: (threadId) => |
| ... | ... | @@ -1284,7 +1288,9 @@ describe("OrchestratorMcpService provider resolution", () => { |
| 1284 | 1288 | } else { |
| 1285 | 1289 | assert.equal(request.modelSelection.model, "codex-alt-model", testCase.name); |
| 1286 | 1290 | } |
| 1287 | }).pipe(Effect.provide(OrchestratorMcpService.layer.pipe(Layer.provide(dependencies)))); | |
| 1291 | }).pipe( | |
| 1292 | Effect.provide(OrchestratorMcpService.layer.pipe(Layer.provide(layerDependencies))), | |
| 1293 | ); | |
| 1288 | 1294 | } |
| 1289 | 1295 | }), |
| 1290 | 1296 | ); |
apps/server/src/mcp/OrchestratorMcpToolkit.integration.test.ts+36-39| ... | ... | @@ -64,15 +64,12 @@ import { |
| 64 | 64 | import * as ProviderAdapterRegistry from "../orchestration-v2/ProviderAdapterRegistry.ts"; |
| 65 | 65 | import * as ProviderContinuationRequests from "../orchestration-v2/ProviderContinuationRequests.ts"; |
| 66 | 66 | import { checkpointWorkspace } from "../orchestration-v2/testkit/ReplayFixtureWorkspace.ts"; |
| 67 | import { | |
| 68 | makeOrchestratorV2ProviderReplayLayer, | |
| 69 | makeOrchestratorV2ReplayLayerWithRegistry, | |
| 70 | } from "../orchestration-v2/testkit/ProviderReplayHarness.ts"; | |
| 67 | import * as ProviderReplayHarness from "../orchestration-v2/testkit/ProviderReplayHarness.ts"; | |
| 71 | 68 | import { |
| 72 | 69 | decodeProviderReplayNdjson, |
| 73 | 70 | materializeReplayTranscriptWorkspace, |
| 74 | 71 | } from "../orchestration-v2/testkit/ReplayTranscriptNdjson.ts"; |
| 75 | import { makeProviderRegistryLayer } from "../provider/testUtils/providerRegistryMock.ts"; | |
| 72 | import * as ProviderRegistryMock from "../provider/testUtils/providerRegistryMock.ts"; | |
| 76 | 73 | import * as ProjectService from "../project/ProjectService.ts"; |
| 77 | 74 | import * as ScheduledTaskService from "../scheduledTasks/ScheduledTaskService.ts"; |
| 78 | 75 | import * as SecretRequests from "../secrets/SecretRequests.ts"; |
| ... | ... | @@ -480,7 +477,7 @@ function scheduledTaskFromUpsert(input: ScheduledTaskUpsertInput): ScheduledTask |
| 480 | 477 | } |
| 481 | 478 | |
| 482 | 479 | /** In-memory server secret store for tests that exercise secret requests. */ |
| 483 | const memorySecretStoreLayer = Layer.sync(ServerSecretStore.ServerSecretStore, () => { | |
| 480 | const layerMemorySecretStore = Layer.sync(ServerSecretStore.ServerSecretStore, () => { | |
| 484 | 481 | const stored = new Map<string, Uint8Array>(); |
| 485 | 482 | return ServerSecretStore.ServerSecretStore.of({ |
| 486 | 483 | get: (name) => Effect.succeed(Option.fromNullishOr(stored.get(name))), |
| ... | ... | @@ -498,7 +495,7 @@ const memorySecretStoreLayer = Layer.sync(ServerSecretStore.ServerSecretStore, ( |
| 498 | 495 | }); |
| 499 | 496 | }); |
| 500 | 497 | |
| 501 | const unusedScheduledTaskStubLayer = Layer.succeed( | |
| 498 | const layerUnusedScheduledTaskStub = Layer.succeed( | |
| 502 | 499 | ScheduledTaskService.ScheduledTaskService, |
| 503 | 500 | ScheduledTaskService.ScheduledTaskService.of({ |
| 504 | 501 | list: () => Effect.succeed({ tasks: [] }), |
| ... | ... | @@ -524,7 +521,7 @@ describe("orchestrator MCP toolkit", () => { |
| 524 | 521 | const capturedTurns = yield* Ref.make<ReadonlyArray<CapturedTurn>>([]); |
| 525 | 522 | const parentTerminalGates = new Map<ThreadId, Deferred.Deferred<void>>(); |
| 526 | 523 | const deliveryTerminalGates = new Map<ThreadId, Deferred.Deferred<void>>(); |
| 527 | const registryLayer = ProviderAdapterRegistry.makeLayer([ | |
| 524 | const layerRegistry = ProviderAdapterRegistry.layerFromAdapters([ | |
| 528 | 525 | makeDeterministicAdapter({ |
| 529 | 526 | instanceId: codexInstanceId, |
| 530 | 527 | driver: ProviderDriverKind.make("codex"), |
| ... | ... | @@ -566,7 +563,7 @@ describe("orchestrator MCP toolkit", () => { |
| 566 | 563 | const continuationOffers = yield* Ref.make< |
| 567 | 564 | ReadonlyArray<ProviderContinuationRequests.ProviderContinuationRequest> |
| 568 | 565 | >([]); |
| 569 | const continuationProbeLayer = Layer.succeed( | |
| 566 | const layerContinuationProbe = Layer.succeed( | |
| 570 | 567 | ProviderContinuationRequests.ProviderContinuationRequests, |
| 571 | 568 | { |
| 572 | 569 | offer: (request) => |
| ... | ... | @@ -596,7 +593,7 @@ describe("orchestrator MCP toolkit", () => { |
| 596 | 593 | expect(yield* Ref.get(continuationOffers)).toHaveLength(count); |
| 597 | 594 | } |
| 598 | 595 | }); |
| 599 | const orchestratorLayer = makeOrchestratorV2ReplayLayerWithRegistry( | |
| 596 | const layerOrchestrator = ProviderReplayHarness.layerWithRegistry( | |
| 600 | 597 | { |
| 601 | 598 | name: "orchestrator-mcp-toolkit", |
| 602 | 599 | runtimePolicyOverride: { |
| ... | ... | @@ -609,13 +606,13 @@ describe("orchestrator MCP toolkit", () => { |
| 609 | 606 | }, |
| 610 | 607 | }, |
| 611 | 608 | }, |
| 612 | registryLayer, | |
| 613 | ).pipe(Layer.provide(continuationProbeLayer)); | |
| 614 | const orchestrationLayer = Layer.merge( | |
| 615 | orchestratorLayer, | |
| 616 | ThreadManagementService.layer.pipe(Layer.provide(orchestratorLayer)), | |
| 609 | layerRegistry, | |
| 610 | ).pipe(Layer.provide(layerContinuationProbe)); | |
| 611 | const layerOrchestration = Layer.merge( | |
| 612 | layerOrchestrator, | |
| 613 | ThreadManagementService.layer.pipe(Layer.provide(layerOrchestrator)), | |
| 617 | 614 | ); |
| 618 | const providerRegistryLayer = makeProviderRegistryLayer([ | |
| 615 | const layerProviderRegistry = ProviderRegistryMock.layer([ | |
| 619 | 616 | makeProviderSnapshot({ |
| 620 | 617 | instanceId: codexInstanceId, |
| 621 | 618 | driver: ProviderDriverKind.make("codex"), |
| ... | ... | @@ -647,7 +644,7 @@ describe("orchestrator MCP toolkit", () => { |
| 647 | 644 | // In-memory ScheduledTaskService stub so the schedule/list/update/ |
| 648 | 645 | // delete tools can be exercised without SQL/launch wiring. |
| 649 | 646 | const scheduledStore = yield* Ref.make<ReadonlyArray<ScheduledTask>>([]); |
| 650 | const scheduledTaskStubLayer = Layer.succeed( | |
| 647 | const layerScheduledTaskStub = Layer.succeed( | |
| 651 | 648 | ScheduledTaskService.ScheduledTaskService, |
| 652 | 649 | ScheduledTaskService.ScheduledTaskService.of({ |
| 653 | 650 | list: () => Ref.get(scheduledStore).pipe(Effect.map((tasks) => ({ tasks }))), |
| ... | ... | @@ -674,15 +671,15 @@ describe("orchestrator MCP toolkit", () => { |
| 674 | 671 | triggerWebhook: () => Effect.die("unused in this test"), |
| 675 | 672 | }), |
| 676 | 673 | ); |
| 677 | const testLayer = Layer.merge( | |
| 678 | McpHttpServer.OrchestratorToolkitRegistrationLive, | |
| 679 | McpHttpServer.ThreadToolkitRegistrationLive, | |
| 674 | const layerTest = Layer.merge( | |
| 675 | McpHttpServer.layerOrchestratorToolkit, | |
| 676 | McpHttpServer.layerThreadToolkit, | |
| 680 | 677 | ).pipe( |
| 681 | 678 | Layer.provideMerge(McpServer.McpServer.layer), |
| 682 | Layer.provideMerge(orchestrationLayer), | |
| 683 | Layer.provide(registryLayer), | |
| 684 | Layer.provide(providerRegistryLayer), | |
| 685 | Layer.provide(scheduledTaskStubLayer), | |
| 679 | Layer.provideMerge(layerOrchestration), | |
| 680 | Layer.provide(layerRegistry), | |
| 681 | Layer.provide(layerProviderRegistry), | |
| 682 | Layer.provide(layerScheduledTaskStub), | |
| 686 | 683 | Layer.provide( |
| 687 | 684 | Layer.mock(ProjectService.ProjectService)({ |
| 688 | 685 | getById: (id) => |
| ... | ... | @@ -695,8 +692,8 @@ describe("orchestrator MCP toolkit", () => { |
| 695 | 692 | ), |
| 696 | 693 | Layer.provideMerge( |
| 697 | 694 | SecretRequests.layer.pipe( |
| 698 | Layer.provide(memorySecretStoreLayer), | |
| 699 | Layer.provide(orchestrationLayer), | |
| 695 | Layer.provide(layerMemorySecretStore), | |
| 696 | Layer.provide(layerOrchestration), | |
| 700 | 697 | ), |
| 701 | 698 | ), |
| 702 | 699 | Layer.provide(NodeServices.layer), |
| ... | ... | @@ -3677,7 +3674,7 @@ describe("orchestrator MCP toolkit", () => { |
| 3677 | 3674 | thirdFanoutDelivery.messageId, |
| 3678 | 3675 | ]), |
| 3679 | 3676 | ); |
| 3680 | }).pipe(Effect.provide(testLayer)); | |
| 3677 | }).pipe(Effect.provide(layerTest)); | |
| 3681 | 3678 | }), |
| 3682 | 3679 | ), |
| 3683 | 3680 | ); |
| ... | ... | @@ -3690,7 +3687,7 @@ describe("orchestrator MCP toolkit", () => { |
| 3690 | 3687 | const transcript = yield* CodexOrchestratorReplayHarness.decodeTranscript( |
| 3691 | 3688 | materializeReplayTranscriptWorkspace(rawTranscript, cwd), |
| 3692 | 3689 | ); |
| 3693 | const orchestratorLayer = makeOrchestratorV2ProviderReplayLayer( | |
| 3690 | const layerOrchestrator = ProviderReplayHarness.layerProviderReplay( | |
| 3694 | 3691 | { |
| 3695 | 3692 | name: "delegated-task-status/codex", |
| 3696 | 3693 | transcript, |
| ... | ... | @@ -3699,30 +3696,30 @@ describe("orchestrator MCP toolkit", () => { |
| 3699 | 3696 | }, |
| 3700 | 3697 | CodexOrchestratorReplayHarness, |
| 3701 | 3698 | ); |
| 3702 | const orchestrationLayer = Layer.merge( | |
| 3703 | orchestratorLayer, | |
| 3704 | ThreadManagementService.layer.pipe(Layer.provide(orchestratorLayer)), | |
| 3699 | const layerOrchestration = Layer.merge( | |
| 3700 | layerOrchestrator, | |
| 3701 | ThreadManagementService.layer.pipe(Layer.provide(layerOrchestrator)), | |
| 3705 | 3702 | ); |
| 3706 | const providerRegistryLayer = makeProviderRegistryLayer([ | |
| 3703 | const layerProviderRegistry = ProviderRegistryMock.layer([ | |
| 3707 | 3704 | makeProviderSnapshot({ |
| 3708 | 3705 | instanceId: codexInstanceId, |
| 3709 | 3706 | driver: ProviderDriverKind.make("codex"), |
| 3710 | 3707 | model: codexModel, |
| 3711 | 3708 | }), |
| 3712 | 3709 | ]); |
| 3713 | const testLayer = McpHttpServer.OrchestratorToolkitRegistrationLive.pipe( | |
| 3710 | const layerTest = McpHttpServer.layerOrchestratorToolkit.pipe( | |
| 3714 | 3711 | Layer.provideMerge(McpServer.McpServer.layer), |
| 3715 | Layer.provideMerge(orchestrationLayer), | |
| 3712 | Layer.provideMerge(layerOrchestration), | |
| 3716 | 3713 | Layer.provide( |
| 3717 | 3714 | CodexOrchestratorReplayHarness.makeProviderAdapterRegistryLayer(transcript), |
| 3718 | 3715 | ), |
| 3719 | Layer.provide(providerRegistryLayer), | |
| 3720 | Layer.provide(unusedScheduledTaskStubLayer), | |
| 3716 | Layer.provide(layerProviderRegistry), | |
| 3717 | Layer.provide(layerUnusedScheduledTaskStub), | |
| 3721 | 3718 | Layer.provide(Layer.mock(ProjectService.ProjectService)({})), |
| 3722 | 3719 | Layer.provideMerge( |
| 3723 | 3720 | SecretRequests.layer.pipe( |
| 3724 | Layer.provide(memorySecretStoreLayer), | |
| 3725 | Layer.provide(orchestrationLayer), | |
| 3721 | Layer.provide(layerMemorySecretStore), | |
| 3722 | Layer.provide(layerOrchestration), | |
| 3726 | 3723 | ), |
| 3727 | 3724 | ), |
| 3728 | 3725 | Layer.provide(NodeServices.layer), |
| ... | ... | @@ -3998,7 +3995,7 @@ describe("orchestrator MCP toolkit", () => { |
| 3998 | 3995 | latestTerminalSummary: queuedFollowupResult, |
| 3999 | 3996 | latestTerminalResultContextTransferId: null, |
| 4000 | 3997 | }); |
| 4001 | }).pipe(Effect.provide(testLayer)); | |
| 3998 | }).pipe(Effect.provide(layerTest)); | |
| 4002 | 3999 | }), |
| 4003 | 4000 | ), |
| 4004 | 4001 | ); |
apps/server/src/mcp/PreviewAutomationBroker.test.ts+2-2| ... | ... | @@ -29,7 +29,7 @@ import * as TestClock from "effect/testing/TestClock"; |
| 29 | 29 | import * as RpcGroup from "effect/rpc/RpcGroup"; |
| 30 | 30 | import * as RpcTest from "effect/rpc/RpcTest"; |
| 31 | 31 | |
| 32 | import { rpcScopeAuthorizationLayer } from "../auth/RpcAuthorization.ts"; | |
| 32 | import * as RpcAuthorization from "../auth/RpcAuthorization.ts"; | |
| 33 | 33 | import * as PreviewAutomationBroker from "./PreviewAutomationBroker.ts"; |
| 34 | 34 | |
| 35 | 35 | const makeBroker = PreviewAutomationBroker.make.pipe(Effect.provide(NodeServices.layer)); |
| ... | ... | @@ -1276,7 +1276,7 @@ it.effect("evicts an unanswered host and lets later calls use a healthy runtime" |
| 1276 | 1276 | group.toLayer({ |
| 1277 | 1277 | [WS_METHODS.previewAutomationConnect]: (host) => Stream.unwrap(broker.connect(host)), |
| 1278 | 1278 | }), |
| 1279 | rpcScopeAuthorizationLayer([AuthOrchestrationOperateScope]), | |
| 1279 | RpcAuthorization.layer([AuthOrchestrationOperateScope]), | |
| 1280 | 1280 | ), |
| 1281 | 1281 | ), |
| 1282 | 1282 | ); |
apps/server/src/mcp/ThreadMetadataMcpService.test.ts+5-5| ... | ... | @@ -23,7 +23,7 @@ const scope: McpInvocationContext.McpInvocationScope = { |
| 23 | 23 | issuedAt: 1, |
| 24 | 24 | }; |
| 25 | 25 | |
| 26 | function serviceLayer( | |
| 26 | function layerService( | |
| 27 | 27 | getThreadShell: ThreadManagement.ThreadManagementService["Service"]["getThreadShell"], |
| 28 | 28 | ) { |
| 29 | 29 | return ThreadMetadataMcp.layer.pipe( |
| ... | ... | @@ -51,7 +51,7 @@ const updateCallingThread = Effect.gen(function* () { |
| 51 | 51 | it.effect("reports an absent calling thread as thread_not_found", () => |
| 52 | 52 | Effect.gen(function* () { |
| 53 | 53 | const error = yield* updateCallingThread.pipe( |
| 54 | Effect.provide(serviceLayer(() => Effect.succeed(null))), | |
| 54 | Effect.provide(layerService(() => Effect.succeed(null))), | |
| 55 | 55 | Effect.flip, |
| 56 | 56 | ); |
| 57 | 57 | |
| ... | ... | @@ -63,7 +63,7 @@ it.effect("keeps calling-thread storage failures as orchestration errors", () => |
| 63 | 63 | Effect.gen(function* () { |
| 64 | 64 | const error = yield* updateCallingThread.pipe( |
| 65 | 65 | Effect.provide( |
| 66 | serviceLayer(() => | |
| 66 | layerService(() => | |
| 67 | 67 | Effect.fail( |
| 68 | 68 | new OrchestratorProjectionError({ |
| 69 | 69 | threadId, |
| ... | ... | @@ -115,7 +115,7 @@ it.effect("refuses to change a thread that runs above the caller's modes", () => |
| 115 | 115 | title: "Renamed from a narrower thread", |
| 116 | 116 | }); |
| 117 | 117 | }).pipe( |
| 118 | Effect.provide(serviceLayer((id) => Effect.succeed((shells.get(id) ?? null) as never))), | |
| 118 | Effect.provide(layerService((id) => Effect.succeed((shells.get(id) ?? null) as never))), | |
| 119 | 119 | Effect.flip, |
| 120 | 120 | ); |
| 121 | 121 | |
| ... | ... | @@ -159,7 +159,7 @@ it.effect("refuses another thread's metadata to a thread caller whose run has en |
| 159 | 159 | title: "Renamed after the run ended", |
| 160 | 160 | }); |
| 161 | 161 | }).pipe( |
| 162 | Effect.provide(serviceLayer((id) => Effect.succeed((shells.get(id) ?? null) as never))), | |
| 162 | Effect.provide(layerService((id) => Effect.succeed((shells.get(id) ?? null) as never))), | |
| 163 | 163 | Effect.flip, |
| 164 | 164 | ); |
| 165 | 165 |
apps/server/src/mcp/WorktreeMcpService.test.ts+2-2| ... | ... | @@ -294,7 +294,7 @@ const makeHarness = (options: HarnessOptions = {}) => { |
| 294 | 294 | // inlined so the test does not depend on the host's path module. |
| 295 | 295 | const win32IsAbsolute = (value: string) => /^(?:[a-zA-Z]:[\\/]|[\\/])/.test(value); |
| 296 | 296 | const posixIsAbsolute = (value: string) => value.startsWith("/"); |
| 297 | const serviceLayer = | |
| 297 | const layerService = | |
| 298 | 298 | options.pathSemantics === undefined |
| 299 | 299 | ? WorktreeMcpService.layer |
| 300 | 300 | : WorktreeMcpService.layer.pipe( |
| ... | ... | @@ -304,7 +304,7 @@ const makeHarness = (options: HarnessOptions = {}) => { |
| 304 | 304 | } as unknown as Path.Path), |
| 305 | 305 | ), |
| 306 | 306 | ); |
| 307 | const layer = serviceLayer.pipe( | |
| 307 | const layer = layerService.pipe( | |
| 308 | 308 | Layer.provide( |
| 309 | 309 | Layer.mergeAll( |
| 310 | 310 | Layer.mock(ThreadManagementService.ThreadManagementService)({ |
apps/server/src/mcp/toolkits/attachment/handlers.ts+1-1| ... | ... | @@ -31,7 +31,7 @@ export function resolveAttachmentReferences( |
| 31 | 31 | }); |
| 32 | 32 | } |
| 33 | 33 | |
| 34 | export const AttachmentHandlersLive = AttachmentToolkit.toLayer({ | |
| 34 | export const layer = AttachmentToolkit.toLayer({ | |
| 35 | 35 | t3_attachment_prepare_upload: (input) => |
| 36 | 36 | Effect.gen(function* () { |
| 37 | 37 | yield* readMutationCaller(); |
apps/server/src/mcp/toolkits/core.test.ts+7-7| ... | ... | @@ -137,7 +137,7 @@ it.effect("checks capability before accessing services through the production re |
| 137 | 137 | expect(declaredFailure(result)).toMatchObject({ code: "capability_denied" }); |
| 138 | 138 | }).pipe( |
| 139 | 139 | Effect.provide( |
| 140 | McpHttpServer.ThreadToolkitRegistrationLive.pipe( | |
| 140 | McpHttpServer.layerThreadToolkit.pipe( | |
| 141 | 141 | Layer.provideMerge(McpServer.McpServer.layer), |
| 142 | 142 | Layer.provide(NodeCrypto.layer), |
| 143 | 143 | Layer.provide(Layer.mock(ThreadManagement.ThreadManagementService)({})), |
| ... | ... | @@ -162,7 +162,7 @@ it.effect("returns a bounded public failure without serializing storage causes", |
| 162 | 162 | }); |
| 163 | 163 | }).pipe( |
| 164 | 164 | Effect.provide( |
| 165 | McpHttpServer.ThreadToolkitRegistrationLive.pipe( | |
| 165 | McpHttpServer.layerThreadToolkit.pipe( | |
| 166 | 166 | Layer.provideMerge(McpServer.McpServer.layer), |
| 167 | 167 | Layer.provide(NodeCrypto.layer), |
| 168 | 168 | Layer.provide( |
| ... | ... | @@ -203,7 +203,7 @@ it.effect("returns an HTML render reference that Codex and Claude tool rows both |
| 203 | 203 | ).toEqual(reference); |
| 204 | 204 | }).pipe( |
| 205 | 205 | Effect.provide( |
| 206 | McpHttpServer.HtmlToolkitRegistrationLive.pipe( | |
| 206 | McpHttpServer.layerHtmlToolkit.pipe( | |
| 207 | 207 | Layer.provideMerge(McpServer.McpServer.layer), |
| 208 | 208 | Layer.provide(ServerConfig.layerTest(process.cwd(), { prefix: "t3-mcp-html-render-" })), |
| 209 | 209 | Layer.provide(NodeServices.layer), |
| ... | ... | @@ -332,7 +332,7 @@ it.effect("a client caller targets any thread within its ceiling and cannot act |
| 332 | 332 | expect(declaredFailure(forked)).toMatchObject({ code: "target_required" }); |
| 333 | 333 | }).pipe( |
| 334 | 334 | Effect.provide( |
| 335 | McpHttpServer.ThreadToolkitRegistrationLive.pipe( | |
| 335 | McpHttpServer.layerThreadToolkit.pipe( | |
| 336 | 336 | Layer.provideMerge(McpServer.McpServer.layer), |
| 337 | 337 | Layer.provide(NodeCrypto.layer), |
| 338 | 338 | Layer.provide( |
| ... | ... | @@ -379,7 +379,7 @@ it.effect("refuses act-as-caller tools to a client caller", () => |
| 379 | 379 | expect(declaredFailure(result)).toMatchObject({ code: "thread_credential_required" }); |
| 380 | 380 | }).pipe( |
| 381 | 381 | Effect.provide( |
| 382 | McpHttpServer.OrchestratorToolkitRegistrationLive.pipe( | |
| 382 | McpHttpServer.layerOrchestratorToolkit.pipe( | |
| 383 | 383 | Layer.provideMerge(McpServer.McpServer.layer), |
| 384 | 384 | Layer.provide(NodeCrypto.layer), |
| 385 | 385 | Layer.provide(Layer.mock(ThreadManagement.ThreadManagementService)({})), |
| ... | ... | @@ -417,7 +417,7 @@ it.effect("a caller cannot rewrite a scheduled task that runs above its own mode |
| 417 | 417 | expect(allowed.isError).toBe(false); |
| 418 | 418 | }).pipe( |
| 419 | 419 | Effect.provide( |
| 420 | McpHttpServer.OrchestratorToolkitRegistrationLive.pipe( | |
| 420 | McpHttpServer.layerOrchestratorToolkit.pipe( | |
| 421 | 421 | Layer.provideMerge(McpServer.McpServer.layer), |
| 422 | 422 | Layer.provide(NodeCrypto.layer), |
| 423 | 423 | Layer.provide(Layer.mock(ThreadManagement.ThreadManagementService)({})), |
| ... | ... | @@ -483,7 +483,7 @@ it.effect("a caller cannot interrupt a thread that runs above its own modes", () |
| 483 | 483 | expect(declaredFailure(result)).toMatchObject({ code: "runtime_mode_escalation_denied" }); |
| 484 | 484 | }).pipe( |
| 485 | 485 | Effect.provide( |
| 486 | McpHttpServer.OrchestratorToolkitRegistrationLive.pipe( | |
| 486 | McpHttpServer.layerOrchestratorToolkit.pipe( | |
| 487 | 487 | Layer.provideMerge(McpServer.McpServer.layer), |
| 488 | 488 | Layer.provide(NodeCrypto.layer), |
| 489 | 489 | Layer.provide( |
apps/server/src/mcp/toolkits/device/handlers.ts+2-2| ... | ... | @@ -258,8 +258,8 @@ export function pngDimensions(png: Uint8Array): { width: number; height: number |
| 258 | 258 | |
| 259 | 259 | const { device_screenshot, ...standardHandlers } = handlers; |
| 260 | 260 | |
| 261 | export const DeviceStandardToolkitHandlersLive = DeviceStandardToolkit.toLayer(standardHandlers); | |
| 261 | export const layerStandard = DeviceStandardToolkit.toLayer(standardHandlers); | |
| 262 | 262 | |
| 263 | export const DeviceScreenshotToolkitHandlersLive = DeviceScreenshotToolkit.toLayer({ | |
| 263 | export const layerScreenshot = DeviceScreenshotToolkit.toLayer({ | |
| 264 | 264 | device_screenshot, |
| 265 | 265 | }); |
apps/server/src/mcp/toolkits/environment/handlers.ts+1-1| ... | ... | @@ -44,7 +44,7 @@ const access = (writable = false) => |
| 44 | 44 | }); |
| 45 | 45 | return { ...context, descriptor, settings: yield* Settings.ServerSettingsService }; |
| 46 | 46 | }); |
| 47 | export const EnvironmentHandlersLive = EnvironmentToolkit.toLayer({ | |
| 47 | export const layer = EnvironmentToolkit.toLayer({ | |
| 48 | 48 | t3_environment_read: () => |
| 49 | 49 | Effect.gen(function* () { |
| 50 | 50 | const { descriptor, settings } = yield* access(); |
apps/server/src/mcp/toolkits/html/handlers.ts+2-2| ... | ... | @@ -58,10 +58,10 @@ const handlers = { |
| 58 | 58 | }), |
| 59 | 59 | } satisfies Parameters<typeof HtmlToolkit.toLayer>[0]; |
| 60 | 60 | |
| 61 | export const HtmlPreviewToolkitHandlersLive = HtmlPreviewToolkit.toLayer({ | |
| 61 | export const layerPreview = HtmlPreviewToolkit.toLayer({ | |
| 62 | 62 | html_preview: handlers.html_preview, |
| 63 | 63 | }); |
| 64 | 64 | |
| 65 | export const HtmlRenderToolkitHandlersLive = HtmlRenderToolkit.toLayer({ | |
| 65 | export const layerRender = HtmlRenderToolkit.toLayer({ | |
| 66 | 66 | html_render: handlers.html_render, |
| 67 | 67 | }); |
apps/server/src/mcp/toolkits/orchestrator/handlers.ts+1-1| ... | ... | @@ -104,4 +104,4 @@ const handlers = { |
| 104 | 104 | }), |
| 105 | 105 | } satisfies Parameters<typeof OrchestratorToolkit.toLayer>[0]; |
| 106 | 106 | |
| 107 | export const OrchestratorToolkitHandlersLive = OrchestratorToolkit.toLayer(handlers); | |
| 107 | export const layer = OrchestratorToolkit.toLayer(handlers); |
apps/server/src/mcp/toolkits/preview/handlers.ts+2-2| ... | ... | @@ -233,8 +233,8 @@ const handlers = { |
| 233 | 233 | |
| 234 | 234 | const { preview_snapshot, ...standardHandlers } = handlers; |
| 235 | 235 | |
| 236 | export const PreviewStandardToolkitHandlersLive = PreviewStandardToolkit.toLayer(standardHandlers); | |
| 236 | export const layerStandard = PreviewStandardToolkit.toLayer(standardHandlers); | |
| 237 | 237 | |
| 238 | export const PreviewSnapshotToolkitHandlersLive = PreviewSnapshotToolkit.toLayer({ | |
| 238 | export const layerSnapshot = PreviewSnapshotToolkit.toLayer({ | |
| 239 | 239 | preview_snapshot, |
| 240 | 240 | }); |
apps/server/src/mcp/toolkits/previewControls/handlers.test.ts+5-5| ... | ... | @@ -14,7 +14,7 @@ import * as Stream from "effect/Stream"; |
| 14 | 14 | import * as Preview from "../../../preview/Manager.ts"; |
| 15 | 15 | import * as ServerSettings from "../../../serverSettings.ts"; |
| 16 | 16 | import * as McpInvocationContext from "../../McpInvocationContext.ts"; |
| 17 | import { PreviewControlsHandlersLive } from "./handlers.ts"; | |
| 17 | import * as PreviewControlsHandlers from "./handlers.ts"; | |
| 18 | 18 | import { PreviewControlsToolkit } from "./tools.ts"; |
| 19 | 19 | |
| 20 | 20 | it.effect.each([ |
| ... | ... | @@ -47,7 +47,7 @@ it.effect.each([ |
| 47 | 47 | }; |
| 48 | 48 | const manager = yield* Preview.make; |
| 49 | 49 | const tab = yield* manager.open({ threadId, url: "http://localhost:3000" }); |
| 50 | const dependencies = Layer.mergeAll( | |
| 50 | const layerDependencies = Layer.mergeAll( | |
| 51 | 51 | Layer.succeed(Preview.PreviewManager, manager), |
| 52 | 52 | Layer.succeed(McpInvocationContext.McpInvocationContext, scope), |
| 53 | 53 | Layer.mock(ServerSettings.ServerSettingsService)({ |
| ... | ... | @@ -55,14 +55,14 @@ it.effect.each([ |
| 55 | 55 | }), |
| 56 | 56 | ); |
| 57 | 57 | const toolkit = yield* PreviewControlsToolkit.pipe( |
| 58 | Effect.provide(PreviewControlsHandlersLive.pipe(Layer.provide(dependencies))), | |
| 58 | Effect.provide(PreviewControlsHandlers.layer.pipe(Layer.provide(layerDependencies))), | |
| 59 | 59 | ); |
| 60 | 60 | const listed = yield* toolkit |
| 61 | 61 | .handle("t3_preview_list", {}) |
| 62 | .pipe(Stream.unwrap, Stream.runCollect, Effect.provide(dependencies)); | |
| 62 | .pipe(Stream.unwrap, Stream.runCollect, Effect.provide(layerDependencies)); | |
| 63 | 63 | const closed = yield* toolkit |
| 64 | 64 | .handle("t3_preview_close", { tabId: tab.tabId }) |
| 65 | .pipe(Stream.unwrap, Stream.runCollect, Effect.provide(dependencies)); | |
| 65 | .pipe(Stream.unwrap, Stream.runCollect, Effect.provide(layerDependencies)); | |
| 66 | 66 | if (projectAccess) { |
| 67 | 67 | expect(listed.at(-1)?.result).toMatchObject({ sessions: [tab], nextCursor: null }); |
| 68 | 68 | expect(closed.at(-1)?.result).toEqual({}); |
apps/server/src/mcp/toolkits/previewControls/handlers.ts+1-1| ... | ... | @@ -9,7 +9,7 @@ const access = Effect.gen(function* () { |
| 9 | 9 | const scope = yield* requireThreadMcpCapability("preview"); |
| 10 | 10 | return { scope, manager: yield* Preview.PreviewManager }; |
| 11 | 11 | }); |
| 12 | export const PreviewControlsHandlersLive = PreviewControlsToolkit.toLayer({ | |
| 12 | export const layer = PreviewControlsToolkit.toLayer({ | |
| 13 | 13 | t3_preview_list: (input) => |
| 14 | 14 | Effect.gen(function* () { |
| 15 | 15 | const { scope, manager } = yield* access; |
apps/server/src/mcp/toolkits/project/handlers.test.ts+13-13| ... | ... | @@ -20,7 +20,7 @@ import * as ServerConfig from "../../../config.ts"; |
| 20 | 20 | import * as Project from "../../../project/ProjectService.ts"; |
| 21 | 21 | import * as ManagedProjectFolders from "../../../project/ManagedProjectFolders.ts"; |
| 22 | 22 | import * as McpInvocationContext from "../../McpInvocationContext.ts"; |
| 23 | import { ProjectHandlersLive } from "./handlers.ts"; | |
| 23 | import * as ProjectHandlers from "./handlers.ts"; | |
| 24 | 24 | import { ProjectToolkit } from "./tools.ts"; |
| 25 | 25 | |
| 26 | 26 | it.effect("attributes a launched thread's first message to the calling thread", () => |
| ... | ... | @@ -41,7 +41,7 @@ it.effect("attributes a launched thread's first message to the calling thread", |
| 41 | 41 | deletedAt: null, |
| 42 | 42 | } as OrchestrationV2ThreadShell; |
| 43 | 43 | let launchedSender: ThreadId | undefined; |
| 44 | const dependencies = Layer.mergeAll( | |
| 44 | const layerDependencies = Layer.mergeAll( | |
| 45 | 45 | NodeCrypto.layer, |
| 46 | 46 | Layer.succeed(McpInvocationContext.McpInvocationContext, { |
| 47 | 47 | environmentId: EnvironmentId.make("environment"), |
| ... | ... | @@ -79,11 +79,11 @@ it.effect("attributes a launched thread's first message to the calling thread", |
| 79 | 79 | ), |
| 80 | 80 | ); |
| 81 | 81 | const toolkit = yield* ProjectToolkit.pipe( |
| 82 | Effect.provide(ProjectHandlersLive.pipe(Layer.provide(dependencies))), | |
| 82 | Effect.provide(ProjectHandlers.layer.pipe(Layer.provide(layerDependencies))), | |
| 83 | 83 | ); |
| 84 | 84 | const result = yield* toolkit |
| 85 | 85 | .handle("t3_thread_launch", { title: "Audit", message: "Review the change" }) |
| 86 | .pipe(Stream.unwrap, Stream.runCollect, Effect.provide(dependencies)); | |
| 86 | .pipe(Stream.unwrap, Stream.runCollect, Effect.provide(layerDependencies)); | |
| 87 | 87 | expect(result.at(-1)?.result).toMatchObject({ projectId, modelSelection }); |
| 88 | 88 | expect(launchedSender).toBe(sourceThreadId); |
| 89 | 89 | }), |
| ... | ... | @@ -107,7 +107,7 @@ it.effect("launches a scratch thread into the Scratch project", () => |
| 107 | 107 | deletedAt: null, |
| 108 | 108 | } as OrchestrationV2ThreadShell; |
| 109 | 109 | const launched: Array<ThreadLaunch.ThreadLaunchInput> = []; |
| 110 | const dependencies = Layer.mergeAll( | |
| 110 | const layerDependencies = Layer.mergeAll( | |
| 111 | 111 | NodeCrypto.layer, |
| 112 | 112 | Layer.succeed(McpInvocationContext.McpInvocationContext, { |
| 113 | 113 | environmentId: EnvironmentId.make("environment"), |
| ... | ... | @@ -148,12 +148,12 @@ it.effect("launches a scratch thread into the Scratch project", () => |
| 148 | 148 | ), |
| 149 | 149 | ); |
| 150 | 150 | const toolkit = yield* ProjectToolkit.pipe( |
| 151 | Effect.provide(ProjectHandlersLive.pipe(Layer.provide(dependencies))), | |
| 151 | Effect.provide(ProjectHandlers.layer.pipe(Layer.provide(layerDependencies))), | |
| 152 | 152 | ); |
| 153 | 153 | const handle = (params: Parameters<typeof toolkit.handle<"t3_thread_launch">>[1]) => |
| 154 | 154 | toolkit |
| 155 | 155 | .handle("t3_thread_launch", params) |
| 156 | .pipe(Stream.unwrap, Stream.runCollect, Effect.provide(dependencies)); | |
| 156 | .pipe(Stream.unwrap, Stream.runCollect, Effect.provide(layerDependencies)); | |
| 157 | 157 | |
| 158 | 158 | const result = yield* handle({ title: "Notes", scratch: true, message: "Draft a list" }); |
| 159 | 159 | expect(result.at(-1)?.result).toMatchObject({ projectId: scratchProjectId }); |
| ... | ... | @@ -199,7 +199,7 @@ it.effect("starts a project from just a title when workspaceRoot is omitted", () |
| 199 | 199 | updatedAt: "2026-10-01T00:00:00.000Z", |
| 200 | 200 | deletedAt: null, |
| 201 | 201 | }; |
| 202 | const dependencies = Layer.mergeAll( | |
| 202 | const layerDependencies = Layer.mergeAll( | |
| 203 | 203 | NodeCrypto.layer, |
| 204 | 204 | Layer.succeed(McpInvocationContext.McpInvocationContext, { |
| 205 | 205 | environmentId: EnvironmentId.make("environment"), |
| ... | ... | @@ -246,12 +246,12 @@ it.effect("starts a project from just a title when workspaceRoot is omitted", () |
| 246 | 246 | ), |
| 247 | 247 | ); |
| 248 | 248 | const toolkit = yield* ProjectToolkit.pipe( |
| 249 | Effect.provide(ProjectHandlersLive.pipe(Layer.provide(dependencies))), | |
| 249 | Effect.provide(ProjectHandlers.layer.pipe(Layer.provide(layerDependencies))), | |
| 250 | 250 | ); |
| 251 | 251 | const handle = (params: Parameters<typeof toolkit.handle<"t3_project_create">>[1]) => |
| 252 | 252 | toolkit |
| 253 | 253 | .handle("t3_project_create", params) |
| 254 | .pipe(Stream.unwrap, Stream.runCollect, Effect.provide(dependencies)); | |
| 254 | .pipe(Stream.unwrap, Stream.runCollect, Effect.provide(layerDependencies)); | |
| 255 | 255 | |
| 256 | 256 | const result = yield* handle({ title: "Pinball Stats" }); |
| 257 | 257 | expect(result.at(-1)?.result).toMatchObject({ |
| ... | ... | @@ -283,7 +283,7 @@ const clientLaunchHarness = (input: { |
| 283 | 283 | }) => { |
| 284 | 284 | const projectId = ProjectId.make("project:client-target"); |
| 285 | 285 | const modelSelection = { instanceId: ProviderInstanceId.make("claude"), model: "claude-opus" }; |
| 286 | const dependencies = Layer.mergeAll( | |
| 286 | const layerDependencies = Layer.mergeAll( | |
| 287 | 287 | NodeCrypto.layer, |
| 288 | 288 | Layer.succeed(McpInvocationContext.McpInvocationContext, { |
| 289 | 289 | environmentId: EnvironmentId.make("environment"), |
| ... | ... | @@ -329,7 +329,7 @@ const clientLaunchHarness = (input: { |
| 329 | 329 | Layer.provide(NodeServices.layer), |
| 330 | 330 | ), |
| 331 | 331 | ); |
| 332 | return { projectId, modelSelection, dependencies }; | |
| 332 | return { projectId, modelSelection, dependencies: layerDependencies }; | |
| 333 | 333 | }; |
| 334 | 334 | |
| 335 | 335 | it.effect("a client launches at its ceiling with the project's default model", () => |
| ... | ... | @@ -340,7 +340,7 @@ it.effect("a client launches at its ceiling with the project's default model", ( |
| 340 | 340 | launched, |
| 341 | 341 | }); |
| 342 | 342 | const toolkit = yield* ProjectToolkit.pipe( |
| 343 | Effect.provide(ProjectHandlersLive.pipe(Layer.provide(dependencies))), | |
| 343 | Effect.provide(ProjectHandlers.layer.pipe(Layer.provide(dependencies))), | |
| 344 | 344 | ); |
| 345 | 345 | const handle = (params: Parameters<typeof toolkit.handle<"t3_thread_launch">>[1]) => |
| 346 | 346 | toolkit |
apps/server/src/mcp/toolkits/project/handlers.ts+1-1| ... | ... | @@ -38,7 +38,7 @@ const mutation = Effect.gen(function* () { |
| 38 | 38 | ); |
| 39 | 39 | return yield* Project.ProjectService; |
| 40 | 40 | }); |
| 41 | export const ProjectHandlersLive = ProjectToolkit.toLayer({ | |
| 41 | export const layer = ProjectToolkit.toLayer({ | |
| 42 | 42 | t3_thread_launch: (input) => |
| 43 | 43 | Effect.gen(function* () { |
| 44 | 44 | const context = yield* readMutationCaller(); |
apps/server/src/mcp/toolkits/pullRequests/handlers.test.ts+5-4| ... | ... | @@ -23,7 +23,8 @@ import { |
| 23 | 23 | } from "../../../orchestration-v2/testkit/pullRequestFixtures.ts"; |
| 24 | 24 | import * as ProjectService from "../../../project/ProjectService.ts"; |
| 25 | 25 | import * as McpInvocationContext from "../../McpInvocationContext.ts"; |
| 26 | import { listThreadPullRequests, PullRequestsToolkitHandlersLive } from "./handlers.ts"; | |
| 26 | import { listThreadPullRequests } from "./handlers.ts"; | |
| 27 | import * as PullRequestsHandlers from "./handlers.ts"; | |
| 27 | 28 | import { PullRequestLinkFailedError, PullRequestsToolkit } from "./tools.ts"; |
| 28 | 29 | |
| 29 | 30 | const PROJECT_ID = ProjectId.make("project-1"); |
| ... | ... | @@ -152,7 +153,7 @@ const makeHarness = Effect.fn("makePullRequestsToolkitHarness")(function* ( |
| 152 | 153 | yield* Ref.update(commands, (recorded) => [...recorded, command]); |
| 153 | 154 | return { sequence: 1, storedEvents: [] }; |
| 154 | 155 | }); |
| 155 | const dependencies = Layer.mergeAll( | |
| 156 | const layerDependencies = Layer.mergeAll( | |
| 156 | 157 | Layer.mock(ProjectService.ProjectService)({ |
| 157 | 158 | getShell: () => Effect.succeed(Option.fromNullishOr(project)), |
| 158 | 159 | }), |
| ... | ... | @@ -164,7 +165,7 @@ const makeHarness = Effect.fn("makePullRequestsToolkitHarness")(function* ( |
| 164 | 165 | Layer.succeed(Crypto.Crypto, testCrypto), |
| 165 | 166 | ); |
| 166 | 167 | const toolkit = yield* PullRequestsToolkit.pipe( |
| 167 | Effect.provide(PullRequestsToolkitHandlersLive.pipe(Layer.provide(dependencies))), | |
| 168 | Effect.provide(PullRequestsHandlers.layer.pipe(Layer.provide(layerDependencies))), | |
| 168 | 169 | ); |
| 169 | 170 | const call = <Name extends keyof typeof PullRequestsToolkit.tools>( |
| 170 | 171 | name: Name, |
| ... | ... | @@ -179,7 +180,7 @@ const makeHarness = Effect.fn("makePullRequestsToolkitHarness")(function* ( |
| 179 | 180 | (chunk) => chunk.at(-1)!.result as Tool.Success<(typeof PullRequestsToolkit.tools)[Name]>, |
| 180 | 181 | ), |
| 181 | 182 | Effect.provideService(McpInvocationContext.McpInvocationContext, invocation(capabilities)), |
| 182 | Effect.provide(dependencies), | |
| 183 | Effect.provide(layerDependencies), | |
| 183 | 184 | ); |
| 184 | 185 | return { commands, call }; |
| 185 | 186 | }); |
apps/server/src/mcp/toolkits/pullRequests/handlers.ts+1-1| ... | ... | @@ -379,4 +379,4 @@ const make = Effect.gen(function* () { |
| 379 | 379 | }); |
| 380 | 380 | }); |
| 381 | 381 | |
| 382 | export const PullRequestsToolkitHandlersLive = PullRequestsToolkit.toLayer(make); | |
| 382 | export const layer = PullRequestsToolkit.toLayer(make); |
apps/server/src/mcp/toolkits/thread/handlers.ts+1-1| ... | ... | @@ -75,7 +75,7 @@ const readQuestion = Effect.fn("mcp.readQuestion")(function* ( |
| 75 | 75 | }); |
| 76 | 76 | return { ...context, request, item }; |
| 77 | 77 | }); |
| 78 | export const ThreadToolkitHandlersLive = ThreadToolkit.toLayer({ | |
| 78 | export const layer = ThreadToolkit.toLayer({ | |
| 79 | 79 | run_scheduled_task_now: (input) => |
| 80 | 80 | Effect.gen(function* () { |
| 81 | 81 | yield* readFullAccessCaller( |
apps/server/src/mcp/toolkits/worktree/handlers.ts+1-1| ... | ... | @@ -48,4 +48,4 @@ const handlers = { |
| 48 | 48 | }), |
| 49 | 49 | } satisfies Parameters<typeof WorktreeToolkit.toLayer>[0]; |
| 50 | 50 | |
| 51 | export const WorktreeToolkitHandlersLive = WorktreeToolkit.toLayer(handlers); | |
| 51 | export const layer = WorktreeToolkit.toLayer(handlers); |
apps/server/src/mcp/toolkits/worktree/registration.test.ts+4-4| ... | ... | @@ -26,7 +26,7 @@ import * as McpHttpServer from "../../McpHttpServer.ts"; |
| 26 | 26 | import * as McpSessionRegistry from "../../McpSessionRegistry.ts"; |
| 27 | 27 | import * as PreviewAutomationBroker from "../../PreviewAutomationBroker.ts"; |
| 28 | 28 | |
| 29 | const StubServicesLive = Layer.mergeAll( | |
| 29 | const layerStubServices = Layer.mergeAll( | |
| 30 | 30 | Layer.mock(Orchestrator.OrchestratorV2)({}), |
| 31 | 31 | Layer.mock(ProjectionStore.ProjectionStoreV2)({}), |
| 32 | 32 | Layer.mock(DeviceService.DeviceService)({}), |
| ... | ... | @@ -66,8 +66,8 @@ const decodeToolsListPayload = Schema.decodeUnknownEffect(ToolsListPayload); |
| 66 | 66 | it.effect("production mcp layer lists worktree tools over http", () => |
| 67 | 67 | Effect.scoped( |
| 68 | 68 | Effect.gen(function* () { |
| 69 | const routes = McpHttpServer.layer.pipe(Layer.provide(McpSessionRegistry.layer)); | |
| 70 | yield* HttpRouter.serve(routes, { | |
| 69 | const layerRoutes = McpHttpServer.layer.pipe(Layer.provide(McpSessionRegistry.layer)); | |
| 70 | yield* HttpRouter.serve(layerRoutes, { | |
| 71 | 71 | disableListenLog: true, |
| 72 | 72 | disableLogger: true, |
| 73 | 73 | }).pipe( |
| ... | ... | @@ -77,7 +77,7 @@ it.effect("production mcp layer lists worktree tools over http", () => |
| 77 | 77 | }), |
| 78 | 78 | ), |
| 79 | 79 | Layer.provide(PreviewAutomationBroker.layer), |
| 80 | Layer.provide(StubServicesLive), | |
| 80 | Layer.provide(layerStubServices), | |
| 81 | 81 | Layer.build, |
| 82 | 82 | ); |
| 83 | 83 |
apps/server/src/observability/Layers/Observability.ts+14-17| ... | ... | @@ -1,9 +1,6 @@ |
| 1 | import { httpHeaderRedactionLayer } from "@t3tools/shared/httpObservability"; | |
| 2 | import { | |
| 3 | makeLocalFileTracer, | |
| 4 | makeTraceSink, | |
| 5 | otlpSerializationLayer, | |
| 6 | } from "@t3tools/shared/observability"; | |
| 1 | import * as HttpObservability from "@t3tools/shared/httpObservability"; | |
| 2 | import { makeLocalFileTracer, makeTraceSink } from "@t3tools/shared/observability"; | |
| 3 | import * as SharedObservability from "@t3tools/shared/observability"; | |
| 7 | 4 | import * as OtelEnvironment from "@t3tools/shared/otelEnvironment"; |
| 8 | 5 | import * as Effect from "effect/Effect"; |
| 9 | 6 | import * as Layer from "effect/Layer"; |
| ... | ... | @@ -15,10 +12,10 @@ import * as OtlpTracer from "effect/observability/OtlpTracer"; |
| 15 | 12 | |
| 16 | 13 | import * as ServerConfig from "../../config.ts"; |
| 17 | 14 | import * as ResourceAttribution from "../../resourceTelemetry/ResourceAttribution.ts"; |
| 18 | import { ServerLoggerLive } from "../../serverLogger.ts"; | |
| 15 | import * as ServerLogger from "../../serverLogger.ts"; | |
| 19 | 16 | import * as BrowserTraceCollector from "../BrowserTraceCollector.ts"; |
| 20 | 17 | |
| 21 | export const ObservabilityLive = Layer.unwrap( | |
| 18 | export const layer = Layer.unwrap( | |
| 22 | 19 | Effect.gen(function* () { |
| 23 | 20 | const config = yield* ServerConfig.ServerConfig; |
| 24 | 21 | |
| ... | ... | @@ -26,17 +23,17 @@ export const ObservabilityLive = Layer.unwrap( |
| 26 | 23 | const metrics = config.otlpMetricsExport; |
| 27 | 24 | // The trace serializer stays in the returned context because the browser |
| 28 | 25 | // trace forwarder exports on the same signal. |
| 29 | const serializationLayer = otlpSerializationLayer(traces.protocol); | |
| 26 | const layerSerialization = SharedObservability.layerOtlpSerialization(traces.protocol); | |
| 30 | 27 | const resource = ServerConfig.otlpResource(config); |
| 31 | 28 | const attribution = yield* ResourceAttribution.ResourceAttribution; |
| 32 | 29 | |
| 33 | const traceReferencesLayer = Layer.mergeAll( | |
| 30 | const layerTraceReferences = Layer.mergeAll( | |
| 34 | 31 | Layer.succeed(Tracer.MinimumTraceLevel, config.traceMinLevel), |
| 35 | 32 | Layer.succeed(References.TracerTimingEnabled, config.traceTimingEnabled), |
| 36 | httpHeaderRedactionLayer, | |
| 33 | HttpObservability.layer, | |
| 37 | 34 | ); |
| 38 | 35 | |
| 39 | const tracerLayer = Layer.unwrap( | |
| 36 | const layerTracer = Layer.unwrap( | |
| 40 | 37 | Effect.gen(function* () { |
| 41 | 38 | const sink = yield* makeTraceSink({ |
| 42 | 39 | filePath: config.serverTracePath, |
| ... | ... | @@ -76,7 +73,7 @@ export const ObservabilityLive = Layer.unwrap( |
| 76 | 73 | BrowserTraceCollector.layer(sink), |
| 77 | 74 | ); |
| 78 | 75 | }), |
| 79 | ).pipe(Layer.provide(OtlpExporter.layerFlusher), Layer.provideMerge(serializationLayer)); | |
| 76 | ).pipe(Layer.provide(OtlpExporter.layerFlusher), Layer.provideMerge(layerSerialization)); | |
| 80 | 77 | |
| 81 | 78 | const metricsLayer = |
| 82 | 79 | config.otlpMetricsUrl === undefined |
| ... | ... | @@ -86,16 +83,16 @@ export const ObservabilityLive = Layer.unwrap( |
| 86 | 83 | exportInterval: `${metrics.exportIntervalMs} millis`, |
| 87 | 84 | headers: metrics.headers, |
| 88 | 85 | resource, |
| 89 | }).pipe(Layer.provide(otlpSerializationLayer(metrics.protocol))); | |
| 86 | }).pipe(Layer.provide(SharedObservability.layerOtlpSerialization(metrics.protocol))); | |
| 90 | 87 | |
| 91 | 88 | // Logged once the server's loggers are installed, so the warnings use them. |
| 92 | const otelWarningsLayer = Layer.effectDiscard( | |
| 89 | const layerOtelWarnings = Layer.effectDiscard( | |
| 93 | 90 | Effect.forEach(config.otelEnvironment.warnings, (warning) => Effect.logWarning(warning)), |
| 94 | 91 | ); |
| 95 | 92 | |
| 96 | return otelWarningsLayer.pipe( | |
| 93 | return layerOtelWarnings.pipe( | |
| 97 | 94 | Layer.provideMerge( |
| 98 | Layer.mergeAll(ServerLoggerLive, traceReferencesLayer, tracerLayer, metricsLayer), | |
| 95 | Layer.mergeAll(ServerLogger.layer, layerTraceReferences, layerTracer, metricsLayer), | |
| 99 | 96 | ), |
| 100 | 97 | Layer.provide( |
| 101 | 98 | OtelEnvironment.layerResourceAttributes(config.otelEnvironment.resourceAttributes), |
apps/server/src/orchestration-v2/AcpRegistryOrchestratorV2.live.test.ts+32-32| ... | ... | @@ -23,12 +23,12 @@ import * as CheckpointStore from "../checkpointing/CheckpointStore.ts"; |
| 23 | 23 | import * as BackgroundPolicy from "../background/BackgroundPolicy.ts"; |
| 24 | 24 | import * as HostPowerMonitor from "../background/HostPowerMonitor.ts"; |
| 25 | 25 | import * as ServerConfig from "../config.ts"; |
| 26 | import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts"; | |
| 26 | import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts"; | |
| 27 | 27 | import * as AntigravityInstallation from "../provider/AntigravityInstallation.ts"; |
| 28 | 28 | import * as CodexInstallation from "../provider/CodexInstallation.ts"; |
| 29 | 29 | import * as ServerEnvironment from "../environment/ServerEnvironment.ts"; |
| 30 | 30 | import * as ModelManifest from "../provider/ModelManifest.ts"; |
| 31 | import { ProviderInstanceRegistryHydrationLive } from "../provider/Layers/ProviderInstanceRegistryHydration.ts"; | |
| 31 | import * as ProviderInstanceRegistryHydration from "../provider/Layers/ProviderInstanceRegistryHydration.ts"; | |
| 32 | 32 | import * as ProviderEventLoggers from "../provider/Layers/ProviderEventLoggers.ts"; |
| 33 | 33 | import * as OpenCodeRuntime from "../provider/opencodeRuntime.ts"; |
| 34 | 34 | import * as OpenCodeServerLedger from "../provider/OpenCodeServerLedger.ts"; |
| ... | ... | @@ -36,8 +36,8 @@ import * as ServerSettings from "../serverSettings.ts"; |
| 36 | 36 | import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts"; |
| 37 | 37 | import * as VcsProcess from "../vcs/VcsProcess.ts"; |
| 38 | 38 | import * as Orchestrator from "./Orchestrator.ts"; |
| 39 | import { worktreeRepairDependenciesTestLayer } from "./ProviderTurnStartService.testkit.ts"; | |
| 40 | import { OrchestrationV2LayerLive } from "./runtimeLayer.ts"; | |
| 39 | import * as ProviderTurnStartServiceTestkit from "./ProviderTurnStartService.testkit.ts"; | |
| 40 | import * as RuntimeLayer from "./runtimeLayer.ts"; | |
| 41 | 41 | import * as McpSessionRegistryTestkit from "../mcp/McpSessionRegistry.testkit.ts"; |
| 42 | 42 | |
| 43 | 43 | // The Antigravity switch is a durable, named conformance fixture for Google's |
| ... | ... | @@ -46,7 +46,7 @@ import * as McpSessionRegistryTestkit from "../mcp/McpSessionRegistry.testkit.ts |
| 46 | 46 | // |
| 47 | 47 | // T3_ACP_ANTIGRAVITY_LIVE=1 ../../node_modules/.bin/vp test run \ |
| 48 | 48 | // src/orchestration-v2/AcpRegistryOrchestratorV2.live.test.ts |
| 49 | const PlatformTestLayer = Layer.merge( | |
| 49 | const layerPlatformTest = Layer.merge( | |
| 50 | 50 | NodeServices.layer, |
| 51 | 51 | Layer.mock(SourceControlProviderRegistry.SourceControlProviderRegistry)({ |
| 52 | 52 | resolveLink: () => Effect.die("unused title link"), |
| ... | ... | @@ -64,19 +64,19 @@ const liveModelSelection = { |
| 64 | 64 | model: "default", |
| 65 | 65 | } satisfies ModelSelection; |
| 66 | 66 | |
| 67 | const serverConfigLayer = ServerConfig.layerTest(process.cwd(), { | |
| 67 | const layerServerConfig = ServerConfig.layerTest(process.cwd(), { | |
| 68 | 68 | prefix: "t3-acp-registry-v2-live-", |
| 69 | 69 | }); |
| 70 | 70 | |
| 71 | const vcsDriverRegistryLayer = VcsDriverRegistry.layer.pipe( | |
| 71 | const layerVcsDriverRegistry = VcsDriverRegistry.layer.pipe( | |
| 72 | 72 | Layer.provide(VcsProcess.layer), |
| 73 | Layer.provide(serverConfigLayer), | |
| 74 | Layer.provide(PlatformTestLayer), | |
| 73 | Layer.provide(layerServerConfig), | |
| 74 | Layer.provide(layerPlatformTest), | |
| 75 | 75 | ); |
| 76 | 76 | |
| 77 | const checkpointStoreLayer = CheckpointStore.layer.pipe(Layer.provide(vcsDriverRegistryLayer)); | |
| 77 | const layerCheckpointStore = CheckpointStore.layer.pipe(Layer.provide(layerVcsDriverRegistry)); | |
| 78 | 78 | |
| 79 | const serverSettingsLayer = ServerSettings.layerTest({ | |
| 79 | const layerServerSettings = ServerSettings.layerTest({ | |
| 80 | 80 | providerInstances: { |
| 81 | 81 | [liveInstanceId]: { |
| 82 | 82 | driver: ProviderDriverKind.make("acpRegistry"), |
| ... | ... | @@ -89,24 +89,24 @@ const serverSettingsLayer = ServerSettings.layerTest({ |
| 89 | 89 | }, |
| 90 | 90 | }, |
| 91 | 91 | }); |
| 92 | const backgroundPolicyLayer = BackgroundPolicy.layer.pipe( | |
| 92 | const layerBackgroundPolicy = BackgroundPolicy.layer.pipe( | |
| 93 | 93 | Layer.provide(Layer.effect(HostPowerMonitor.HostPowerMonitor, HostPowerMonitor.make())), |
| 94 | Layer.provide(serverSettingsLayer), | |
| 94 | Layer.provide(layerServerSettings), | |
| 95 | 95 | ); |
| 96 | const providerInstanceRegistryLayer = ProviderInstanceRegistryHydrationLive.pipe( | |
| 96 | const layerProviderInstanceRegistry = ProviderInstanceRegistryHydration.layer.pipe( | |
| 97 | 97 | Layer.provide( |
| 98 | 98 | Layer.mergeAll( |
| 99 | serverConfigLayer.pipe(Layer.provide(PlatformTestLayer)), | |
| 100 | serverSettingsLayer, | |
| 99 | layerServerConfig.pipe(Layer.provide(layerPlatformTest)), | |
| 100 | layerServerSettings, | |
| 101 | 101 | ServerSecretStore.layer.pipe( |
| 102 | Layer.provide(serverConfigLayer), | |
| 103 | Layer.provide(PlatformTestLayer), | |
| 102 | Layer.provide(layerServerConfig), | |
| 103 | Layer.provide(layerPlatformTest), | |
| 104 | 104 | ), |
| 105 | 105 | NodeServices.layer, |
| 106 | 106 | FetchHttpClient.layer, |
| 107 | OpenCodeRuntime.OpenCodeRuntimeLive.pipe( | |
| 107 | OpenCodeRuntime.layer.pipe( | |
| 108 | 108 | Layer.provide(OpenCodeServerLedger.layerTest), |
| 109 | Layer.provide(PlatformTestLayer), | |
| 109 | Layer.provide(layerPlatformTest), | |
| 110 | 110 | ), |
| 111 | 111 | Layer.succeed( |
| 112 | 112 | ProviderEventLoggers.ProviderEventLoggers, |
| ... | ... | @@ -114,9 +114,9 @@ const providerInstanceRegistryLayer = ProviderInstanceRegistryHydrationLive.pipe |
| 114 | 114 | ), |
| 115 | 115 | ModelManifest.layerTest, |
| 116 | 116 | AntigravityInstallation.AntigravityInstallation.layer.pipe( |
| 117 | Layer.provide(serverConfigLayer.pipe(Layer.provide(PlatformTestLayer))), | |
| 117 | Layer.provide(layerServerConfig.pipe(Layer.provide(layerPlatformTest))), | |
| 118 | 118 | Layer.provide(FetchHttpClient.layer), |
| 119 | Layer.provide(PlatformTestLayer), | |
| 119 | Layer.provide(layerPlatformTest), | |
| 120 | 120 | ), |
| 121 | 121 | // The Codex driver now resolves managed ChatGPT installs; these runs never launch Codex. |
| 122 | 122 | Layer.mock(CodexInstallation.CodexInstallation)({ |
| ... | ... | @@ -131,17 +131,17 @@ const providerInstanceRegistryLayer = ProviderInstanceRegistryHydrationLive.pipe |
| 131 | 131 | ), |
| 132 | 132 | ); |
| 133 | 133 | |
| 134 | const liveLayer = OrchestrationV2LayerLive.pipe( | |
| 134 | const layerLive = RuntimeLayer.layer.pipe( | |
| 135 | 135 | Layer.provide(McpSessionRegistryTestkit.layer), |
| 136 | Layer.provide(SqlitePersistenceMemory), | |
| 137 | Layer.provide(checkpointStoreLayer), | |
| 138 | Layer.provide(serverConfigLayer), | |
| 139 | Layer.provide(serverSettingsLayer), | |
| 140 | Layer.provide(providerInstanceRegistryLayer), | |
| 136 | Layer.provide(SqlitePersistence.layerMemory), | |
| 137 | Layer.provide(layerCheckpointStore), | |
| 138 | Layer.provide(layerServerConfig), | |
| 139 | Layer.provide(layerServerSettings), | |
| 140 | Layer.provide(layerProviderInstanceRegistry), | |
| 141 | 141 | Layer.provide(ResetCreditCoordinator.layer), |
| 142 | Layer.provide(backgroundPolicyLayer), | |
| 143 | Layer.provide(worktreeRepairDependenciesTestLayer), | |
| 144 | Layer.provide(PlatformTestLayer), | |
| 142 | Layer.provide(layerBackgroundPolicy), | |
| 143 | Layer.provide(ProviderTurnStartServiceTestkit.layer), | |
| 144 | Layer.provide(layerPlatformTest), | |
| 145 | 145 | ); |
| 146 | 146 | |
| 147 | 147 | const waitForIdle = Effect.fn("AcpRegistryOrchestratorV2Live.waitForIdle")(function* ( |
| ... | ... | @@ -248,7 +248,7 @@ describe.runIf(runAntigravityFixture || process.env.T3_ACP_REGISTRY_LIVE_ORCHEST |
| 248 | 248 | finalProjection.providerTurns.map((turn) => turn.status), |
| 249 | 249 | ["completed", "completed"], |
| 250 | 250 | ); |
| 251 | }).pipe(Effect.provide(liveLayer), Effect.scoped), | |
| 251 | }).pipe(Effect.provide(layerLive), Effect.scoped), | |
| 252 | 252 | 480_000, |
| 253 | 253 | ); |
| 254 | 254 | }, |
apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.test.ts+90-90| ... | ... | @@ -100,11 +100,11 @@ import { |
| 100 | 100 | |
| 101 | 101 | const DEFAULT_GROK_SETTINGS = Schema.decodeSync(GrokSettings)({}); |
| 102 | 102 | |
| 103 | const serverConfigLayer = ServerConfig.layerTest(process.cwd(), { | |
| 103 | const layerServerConfig = ServerConfig.layerTest(process.cwd(), { | |
| 104 | 104 | prefix: "t3-acp-v2-adapter-", |
| 105 | 105 | }).pipe(Layer.provide(NodeServices.layer)); |
| 106 | 106 | |
| 107 | const testLayer = Layer.mergeAll(NodeServices.layer, IdAllocator.layer, serverConfigLayer); | |
| 107 | const layerTest = Layer.mergeAll(NodeServices.layer, IdAllocator.layer, layerServerConfig); | |
| 108 | 108 | const ACP_TEST_DRIVER = ProviderDriverKind.make("acp-test"); |
| 109 | 109 | const decodeUnknownJson = Schema.decodeUnknownOption(Schema.fromJsonString(Schema.Unknown)); |
| 110 | 110 | |
| ... | ... | @@ -692,7 +692,7 @@ describe("AcpAdapterV2", () => { |
| 692 | 692 | assert.equal(retries.at(-1)?.status, "completed"); |
| 693 | 693 | assert.equal(retries.at(-1)?.title, "Provider recovered"); |
| 694 | 694 | } |
| 695 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 695 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 696 | 696 | ); |
| 697 | 697 | |
| 698 | 698 | it("preserves legacy ids and scopes v2 ids by provider instance", () => { |
| ... | ... | @@ -777,7 +777,7 @@ describe("AcpAdapterV2", () => { |
| 777 | 777 | assert.deepEqual(mcpServer.args, ["acp-mcp-bridge"]); |
| 778 | 778 | assert.equal(runtimeInput?.processEnvironment?.T3_ACP_MCP_NODE, process.execPath); |
| 779 | 779 | assert.equal(runtimeInput?.processEnvironment?.T3_ACP_MCP_ENTRYPOINT, undefined); |
| 780 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 780 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 781 | 781 | ); |
| 782 | 782 | |
| 783 | 783 | it.live("refreshes ACP prompt instructions when the interaction mode changes", () => |
| ... | ... | @@ -904,7 +904,7 @@ describe("AcpAdapterV2", () => { |
| 904 | 904 | "session/set_config_option", |
| 905 | 905 | "Build should restore the native mode that T3 temporarily replaced for Plan", |
| 906 | 906 | ); |
| 907 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 907 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 908 | 908 | ); |
| 909 | 909 | |
| 910 | 910 | it.effect("starts a new replay message after ACP v2 plan boundaries", () => |
| ... | ... | @@ -995,7 +995,7 @@ describe("AcpAdapterV2", () => { |
| 995 | 995 | snapshot.messages.map((message) => message.text), |
| 996 | 996 | ["before plan", "after plan"], |
| 997 | 997 | ); |
| 998 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 998 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 999 | 999 | ); |
| 1000 | 1000 | |
| 1001 | 1001 | it.effect("keeps Devin parent paragraphs intact while projecting native child work", () => |
| ... | ... | @@ -1303,7 +1303,7 @@ describe("AcpAdapterV2", () => { |
| 1303 | 1303 | ); |
| 1304 | 1304 | assert.equal(parentTools.length, 2); |
| 1305 | 1305 | assert.equal(parentTools[0]?.title, "Parent tool finished"); |
| 1306 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 1306 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 1307 | 1307 | ); |
| 1308 | 1308 | |
| 1309 | 1309 | it.effect("projects ACP v2 fidelity updates into first-class orchestration items", () => |
| ... | ... | @@ -1495,7 +1495,7 @@ describe("AcpAdapterV2", () => { |
| 1495 | 1495 | afterOmittedPatch.messages.some((message) => message.text === "late authoritative text"), |
| 1496 | 1496 | "omitted late message content must preserve the authoritative text", |
| 1497 | 1497 | ); |
| 1498 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 1498 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 1499 | 1499 | ); |
| 1500 | 1500 | |
| 1501 | 1501 | it.live( |
| ... | ... | @@ -1582,7 +1582,7 @@ describe("AcpAdapterV2", () => { |
| 1582 | 1582 | nativeTurnId: "persisted-session:turn:1", |
| 1583 | 1583 | }), |
| 1584 | 1584 | ); |
| 1585 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 1585 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 1586 | 1586 | ); |
| 1587 | 1587 | |
| 1588 | 1588 | it.live("preserves new-session fallback when an eager ACP session load is stale", () => |
| ... | ... | @@ -1689,7 +1689,7 @@ describe("AcpAdapterV2", () => { |
| 1689 | 1689 | ), |
| 1690 | 1690 | }), |
| 1691 | 1691 | ); |
| 1692 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 1692 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 1693 | 1693 | ); |
| 1694 | 1694 | |
| 1695 | 1695 | it.live("cleans detached fixtures when an assertion aborts the test scope", () => |
| ... | ... | @@ -1728,7 +1728,7 @@ describe("AcpAdapterV2", () => { |
| 1728 | 1728 | Option.isSome(yield* waitForProcessesToExit(published)), |
| 1729 | 1729 | "detached cleanup finalizer must reap the Bash and sleep fixture", |
| 1730 | 1730 | ); |
| 1731 | }).pipe(Effect.provide(testLayer)), | |
| 1731 | }).pipe(Effect.provide(layerTest)), | |
| 1732 | 1732 | ); |
| 1733 | 1733 | |
| 1734 | 1734 | it.live("replaces an unexpectedly terminated ACP runtime before the next turn", () => |
| ... | ... | @@ -1810,7 +1810,7 @@ describe("AcpAdapterV2", () => { |
| 1810 | 1810 | ); |
| 1811 | 1811 | assert.equal(runtimeOrdinalSeen, 2); |
| 1812 | 1812 | assert.lengthOf(runtimeInputs, 2); |
| 1813 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 1813 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 1814 | 1814 | ); |
| 1815 | 1815 | |
| 1816 | 1816 | it.live("reaps detached native work when the provider exits before explicit teardown", () => |
| ... | ... | @@ -1895,7 +1895,7 @@ describe("AcpAdapterV2", () => { |
| 1895 | 1895 | Option.isSome(yield* waitForProcessesToExit(pids)), |
| 1896 | 1896 | "provider termination must reap the detached launcher, Bash, and sleep processes", |
| 1897 | 1897 | ); |
| 1898 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 1898 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 1899 | 1899 | ); |
| 1900 | 1900 | |
| 1901 | 1901 | it.live("surfaces reduced guarantee when delegated cgroup containment is unavailable", () => |
| ... | ... | @@ -1952,7 +1952,7 @@ describe("AcpAdapterV2", () => { |
| 1952 | 1952 | }); |
| 1953 | 1953 | yield* runtime.ensureThread({ threadId, modelSelection, runtimePolicy }); |
| 1954 | 1954 | assert.equal(containment, "process-ledger-reduced-guarantee"); |
| 1955 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 1955 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 1956 | 1956 | ); |
| 1957 | 1957 | |
| 1958 | 1958 | it.live("cleans a cgroup lease when the pre-exec join wrapper fails", () => |
| ... | ... | @@ -2030,7 +2030,7 @@ describe("AcpAdapterV2", () => { |
| 2030 | 2030 | assert.equal(createCalls, 1); |
| 2031 | 2031 | assert.isAtLeast(killCalls, 1); |
| 2032 | 2032 | assert.isAtLeast(removeCalls, 1); |
| 2033 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 2033 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 2034 | 2034 | ); |
| 2035 | 2035 | |
| 2036 | 2036 | it.effect("negotiates and executes optional native session forks through the ACP runtime", () => |
| ... | ... | @@ -2240,7 +2240,7 @@ describe("AcpAdapterV2", () => { |
| 2240 | 2240 | { requestId: "test-terminal-output", method: "terminal/output" }, |
| 2241 | 2241 | ); |
| 2242 | 2242 | assert.equal(terminalOutput.output, "Bearer target-thread-token"); |
| 2243 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 2243 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 2244 | 2244 | ); |
| 2245 | 2245 | |
| 2246 | 2246 | it.effect( |
| ... | ... | @@ -2339,7 +2339,7 @@ describe("AcpAdapterV2", () => { |
| 2339 | 2339 | { method: "fs/read_text_file", errorCode: -32601 }, |
| 2340 | 2340 | ]); |
| 2341 | 2341 | assert.isFalse(yield* fileSystem.exists(probePath)); |
| 2342 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 2342 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 2343 | 2343 | ); |
| 2344 | 2344 | |
| 2345 | 2345 | it.effect("does not turn an unknown permission approval into an execute grant", () => |
| ... | ... | @@ -2476,7 +2476,7 @@ describe("AcpAdapterV2", () => { |
| 2476 | 2476 | providerTurnId: pending.runtimeRequest.providerTurnId, |
| 2477 | 2477 | }); |
| 2478 | 2478 | yield* Fiber.join(turnFiber); |
| 2479 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 2479 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 2480 | 2480 | ); |
| 2481 | 2481 | |
| 2482 | 2482 | it.effect("fails missing native ACP session ids through the typed start-turn error channel", () => |
| ... | ... | @@ -2538,7 +2538,7 @@ describe("AcpAdapterV2", () => { |
| 2538 | 2538 | assert.equal(error._tag, "ProviderAdapterTurnStartError"); |
| 2539 | 2539 | assert.instanceOf(error.cause, ProviderAdapterProtocolError); |
| 2540 | 2540 | assert.include(String(error.cause), "missing its ACP session id"); |
| 2541 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 2541 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 2542 | 2542 | ); |
| 2543 | 2543 | |
| 2544 | 2544 | it.effect("replaces the ACP session and clears conversation state on rollback", () => |
| ... | ... | @@ -2705,7 +2705,7 @@ describe("AcpAdapterV2", () => { |
| 2705 | 2705 | } |
| 2706 | 2706 | } |
| 2707 | 2707 | assert.equal(secondStatus, "completed"); |
| 2708 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 2708 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 2709 | 2709 | ); |
| 2710 | 2710 | |
| 2711 | 2711 | it.effect("quarantines callbacks from a failed rollback replacement before retrying", () => |
| ... | ... | @@ -2866,7 +2866,7 @@ describe("AcpAdapterV2", () => { |
| 2866 | 2866 | status: "running", |
| 2867 | 2867 | }); |
| 2868 | 2868 | assert.isFalse(yield* hasPendingBackgroundWork); |
| 2869 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 2869 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 2870 | 2870 | ); |
| 2871 | 2871 | |
| 2872 | 2872 | it.effect("keeps the original ACP session usable when a staged replacement terminates", () => |
| ... | ... | @@ -2982,7 +2982,7 @@ describe("AcpAdapterV2", () => { |
| 2982 | 2982 | break; |
| 2983 | 2983 | } |
| 2984 | 2984 | } |
| 2985 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 2985 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 2986 | 2986 | ); |
| 2987 | 2987 | |
| 2988 | 2988 | it.effect("closes an idle ACP session exactly once through the transition permit", () => |
| ... | ... | @@ -3031,7 +3031,7 @@ describe("AcpAdapterV2", () => { |
| 3031 | 3031 | yield* Scope.close(sessionScope, Exit.void); |
| 3032 | 3032 | const finalizerMethods = yield* pollProtocolMethods(protocolEvents); |
| 3033 | 3033 | assert.equal(finalizerMethods.filter((method) => method === "session/close").length, 1); |
| 3034 | }).pipe(Effect.provide(testLayer)), | |
| 3034 | }).pipe(Effect.provide(layerTest)), | |
| 3035 | 3035 | ); |
| 3036 | 3036 | |
| 3037 | 3037 | it.effect.each(["grok-build", "composer-2"])( |
| ... | ... | @@ -3088,7 +3088,7 @@ describe("AcpAdapterV2", () => { |
| 3088 | 3088 | ).length, |
| 3089 | 3089 | model === "grok-build" ? 0 : 1, |
| 3090 | 3090 | ); |
| 3091 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 3091 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 3092 | 3092 | ); |
| 3093 | 3093 | |
| 3094 | 3094 | it.live("Grok reapplies an explicit return to the session's setup-time model", () => |
| ... | ... | @@ -3169,7 +3169,7 @@ describe("AcpAdapterV2", () => { |
| 3169 | 3169 | ).length, |
| 3170 | 3170 | 3, |
| 3171 | 3171 | ); |
| 3172 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 3172 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 3173 | 3173 | ); |
| 3174 | 3174 | |
| 3175 | 3175 | it.effect("skips requested options that the active ACP session does not expose", () => |
| ... | ... | @@ -3218,7 +3218,7 @@ describe("AcpAdapterV2", () => { |
| 3218 | 3218 | }); |
| 3219 | 3219 | |
| 3220 | 3220 | assert.equal(runtime.providerSession.status, "ready"); |
| 3221 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 3221 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 3222 | 3222 | ); |
| 3223 | 3223 | |
| 3224 | 3224 | it.effect("reconfigures a loaded ACP session from its own active setup metadata", () => |
| ... | ... | @@ -3329,7 +3329,7 @@ describe("AcpAdapterV2", () => { |
| 3329 | 3329 | rawProtocolRequestParam(event, "configId") === "model", |
| 3330 | 3330 | ); |
| 3331 | 3331 | assert.lengthOf(modelConfigurationRequests, 2); |
| 3332 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 3332 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 3333 | 3333 | ); |
| 3334 | 3334 | |
| 3335 | 3335 | it.live("terminalizes an empty successful foreground Bash tool when the turn completes", () => |
| ... | ... | @@ -3476,7 +3476,7 @@ describe("AcpAdapterV2", () => { |
| 3476 | 3476 | Stream.runHead, |
| 3477 | 3477 | ); |
| 3478 | 3478 | assert.isTrue(Option.isSome(loadAfterRestart)); |
| 3479 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 3479 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 3480 | 3480 | ); |
| 3481 | 3481 | |
| 3482 | 3482 | it.effect("drains native ACP cancellation before admitting the next prompt", () => |
| ... | ... | @@ -3590,7 +3590,7 @@ describe("AcpAdapterV2", () => { |
| 3590 | 3590 | ), |
| 3591 | 3591 | ); |
| 3592 | 3592 | assert.equal(nextTerminal.type === "turn.terminal" && nextTerminal.status, "completed"); |
| 3593 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 3593 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 3594 | 3594 | ); |
| 3595 | 3595 | |
| 3596 | 3596 | it.effect("cancels pending permission requests while interrupting an ACP turn", () => |
| ... | ... | @@ -3691,7 +3691,7 @@ describe("AcpAdapterV2", () => { |
| 3691 | 3691 | ), |
| 3692 | 3692 | ); |
| 3693 | 3693 | assert.equal(terminal.type, "turn.terminal"); |
| 3694 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 3694 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 3695 | 3695 | ); |
| 3696 | 3696 | |
| 3697 | 3697 | it.live("keeps hard teardown excluded until a permission response is enqueued", () => |
| ... | ... | @@ -3790,7 +3790,7 @@ describe("AcpAdapterV2", () => { |
| 3790 | 3790 | yield* Deferred.succeed(releaseResponseAcknowledgement, undefined); |
| 3791 | 3791 | yield* Fiber.join(responseFiber); |
| 3792 | 3792 | yield* Fiber.join(interruptFiber); |
| 3793 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 3793 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 3794 | 3794 | ); |
| 3795 | 3795 | |
| 3796 | 3796 | it.live("carries elicitation request identity through the completed stdout write", () => |
| ... | ... | @@ -3879,7 +3879,7 @@ describe("AcpAdapterV2", () => { |
| 3879 | 3879 | assert.isUndefined(responseFiber.pollUnsafe()); |
| 3880 | 3880 | yield* Deferred.succeed(releaseResponseAcknowledgement, undefined); |
| 3881 | 3881 | yield* Fiber.join(responseFiber); |
| 3882 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 3882 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 3883 | 3883 | ); |
| 3884 | 3884 | |
| 3885 | 3885 | it.live("auto-approves tagged MCP elicitations under full-access policy", () => |
| ... | ... | @@ -3947,7 +3947,7 @@ describe("AcpAdapterV2", () => { |
| 3947 | 3947 | |
| 3948 | 3948 | assert.isFalse(events.some((event) => event.type === "runtime_request.updated")); |
| 3949 | 3949 | assert.isTrue(events.some((event) => event.type === "turn.terminal")); |
| 3950 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 3950 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 3951 | 3951 | ); |
| 3952 | 3952 | |
| 3953 | 3953 | it.live("fails a held native response acknowledgement before normal session close", () => |
| ... | ... | @@ -4055,7 +4055,7 @@ describe("AcpAdapterV2", () => { |
| 4055 | 4055 | assert.include(responseLifecycle, "failed"); |
| 4056 | 4056 | yield* Deferred.succeed(releaseResponseAcknowledgement, undefined); |
| 4057 | 4057 | yield* Fiber.join(closeFiber); |
| 4058 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 4058 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 4059 | 4059 | ); |
| 4060 | 4060 | |
| 4061 | 4061 | it.live("rejects delayed native response registration when normal close wins the permit", () => |
| ... | ... | @@ -4169,7 +4169,7 @@ describe("AcpAdapterV2", () => { |
| 4169 | 4169 | assert.equal(responseLifecycle.filter((event) => event === "admission_rejected").length, 1); |
| 4170 | 4170 | yield* Deferred.succeed(releaseTransportClose, undefined); |
| 4171 | 4171 | yield* Fiber.join(closeFiber); |
| 4172 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 4172 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 4173 | 4173 | ); |
| 4174 | 4174 | |
| 4175 | 4175 | it.live("bounds a missing pending permission response acknowledgement", () => |
| ... | ... | @@ -4310,7 +4310,7 @@ describe("AcpAdapterV2", () => { |
| 4310 | 4310 | assert.include(responseLifecycle, "late_noop"); |
| 4311 | 4311 | yield* Scope.close(sessionScope, Exit.void); |
| 4312 | 4312 | assert.notInclude(yield* pollProtocolMethods(protocolEvents), "session/close"); |
| 4313 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 4313 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 4314 | 4314 | ); |
| 4315 | 4315 | |
| 4316 | 4316 | it.live("defers caller cancellation until a pending response acknowledgement is bounded", () => |
| ... | ... | @@ -4426,7 +4426,7 @@ describe("AcpAdapterV2", () => { |
| 4426 | 4426 | yield* Fiber.join(cancellationFiber); |
| 4427 | 4427 | yield* Fiber.join(interruptFiber); |
| 4428 | 4428 | assert.isTrue(yield* Deferred.isDone(releaseNativeHook)); |
| 4429 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 4429 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 4430 | 4430 | ); |
| 4431 | 4431 | |
| 4432 | 4432 | it.live("waits for immediate allow and deny permission responses before hard teardown", () => |
| ... | ... | @@ -4531,7 +4531,7 @@ describe("AcpAdapterV2", () => { |
| 4531 | 4531 | yield* Fiber.interrupt(turnFiber).pipe(Effect.forkDetach); |
| 4532 | 4532 | }).pipe(Effect.scoped); |
| 4533 | 4533 | } |
| 4534 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 4534 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 4535 | 4535 | ); |
| 4536 | 4536 | |
| 4537 | 4537 | it.live("waits for immediate URL elicitation responses before hard teardown", () => |
| ... | ... | @@ -4624,7 +4624,7 @@ describe("AcpAdapterV2", () => { |
| 4624 | 4624 | yield* Deferred.succeed(releaseResponseAcknowledgement, undefined); |
| 4625 | 4625 | yield* Fiber.join(interruptFiber); |
| 4626 | 4626 | yield* Fiber.interrupt(turnFiber).pipe(Effect.forkDetach); |
| 4627 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 4627 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 4628 | 4628 | ); |
| 4629 | 4629 | |
| 4630 | 4630 | it.live("bounds a missing immediate response acknowledgement before hard teardown", () => |
| ... | ... | @@ -4717,7 +4717,7 @@ describe("AcpAdapterV2", () => { |
| 4717 | 4717 | }); |
| 4718 | 4718 | assert.isAtLeast((yield* Clock.currentTimeMillis) - startedAt, 1_500); |
| 4719 | 4719 | yield* Fiber.interrupt(turnFiber).pipe(Effect.forkDetach); |
| 4720 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 4720 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 4721 | 4721 | ); |
| 4722 | 4722 | |
| 4723 | 4723 | it.live("rejects an elicitation response when hard teardown wins admission", () => |
| ... | ... | @@ -4830,7 +4830,7 @@ describe("AcpAdapterV2", () => { |
| 4830 | 4830 | assert.fail("teardown winning admission must reject the elicitation response"); |
| 4831 | 4831 | } |
| 4832 | 4832 | assert.include(Cause.pretty(responseExit.cause), "No pending ACP runtime request"); |
| 4833 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 4833 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 4834 | 4834 | ); |
| 4835 | 4835 | |
| 4836 | 4836 | it.effect("releases an ACP turn when cancellation times out", () => |
| ... | ... | @@ -4933,7 +4933,7 @@ describe("AcpAdapterV2", () => { |
| 4933 | 4933 | ), |
| 4934 | 4934 | Stream.runHead, |
| 4935 | 4935 | ); |
| 4936 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 4936 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 4937 | 4937 | ); |
| 4938 | 4938 | |
| 4939 | 4939 | it.live("treats a second hard Stop as success when the turn is already gone", () => |
| ... | ... | @@ -5031,7 +5031,7 @@ describe("AcpAdapterV2", () => { |
| 5031 | 5031 | } |
| 5032 | 5032 | } |
| 5033 | 5033 | assert.equal(terminal, "interrupted"); |
| 5034 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 5034 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 5035 | 5035 | ); |
| 5036 | 5036 | |
| 5037 | 5037 | it.effect("finalizes a settled turn held open for background work when interrupted", () => |
| ... | ... | @@ -5125,7 +5125,7 @@ describe("AcpAdapterV2", () => { |
| 5125 | 5125 | } |
| 5126 | 5126 | } |
| 5127 | 5127 | assert.equal(terminalStatus, "interrupted"); |
| 5128 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 5128 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 5129 | 5129 | ); |
| 5130 | 5130 | |
| 5131 | 5131 | it.effect( |
| ... | ... | @@ -5273,7 +5273,7 @@ describe("AcpAdapterV2", () => { |
| 5273 | 5273 | } |
| 5274 | 5274 | assert.equal(carriedItemStatus, "completed"); |
| 5275 | 5275 | assert.equal(secondTerminalStatus, "completed"); |
| 5276 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 5276 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 5277 | 5277 | ); |
| 5278 | 5278 | |
| 5279 | 5279 | it.effect( |
| ... | ... | @@ -5422,7 +5422,7 @@ describe("AcpAdapterV2", () => { |
| 5422 | 5422 | yield* hasPendingBackgroundWork, |
| 5423 | 5423 | "hasPendingBackgroundWork must clear after carryover subagent terminals", |
| 5424 | 5424 | ); |
| 5425 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 5425 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 5426 | 5426 | ); |
| 5427 | 5427 | |
| 5428 | 5428 | it.effect("handles a child terminal after carryover rehydrate", () => |
| ... | ... | @@ -5657,7 +5657,7 @@ describe("AcpAdapterV2", () => { |
| 5657 | 5657 | |
| 5658 | 5658 | yield* Deferred.succeed(releaseSecondPromptCompletion, undefined); |
| 5659 | 5659 | yield* Fiber.join(secondTurnFiber); |
| 5660 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 5660 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 5661 | 5661 | ); |
| 5662 | 5662 | |
| 5663 | 5663 | it.effect( |
| ... | ... | @@ -5880,7 +5880,7 @@ describe("AcpAdapterV2", () => { |
| 5880 | 5880 | "finalize-window terminal must clear the carryover pin", |
| 5881 | 5881 | ); |
| 5882 | 5882 | }).pipe(Effect.provideService(Clock.Clock, blockingClock)); |
| 5883 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 5883 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 5884 | 5884 | ); |
| 5885 | 5885 | |
| 5886 | 5886 | it.effect( |
| ... | ... | @@ -6101,7 +6101,7 @@ describe("AcpAdapterV2", () => { |
| 6101 | 6101 | assert.equal(attachTerminal, "completed"); |
| 6102 | 6102 | assert.equal(completedAfterAttach, 1); |
| 6103 | 6103 | assert.isFalse(yield* hasPendingBackgroundWork); |
| 6104 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 6104 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 6105 | 6105 | ); |
| 6106 | 6106 | |
| 6107 | 6107 | it.effect("finishes a settled root's carryover subagent from its structured end", () => |
| ... | ... | @@ -6249,7 +6249,7 @@ describe("AcpAdapterV2", () => { |
| 6249 | 6249 | yield* hasPendingBackgroundWork, |
| 6250 | 6250 | "a finished carryover subagent stops pinning", |
| 6251 | 6251 | ); |
| 6252 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 6252 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 6253 | 6253 | ); |
| 6254 | 6254 | |
| 6255 | 6255 | it.effect("projects completed-root carryover eagerly and drain cannot resurrect it", () => |
| ... | ... | @@ -6499,7 +6499,7 @@ describe("AcpAdapterV2", () => { |
| 6499 | 6499 | "buffered spawn ACK must not create a continuation-owned turn item", |
| 6500 | 6500 | ); |
| 6501 | 6501 | assert.isFalse(yield* hasPendingBackgroundWork); |
| 6502 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 6502 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 6503 | 6503 | ); |
| 6504 | 6504 | |
| 6505 | 6505 | it.effect( |
| ... | ... | @@ -6787,7 +6787,7 @@ describe("AcpAdapterV2", () => { |
| 6787 | 6787 | "queued continuation must drain the wake content after the user run", |
| 6788 | 6788 | ); |
| 6789 | 6789 | assert.isFalse(yield* hasPendingBackgroundWork); |
| 6790 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 6790 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 6791 | 6791 | ); |
| 6792 | 6792 | |
| 6793 | 6793 | it.effect( |
| ... | ... | @@ -7030,7 +7030,7 @@ describe("AcpAdapterV2", () => { |
| 7030 | 7030 | } |
| 7031 | 7031 | assert.equal(completedSubagentTurnItems, 1); |
| 7032 | 7032 | assert.isFalse(yield* hasPendingBackgroundWork); |
| 7033 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 7033 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 7034 | 7034 | ); |
| 7035 | 7035 | |
| 7036 | 7036 | it.effect("projects an interrupted root-session end notice at the next attach", () => |
| ... | ... | @@ -7257,7 +7257,7 @@ describe("AcpAdapterV2", () => { |
| 7257 | 7257 | } |
| 7258 | 7258 | assert.equal(completedSubagentTurnItems, 1); |
| 7259 | 7259 | assert.isFalse(yield* hasPendingBackgroundWork); |
| 7260 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 7260 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 7261 | 7261 | ); |
| 7262 | 7262 | |
| 7263 | 7263 | it.effect( |
| ... | ... | @@ -7528,7 +7528,7 @@ describe("AcpAdapterV2", () => { |
| 7528 | 7528 | yield* hasPendingBackgroundWork, |
| 7529 | 7529 | "pin must clear after the continuation drains, not when the child session completed", |
| 7530 | 7530 | ); |
| 7531 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 7531 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 7532 | 7532 | ); |
| 7533 | 7533 | |
| 7534 | 7534 | it.effect( |
| ... | ... | @@ -7770,7 +7770,7 @@ describe("AcpAdapterV2", () => { |
| 7770 | 7770 | yield* hasPendingBackgroundWork, |
| 7771 | 7771 | "hasPendingBackgroundWork must end false after the continuation drains", |
| 7772 | 7772 | ); |
| 7773 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 7773 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 7774 | 7774 | ); |
| 7775 | 7775 | |
| 7776 | 7776 | it.effect( |
| ... | ... | @@ -7948,7 +7948,7 @@ describe("AcpAdapterV2", () => { |
| 7948 | 7948 | 1, |
| 7949 | 7949 | "settled soft steer must not respawn the ACP runtime process", |
| 7950 | 7950 | ); |
| 7951 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 7951 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 7952 | 7952 | ); |
| 7953 | 7953 | |
| 7954 | 7954 | it.live("preserveRuntimeOnSettledInterrupt does not soften a mid-prompt steering interrupt", () => |
| ... | ... | @@ -8036,7 +8036,7 @@ describe("AcpAdapterV2", () => { |
| 8036 | 8036 | assert.fail("mid-prompt steering interrupt must still take the hard teardown path"); |
| 8037 | 8037 | } |
| 8038 | 8038 | assert.include(Cause.pretty(interruptExit.cause), "session is poisoned"); |
| 8039 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 8039 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 8040 | 8040 | ); |
| 8041 | 8041 | |
| 8042 | 8042 | it.live( |
| ... | ... | @@ -8232,7 +8232,7 @@ describe("AcpAdapterV2", () => { |
| 8232 | 8232 | 0, |
| 8233 | 8233 | "a cancel-backgrounded task completion must not wake a synthetic continuation run", |
| 8234 | 8234 | ); |
| 8235 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 8235 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 8236 | 8236 | ); |
| 8237 | 8237 | |
| 8238 | 8238 | it.live("direct Stop quarantine drops late background task mutations from the stopped run", () => |
| ... | ... | @@ -8379,7 +8379,7 @@ describe("AcpAdapterV2", () => { |
| 8379 | 8379 | yield* hasPendingBackgroundWork, |
| 8380 | 8380 | "direct Stop quarantine must drop residual background task mutations from the stopped run", |
| 8381 | 8381 | ); |
| 8382 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 8382 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 8383 | 8383 | ); |
| 8384 | 8384 | |
| 8385 | 8385 | it.live("production Grok interrupt flags still hard-kill and respawn on user Stop", () => |
| ... | ... | @@ -8505,7 +8505,7 @@ describe("AcpAdapterV2", () => { |
| 8505 | 8505 | 2, |
| 8506 | 8506 | "user Stop with production Grok flags must replace the ACP runtime process", |
| 8507 | 8507 | ); |
| 8508 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 8508 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 8509 | 8509 | ); |
| 8510 | 8510 | |
| 8511 | 8511 | // it.live: ownDetachedProcessGroup teardown uses wall-clock sleeps; under |
| ... | ... | @@ -8814,7 +8814,7 @@ describe("AcpAdapterV2", () => { |
| 8814 | 8814 | "Stop quarantine must drop turn-1 subagent carryover on the respawned runtime", |
| 8815 | 8815 | ); |
| 8816 | 8816 | assert.equal(secondTerminalStatus, "completed"); |
| 8817 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 8817 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 8818 | 8818 | ); |
| 8819 | 8819 | |
| 8820 | 8820 | it.live("Direct Stop projects an interrupt-deferred terminal exactly once", () => |
| ... | ... | @@ -9009,7 +9009,7 @@ describe("AcpAdapterV2", () => { |
| 9009 | 9009 | } |
| 9010 | 9010 | assert.equal(completedAfterStop, 1); |
| 9011 | 9011 | assert.isFalse(yield* hasPendingBackgroundWork); |
| 9012 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 9012 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 9013 | 9013 | ); |
| 9014 | 9014 | |
| 9015 | 9015 | it.effect( |
| ... | ... | @@ -9179,7 +9179,7 @@ describe("AcpAdapterV2", () => { |
| 9179 | 9179 | } |
| 9180 | 9180 | assert.equal(firstTerminalStatus, "interrupted"); |
| 9181 | 9181 | assert.equal(subagentPhase, "spawn"); |
| 9182 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 9182 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 9183 | 9183 | ); |
| 9184 | 9184 | |
| 9185 | 9185 | it.effect( |
| ... | ... | @@ -9310,7 +9310,7 @@ describe("AcpAdapterV2", () => { |
| 9310 | 9310 | yield* hasPendingBackgroundWork, |
| 9311 | 9311 | "wake buffer must not stay non-empty and pin idle release after an already-handled re-report", |
| 9312 | 9312 | ); |
| 9313 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 9313 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 9314 | 9314 | ); |
| 9315 | 9315 | |
| 9316 | 9316 | it.effect( |
| ... | ... | @@ -9432,7 +9432,7 @@ describe("AcpAdapterV2", () => { |
| 9432 | 9432 | yield* first.clearIfCurrent!(); |
| 9433 | 9433 | yield* lateTool("new-result-after-worker-drop"); |
| 9434 | 9434 | assert.lengthOf(continuationRequests, 2); |
| 9435 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 9435 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 9436 | 9436 | ); |
| 9437 | 9437 | |
| 9438 | 9438 | it.effect("keeps a buffered continuation current when a user turn starts before dispatch", () => |
| ... | ... | @@ -9639,7 +9639,7 @@ describe("AcpAdapterV2", () => { |
| 9639 | 9639 | assert.equal(continuationTerminalStatus, "completed"); |
| 9640 | 9640 | assert.isTrue(bufferedTextSeen, "continuation must drain the wake buffer after the user run"); |
| 9641 | 9641 | assert.isFalse(yield* hasPendingBackgroundWork); |
| 9642 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 9642 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 9643 | 9643 | ); |
| 9644 | 9644 | |
| 9645 | 9645 | it.effect( |
| ... | ... | @@ -9805,7 +9805,7 @@ describe("AcpAdapterV2", () => { |
| 9805 | 9805 | reportSeen, |
| 9806 | 9806 | "the injected-turn report must project before the turn finalizes", |
| 9807 | 9807 | ); |
| 9808 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 9808 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 9809 | 9809 | ); |
| 9810 | 9810 | |
| 9811 | 9811 | it.effect( |
| ... | ... | @@ -10007,7 +10007,7 @@ describe("AcpAdapterV2", () => { |
| 10007 | 10007 | 0, |
| 10008 | 10008 | "settle-without-report with mid-hold ext completion must not open a wake after the report streams", |
| 10009 | 10009 | ); |
| 10010 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 10010 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 10011 | 10011 | ); |
| 10012 | 10012 | |
| 10013 | 10013 | it.effect( |
| ... | ... | @@ -10247,7 +10247,7 @@ describe("AcpAdapterV2", () => { |
| 10247 | 10247 | 0, |
| 10248 | 10248 | "pre-settle arm must be cleared when the injected report streams; no duplicate wake", |
| 10249 | 10249 | ); |
| 10250 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 10250 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 10251 | 10251 | ); |
| 10252 | 10252 | |
| 10253 | 10253 | it.effect( |
| ... | ... | @@ -10466,7 +10466,7 @@ describe("AcpAdapterV2", () => { |
| 10466 | 10466 | 1, |
| 10467 | 10467 | "exactly one continuation when the last running task ends post-finalize with kept midTurn marks", |
| 10468 | 10468 | ); |
| 10469 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 10469 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 10470 | 10470 | ); |
| 10471 | 10471 | |
| 10472 | 10472 | it.effect( |
| ... | ... | @@ -10681,7 +10681,7 @@ describe("AcpAdapterV2", () => { |
| 10681 | 10681 | 0, |
| 10682 | 10682 | "interrupted turns must clear midTurn marks; B ending post-finalize must not offer", |
| 10683 | 10683 | ); |
| 10684 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 10684 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 10685 | 10685 | ); |
| 10686 | 10686 | |
| 10687 | 10687 | it.effect( |
| ... | ... | @@ -11055,7 +11055,7 @@ describe("AcpAdapterV2", () => { |
| 11055 | 11055 | yield* hasPendingBackgroundWork, |
| 11056 | 11056 | "wake buffer must not retain turn-1 or turn-2 in-turn-handled ack residue", |
| 11057 | 11057 | ); |
| 11058 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 11058 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 11059 | 11059 | ); |
| 11060 | 11060 | |
| 11061 | 11061 | it.effect("a wake names work that ended while the previous wake was queued", () => |
| ... | ... | @@ -11209,7 +11209,7 @@ describe("AcpAdapterV2", () => { |
| 11209 | 11209 | outcome: "completed", |
| 11210 | 11210 | summary: 'Command "sleep b" finished', |
| 11211 | 11211 | }); |
| 11212 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 11212 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 11213 | 11213 | ); |
| 11214 | 11214 | |
| 11215 | 11215 | it.effect("mid-turn completed mutation defers offer until finalize only when unhandled", () => |
| ... | ... | @@ -11538,7 +11538,7 @@ describe("AcpAdapterV2", () => { |
| 11538 | 11538 | 0, |
| 11539 | 11539 | "post-finalize must not offer when the mid-turn completion was handled in-turn", |
| 11540 | 11540 | ); |
| 11541 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 11541 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 11542 | 11542 | ); |
| 11543 | 11543 | |
| 11544 | 11544 | it.effect( |
| ... | ... | @@ -11727,7 +11727,7 @@ describe("AcpAdapterV2", () => { |
| 11727 | 11727 | beforeWhitespace.messages, |
| 11728 | 11728 | "whitespace-only late chunks must not mutate the loaded history snapshot", |
| 11729 | 11729 | ); |
| 11730 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 11730 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 11731 | 11731 | ); |
| 11732 | 11732 | |
| 11733 | 11733 | it.effect("empty-drain continuation turn waits the quiet window so late frames can attach", () => |
| ... | ... | @@ -11971,7 +11971,7 @@ describe("AcpAdapterV2", () => { |
| 11971 | 11971 | } |
| 11972 | 11972 | } |
| 11973 | 11973 | assert.equal(continuationTerminal, "completed"); |
| 11974 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 11974 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 11975 | 11975 | ); |
| 11976 | 11976 | |
| 11977 | 11977 | it.effect("empty-drain continuation turn finalizes after the quiet window with no frames", () => |
| ... | ... | @@ -12161,7 +12161,7 @@ describe("AcpAdapterV2", () => { |
| 12161 | 12161 | } |
| 12162 | 12162 | } |
| 12163 | 12163 | assert.equal(continuationTerminal, "completed"); |
| 12164 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 12164 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 12165 | 12165 | ); |
| 12166 | 12166 | |
| 12167 | 12167 | it.effect("restarts the ACP child process before the next prompt after interrupt", () => |
| ... | ... | @@ -12258,7 +12258,7 @@ describe("AcpAdapterV2", () => { |
| 12258 | 12258 | Option.isSome(loadAfterRestart), |
| 12259 | 12259 | "post-interrupt startTurn should respawn the runtime and replay session/resume", |
| 12260 | 12260 | ); |
| 12261 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 12261 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 12262 | 12262 | ); |
| 12263 | 12263 | |
| 12264 | 12264 | it.effect("Windows teardown is one-shot explicitly with independent finalizer cleanup", () => |
| ... | ... | @@ -12304,7 +12304,7 @@ describe("AcpAdapterV2", () => { |
| 12304 | 12304 | assert.equal(taskkillCommands.length, 1); |
| 12305 | 12305 | yield* Scope.close(runtimeScope, Exit.void); |
| 12306 | 12306 | assert.equal(taskkillCommands.length, 1); |
| 12307 | }).pipe(Effect.provide(testLayer)), | |
| 12307 | }).pipe(Effect.provide(layerTest)), | |
| 12308 | 12308 | ); |
| 12309 | 12309 | |
| 12310 | 12310 | it("accepts only taskkill exit code zero as successful tree termination", () => { |
| ... | ... | @@ -12407,7 +12407,7 @@ describe("AcpAdapterV2", () => { |
| 12407 | 12407 | ); |
| 12408 | 12408 | const error = yield* Effect.flip(runtime.terminateProcessGroup!); |
| 12409 | 12409 | assert.equal(error.detail, "mock taskkill failure"); |
| 12410 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 12410 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 12411 | 12411 | ); |
| 12412 | 12412 | |
| 12413 | 12413 | it.live("poisons the session when hard teardown defects and blocks replacement work", () => |
| ... | ... | @@ -12625,7 +12625,7 @@ describe("AcpAdapterV2", () => { |
| 12625 | 12625 | assert.isFalse(processExists(commandSleepPid!)); |
| 12626 | 12626 | const finalizerMethods = yield* pollProtocolMethods(protocolEvents); |
| 12627 | 12627 | assert.notInclude(finalizerMethods, "session/close"); |
| 12628 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 12628 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 12629 | 12629 | ); |
| 12630 | 12630 | |
| 12631 | 12631 | it.live("durably poisons start and resume when required hard teardown is unavailable", () => |
| ... | ... | @@ -12714,7 +12714,7 @@ describe("AcpAdapterV2", () => { |
| 12714 | 12714 | .pipe(Effect.exit); |
| 12715 | 12715 | if (Exit.isSuccess(resumeExit)) assert.fail("poisoned session must reject resumeThread"); |
| 12716 | 12716 | assert.include(Cause.pretty(resumeExit.cause), "session is poisoned"); |
| 12717 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 12717 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 12718 | 12718 | ); |
| 12719 | 12719 | |
| 12720 | 12720 | it.live("holds concurrent startTurn behind successful hard teardown and reloads once", () => |
| ... | ... | @@ -12827,7 +12827,7 @@ describe("AcpAdapterV2", () => { |
| 12827 | 12827 | Stream.runHead, |
| 12828 | 12828 | ); |
| 12829 | 12829 | } |
| 12830 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 12830 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 12831 | 12831 | ); |
| 12832 | 12832 | |
| 12833 | 12833 | it.live("quarantines old-runtime callbacks after successful hard teardown", () => |
| ... | ... | @@ -13189,7 +13189,7 @@ describe("AcpAdapterV2", () => { |
| 13189 | 13189 | }), |
| 13190 | 13190 | requestRuntimeRestart: true, |
| 13191 | 13191 | }); |
| 13192 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 13192 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 13193 | 13193 | ); |
| 13194 | 13194 | |
| 13195 | 13195 | it.live("keeps stale deferred cleanup inert while replacement requests remain live", () => |
| ... | ... | @@ -13314,7 +13314,7 @@ describe("AcpAdapterV2", () => { |
| 13314 | 13314 | } |
| 13315 | 13315 | replacementTerminal = event.type === "turn.terminal"; |
| 13316 | 13316 | } |
| 13317 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 13317 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 13318 | 13318 | ); |
| 13319 | 13319 | |
| 13320 | 13320 | it.live("resolves owner cancellation and concurrent resume waiters after hard teardown", () => |
| ... | ... | @@ -13447,7 +13447,7 @@ describe("AcpAdapterV2", () => { |
| 13447 | 13447 | const methodsAfterRestart = yield* pollProtocolMethods(protocolEvents); |
| 13448 | 13448 | assert.equal(methodsAfterRestart.filter((method) => method === "session/resume").length, 2); |
| 13449 | 13449 | assert.equal(methodsAfterRestart.filter((method) => method === "session/fork").length, 1); |
| 13450 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 13450 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 13451 | 13451 | ); |
| 13452 | 13452 | |
| 13453 | 13453 | it.live("direct Stop skips uninterruptible ACP cancel and recovers after native teardown", () => |
| ... | ... | @@ -13723,7 +13723,7 @@ describe("AcpAdapterV2", () => { |
| 13723 | 13723 | stoppedRunTextOnFollowUp, |
| 13724 | 13724 | "stopped-run residual text must not attach to the follow-up run", |
| 13725 | 13725 | ); |
| 13726 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 13726 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 13727 | 13727 | ); |
| 13728 | 13728 | |
| 13729 | 13729 | it.effect( |
| ... | ... | @@ -13878,7 +13878,7 @@ describe("AcpAdapterV2", () => { |
| 13878 | 13878 | carriedCompleted, |
| 13879 | 13879 | "Direct Stop must not carry a stopped subagent into the follow-up run", |
| 13880 | 13880 | ); |
| 13881 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 13881 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 13882 | 13882 | ); |
| 13883 | 13883 | |
| 13884 | 13884 | it.live("restart_active terminates native work and reloads a clean runtime", () => |
| ... | ... | @@ -14064,7 +14064,7 @@ describe("AcpAdapterV2", () => { |
| 14064 | 14064 | } |
| 14065 | 14065 | assert.equal(secondTerminal, "completed"); |
| 14066 | 14066 | assert.isFalse(staleEventSeen, "interrupted runtime events must not attach to attempt 2"); |
| 14067 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 14067 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 14068 | 14068 | ); |
| 14069 | 14069 | }); |
| 14070 | 14070 |
apps/server/src/orchestration-v2/Adapters/AcpRegistryAdapterV2.test.ts+9-9| ... | ... | @@ -43,11 +43,11 @@ const decodeAcpRegistryAdapterSettings = Schema.decodeUnknownEffect( |
| 43 | 43 | AcpRegistryAdapterV2Driver.configSchema, |
| 44 | 44 | ); |
| 45 | 45 | |
| 46 | const serverConfigLayer = ServerConfig.layerTest(process.cwd(), { | |
| 46 | const layerServerConfig = ServerConfig.layerTest(process.cwd(), { | |
| 47 | 47 | prefix: "t3-acp-registry-v2-adapter-", |
| 48 | 48 | }).pipe(Layer.provide(NodeServices.layer)); |
| 49 | 49 | |
| 50 | const registryLayer = Layer.succeed( | |
| 50 | const layerRegistry = Layer.succeed( | |
| 51 | 51 | HttpClient.HttpClient, |
| 52 | 52 | HttpClient.make((request) => |
| 53 | 53 | Effect.succeed( |
| ... | ... | @@ -83,11 +83,11 @@ const registryLayer = Layer.succeed( |
| 83 | 83 | ), |
| 84 | 84 | ); |
| 85 | 85 | |
| 86 | const testLayer = Layer.mergeAll( | |
| 86 | const layerTest = Layer.mergeAll( | |
| 87 | 87 | NodeServices.layer, |
| 88 | 88 | IdAllocator.layer, |
| 89 | serverConfigLayer, | |
| 90 | registryLayer, | |
| 89 | layerServerConfig, | |
| 90 | layerRegistry, | |
| 91 | 91 | ServerSettings.layerTest(), |
| 92 | 92 | ); |
| 93 | 93 | |
| ... | ... | @@ -247,7 +247,7 @@ describe("AcpRegistryAdapterV2", () => { |
| 247 | 247 | answer("session/set_mode", {}), |
| 248 | 248 | ], |
| 249 | 249 | storedModePick: "autoEdit", |
| 250 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 250 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 251 | 251 | ); |
| 252 | 252 | |
| 253 | 253 | it.effect("switches a mode config option under its own id", () => |
| ... | ... | @@ -262,7 +262,7 @@ describe("AcpRegistryAdapterV2", () => { |
| 262 | 262 | answer("session/set_config_option", { configOptions: [permissionModeOption("auto")] }), |
| 263 | 263 | ], |
| 264 | 264 | storedModePick: "auto", |
| 265 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 265 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 266 | 266 | ); |
| 267 | 267 | }); |
| 268 | 268 | |
| ... | ... | @@ -339,7 +339,7 @@ describe("AcpRegistryAdapterV2", () => { |
| 339 | 339 | fs: { readTextFile: false, writeTextFile: false }, |
| 340 | 340 | terminal: false, |
| 341 | 341 | }); |
| 342 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 342 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 343 | 343 | ); |
| 344 | 344 | |
| 345 | 345 | it.effect("opens a real ACP child process resolved from registry configuration", () => |
| ... | ... | @@ -485,6 +485,6 @@ describe("AcpRegistryAdapterV2", () => { |
| 485 | 485 | name: "Auto", |
| 486 | 486 | description: null, |
| 487 | 487 | }); |
| 488 | }).pipe(Effect.provide(testLayer), Effect.scoped), | |
| 488 | }).pipe(Effect.provide(layerTest), Effect.scoped), | |
| 489 | 489 | ); |
| 490 | 490 | }); |
apps/server/src/orchestration-v2/Adapters/AcpRegistryAdapterV2.testkit.ts+5-5| ... | ... | @@ -33,16 +33,16 @@ const REPLAY_SETTINGS = Schema.decodeUnknownSync(AcpRegistrySettings)({ |
| 33 | 33 | authMethodId: "replay", |
| 34 | 34 | }); |
| 35 | 35 | |
| 36 | function makeAcpRegistryProviderAdapterRegistryReplayLayer( | |
| 36 | function layerAcpRegistryProviderAdapterRegistryReplay( | |
| 37 | 37 | transcript: AcpReplayTranscript, |
| 38 | 38 | options: { readonly replayGate?: ProviderReplayGate } = {}, |
| 39 | 39 | ) { |
| 40 | const serverConfigLayer = Layer.effect( | |
| 40 | const layerServerConfig = Layer.effect( | |
| 41 | 41 | ServerConfig.ServerConfig, |
| 42 | 42 | makeReplayServerConfig(`acp-registry-${transcript.scenario}`).pipe(Effect.orDie), |
| 43 | 43 | ).pipe(Layer.provide(NodeServices.layer)); |
| 44 | 44 | |
| 45 | return ProviderAdapterRegistry.makeLayerEffect( | |
| 45 | return ProviderAdapterRegistry.layerFromAdaptersEffect( | |
| 46 | 46 | Effect.gen(function* () { |
| 47 | 47 | const fileSystem = yield* FileSystem.FileSystem; |
| 48 | 48 | const path = yield* Path.Path; |
| ... | ... | @@ -86,7 +86,7 @@ function makeAcpRegistryProviderAdapterRegistryReplayLayer( |
| 86 | 86 | return [adapter]; |
| 87 | 87 | }), |
| 88 | 88 | ).pipe( |
| 89 | Layer.provide(Layer.mergeAll(serverConfigLayer, NodeServices.layer, IdAllocator.layer)), | |
| 89 | Layer.provide(Layer.mergeAll(layerServerConfig, NodeServices.layer, IdAllocator.layer)), | |
| 90 | 90 | // Held inbound lines must not outlive the scenario and wedge teardown. |
| 91 | 91 | Layer.merge( |
| 92 | 92 | Layer.effectDiscard( |
| ... | ... | @@ -105,5 +105,5 @@ export const AcpRegistryOrchestratorReplayHarness: OrchestratorV2ProviderReplayH |
| 105 | 105 | decodeAcpReplayTranscript(transcript, ACP_REGISTRY_PROVIDER, { |
| 106 | 106 | retargetProvider: true, |
| 107 | 107 | }), |
| 108 | makeProviderAdapterRegistryLayer: makeAcpRegistryProviderAdapterRegistryReplayLayer, | |
| 108 | makeProviderAdapterRegistryLayer: layerAcpRegistryProviderAdapterRegistryReplay, | |
| 109 | 109 | }; |
apps/server/src/orchestration-v2/Adapters/AntigravityAdapterV2.test.ts+4-4| ... | ... | @@ -126,7 +126,7 @@ describe("AntigravityAdapterV2 flavor", () => { |
| 126 | 126 | }); |
| 127 | 127 | }); |
| 128 | 128 | |
| 129 | const sessionLayer = Layer.mergeAll( | |
| 129 | const layerSession = Layer.mergeAll( | |
| 130 | 130 | NodeServices.layer, |
| 131 | 131 | IdAllocator.layer, |
| 132 | 132 | ServerConfig.layerTest(process.cwd(), { prefix: "t3-antigravity-v2-adapter-" }).pipe( |
| ... | ... | @@ -284,7 +284,7 @@ describe("AntigravityAdapterV2 client file system", () => { |
| 284 | 284 | context("fs/read_text_file"), |
| 285 | 285 | ); |
| 286 | 286 | assert.equal(viaInsideLink.content, "inside"); |
| 287 | }).pipe(Effect.provide(sessionLayer), Effect.scoped), | |
| 287 | }).pipe(Effect.provide(layerSession), Effect.scoped), | |
| 288 | 288 | ); |
| 289 | 289 | }); |
| 290 | 290 | |
| ... | ... | @@ -419,7 +419,7 @@ describe("AntigravityAdapterV2 workspace changes", () => { |
| 419 | 419 | context, |
| 420 | 420 | ).pipe(Effect.exit); |
| 421 | 421 | assert.isTrue(Exit.isFailure(fromA), "the previous workspace is no longer readable"); |
| 422 | }).pipe(Effect.provide(sessionLayer), Effect.scoped), | |
| 422 | }).pipe(Effect.provide(layerSession), Effect.scoped), | |
| 423 | 423 | ); |
| 424 | 424 | }); |
| 425 | 425 | |
| ... | ... | @@ -532,6 +532,6 @@ describe("AntigravityAdapterV2 client file system under restrictive policies", ( |
| 532 | 532 | assert.isTrue(Exit.isFailure(outsideWrite), name); |
| 533 | 533 | assert.isFalse(yield* fileSystem.exists(path.join(outside, "x.ts")), name); |
| 534 | 534 | } |
| 535 | }).pipe(Effect.provide(sessionLayer), Effect.scoped), | |
| 535 | }).pipe(Effect.provide(layerSession), Effect.scoped), | |
| 536 | 536 | ); |
| 537 | 537 | }); |
apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.testkit.ts+9-9| ... | ... | @@ -936,7 +936,7 @@ function replayQueryRunnerService( |
| 936 | 936 | }); |
| 937 | 937 | } |
| 938 | 938 | |
| 939 | function makeClaudeAgentSdkReplayQueryRunnerLayer( | |
| 939 | function layerClaudeAgentSdkReplayQueryRunner( | |
| 940 | 940 | transcript: ClaudeAgentSdkReplayTranscript, |
| 941 | 941 | options: { readonly replayGate?: ProviderReplayGate } = {}, |
| 942 | 942 | ): Layer.Layer<ClaudeAdapterV2.ClaudeAgentSdkQueryRunner> { |
| ... | ... | @@ -946,7 +946,7 @@ function makeClaudeAgentSdkReplayQueryRunnerLayer( |
| 946 | 946 | ); |
| 947 | 947 | } |
| 948 | 948 | |
| 949 | function makeClaudeAgentSdkReplayLayer( | |
| 949 | function layerClaudeAgentSdkReplay( | |
| 950 | 950 | transcript: ClaudeAgentSdkReplayTranscript, |
| 951 | 951 | options: { |
| 952 | 952 | readonly replayGate?: ProviderReplayGate; |
| ... | ... | @@ -974,18 +974,18 @@ function makeClaudeAgentSdkReplayLayer( |
| 974 | 974 | ); |
| 975 | 975 | } |
| 976 | 976 | |
| 977 | function makeClaudeProviderAdapterRegistryReplayLayer( | |
| 977 | function layerClaudeProviderAdapterRegistryReplay( | |
| 978 | 978 | transcript: ClaudeAgentSdkReplayTranscript, |
| 979 | 979 | options: { |
| 980 | 980 | readonly replayGate?: ProviderReplayGate; |
| 981 | 981 | readonly queryRunner?: ClaudeQueryRunner; |
| 982 | 982 | } = {}, |
| 983 | 983 | ) { |
| 984 | const serverConfigLayer = Layer.effect( | |
| 984 | const layerServerConfig = Layer.effect( | |
| 985 | 985 | ServerConfig.ServerConfig, |
| 986 | 986 | makeReplayServerConfig(transcript.scenario).pipe(Effect.orDie), |
| 987 | 987 | ).pipe(Layer.provide(NodeServices.layer)); |
| 988 | return ProviderAdapterRegistry.makeDriverLayer({ | |
| 988 | return ProviderAdapterRegistry.layerFromDrivers({ | |
| 989 | 989 | drivers: [ClaudeAdapterV2.ClaudeAdapterV2Driver], |
| 990 | 990 | configMap: { |
| 991 | 991 | [ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID]: { |
| ... | ... | @@ -995,10 +995,10 @@ function makeClaudeProviderAdapterRegistryReplayLayer( |
| 995 | 995 | }).pipe( |
| 996 | 996 | Layer.provide( |
| 997 | 997 | Layer.mergeAll( |
| 998 | makeClaudeAgentSdkReplayLayer(transcript, options), | |
| 998 | layerClaudeAgentSdkReplay(transcript, options), | |
| 999 | 999 | IdAllocator.layer, |
| 1000 | 1000 | NodeServices.layer, |
| 1001 | serverConfigLayer, | |
| 1001 | layerServerConfig, | |
| 1002 | 1002 | ), |
| 1003 | 1003 | ), |
| 1004 | 1004 | ); |
| ... | ... | @@ -2824,7 +2824,7 @@ export const ClaudeOrchestratorReplayHarness: OrchestratorV2ProviderReplayHarnes |
| 2824 | 2824 | ), |
| 2825 | 2825 | ), |
| 2826 | 2826 | makeProviderAdapterRegistryLayer: (transcript, options) => |
| 2827 | makeClaudeProviderAdapterRegistryReplayLayer(transcript, options), | |
| 2827 | layerClaudeProviderAdapterRegistryReplay(transcript, options), | |
| 2828 | 2828 | }; |
| 2829 | 2829 | |
| 2830 | 2830 | /** |
| ... | ... | @@ -2837,7 +2837,7 @@ export function makeClaudeRestartReplayHarness(transcript: ClaudeAgentSdkReplayT |
| 2837 | 2837 | harness: { |
| 2838 | 2838 | ...ClaudeOrchestratorReplayHarness, |
| 2839 | 2839 | makeProviderAdapterRegistryLayer: (replayed) => |
| 2840 | makeClaudeProviderAdapterRegistryReplayLayer(replayed, { queryRunner }), | |
| 2840 | layerClaudeProviderAdapterRegistryReplay(replayed, { queryRunner }), | |
| 2841 | 2841 | } satisfies typeof ClaudeOrchestratorReplayHarness, |
| 2842 | 2842 | assertComplete: replayQueryRunnerService(transcript, queryRunner).assertComplete, |
| 2843 | 2843 | }; |
apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts+1-1| ... | ... | @@ -593,7 +593,7 @@ export function makeClaudeAgentSdkProtocolLogger(input: { |
| 593 | 593 | }; |
| 594 | 594 | } |
| 595 | 595 | |
| 596 | export const claudeAgentSdkQueryRunnerLiveLayer: Layer.Layer< | |
| 596 | export const layerQueryRunner: Layer.Layer< | |
| 597 | 597 | ClaudeAgentSdkQueryRunner, |
| 598 | 598 | never, |
| 599 | 599 | Crypto.Crypto | ProviderEventLoggers.ProviderEventLoggers |
apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts+9-12| ... | ... | @@ -56,7 +56,7 @@ import * as ProviderEventLoggers from "../../provider/Layers/ProviderEventLogger |
| 56 | 56 | import * as IdAllocator from "../IdAllocator.ts"; |
| 57 | 57 | import * as EffectWorker from "../EffectWorker.ts"; |
| 58 | 58 | import * as Orchestrator from "../Orchestrator.ts"; |
| 59 | import { makeOrchestratorV2ReplayLayerWithRegistry } from "../testkit/ProviderReplayHarness.ts"; | |
| 59 | import * as ProviderReplayHarness from "../testkit/ProviderReplayHarness.ts"; | |
| 60 | 60 | import { |
| 61 | 61 | ProviderAdapterForkThreadError, |
| 62 | 62 | ProviderAdapterOpenSessionError, |
| ... | ... | @@ -67,11 +67,8 @@ import { |
| 67 | 67 | } from "../ProviderAdapter.ts"; |
| 68 | 68 | import type { ProviderContinuationRequest } from "../ProviderContinuationRequests.ts"; |
| 69 | 69 | import * as CodexAdapterV2 from "./CodexAdapterV2.ts"; |
| 70 | import { | |
| 71 | makeReplayServerConfig, | |
| 72 | makeCodexProviderAdapterRegistryReplayLayer, | |
| 73 | withCodexReplayChildMetadata, | |
| 74 | } from "./CodexAdapterV2.testkit.ts"; | |
| 70 | import { makeReplayServerConfig, withCodexReplayChildMetadata } from "./CodexAdapterV2.testkit.ts"; | |
| 71 | import * as CodexAdapterV2Testkit from "./CodexAdapterV2.testkit.ts"; | |
| 75 | 72 | |
| 76 | 73 | const encodeUnknownJson = Schema.encodeUnknownSync(Schema.fromJsonString(Schema.Unknown)); |
| 77 | 74 | const replayTranscriptJson = Schema.fromJsonString(CodexReplay.CodexAppServerReplayTranscript); |
| ... | ... | @@ -720,7 +717,7 @@ describe("CodexAdapterV2 process spawning", () => { |
| 720 | 717 | ); |
| 721 | 718 | }); |
| 722 | 719 | const factory = yield* CodexAdapterV2.CodexAppServerClientFactory.pipe( |
| 723 | Effect.provide(CodexAdapterV2.codexAppServerClientFactoryFromSettingsLayer), | |
| 720 | Effect.provide(CodexAdapterV2.layerAppServerClientFactory), | |
| 724 | 721 | Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner), |
| 725 | 722 | Effect.provideService( |
| 726 | 723 | ProviderEventLoggers.ProviderEventLoggers, |
| ... | ... | @@ -775,7 +772,7 @@ describe("CodexAdapterV2 process spawning", () => { |
| 775 | 772 | }).pipe( |
| 776 | 773 | Effect.provide( |
| 777 | 774 | Layer.mergeAll( |
| 778 | CodexAdapterV2.codexAppServerClientFactoryFromSettingsLayer, | |
| 775 | CodexAdapterV2.layerAppServerClientFactory, | |
| 779 | 776 | ServerConfig.layerTest(process.cwd(), { prefix: "t3-codex-binary-home-" }), |
| 780 | 777 | ), |
| 781 | 778 | ), |
| ... | ... | @@ -4238,9 +4235,9 @@ describe("CodexAdapterV2 post-settle continuation", () => { |
| 4238 | 4235 | ); |
| 4239 | 4236 | }).pipe( |
| 4240 | 4237 | Effect.provide( |
| 4241 | makeOrchestratorV2ReplayLayerWithRegistry( | |
| 4238 | ProviderReplayHarness.layerWithRegistry( | |
| 4242 | 4239 | { name: "codex-background-stop", runtimePolicyOverride: { cwd } }, |
| 4243 | makeCodexProviderAdapterRegistryReplayLayer({ | |
| 4240 | CodexAdapterV2Testkit.layer({ | |
| 4244 | 4241 | transcript: localTranscript, |
| 4245 | 4242 | driver: replayDriver, |
| 4246 | 4243 | }), |
| ... | ... | @@ -4361,9 +4358,9 @@ describe("CodexAdapterV2 post-settle continuation", () => { |
| 4361 | 4358 | ); |
| 4362 | 4359 | }).pipe( |
| 4363 | 4360 | Effect.provide( |
| 4364 | makeOrchestratorV2ReplayLayerWithRegistry( | |
| 4361 | ProviderReplayHarness.layerWithRegistry( | |
| 4365 | 4362 | { name: "codex-background-stop-untracked", runtimePolicyOverride: { cwd } }, |
| 4366 | makeCodexProviderAdapterRegistryReplayLayer({ transcript: localTranscript }), | |
| 4363 | CodexAdapterV2Testkit.layer({ transcript: localTranscript }), | |
| 4367 | 4364 | { runEffectWorker: false }, |
| 4368 | 4365 | ), |
| 4369 | 4366 | ), |
apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.testkit.ts+10-12| ... | ... | @@ -177,18 +177,18 @@ export function makeReplayServerConfig( |
| 177 | 177 | }); |
| 178 | 178 | } |
| 179 | 179 | |
| 180 | export function makeCodexProviderAdapterRegistryReplayLayer(input: { | |
| 180 | export function layer(input: { | |
| 181 | 181 | readonly transcript: CodexReplay.CodexAppServerReplayTranscript; |
| 182 | 182 | readonly driver?: CodexReplay.CodexAppServerReplayDriver; |
| 183 | 183 | }) { |
| 184 | const replayLayer = | |
| 184 | const layerReplay = | |
| 185 | 185 | input.driver === undefined |
| 186 | 186 | ? CodexReplay.layerReplay(input.transcript) |
| 187 | 187 | : CodexReplay.layerReplayWithDriver(input.driver); |
| 188 | const replayClientFactoryLayer = Layer.succeed(CodexAdapterV2.CodexAppServerClientFactory, { | |
| 188 | const layerReplayClientFactory = Layer.succeed(CodexAdapterV2.CodexAppServerClientFactory, { | |
| 189 | 189 | open: (openInput) => |
| 190 | 190 | Effect.gen(function* () { |
| 191 | const context = yield* Layer.build(replayLayer).pipe( | |
| 191 | const context = yield* Layer.build(layerReplay).pipe( | |
| 192 | 192 | Effect.mapError( |
| 193 | 193 | (cause) => |
| 194 | 194 | new ProviderAdapterOpenSessionError({ |
| ... | ... | @@ -204,11 +204,11 @@ export function makeCodexProviderAdapterRegistryReplayLayer(input: { |
| 204 | 204 | ); |
| 205 | 205 | }), |
| 206 | 206 | }); |
| 207 | const serverConfigLayer = Layer.effect( | |
| 207 | const layerServerConfig = Layer.effect( | |
| 208 | 208 | ServerConfig.ServerConfig, |
| 209 | 209 | makeReplayServerConfig(input.transcript.scenario).pipe(Effect.orDie), |
| 210 | 210 | ).pipe(Layer.provide(NodeServices.layer)); |
| 211 | const registryLayer = ProviderAdapterRegistry.makeDriverLayer({ | |
| 211 | const layerRegistry = ProviderAdapterRegistry.layerFromDrivers({ | |
| 212 | 212 | drivers: [CodexAdapterV2.CodexAdapterV2Driver], |
| 213 | 213 | configMap: { |
| 214 | 214 | [CodexAdapterV2.CODEX_DEFAULT_INSTANCE_ID]: { |
| ... | ... | @@ -218,15 +218,15 @@ export function makeCodexProviderAdapterRegistryReplayLayer(input: { |
| 218 | 218 | }).pipe( |
| 219 | 219 | Layer.provide( |
| 220 | 220 | Layer.mergeAll( |
| 221 | replayClientFactoryLayer, | |
| 222 | serverConfigLayer, | |
| 221 | layerReplayClientFactory, | |
| 222 | layerServerConfig, | |
| 223 | 223 | NodeServices.layer, |
| 224 | 224 | IdAllocator.layer, |
| 225 | 225 | ), |
| 226 | 226 | ), |
| 227 | 227 | ); |
| 228 | 228 | |
| 229 | return registryLayer; | |
| 229 | return layerRegistry; | |
| 230 | 230 | } |
| 231 | 231 | |
| 232 | 232 | const decodeCodexAppServerReplayTranscript = Schema.decodeUnknownEffect( |
| ... | ... | @@ -267,9 +267,7 @@ export const CodexOrchestratorReplayHarness: OrchestratorV2ProviderReplayHarness |
| 267 | 267 | Effect.promise((signal) => replayGate.beforeEmit(entry.label, signal)), |
| 268 | 268 | }, |
| 269 | 269 | ); |
| 270 | return yield* Layer.build( | |
| 271 | makeCodexProviderAdapterRegistryReplayLayer({ transcript, driver }), | |
| 272 | ); | |
| 270 | return yield* Layer.build(layer({ transcript, driver })); | |
| 273 | 271 | }), |
| 274 | 272 | ); |
| 275 | 273 | }, |
apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts+2-2| ... | ... | @@ -1340,7 +1340,7 @@ export const makeCodexAppServerSpawnCommand = Effect.fn( |
| 1340 | 1340 | }); |
| 1341 | 1341 | }); |
| 1342 | 1342 | |
| 1343 | const makeCodexAppServerClientFactoryCommandLayer = ( | |
| 1343 | const layerCodexAppServerClientFactoryCommand = ( | |
| 1344 | 1344 | options: CodexClient.CodexAppServerClientOptions & { |
| 1345 | 1345 | readonly command: string; |
| 1346 | 1346 | readonly args?: ReadonlyArray<string>; |
| ... | ... | @@ -1456,7 +1456,7 @@ function isSensitiveCodexProtocolKey(key: string): boolean { |
| 1456 | 1456 | ); |
| 1457 | 1457 | } |
| 1458 | 1458 | |
| 1459 | export const codexAppServerClientFactoryFromSettingsLayer: Layer.Layer< | |
| 1459 | export const layerAppServerClientFactory: Layer.Layer< | |
| 1460 | 1460 | CodexAppServerClientFactory, |
| 1461 | 1461 | never, |
| 1462 | 1462 | ChildProcessSpawner.ChildProcessSpawner | ProviderEventLoggers |
apps/server/src/orchestration-v2/Adapters/CursorAdapterV2.testkit.ts+9-10| ... | ... | @@ -492,7 +492,7 @@ export function makeCursorAgentSdkReplayRunner( |
| 492 | 492 | }; |
| 493 | 493 | } |
| 494 | 494 | |
| 495 | function makeCursorAgentSdkReplayLayer( | |
| 495 | function layerCursorAgentSdkReplay( | |
| 496 | 496 | transcript: CursorAgentSdkReplayTranscript, |
| 497 | 497 | options?: { |
| 498 | 498 | readonly runner?: CursorAgentSdk.CursorAgentSdkRunnerShape; |
| ... | ... | @@ -598,20 +598,20 @@ function makeReplayServerConfig( |
| 598 | 598 | }); |
| 599 | 599 | } |
| 600 | 600 | |
| 601 | export function makeCursorProviderAdapterRegistryReplayLayer( | |
| 601 | export function layer( | |
| 602 | 602 | transcript: CursorAgentSdkReplayTranscript, |
| 603 | 603 | options?: { |
| 604 | 604 | readonly runner?: CursorAgentSdk.CursorAgentSdkRunnerShape; |
| 605 | 605 | readonly assertCompleteOnFinalize?: boolean; |
| 606 | 606 | }, |
| 607 | 607 | ) { |
| 608 | const serverConfigLayer = Layer.effect( | |
| 608 | const layerServerConfig = Layer.effect( | |
| 609 | 609 | ServerConfig.ServerConfig, |
| 610 | 610 | makeReplayServerConfig(transcript.scenario).pipe(Effect.orDie), |
| 611 | 611 | ).pipe(Layer.provide(NodeServices.layer)); |
| 612 | 612 | // Skill discovery also scans user roots under HOME; an empty HOME keeps |
| 613 | 613 | // replays from picking up the host's own skills. |
| 614 | const hostEnvironmentLayer = Layer.effect( | |
| 614 | const layerHostEnvironment = Layer.effect( | |
| 615 | 615 | HostProcessEnvironment, |
| 616 | 616 | Effect.gen(function* () { |
| 617 | 617 | const fs = yield* FileSystem.FileSystem; |
| ... | ... | @@ -619,7 +619,7 @@ export function makeCursorProviderAdapterRegistryReplayLayer( |
| 619 | 619 | return { HOME: home }; |
| 620 | 620 | }).pipe(Effect.orDie), |
| 621 | 621 | ).pipe(Layer.provide(NodeServices.layer)); |
| 622 | return ProviderAdapterRegistry.makeDriverLayer({ | |
| 622 | return ProviderAdapterRegistry.layerFromDrivers({ | |
| 623 | 623 | drivers: [CursorAdapterV2Driver], |
| 624 | 624 | configMap: { |
| 625 | 625 | [CURSOR_DEFAULT_INSTANCE_ID]: { |
| ... | ... | @@ -629,9 +629,9 @@ export function makeCursorProviderAdapterRegistryReplayLayer( |
| 629 | 629 | }).pipe( |
| 630 | 630 | Layer.provide( |
| 631 | 631 | Layer.mergeAll( |
| 632 | makeCursorAgentSdkReplayLayer(transcript, options), | |
| 633 | serverConfigLayer, | |
| 634 | hostEnvironmentLayer, | |
| 632 | layerCursorAgentSdkReplay(transcript, options), | |
| 633 | layerServerConfig, | |
| 634 | layerHostEnvironment, | |
| 635 | 635 | NodeServices.layer, |
| 636 | 636 | IdAllocator.layer, |
| 637 | 637 | ), |
| ... | ... | @@ -666,8 +666,7 @@ export const CursorOrchestratorReplayHarness: OrchestratorV2ProviderReplayHarnes |
| 666 | 666 | }), |
| 667 | 667 | ), |
| 668 | 668 | ), |
| 669 | makeProviderAdapterRegistryLayer: (transcript) => | |
| 670 | makeCursorProviderAdapterRegistryReplayLayer(transcript), | |
| 669 | makeProviderAdapterRegistryLayer: (transcript) => layer(transcript), | |
| 671 | 670 | }; |
| 672 | 671 | |
| 673 | 672 | function sanitizeReplayText( |
apps/server/src/orchestration-v2/Adapters/CursorAgentSdk.test.ts+6-6| ... | ... | @@ -104,7 +104,7 @@ vi.mock("../../provider/cursorSdk.ts", () => ({ |
| 104 | 104 | }, |
| 105 | 105 | })); |
| 106 | 106 | |
| 107 | const testLayer = CursorAgentSdk.cursorAgentSdkRunnerLiveLayer.pipe( | |
| 107 | const layerTest = CursorAgentSdk.layer.pipe( | |
| 108 | 108 | Layer.provide( |
| 109 | 109 | Layer.succeed(ProviderEventLoggers.ProviderEventLoggers, { |
| 110 | 110 | native: { |
| ... | ... | @@ -147,7 +147,7 @@ describe("CursorAgentSdkRunner", () => { |
| 147 | 147 | yield* run.wait; |
| 148 | 148 | |
| 149 | 149 | assert.deepStrictEqual(cursorSdkMock.runOperations, ["run.start"]); |
| 150 | }).pipe(Effect.provide(testLayer)), | |
| 150 | }).pipe(Effect.provide(layerTest)), | |
| 151 | 151 | ); |
| 152 | 152 | |
| 153 | 153 | it.effect("retries a resumed send after cancelling its abandoned local run", () => |
| ... | ... | @@ -188,7 +188,7 @@ describe("CursorAgentSdkRunner", () => { |
| 188 | 188 | cwd: process.cwd(), |
| 189 | 189 | }, |
| 190 | 190 | ]); |
| 191 | }).pipe(Effect.provide(testLayer)), | |
| 191 | }).pipe(Effect.provide(layerTest)), | |
| 192 | 192 | ); |
| 193 | 193 | |
| 194 | 194 | it.effect("logs agent.close before invoking the Cursor SDK", () => |
| ... | ... | @@ -221,7 +221,7 @@ describe("CursorAgentSdkRunner", () => { |
| 221 | 221 | "sdk:agent.close", |
| 222 | 222 | ]); |
| 223 | 223 | assert.equal(cursorSdkMock.agentClose.mock.calls.length, 1); |
| 224 | }).pipe(Effect.provide(testLayer)), | |
| 224 | }).pipe(Effect.provide(layerTest)), | |
| 225 | 225 | ); |
| 226 | 226 | |
| 227 | 227 | it.effect("logs agent.close even when the Cursor SDK close fails", () => |
| ... | ... | @@ -259,7 +259,7 @@ describe("CursorAgentSdkRunner", () => { |
| 259 | 259 | ]); |
| 260 | 260 | assert.equal(cursorSdkMock.agentClose.mock.calls.length, 1); |
| 261 | 261 | cursorSdkMock.setAgentCloseFailure(undefined); |
| 262 | }).pipe(Effect.provide(testLayer)), | |
| 262 | }).pipe(Effect.provide(layerTest)), | |
| 263 | 263 | ); |
| 264 | 264 | |
| 265 | 265 | it.effect("surfaces interaction callback failures through run.wait", () => |
| ... | ... | @@ -299,6 +299,6 @@ describe("CursorAgentSdkRunner", () => { |
| 299 | 299 | assert.equal(cursorSdkMock.create.mock.calls.length, 1); |
| 300 | 300 | assert.equal(cursorSdkMock.send.mock.calls.length, 1); |
| 301 | 301 | assert.equal(cursorSdkMock.runWait.mock.calls.length, 1); |
| 302 | }).pipe(Effect.provide(testLayer)), | |
| 302 | }).pipe(Effect.provide(layerTest)), | |
| 303 | 303 | ); |
| 304 | 304 | }); |
apps/server/src/orchestration-v2/Adapters/CursorAgentSdk.ts+1-1| ... | ... | @@ -593,7 +593,7 @@ export function makeCursorAgentSdkRunner( |
| 593 | 593 | }); |
| 594 | 594 | } |
| 595 | 595 | |
| 596 | export const cursorAgentSdkRunnerLiveLayer: Layer.Layer< | |
| 596 | export const layer: Layer.Layer< | |
| 597 | 597 | CursorAgentSdkRunner, |
| 598 | 598 | never, |
| 599 | 599 | ProviderEventLoggers.ProviderEventLoggers |
apps/server/src/orchestration-v2/Adapters/GrokAdapterV2.test.ts+3-3| ... | ... | @@ -311,10 +311,10 @@ describe("Grok permission prompts", () => { |
| 311 | 311 | }); |
| 312 | 312 | |
| 313 | 313 | describe("Grok launch permission mode", () => { |
| 314 | const serverConfigLayer = ServerConfig.layerTest(process.cwd(), { | |
| 314 | const layerServerConfig = ServerConfig.layerTest(process.cwd(), { | |
| 315 | 315 | prefix: "t3-grok-v2-launch-", |
| 316 | 316 | }).pipe(Layer.provide(NodeServices.layer)); |
| 317 | const testLayer = Layer.mergeAll(NodeServices.layer, IdAllocator.layer, serverConfigLayer); | |
| 317 | const layerTest = Layer.mergeAll(NodeServices.layer, IdAllocator.layer, layerServerConfig); | |
| 318 | 318 | |
| 319 | 319 | // Opens a session through the adapter's own Grok runtime factory and returns |
| 320 | 320 | // the argv it tried to launch. The spawn fails after recording, so no |
| ... | ... | @@ -357,7 +357,7 @@ describe("Grok launch permission mode", () => { |
| 357 | 357 | }).pipe( |
| 358 | 358 | // Keep the launch argv unwrapped by the Linux cgroup shim. |
| 359 | 359 | Effect.provideService(HostProcessPlatform, "darwin"), |
| 360 | Effect.provide(testLayer), | |
| 360 | Effect.provide(layerTest), | |
| 361 | 361 | ); |
| 362 | 362 | |
| 363 | 363 | const policy = ( |
apps/server/src/orchestration-v2/Adapters/GrokAdapterV2.testkit.ts+5-5| ... | ... | @@ -33,16 +33,16 @@ import { GROK_DEFAULT_INSTANCE_ID, GROK_PROVIDER, makeGrokAdapterV2 } from "./Gr |
| 33 | 33 | |
| 34 | 34 | const DEFAULT_GROK_SETTINGS = Schema.decodeUnknownSync(GrokSettings)({}); |
| 35 | 35 | |
| 36 | function makeGrokProviderAdapterRegistryReplayLayer( | |
| 36 | function layerGrokProviderAdapterRegistryReplay( | |
| 37 | 37 | transcript: AcpReplayTranscript, |
| 38 | 38 | options: { readonly replayGate?: ProviderReplayGate } = {}, |
| 39 | 39 | ) { |
| 40 | const serverConfigLayer = Layer.effect( | |
| 40 | const layerServerConfig = Layer.effect( | |
| 41 | 41 | ServerConfig.ServerConfig, |
| 42 | 42 | makeReplayServerConfig(`grok-${transcript.scenario}`).pipe(Effect.orDie), |
| 43 | 43 | ).pipe(Layer.provide(NodeServices.layer)); |
| 44 | 44 | |
| 45 | return ProviderAdapterRegistry.makeLayerEffect( | |
| 45 | return ProviderAdapterRegistry.layerFromAdaptersEffect( | |
| 46 | 46 | Effect.gen(function* () { |
| 47 | 47 | const fileSystem = yield* FileSystem.FileSystem; |
| 48 | 48 | const path = yield* Path.Path; |
| ... | ... | @@ -102,7 +102,7 @@ function makeGrokProviderAdapterRegistryReplayLayer( |
| 102 | 102 | return [adapter]; |
| 103 | 103 | }), |
| 104 | 104 | ).pipe( |
| 105 | Layer.provide(Layer.mergeAll(serverConfigLayer, NodeServices.layer, IdAllocator.layer)), | |
| 105 | Layer.provide(Layer.mergeAll(layerServerConfig, NodeServices.layer, IdAllocator.layer)), | |
| 106 | 106 | // Held inbound lines must not outlive the scenario and wedge teardown. |
| 107 | 107 | Layer.merge( |
| 108 | 108 | Layer.effectDiscard( |
| ... | ... | @@ -118,5 +118,5 @@ export const GrokOrchestratorReplayHarness: OrchestratorV2ProviderReplayHarness< |
| 118 | 118 | > = { |
| 119 | 119 | driver: GROK_PROVIDER, |
| 120 | 120 | decodeTranscript: (transcript) => decodeAcpReplayTranscript(transcript, GROK_PROVIDER), |
| 121 | makeProviderAdapterRegistryLayer: makeGrokProviderAdapterRegistryReplayLayer, | |
| 121 | makeProviderAdapterRegistryLayer: layerGrokProviderAdapterRegistryReplay, | |
| 122 | 122 | }; |
apps/server/src/orchestration-v2/Adapters/OpenCode2AdapterV2.testkit.ts+8-6| ... | ... | @@ -270,20 +270,22 @@ const makeReplayAdapter = ( |
| 270 | 270 | ); |
| 271 | 271 | }); |
| 272 | 272 | |
| 273 | const replayServerConfig = (scenario: string) => | |
| 273 | const layerReplayServerConfig = (scenario: string) => | |
| 274 | 274 | Layer.effect(ServerConfig.ServerConfig, makeReplayServerConfig(scenario).pipe(Effect.orDie)).pipe( |
| 275 | 275 | Layer.provide(NodeServices.layer), |
| 276 | 276 | ); |
| 277 | 277 | |
| 278 | function makeRegistryLayer( | |
| 278 | function layerRegistry( | |
| 279 | 279 | transcript: OpenCode2ReplayTranscript, |
| 280 | 280 | options?: { readonly replayGate?: ProviderReplayGate }, |
| 281 | 281 | ) { |
| 282 | 282 | return Layer.unwrap( |
| 283 | 283 | makeReplayAdapter(transcript, { external: true, ...options }).pipe( |
| 284 | Effect.map((adapter) => ProviderAdapterRegistry.makeLayer([adapter])), | |
| 284 | Effect.map((adapter) => ProviderAdapterRegistry.layerFromAdapters([adapter])), | |
| 285 | 285 | ), |
| 286 | ).pipe(Layer.provide(Layer.mergeAll(replayServerConfig(transcript.scenario), IdAllocator.layer))); | |
| 286 | ).pipe( | |
| 287 | Layer.provide(Layer.mergeAll(layerReplayServerConfig(transcript.scenario), IdAllocator.layer)), | |
| 288 | ); | |
| 287 | 289 | } |
| 288 | 290 | |
| 289 | 291 | /** |
| ... | ... | @@ -320,7 +322,7 @@ export const openCode2ReplayRuntime = ( |
| 320 | 322 | }, |
| 321 | 323 | }); |
| 322 | 324 | }).pipe( |
| 323 | Effect.provide(Layer.mergeAll(replayServerConfig("opencode2_adapter"), IdAllocator.layer)), | |
| 325 | Effect.provide(Layer.mergeAll(layerReplayServerConfig("opencode2_adapter"), IdAllocator.layer)), | |
| 324 | 326 | ); |
| 325 | 327 | |
| 326 | 328 | export const OpenCode2OrchestratorReplayHarness: OrchestratorV2ProviderReplayHarness< |
| ... | ... | @@ -340,5 +342,5 @@ export const OpenCode2OrchestratorReplayHarness: OrchestratorV2ProviderReplayHar |
| 340 | 342 | }), |
| 341 | 343 | ), |
| 342 | 344 | ), |
| 343 | makeProviderAdapterRegistryLayer: makeRegistryLayer, | |
| 345 | makeProviderAdapterRegistryLayer: layerRegistry, | |
| 344 | 346 | }; |
apps/server/src/orchestration-v2/Adapters/OpenCodeAdapterV2.testkit.ts+7-7| ... | ... | @@ -389,7 +389,7 @@ function makeReplayClient(controller: OpenCodeReplayController): OpencodeClient |
| 389 | 389 | } as unknown as OpencodeClient; |
| 390 | 390 | } |
| 391 | 391 | |
| 392 | function makeOpenCodeReplayRuntimeLayer(transcript: OpenCodeSdkReplayTranscript) { | |
| 392 | function layerOpenCodeReplayRuntime(transcript: OpenCodeSdkReplayTranscript) { | |
| 393 | 393 | return Layer.effect( |
| 394 | 394 | OpenCodeRuntime.OpenCodeRuntime, |
| 395 | 395 | Effect.gen(function* () { |
| ... | ... | @@ -456,12 +456,12 @@ function makeOpenCodeReplayRuntimeLayer(transcript: OpenCodeSdkReplayTranscript) |
| 456 | 456 | ); |
| 457 | 457 | } |
| 458 | 458 | |
| 459 | function makeOpenCodeProviderAdapterRegistryReplayLayer(transcript: OpenCodeSdkReplayTranscript) { | |
| 460 | const serverConfigLayer = Layer.effect( | |
| 459 | function layerOpenCodeProviderAdapterRegistryReplay(transcript: OpenCodeSdkReplayTranscript) { | |
| 460 | const layerServerConfig = Layer.effect( | |
| 461 | 461 | ServerConfig.ServerConfig, |
| 462 | 462 | makeReplayServerConfig(transcript.scenario).pipe(Effect.orDie), |
| 463 | 463 | ).pipe(Layer.provide(NodeServices.layer)); |
| 464 | return ProviderAdapterRegistry.makeDriverLayer({ | |
| 464 | return ProviderAdapterRegistry.layerFromDrivers({ | |
| 465 | 465 | drivers: [OpenCodeAdapterV2Driver], |
| 466 | 466 | configMap: { |
| 467 | 467 | [OPENCODE_DEFAULT_INSTANCE_ID]: { |
| ... | ... | @@ -472,8 +472,8 @@ function makeOpenCodeProviderAdapterRegistryReplayLayer(transcript: OpenCodeSdkR |
| 472 | 472 | }).pipe( |
| 473 | 473 | Layer.provide( |
| 474 | 474 | Layer.mergeAll( |
| 475 | makeOpenCodeReplayRuntimeLayer(transcript), | |
| 476 | serverConfigLayer, | |
| 475 | layerOpenCodeReplayRuntime(transcript), | |
| 476 | layerServerConfig, | |
| 477 | 477 | NodeServices.layer, |
| 478 | 478 | IdAllocator.layer, |
| 479 | 479 | Layer.succeed( |
| ... | ... | @@ -508,5 +508,5 @@ export const OpenCodeOrchestratorReplayHarness: OrchestratorV2ProviderReplayHarn |
| 508 | 508 | }), |
| 509 | 509 | ), |
| 510 | 510 | ), |
| 511 | makeProviderAdapterRegistryLayer: makeOpenCodeProviderAdapterRegistryReplayLayer, | |
| 511 | makeProviderAdapterRegistryLayer: layerOpenCodeProviderAdapterRegistryReplay, | |
| 512 | 512 | }; |
apps/server/src/orchestration-v2/Adapters/PiAdapterV2.test.ts+46-46| ... | ... | @@ -43,11 +43,11 @@ import { handoffBudget } from "../ContextHandoffBudget.ts"; |
| 43 | 43 | import { makePiAdapterV2, PI_PROVIDER } from "./PiAdapterV2.ts"; |
| 44 | 44 | import { makePiRpcConnection, type PiRpcRecord } from "./PiRpc.ts"; |
| 45 | 45 | |
| 46 | const serverConfigLayer = ServerConfig.layerTest(process.cwd(), { | |
| 46 | const layerServerConfig = ServerConfig.layerTest(process.cwd(), { | |
| 47 | 47 | prefix: "t3-pi-v2-adapter-", |
| 48 | 48 | }).pipe(Layer.provide(NodeServices.layer)); |
| 49 | 49 | |
| 50 | const testLayer = Layer.mergeAll(NodeServices.layer, IdAllocator.layer, serverConfigLayer); | |
| 50 | const layerTest = Layer.mergeAll(NodeServices.layer, IdAllocator.layer, layerServerConfig); | |
| 51 | 51 | |
| 52 | 52 | const decodeJsonLine = Schema.decodeSync(Schema.fromJsonString(Schema.Unknown)); |
| 53 | 53 | const encodeJsonLine = Schema.encodeSync(Schema.fromJsonString(Schema.Unknown)); |
| ... | ... | @@ -458,7 +458,7 @@ const expectModelFailure = (errorMessage: string) => |
| 458 | 458 | terminal.status === "failed" && |
| 459 | 459 | terminal.failure.message === errorMessage, |
| 460 | 460 | ); |
| 461 | }).pipe(Effect.scoped, Effect.provide(testLayer)); | |
| 461 | }).pipe(Effect.scoped, Effect.provide(layerTest)); | |
| 462 | 462 | |
| 463 | 463 | describe("PiAdapterV2", () => { |
| 464 | 464 | it.effect("stops provider-initiated work that has no T3 turn owner", () => |
| ... | ... | @@ -481,7 +481,7 @@ describe("PiAdapterV2", () => { |
| 481 | 481 | sessionError.type === "provider_session.updated" && |
| 482 | 482 | sessionError.providerSession.lastError?.includes("invisible tool execution") === true, |
| 483 | 483 | ); |
| 484 | }).pipe(Effect.scoped, Effect.provide(testLayer)), | |
| 484 | }).pipe(Effect.scoped, Effect.provide(layerTest)), | |
| 485 | 485 | ); |
| 486 | 486 | |
| 487 | 487 | it.effect("injects the T3 MCP extension and bearer when a session exists", () => |
| ... | ... | @@ -510,7 +510,7 @@ describe("PiAdapterV2", () => { |
| 510 | 510 | }).pipe( |
| 511 | 511 | Effect.ensuring(Effect.sync(() => McpProviderSession.clearMcpProviderSession(THREAD_ID))), |
| 512 | 512 | Effect.scoped, |
| 513 | Effect.provide(testLayer), | |
| 513 | Effect.provide(layerTest), | |
| 514 | 514 | ), |
| 515 | 515 | ); |
| 516 | 516 | |
| ... | ... | @@ -528,7 +528,7 @@ describe("PiAdapterV2", () => { |
| 528 | 528 | const error = yield* runtime.resumeThread({ providerThread }).pipe(Effect.flip); |
| 529 | 529 | assert.equal(error._tag, "ProviderAdapterResumeThreadError"); |
| 530 | 530 | assert.match(String(error.cause), /while a turn is active/); |
| 531 | }).pipe(Effect.scoped, Effect.provide(testLayer)), | |
| 531 | }).pipe(Effect.scoped, Effect.provide(layerTest)), | |
| 532 | 532 | ); |
| 533 | 533 | |
| 534 | 534 | it.effect("waits for a slow Pi resume without starting a replacement", () => |
| ... | ... | @@ -555,7 +555,7 @@ describe("PiAdapterV2", () => { |
| 555 | 555 | assert.isFalse(fake.allRequests().some((request) => request.type === "new_session")); |
| 556 | 556 | yield* startTurn(runtime, providerThread, "default"); |
| 557 | 557 | yield* fake.takeRequest("prompt"); |
| 558 | }).pipe(Effect.scoped, Effect.provide(testLayer)), | |
| 558 | }).pipe(Effect.scoped, Effect.provide(layerTest)), | |
| 559 | 559 | ); |
| 560 | 560 | |
| 561 | 561 | it.effect("creates a distinct native session after a failed resume", () => |
| ... | ... | @@ -586,7 +586,7 @@ describe("PiAdapterV2", () => { |
| 586 | 586 | ); |
| 587 | 587 | yield* startTurn(runtime, replacement, "default"); |
| 588 | 588 | yield* fake.takeRequest("prompt"); |
| 589 | }).pipe(Effect.scoped, Effect.provide(testLayer)), | |
| 589 | }).pipe(Effect.scoped, Effect.provide(layerTest)), | |
| 590 | 590 | ); |
| 591 | 591 | |
| 592 | 592 | it.effect.each(["veto", "same identity"] as const)( |
| ... | ... | @@ -617,7 +617,7 @@ describe("PiAdapterV2", () => { |
| 617 | 617 | ); |
| 618 | 618 | yield* startTurn(runtime, providerThread, "default").pipe(Effect.flip); |
| 619 | 619 | assert.isFalse(fake.allRequests().some((request) => request.type === "prompt")); |
| 620 | }).pipe(Effect.scoped, Effect.provide(testLayer)), | |
| 620 | }).pipe(Effect.scoped, Effect.provide(layerTest)), | |
| 621 | 621 | ); |
| 622 | 622 | |
| 623 | 623 | it.effect("retires a timed-out lifecycle process before a late switch can race replacement", () => |
| ... | ... | @@ -661,7 +661,7 @@ describe("PiAdapterV2", () => { |
| 661 | 661 | .allRequests() |
| 662 | 662 | .some((request) => request.type === "new_session" || request.type === "prompt"), |
| 663 | 663 | ); |
| 664 | }).pipe(Effect.scoped, Effect.provide(testLayer)), | |
| 664 | }).pipe(Effect.scoped, Effect.provide(layerTest)), | |
| 665 | 665 | ); |
| 666 | 666 | |
| 667 | 667 | it.effect( |
| ... | ... | @@ -707,7 +707,7 @@ describe("PiAdapterV2", () => { |
| 707 | 707 | assert.equal(replacement.nativeThreadRef?.nativeId, "/fake/fresh-after-delay.jsonl"); |
| 708 | 708 | assert.isNull(replacement.contextUsage); |
| 709 | 709 | assert.isNull(replacement.nativeConversationHeadRef); |
| 710 | }).pipe(Effect.scoped, Effect.provide(testLayer)), | |
| 710 | }).pipe(Effect.scoped, Effect.provide(layerTest)), | |
| 711 | 711 | ); |
| 712 | 712 | |
| 713 | 713 | it.effect("replaces a native session when the first resume's state refresh fails", () => |
| ... | ... | @@ -736,7 +736,7 @@ describe("PiAdapterV2", () => { |
| 736 | 736 | yield* startTurn(runtime, replacement, "default"); |
| 737 | 737 | yield* fake.takeRequest("prompt"); |
| 738 | 738 | assert.isTrue(fake.allRequests().some((request) => request.type === "new_session")); |
| 739 | }).pipe(Effect.scoped, Effect.provide(testLayer)), | |
| 739 | }).pipe(Effect.scoped, Effect.provide(layerTest)), | |
| 740 | 740 | ); |
| 741 | 741 | |
| 742 | 742 | it.effect("retires an interrupted switch before accepting further requests", () => |
| ... | ... | @@ -765,7 +765,7 @@ describe("PiAdapterV2", () => { |
| 765 | 765 | }) |
| 766 | 766 | .pipe(Effect.flip); |
| 767 | 767 | assert.isFalse(fake.allRequests().some((request) => request.type === "new_session")); |
| 768 | }).pipe(Effect.scoped, Effect.provide(testLayer)), | |
| 768 | }).pipe(Effect.scoped, Effect.provide(layerTest)), | |
| 769 | 769 | ); |
| 770 | 770 | |
| 771 | 771 | it.effect("budgets legacy native history with Pi's selected model capacity", () => |
| ... | ... | @@ -814,7 +814,7 @@ describe("PiAdapterV2", () => { |
| 814 | 814 | existingProviderThread: { ...providerThread, nativeThreadRef: null }, |
| 815 | 815 | }); |
| 816 | 816 | assert.equal(runtime.getModelContextWindow?.(modelSelection("default")), 32_000); |
| 817 | }).pipe(Effect.scoped, Effect.provide(testLayer)), | |
| 817 | }).pipe(Effect.scoped, Effect.provide(layerTest)), | |
| 818 | 818 | ); |
| 819 | 819 | |
| 820 | 820 | it.effect("adopts the run's provider thread identity instead of minting a second row", () => |
| ... | ... | @@ -856,7 +856,7 @@ describe("PiAdapterV2", () => { |
| 856 | 856 | assert.isTrue( |
| 857 | 857 | updated.type === "provider_thread.updated" && updated.providerThread.id === placeholder.id, |
| 858 | 858 | ); |
| 859 | }).pipe(Effect.scoped, Effect.provide(testLayer)), | |
| 859 | }).pipe(Effect.scoped, Effect.provide(layerTest)), | |
| 860 | 860 | ); |
| 861 | 861 | |
| 862 | 862 | it.effect("resets applied thinking when returning to Pi default", () => |
| ... | ... | @@ -909,7 +909,7 @@ describe("PiAdapterV2", () => { |
| 909 | 909 | assert.equal(replayModel["modelId"], "grok-4.6"); |
| 910 | 910 | const resetLevel = yield* fake.takeRequest("set_thinking_level"); |
| 911 | 911 | assert.equal(resetLevel["level"], "medium"); |
| 912 | }).pipe(Effect.scoped, Effect.provide(testLayer)), | |
| 912 | }).pipe(Effect.scoped, Effect.provide(layerTest)), | |
| 913 | 913 | ); |
| 914 | 914 | |
| 915 | 915 | it.effect("expands a selected $ skill through Pi's native skill command", () => |
| ... | ... | @@ -944,7 +944,7 @@ describe("PiAdapterV2", () => { |
| 944 | 944 | ); |
| 945 | 945 | const prompt = yield* fake.takeRequest("prompt"); |
| 946 | 946 | assert.equal(prompt["message"], "/skill:repo-review Review this change please"); |
| 947 | }).pipe(Effect.scoped, Effect.provide(testLayer)), | |
| 947 | }).pipe(Effect.scoped, Effect.provide(layerTest)), | |
| 948 | 948 | ); |
| 949 | 949 | |
| 950 | 950 | it.effect("expands every selected $ skill through Pi native skill commands", () => |
| ... | ... | @@ -980,7 +980,7 @@ describe("PiAdapterV2", () => { |
| 980 | 980 | yield* startTurn(runtime, providerThread, "default", [], "use $repo-review and $deploy"); |
| 981 | 981 | const prompt = yield* fake.takeRequest("prompt"); |
| 982 | 982 | assert.equal(prompt["message"], "/skill:repo-review /skill:deploy use and"); |
| 983 | }).pipe(Effect.scoped, Effect.provide(testLayer)), | |
| 983 | }).pipe(Effect.scoped, Effect.provide(layerTest)), | |
| 984 | 984 | ); |
| 985 | 985 | |
| 986 | 986 | it.effect.each([ |
| ... | ... | @@ -1060,7 +1060,7 @@ describe("PiAdapterV2", () => { |
| 1060 | 1060 | fake.allRequests().findLast((request) => request.type === "switch_session")?.sessionPath, |
| 1061 | 1061 | forkFile, |
| 1062 | 1062 | ); |
| 1063 | }).pipe(Effect.scoped, Effect.provide(testLayer)), | |
| 1063 | }).pipe(Effect.scoped, Effect.provide(layerTest)), | |
| 1064 | 1064 | ); |
| 1065 | 1065 | |
| 1066 | 1066 | it.effect("presents explicitly namespaced MCP extension tools", () => |
| ... | ... | @@ -1101,7 +1101,7 @@ describe("PiAdapterV2", () => { |
| 1101 | 1101 | }); |
| 1102 | 1102 | assert.deepEqual(event.turnItem.input, { city: "Berlin" }); |
| 1103 | 1103 | } |
| 1104 | }).pipe(Effect.scoped, Effect.provide(testLayer)), | |
| 1104 | }).pipe(Effect.scoped, Effect.provide(layerTest)), | |
| 1105 | 1105 | ); |
| 1106 | 1106 | |
| 1107 | 1107 | it.effect("observes official subagent results without inventing child threads", () => |
| ... | ... | @@ -1193,7 +1193,7 @@ describe("PiAdapterV2", () => { |
| 1193 | 1193 | subagentItem.turnItem.type === "subagent" && |
| 1194 | 1194 | subagentItem.turnItem.childThreadId === null, |
| 1195 | 1195 | ); |
| 1196 | }).pipe(Effect.scoped, Effect.provide(testLayer)), | |
| 1196 | }).pipe(Effect.scoped, Effect.provide(layerTest)), | |
| 1197 | 1197 | ); |
| 1198 | 1198 | |
| 1199 | 1199 | it.effect("persists edit patches and write content on file change items", () => |
| ... | ... | @@ -1289,7 +1289,7 @@ describe("PiAdapterV2", () => { |
| 1289 | 1289 | failedEdit.turnItem.type === "file_change" && |
| 1290 | 1290 | failedEdit.turnItem.diffStr === "Could not find the text in c.ts.", |
| 1291 | 1291 | ); |
| 1292 | }).pipe(Effect.scoped, Effect.provide(testLayer)), | |
| 1292 | }).pipe(Effect.scoped, Effect.provide(layerTest)), | |
| 1293 | 1293 | ); |
| 1294 | 1294 | |
| 1295 | 1295 | it.effect("settles a command-only prompt from its deferred ack and idle probe", () => |
| ... | ... | @@ -1317,7 +1317,7 @@ describe("PiAdapterV2", () => { |
| 1317 | 1317 | // settles the turn as completed. |
| 1318 | 1318 | const terminal = yield* takeEvent((event) => event.type === "turn.terminal"); |
| 1319 | 1319 | assert.isTrue(terminal.type === "turn.terminal" && terminal.status === "completed"); |
| 1320 | }).pipe(Effect.scoped, Effect.provide(testLayer)), | |
| 1320 | }).pipe(Effect.scoped, Effect.provide(layerTest)), | |
| 1321 | 1321 | ); |
| 1322 | 1322 | |
| 1323 | 1323 | it.effect("leaves /compacted as an ordinary prompt", () => |
| ... | ... | @@ -1333,7 +1333,7 @@ describe("PiAdapterV2", () => { |
| 1333 | 1333 | const prompt = yield* fake.takeRequest("prompt"); |
| 1334 | 1334 | assert.equal(prompt["message"], "/compacted please"); |
| 1335 | 1335 | assert.isFalse(fake.allRequests().some((request) => request["type"] === "compact")); |
| 1336 | }).pipe(Effect.scoped, Effect.provide(testLayer)), | |
| 1336 | }).pipe(Effect.scoped, Effect.provide(layerTest)), | |
| 1337 | 1337 | ); |
| 1338 | 1338 | |
| 1339 | 1339 | it.effect("fails a compact that never started", () => |
| ... | ... | @@ -1359,7 +1359,7 @@ describe("PiAdapterV2", () => { |
| 1359 | 1359 | terminal.status === "failed" && |
| 1360 | 1360 | terminal.failure.message === "Nothing to compact (session too small)", |
| 1361 | 1361 | ); |
| 1362 | }).pipe(Effect.scoped, Effect.provide(testLayer)), | |
| 1362 | }).pipe(Effect.scoped, Effect.provide(layerTest)), | |
| 1363 | 1363 | ); |
| 1364 | 1364 | |
| 1365 | 1365 | it.effect("restarts Pi when Stop interrupts a user compact", () => |
| ... | ... | @@ -1389,7 +1389,7 @@ describe("PiAdapterV2", () => { |
| 1389 | 1389 | yield* fake.closeStdout; |
| 1390 | 1390 | const terminal = yield* takeEvent((event) => event.type === "turn.terminal"); |
| 1391 | 1391 | assert.isTrue(terminal.type === "turn.terminal" && terminal.status === "interrupted"); |
| 1392 | }).pipe(Effect.scoped, Effect.provide(testLayer)), | |
| 1392 | }).pipe(Effect.scoped, Effect.provide(layerTest)), | |
| 1393 | 1393 | ); |
| 1394 | 1394 | |
| 1395 | 1395 | it.effect("steers /compact as RPC compact instead of a prompt", () => |
| ... | ... | @@ -1449,7 +1449,7 @@ describe("PiAdapterV2", () => { |
| 1449 | 1449 | yield* fake.takeRequest("get_state"); |
| 1450 | 1450 | const terminal = yield* takeEvent((event) => event.type === "turn.terminal"); |
| 1451 | 1451 | assert.isTrue(terminal.type === "turn.terminal" && terminal.status === "completed"); |
| 1452 | }).pipe(Effect.scoped, Effect.provide(testLayer)), | |
| 1452 | }).pipe(Effect.scoped, Effect.provide(layerTest)), | |
| 1453 | 1453 | ); |
| 1454 | 1454 | |
| 1455 | 1455 | it.effect("persists current xAI capacity text for the thread error banner", () => |
| ... | ... | @@ -1489,7 +1489,7 @@ describe("PiAdapterV2", () => { |
| 1489 | 1489 | const uiResponse = yield* fake.takeRequest("extension_ui_response"); |
| 1490 | 1490 | assert.equal(uiResponse["id"], "ui-trust"); |
| 1491 | 1491 | assert.equal(uiResponse["confirmed"], true); |
| 1492 | }).pipe(Effect.scoped, Effect.provide(testLayer)), | |
| 1492 | }).pipe(Effect.scoped, Effect.provide(layerTest)), | |
| 1493 | 1493 | ); |
| 1494 | 1494 | |
| 1495 | 1495 | it.effect("remembers session approvals only for identical confirmation content", () => |
| ... | ... | @@ -1545,7 +1545,7 @@ describe("PiAdapterV2", () => { |
| 1545 | 1545 | other.type === "runtime_request.updated" && |
| 1546 | 1546 | other.runtimeRequest.nativeRequestRef?.nativeId === "ui-other", |
| 1547 | 1547 | ); |
| 1548 | }).pipe(Effect.scoped, Effect.provide(testLayer)), | |
| 1548 | }).pipe(Effect.scoped, Effect.provide(layerTest)), | |
| 1549 | 1549 | ); |
| 1550 | 1550 | |
| 1551 | 1551 | it.effect("offers an explicit empty value for extension input dialogs", () => |
| ... | ... | @@ -1580,7 +1580,7 @@ describe("PiAdapterV2", () => { |
| 1580 | 1580 | const response = yield* fake.takeRequest("extension_ui_response"); |
| 1581 | 1581 | assert.equal(response["value"], ""); |
| 1582 | 1582 | assert.isUndefined(response["cancelled"]); |
| 1583 | }).pipe(Effect.scoped, Effect.provide(testLayer)), | |
| 1583 | }).pipe(Effect.scoped, Effect.provide(layerTest)), | |
| 1584 | 1584 | ); |
| 1585 | 1585 | |
| 1586 | 1586 | it.effect("raises bridge edit confirmations as file-change approvals", () => |
| ... | ... | @@ -1611,7 +1611,7 @@ describe("PiAdapterV2", () => { |
| 1611 | 1611 | `${title} should be ${requestKind}`, |
| 1612 | 1612 | ); |
| 1613 | 1613 | } |
| 1614 | }).pipe(Effect.scoped, Effect.provide(testLayer)), | |
| 1614 | }).pipe(Effect.scoped, Effect.provide(layerTest)), | |
| 1615 | 1615 | ); |
| 1616 | 1616 | |
| 1617 | 1617 | it.effect("reads a thread snapshot from pi's active branch", () => |
| ... | ... | @@ -1644,7 +1644,7 @@ describe("PiAdapterV2", () => { |
| 1644 | 1644 | assert.equal(snapshot.messages[0]!.text, "hello pi"); |
| 1645 | 1645 | assert.equal(snapshot.messages[1]!.role, "assistant"); |
| 1646 | 1646 | assert.equal(snapshot.messages[1]!.text, "hello back"); |
| 1647 | }).pipe(Effect.scoped, Effect.provide(testLayer)), | |
| 1647 | }).pipe(Effect.scoped, Effect.provide(layerTest)), | |
| 1648 | 1648 | ); |
| 1649 | 1649 | |
| 1650 | 1650 | it.effect("keeps snapshot message identities distinct across native sessions", () => |
| ... | ... | @@ -1678,7 +1678,7 @@ describe("PiAdapterV2", () => { |
| 1678 | 1678 | fake.queueMessages(messages); |
| 1679 | 1679 | const b = yield* runtime.readThreadSnapshot({ providerThread: second }); |
| 1680 | 1680 | assert.notEqual(a.messages[0]!.id, b.messages[0]!.id); |
| 1681 | }).pipe(Effect.scoped, Effect.provide(testLayer)), | |
| 1681 | }).pipe(Effect.scoped, Effect.provide(layerTest)), | |
| 1682 | 1682 | ); |
| 1683 | 1683 | |
| 1684 | 1684 | it.effect("rejects a nonpersistent session UUID instead of treating it as a resumable path", () => |
| ... | ... | @@ -1695,7 +1695,7 @@ describe("PiAdapterV2", () => { |
| 1695 | 1695 | }) |
| 1696 | 1696 | .pipe(Effect.result); |
| 1697 | 1697 | assert.equal(result._tag, "Failure"); |
| 1698 | }).pipe(Effect.scoped, Effect.provide(testLayer)), | |
| 1698 | }).pipe(Effect.scoped, Effect.provide(layerTest)), | |
| 1699 | 1699 | ); |
| 1700 | 1700 | |
| 1701 | 1701 | it.effect("keeps a settled turn's late prompt rejection off the next turn", () => |
| ... | ... | @@ -1731,7 +1731,7 @@ describe("PiAdapterV2", () => { |
| 1731 | 1731 | assert.isTrue( |
| 1732 | 1732 | secondTerminal.type === "turn.terminal" && secondTerminal.status === "completed", |
| 1733 | 1733 | ); |
| 1734 | }).pipe(Effect.scoped, Effect.provide(testLayer)), | |
| 1734 | }).pipe(Effect.scoped, Effect.provide(layerTest)), | |
| 1735 | 1735 | ); |
| 1736 | 1736 | |
| 1737 | 1737 | it.effect("shows compaction progress and completes the same activity row", () => |
| ... | ... | @@ -1797,7 +1797,7 @@ describe("PiAdapterV2", () => { |
| 1797 | 1797 | yield* fake.emit({ type: "agent_settled" }); |
| 1798 | 1798 | const terminal = yield* takeEvent((event) => event.type === "turn.terminal"); |
| 1799 | 1799 | assert.isTrue(terminal.type === "turn.terminal" && terminal.status === "completed"); |
| 1800 | }).pipe(Effect.scoped, Effect.provide(testLayer)), | |
| 1800 | }).pipe(Effect.scoped, Effect.provide(layerTest)), | |
| 1801 | 1801 | ); |
| 1802 | 1802 | |
| 1803 | 1803 | it.effect("uses distinct compaction IDs for first turns in separate threads", () => |
| ... | ... | @@ -1855,7 +1855,7 @@ describe("PiAdapterV2", () => { |
| 1855 | 1855 | first.turnItem.ordinal === second.turnItem.ordinal && |
| 1856 | 1856 | first.turnItem.id !== second.turnItem.id, |
| 1857 | 1857 | ); |
| 1858 | }).pipe(Effect.scoped, Effect.provide(testLayer)), | |
| 1858 | }).pipe(Effect.scoped, Effect.provide(layerTest)), | |
| 1859 | 1859 | ); |
| 1860 | 1860 | |
| 1861 | 1861 | it.effect("shows aborted compactions as stopped", () => |
| ... | ... | @@ -1892,7 +1892,7 @@ describe("PiAdapterV2", () => { |
| 1892 | 1892 | stopped.turnItem.status === "cancelled" && |
| 1893 | 1893 | stopped.turnItem.title === "Context compaction stopped", |
| 1894 | 1894 | ); |
| 1895 | }).pipe(Effect.scoped, Effect.provide(testLayer)), | |
| 1895 | }).pipe(Effect.scoped, Effect.provide(layerTest)), | |
| 1896 | 1896 | ); |
| 1897 | 1897 | |
| 1898 | 1898 | it.effect("keeps the turn open and updates one retry row through final failure", () => |
| ... | ... | @@ -1987,7 +1987,7 @@ describe("PiAdapterV2", () => { |
| 1987 | 1987 | terminal.retry.maxAttempts === 3 && |
| 1988 | 1988 | terminal.retryStartedAt === firstRetry.turnItem.startedAt, |
| 1989 | 1989 | ); |
| 1990 | }).pipe(Effect.scoped, Effect.provide(testLayer)), | |
| 1990 | }).pipe(Effect.scoped, Effect.provide(layerTest)), | |
| 1991 | 1991 | ); |
| 1992 | 1992 | |
| 1993 | 1993 | it.effect("preserves exhausted retry failure through non-retrying compaction", () => |
| ... | ... | @@ -2051,7 +2051,7 @@ describe("PiAdapterV2", () => { |
| 2051 | 2051 | terminal.retry?.attempt === 5 && |
| 2052 | 2052 | terminal.retry.maxAttempts === 5, |
| 2053 | 2053 | ); |
| 2054 | }).pipe(Effect.scoped, Effect.provide(testLayer)), | |
| 2054 | }).pipe(Effect.scoped, Effect.provide(layerTest)), | |
| 2055 | 2055 | ); |
| 2056 | 2056 | |
| 2057 | 2057 | it.effect("marks retry progress recovered when Pi succeeds", () => |
| ... | ... | @@ -2102,7 +2102,7 @@ describe("PiAdapterV2", () => { |
| 2102 | 2102 | yield* fake.emit({ type: "agent_settled" }); |
| 2103 | 2103 | const terminal = yield* takeEvent((event) => event.type === "turn.terminal"); |
| 2104 | 2104 | assert.isTrue(terminal.type === "turn.terminal" && terminal.status === "completed"); |
| 2105 | }).pipe(Effect.scoped, Effect.provide(testLayer)), | |
| 2105 | }).pipe(Effect.scoped, Effect.provide(layerTest)), | |
| 2106 | 2106 | ); |
| 2107 | 2107 | |
| 2108 | 2108 | it.effect("stops active retry progress when the turn is interrupted", () => |
| ... | ... | @@ -2153,7 +2153,7 @@ describe("PiAdapterV2", () => { |
| 2153 | 2153 | ); |
| 2154 | 2154 | const terminal = yield* takeEvent((event) => event.type === "turn.terminal"); |
| 2155 | 2155 | assert.isTrue(terminal.type === "turn.terminal" && terminal.status === "interrupted"); |
| 2156 | }).pipe(Effect.scoped, Effect.provide(testLayer)), | |
| 2156 | }).pipe(Effect.scoped, Effect.provide(layerTest)), | |
| 2157 | 2157 | ); |
| 2158 | 2158 | |
| 2159 | 2159 | it.effect("keeps extension-started compaction and recovery in the settled turn", () => |
| ... | ... | @@ -2201,7 +2201,7 @@ describe("PiAdapterV2", () => { |
| 2201 | 2201 | yield* fake.takeRequest("get_state"); |
| 2202 | 2202 | const terminal = yield* takeEvent((event) => event.type === "turn.terminal"); |
| 2203 | 2203 | assert.isTrue(terminal.type === "turn.terminal" && terminal.status === "completed"); |
| 2204 | }).pipe(Effect.scoped, Effect.provide(testLayer)), | |
| 2204 | }).pipe(Effect.scoped, Effect.provide(layerTest)), | |
| 2205 | 2205 | ); |
| 2206 | 2206 | |
| 2207 | 2207 | it.effect("keeps working after a settle probe fails before detached compaction", () => |
| ... | ... | @@ -2242,7 +2242,7 @@ describe("PiAdapterV2", () => { |
| 2242 | 2242 | yield* fake.takeRequest("get_state"); |
| 2243 | 2243 | const terminal = yield* takeEvent((event) => event.type === "turn.terminal"); |
| 2244 | 2244 | assert.isTrue(terminal.type === "turn.terminal" && terminal.status === "completed"); |
| 2245 | }).pipe(Effect.scoped, Effect.provide(testLayer)), | |
| 2245 | }).pipe(Effect.scoped, Effect.provide(layerTest)), | |
| 2246 | 2246 | ); |
| 2247 | 2247 | |
| 2248 | 2248 | it.effect("restarts Pi when Stop interrupts detached compaction", () => |
| ... | ... | @@ -2279,7 +2279,7 @@ describe("PiAdapterV2", () => { |
| 2279 | 2279 | yield* fake.closeStdout; |
| 2280 | 2280 | const terminal = yield* takeEvent((event) => event.type === "turn.terminal"); |
| 2281 | 2281 | assert.isTrue(terminal.type === "turn.terminal" && terminal.status === "interrupted"); |
| 2282 | }).pipe(Effect.scoped, Effect.provide(testLayer)), | |
| 2282 | }).pipe(Effect.scoped, Effect.provide(layerTest)), | |
| 2283 | 2283 | ); |
| 2284 | 2284 | |
| 2285 | 2285 | it.effect("ignores an idle snapshot made stale by a steer", () => |
| ... | ... | @@ -2357,7 +2357,7 @@ describe("PiAdapterV2", () => { |
| 2357 | 2357 | yield* fake.takeRequest("get_state"); |
| 2358 | 2358 | const terminal = yield* takeEvent((event) => event.type === "turn.terminal"); |
| 2359 | 2359 | assert.isTrue(terminal.type === "turn.terminal" && terminal.status === "completed"); |
| 2360 | }).pipe(Effect.scoped, Effect.provide(testLayer)), | |
| 2360 | }).pipe(Effect.scoped, Effect.provide(layerTest)), | |
| 2361 | 2361 | ); |
| 2362 | 2362 | }); |
| 2363 | 2363 |
apps/server/src/orchestration-v2/Adapters/PiAdapterV2.testkit.ts+5-5| ... | ... | @@ -407,18 +407,18 @@ function makePiReplaySpawner( |
| 407 | 407 | * spawner that tees a live `pi`; replay passes the transcript-backed one. The |
| 408 | 408 | * launch arguments are part of the recorded argv, so replay reuses them. |
| 409 | 409 | */ |
| 410 | export function makePiProviderAdapterRegistryLayer<E, R>(input: { | |
| 410 | export function layer<E, R>(input: { | |
| 411 | 411 | readonly scenario: string; |
| 412 | 412 | readonly spawner: Layer.Layer<ChildProcessSpawner.ChildProcessSpawner, E, R>; |
| 413 | 413 | readonly binaryPath: string; |
| 414 | 414 | readonly launchArgs: string; |
| 415 | 415 | readonly environment?: ProviderInstanceEnvironment; |
| 416 | 416 | }) { |
| 417 | const serverConfigLayer = Layer.effect( | |
| 417 | const layerServerConfig = Layer.effect( | |
| 418 | 418 | ServerConfig.ServerConfig, |
| 419 | 419 | makeReplayServerConfig(`pi-${input.scenario}`).pipe(Effect.orDie), |
| 420 | 420 | ).pipe(Layer.provide(NodeServices.layer)); |
| 421 | return ProviderAdapterRegistry.makeDriverLayer({ | |
| 421 | return ProviderAdapterRegistry.layerFromDrivers({ | |
| 422 | 422 | drivers: [PiAdapterV2Driver], |
| 423 | 423 | configMap: { |
| 424 | 424 | [PI_PROVIDER]: { |
| ... | ... | @@ -430,7 +430,7 @@ export function makePiProviderAdapterRegistryLayer<E, R>(input: { |
| 430 | 430 | }, |
| 431 | 431 | }).pipe( |
| 432 | 432 | Layer.provide(input.spawner), |
| 433 | Layer.provide(Layer.mergeAll(serverConfigLayer, NodeServices.layer, IdAllocator.layer)), | |
| 433 | Layer.provide(Layer.mergeAll(layerServerConfig, NodeServices.layer, IdAllocator.layer)), | |
| 434 | 434 | ); |
| 435 | 435 | } |
| 436 | 436 | |
| ... | ... | @@ -457,7 +457,7 @@ export const PiOrchestratorReplayHarness: OrchestratorV2ProviderReplayHarness< |
| 457 | 457 | ), |
| 458 | 458 | ), |
| 459 | 459 | makeProviderAdapterRegistryLayer: (transcript) => |
| 460 | makePiProviderAdapterRegistryLayer({ | |
| 460 | layer({ | |
| 461 | 461 | scenario: transcript.scenario, |
| 462 | 462 | binaryPath: "pi", |
| 463 | 463 | launchArgs: metadataString(transcript, "launchArgs"), |
apps/server/src/orchestration-v2/AttachmentClaims.test.ts+11-11| ... | ... | @@ -23,7 +23,7 @@ import { |
| 23 | 23 | releaseClaimedAttachments, |
| 24 | 24 | } from "./AttachmentClaims.ts"; |
| 25 | 25 | |
| 26 | const testLayer = Layer.mergeAll(NodeServices.layer).pipe( | |
| 26 | const layerTest = Layer.mergeAll(NodeServices.layer).pipe( | |
| 27 | 27 | Layer.provideMerge(ServerConfig.layerTest(process.cwd(), { prefix: "t3-attachment-claims-" })), |
| 28 | 28 | Layer.provideMerge(NodeServices.layer), |
| 29 | 29 | ); |
| ... | ... | @@ -62,7 +62,7 @@ describe("AttachmentClaims", () => { |
| 62 | 62 | const claimed = yield* claimPendingAttachments({ threadId: "thread-many", attachments }); |
| 63 | 63 | expect(claimed.attachments).toEqual(attachments); |
| 64 | 64 | expect(claimed.claimedPaths).toEqual([]); |
| 65 | }).pipe(Effect.provide(testLayer)), | |
| 65 | }).pipe(Effect.provide(layerTest)), | |
| 66 | 66 | ); |
| 67 | 67 | |
| 68 | 68 | it.effect("rejects oversized image batches before copying a pending upload", () => |
| ... | ... | @@ -89,7 +89,7 @@ describe("AttachmentClaims", () => { |
| 89 | 89 | if (result._tag === "Failure") expect(String(result.cause)).toContain("80 MiB"); |
| 90 | 90 | const config = yield* ServerConfig.ServerConfig; |
| 91 | 91 | expect(NodeFS.readdirSync(config.attachmentsDir)).toHaveLength(1); |
| 92 | }).pipe(Effect.provide(testLayer)), | |
| 92 | }).pipe(Effect.provide(layerTest)), | |
| 93 | 93 | ); |
| 94 | 94 | |
| 95 | 95 | it.effect("claims a pending upload into the thread store and rewrites the id", () => |
| ... | ... | @@ -117,7 +117,7 @@ describe("AttachmentClaims", () => { |
| 117 | 117 | entry.startsWith("pending-"), |
| 118 | 118 | ); |
| 119 | 119 | expect(pendingFiles).toHaveLength(1); |
| 120 | }).pipe(Effect.provide(testLayer)), | |
| 120 | }).pipe(Effect.provide(layerTest)), | |
| 121 | 121 | ); |
| 122 | 122 | |
| 123 | 123 | it.effect("passes already-claimed attachments through untouched", () => |
| ... | ... | @@ -135,7 +135,7 @@ describe("AttachmentClaims", () => { |
| 135 | 135 | }); |
| 136 | 136 | expect(claimed.attachments[0]).toBe(stored); |
| 137 | 137 | expect(claimed.claimedPaths).toHaveLength(0); |
| 138 | }).pipe(Effect.provide(testLayer)), | |
| 138 | }).pipe(Effect.provide(layerTest)), | |
| 139 | 139 | ); |
| 140 | 140 | |
| 141 | 141 | it.effect("rejects a pending ref whose staged file is missing", () => |
| ... | ... | @@ -151,7 +151,7 @@ describe("AttachmentClaims", () => { |
| 151 | 151 | claimPendingAttachments({ threadId: "thread-claims-3", attachments: [missing] }), |
| 152 | 152 | ); |
| 153 | 153 | expect(result._tag).toBe("Failure"); |
| 154 | }).pipe(Effect.provide(testLayer)), | |
| 154 | }).pipe(Effect.provide(layerTest)), | |
| 155 | 155 | ); |
| 156 | 156 | |
| 157 | 157 | it.effect("rejects a size mismatch and cleans up earlier claims from the batch", () => |
| ... | ... | @@ -186,7 +186,7 @@ describe("AttachmentClaims", () => { |
| 186 | 186 | entry.startsWith("thread-claims-4-"), |
| 187 | 187 | ); |
| 188 | 188 | expect(claimedFiles).toHaveLength(0); |
| 189 | }).pipe(Effect.provide(testLayer)), | |
| 189 | }).pipe(Effect.provide(layerTest)), | |
| 190 | 190 | ); |
| 191 | 191 | |
| 192 | 192 | it.effect("rejects duplicate attachment ids before copying anything", () => |
| ... | ... | @@ -218,7 +218,7 @@ describe("AttachmentClaims", () => { |
| 218 | 218 | expect( |
| 219 | 219 | NodeFS.readdirSync(config.attachmentsDir).filter((entry) => entry.startsWith("pending-")), |
| 220 | 220 | ).toHaveLength(1); |
| 221 | }).pipe(Effect.provide(testLayer)), | |
| 221 | }).pipe(Effect.provide(layerTest)), | |
| 222 | 222 | ); |
| 223 | 223 | |
| 224 | 224 | it.effect("rolls back a copy that finishes after the claim is interrupted", () => |
| ... | ... | @@ -272,7 +272,7 @@ describe("AttachmentClaims", () => { |
| 272 | 272 | expect( |
| 273 | 273 | NodeFS.readdirSync(config.attachmentsDir).filter((entry) => entry.startsWith("pending-")), |
| 274 | 274 | ).toHaveLength(1); |
| 275 | }).pipe(Effect.provide(testLayer)), | |
| 275 | }).pipe(Effect.provide(layerTest)), | |
| 276 | 276 | ); |
| 277 | 277 | |
| 278 | 278 | it.effect("releaseClaimedAttachments removes claimed copies best-effort", () => |
| ... | ... | @@ -291,7 +291,7 @@ describe("AttachmentClaims", () => { |
| 291 | 291 | expect(NodeFS.existsSync(claimed.claimedPaths[0]!)).toBe(false); |
| 292 | 292 | // Releasing again is a no-op, not an error. |
| 293 | 293 | yield* releaseClaimedAttachments(claimed.claimedPaths); |
| 294 | }).pipe(Effect.provide(testLayer)), | |
| 294 | }).pipe(Effect.provide(layerTest)), | |
| 295 | 295 | ); |
| 296 | 296 | |
| 297 | 297 | it.effect("treats malformed ids as non-pending and passes them through", () => |
| ... | ... | @@ -311,6 +311,6 @@ describe("AttachmentClaims", () => { |
| 311 | 311 | }); |
| 312 | 312 | expect(claimed.attachments[0]).toBe(bogus); |
| 313 | 313 | expect(claimed.claimedPaths).toHaveLength(0); |
| 314 | }).pipe(Effect.provide(testLayer)), | |
| 314 | }).pipe(Effect.provide(layerTest)), | |
| 315 | 315 | ); |
| 316 | 316 | }); |
apps/server/src/orchestration-v2/BackgroundWorkStop.integration.test.ts+3-3| ... | ... | @@ -31,7 +31,7 @@ import type { |
| 31 | 31 | ProviderAdapterV2TurnInput, |
| 32 | 32 | } from "./ProviderAdapter.ts"; |
| 33 | 33 | import * as ProviderAdapterRegistry from "./ProviderAdapterRegistry.ts"; |
| 34 | import { makeOrchestratorV2ReplayLayerWithRegistry } from "./testkit/ProviderReplayHarness.ts"; | |
| 34 | import * as ProviderReplayHarness from "./testkit/ProviderReplayHarness.ts"; | |
| 35 | 35 | import { checkpointWorkspace } from "./testkit/ReplayFixtureWorkspace.ts"; |
| 36 | 36 | |
| 37 | 37 | const driver = ProviderDriverKind.make("codex"); |
| ... | ... | @@ -571,9 +571,9 @@ const stopEarlierBackgroundWork = ({ |
| 571 | 571 | ); |
| 572 | 572 | }).pipe( |
| 573 | 573 | Effect.provide( |
| 574 | makeOrchestratorV2ReplayLayerWithRegistry( | |
| 574 | ProviderReplayHarness.layerWithRegistry( | |
| 575 | 575 | { name: "background-work-stop" }, |
| 576 | ProviderAdapterRegistry.makeSingleLayer(adapter), | |
| 576 | ProviderAdapterRegistry.layerSingle(adapter), | |
| 577 | 577 | { runEffectWorker: false }, |
| 578 | 578 | ), |
| 579 | 579 | ), |
apps/server/src/orchestration-v2/CheckpointCaptureService.test.ts+11-11| ... | ... | @@ -21,7 +21,7 @@ import * as Layer from "effect/Layer"; |
| 21 | 21 | import * as Ref from "effect/Ref"; |
| 22 | 22 | import * as SqlClient from "effect/sql/SqlClient"; |
| 23 | 23 | |
| 24 | import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts"; | |
| 24 | import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts"; | |
| 25 | 25 | import * as CheckpointStore from "../checkpointing/CheckpointStore.ts"; |
| 26 | 26 | import { VcsProcessTimeoutError } from "@t3tools/contracts"; |
| 27 | 27 | import * as CheckpointService from "./CheckpointService.ts"; |
| ... | ... | @@ -30,9 +30,9 @@ import * as EventSink from "./EventSink.ts"; |
| 30 | 30 | import * as IdAllocator from "./IdAllocator.ts"; |
| 31 | 31 | import * as ProjectionStore from "./ProjectionStore.ts"; |
| 32 | 32 | |
| 33 | const ProjectionStoreTestLayer = Layer.mergeAll( | |
| 34 | ProjectionStore.layer.pipe(Layer.provideMerge(SqlitePersistenceMemory)), | |
| 35 | SqlitePersistenceMemory, | |
| 33 | const layerProjectionStoreTest = Layer.mergeAll( | |
| 34 | ProjectionStore.layer.pipe(Layer.provideMerge(SqlitePersistence.layerMemory)), | |
| 35 | SqlitePersistence.layerMemory, | |
| 36 | 36 | ); |
| 37 | 37 | |
| 38 | 38 | const threadId = ThreadId.make("thread:checkpoint-capture-delegated"); |
| ... | ... | @@ -50,7 +50,7 @@ const modelSelection = { |
| 50 | 50 | model: "gpt-5.4", |
| 51 | 51 | } as const; |
| 52 | 52 | |
| 53 | it.layer(ProjectionStoreTestLayer)("CheckpointCaptureServiceV2", (it) => { | |
| 53 | it.layer(layerProjectionStoreTest)("CheckpointCaptureServiceV2", (it) => { | |
| 54 | 54 | it.effect.each([false, true])( |
| 55 | 55 | "captures without decoding history or losing newer delegated completion, ref lookup fails=%s", |
| 56 | 56 | (refLookupFails) => |
| ... | ... | @@ -265,7 +265,7 @@ it.layer(ProjectionStoreTestLayer)("CheckpointCaptureServiceV2", (it) => { |
| 265 | 265 | ); |
| 266 | 266 | |
| 267 | 267 | const committed = yield* Ref.make<ReadonlyArray<OrchestrationV2DomainEvent>>([]); |
| 268 | const captureLayer = CheckpointCaptureService.layer.pipe( | |
| 268 | const layerCapture = CheckpointCaptureService.layer.pipe( | |
| 269 | 269 | Layer.provide( |
| 270 | 270 | Layer.mergeAll( |
| 271 | 271 | IdAllocator.layer, |
| ... | ... | @@ -376,7 +376,7 @@ it.layer(ProjectionStoreTestLayer)("CheckpointCaptureServiceV2", (it) => { |
| 376 | 376 | yield* Ref.set(committed, []); |
| 377 | 377 | yield* service.execute({ threadId, runId, scopeId }); |
| 378 | 378 | assert.deepEqual(yield* Ref.get(committed), []); |
| 379 | }).pipe(Effect.provide(captureLayer)); | |
| 379 | }).pipe(Effect.provide(layerCapture)); | |
| 380 | 380 | }), |
| 381 | 381 | ); |
| 382 | 382 | |
| ... | ... | @@ -451,7 +451,7 @@ it.layer(ProjectionStoreTestLayer)("CheckpointCaptureServiceV2", (it) => { |
| 451 | 451 | }); |
| 452 | 452 | |
| 453 | 453 | const committed = yield* Ref.make<ReadonlyArray<OrchestrationV2DomainEvent>>([]); |
| 454 | const captureLayer = CheckpointCaptureService.layer.pipe( | |
| 454 | const layerCapture = CheckpointCaptureService.layer.pipe( | |
| 455 | 455 | Layer.provide( |
| 456 | 456 | Layer.mergeAll( |
| 457 | 457 | IdAllocator.layer, |
| ... | ... | @@ -472,7 +472,7 @@ it.layer(ProjectionStoreTestLayer)("CheckpointCaptureServiceV2", (it) => { |
| 472 | 472 | Effect.flatMap((service) => |
| 473 | 473 | service.execute({ threadId: stoppedThreadId, runId: stoppedRunId, scopeId }), |
| 474 | 474 | ), |
| 475 | Effect.provide(captureLayer), | |
| 475 | Effect.provide(layerCapture), | |
| 476 | 476 | ); |
| 477 | 477 | |
| 478 | 478 | assert.deepEqual(yield* Ref.get(committed), []); |
| ... | ... | @@ -680,7 +680,7 @@ it.layer(ProjectionStoreTestLayer)("CheckpointCaptureServiceV2", (it) => { |
| 680 | 680 | capturedAt: cancelledAt, |
| 681 | 681 | }; |
| 682 | 682 | const commits = yield* Ref.make(0); |
| 683 | const captureLayer = CheckpointCaptureService.layer.pipe( | |
| 683 | const layerCapture = CheckpointCaptureService.layer.pipe( | |
| 684 | 684 | Layer.provide( |
| 685 | 685 | Layer.mergeAll( |
| 686 | 686 | IdAllocator.layer, |
| ... | ... | @@ -716,7 +716,7 @@ it.layer(ProjectionStoreTestLayer)("CheckpointCaptureServiceV2", (it) => { |
| 716 | 716 | runId: cancelledRunId, |
| 717 | 717 | scopeId: cancelledScopeId, |
| 718 | 718 | }); |
| 719 | }).pipe(Effect.provide(captureLayer)); | |
| 719 | }).pipe(Effect.provide(layerCapture)); | |
| 720 | 720 | |
| 721 | 721 | assert.equal(yield* Ref.get(commits), 1); |
| 722 | 722 | const projected = yield* projectionStore.getCheckpointCaptureContext(cancelledThreadId, { |
apps/server/src/orchestration-v2/CheckpointRestoreSafety.test.ts+2-2| ... | ... | @@ -98,7 +98,7 @@ it.effect.each([ |
| 98 | 98 | checkpointScopes: [{ id: scopeId, cwd }], |
| 99 | 99 | runs: [{ id: "later-run", ordinal: 1, status: "completed", rootNodeId: null }], |
| 100 | 100 | } as unknown as OrchestrationV2ThreadProjection; |
| 101 | const testLayer = CheckpointRollbackService.layer.pipe( | |
| 101 | const layerTest = CheckpointRollbackService.layer.pipe( | |
| 102 | 102 | Layer.provide( |
| 103 | 103 | Layer.mergeAll( |
| 104 | 104 | NodeServices.layer, |
| ... | ... | @@ -175,7 +175,7 @@ it.effect.each([ |
| 175 | 175 | ), |
| 176 | 176 | ); |
| 177 | 177 | const service = yield* CheckpointRollbackService.CheckpointRollbackServiceV2.pipe( |
| 178 | Effect.provide(testLayer), | |
| 178 | Effect.provide(layerTest), | |
| 179 | 179 | ); |
| 180 | 180 | const restoreFiles = owner !== "conversation"; |
| 181 | 181 | const rejected = !["sibling", "stopped-provider", "shared-provider", "conversation"].includes( |
apps/server/src/orchestration-v2/CheckpointRollbackService.test.ts+11-11| ... | ... | @@ -32,7 +32,7 @@ import * as RuntimePolicy from "./RuntimePolicy.ts"; |
| 32 | 32 | const unrelatedProject = Option.some({ |
| 33 | 33 | workspaceRoot: "/nonexistent/t3-rollback-project", |
| 34 | 34 | } as never); |
| 35 | const checkpointRollbackServiceLayer = CheckpointRollbackService.layer.pipe( | |
| 35 | const layerCheckpointRollbackService = CheckpointRollbackService.layer.pipe( | |
| 36 | 36 | Layer.provide( |
| 37 | 37 | Layer.mergeAll( |
| 38 | 38 | NodeServices.layer, |
| ... | ... | @@ -61,7 +61,7 @@ it.effect("rejects a non-ready checkpoint before opening a session or restoring |
| 61 | 61 | checkpoints: [{ id: checkpointId, scopeId, status: "stale" }], |
| 62 | 62 | checkpointScopes: [{ id: scopeId, cwd: process.cwd() }], |
| 63 | 63 | } as unknown as OrchestrationV2ThreadProjection; |
| 64 | const testLayer = checkpointRollbackServiceLayer.pipe( | |
| 64 | const layerTest = layerCheckpointRollbackService.pipe( | |
| 65 | 65 | Layer.provide( |
| 66 | 66 | Layer.mergeAll( |
| 67 | 67 | Layer.mock(CheckpointService.CheckpointServiceV2)({ restore }), |
| ... | ... | @@ -103,7 +103,7 @@ it.effect("rejects a non-ready checkpoint before opening a session or restoring |
| 103 | 103 | assert.equal(resolveRuntimePolicy.mock.calls.length, 0); |
| 104 | 104 | assert.equal(open.mock.calls.length, 0); |
| 105 | 105 | assert.equal(restore.mock.calls.length, 0); |
| 106 | }).pipe(Effect.provide(testLayer)); | |
| 106 | }).pipe(Effect.provide(layerTest)); | |
| 107 | 107 | }); |
| 108 | 108 | |
| 109 | 109 | it.effect("rejects a rollback when another provider thread became active", () => { |
| ... | ... | @@ -138,7 +138,7 @@ it.effect("rejects a rollback when another provider thread became active", () => |
| 138 | 138 | checkpoints: [{ id: checkpointId, scopeId, status: "ready" }], |
| 139 | 139 | checkpointScopes: [{ id: scopeId, cwd: process.cwd() }], |
| 140 | 140 | } as unknown as OrchestrationV2ThreadProjection; |
| 141 | const testLayer = checkpointRollbackServiceLayer.pipe( | |
| 141 | const layerTest = layerCheckpointRollbackService.pipe( | |
| 142 | 142 | Layer.provide( |
| 143 | 143 | Layer.mergeAll( |
| 144 | 144 | Layer.mock(CheckpointService.CheckpointServiceV2)({ restore }), |
| ... | ... | @@ -180,7 +180,7 @@ it.effect("rejects a rollback when another provider thread became active", () => |
| 180 | 180 | assert.equal(resolveRuntimePolicy.mock.calls.length, 0); |
| 181 | 181 | assert.equal(open.mock.calls.length, 0); |
| 182 | 182 | assert.equal(restore.mock.calls.length, 0); |
| 183 | }).pipe(Effect.provide(testLayer)); | |
| 183 | }).pipe(Effect.provide(layerTest)); | |
| 184 | 184 | }); |
| 185 | 185 | |
| 186 | 186 | it.effect("rejects a rollback when provider selection changed before execution", () => { |
| ... | ... | @@ -218,7 +218,7 @@ it.effect("rejects a rollback when provider selection changed before execution", |
| 218 | 218 | checkpoints: [{ id: checkpointId, scopeId, status: "ready" }], |
| 219 | 219 | checkpointScopes: [{ id: scopeId, cwd: process.cwd() }], |
| 220 | 220 | } as unknown as OrchestrationV2ThreadProjection; |
| 221 | const testLayer = checkpointRollbackServiceLayer.pipe( | |
| 221 | const layerTest = layerCheckpointRollbackService.pipe( | |
| 222 | 222 | Layer.provide( |
| 223 | 223 | Layer.mergeAll( |
| 224 | 224 | Layer.mock(CheckpointService.CheckpointServiceV2)({ restore }), |
| ... | ... | @@ -260,7 +260,7 @@ it.effect("rejects a rollback when provider selection changed before execution", |
| 260 | 260 | assert.equal(resolveRuntimePolicy.mock.calls.length, 0); |
| 261 | 261 | assert.equal(open.mock.calls.length, 0); |
| 262 | 262 | assert.equal(restore.mock.calls.length, 0); |
| 263 | }).pipe(Effect.provide(testLayer)); | |
| 263 | }).pipe(Effect.provide(layerTest)); | |
| 264 | 264 | }); |
| 265 | 265 | |
| 266 | 266 | it.effect("reports a missing provider turn as a structured rollback failure", () => { |
| ... | ... | @@ -289,7 +289,7 @@ it.effect("reports a missing provider turn as a structured rollback failure", () |
| 289 | 289 | attempts: [], |
| 290 | 290 | providerTurns: [], |
| 291 | 291 | } as unknown as OrchestrationV2ThreadProjection; |
| 292 | const testLayer = checkpointRollbackServiceLayer.pipe( | |
| 292 | const layerTest = layerCheckpointRollbackService.pipe( | |
| 293 | 293 | Layer.provide( |
| 294 | 294 | Layer.mergeAll( |
| 295 | 295 | Layer.mock(CheckpointService.CheckpointServiceV2)({ restore }), |
| ... | ... | @@ -333,7 +333,7 @@ it.effect("reports a missing provider turn as a structured rollback failure", () |
| 333 | 333 | ); |
| 334 | 334 | assert.equal(error.cause, undefined); |
| 335 | 335 | assert.equal(restore.mock.calls.length, 0); |
| 336 | }).pipe(Effect.provide(testLayer)); | |
| 336 | }).pipe(Effect.provide(layerTest)); | |
| 337 | 337 | }); |
| 338 | 338 | |
| 339 | 339 | it.effect.each([ |
| ... | ... | @@ -387,7 +387,7 @@ it.effect.each([ |
| 387 | 387 | activeAttemptId: `attempt-${ordinal}`, |
| 388 | 388 | })), |
| 389 | 389 | } as unknown as OrchestrationV2ThreadProjection; |
| 390 | const testLayer = checkpointRollbackServiceLayer.pipe( | |
| 390 | const layerTest = layerCheckpointRollbackService.pipe( | |
| 391 | 391 | Layer.provide( |
| 392 | 392 | Layer.mergeAll( |
| 393 | 393 | Layer.mock(CheckpointService.CheckpointServiceV2)({ |
| ... | ... | @@ -466,7 +466,7 @@ it.effect.each([ |
| 466 | 466 | calls, |
| 467 | 467 | restoreFiles ? ["provider", "files", "projection"] : ["provider", "projection"], |
| 468 | 468 | ); |
| 469 | }).pipe(Effect.provide(testLayer)); | |
| 469 | }).pipe(Effect.provide(layerTest)); | |
| 470 | 470 | }); |
| 471 | 471 | |
| 472 | 472 | it.effect.skipIf(!symlinksSupported)( |
apps/server/src/orchestration-v2/CheckpointScopeOwnership.test.ts+7-7| ... | ... | @@ -19,10 +19,10 @@ import * as IdAllocator from "./IdAllocator.ts"; |
| 19 | 19 | import { checkpointRefForScopeOrdinal } from "./CheckpointService.ts"; |
| 20 | 20 | import * as ProjectionStore from "./ProjectionStore.ts"; |
| 21 | 21 | import * as ThreadManagement from "./ThreadManagementService.ts"; |
| 22 | import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts"; | |
| 23 | const projectionLayer = Layer.mergeAll( | |
| 24 | ProjectionStore.layer.pipe(Layer.provideMerge(SqlitePersistenceMemory)), | |
| 25 | SqlitePersistenceMemory, | |
| 22 | import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts"; | |
| 23 | const layerProjection = Layer.mergeAll( | |
| 24 | ProjectionStore.layer.pipe(Layer.provideMerge(SqlitePersistence.layerMemory)), | |
| 25 | SqlitePersistence.layerMemory, | |
| 26 | 26 | IdAllocator.layer, |
| 27 | 27 | ); |
| 28 | 28 | it.effect("resolves the thread baseline after a second root run replaces scope ownership", () => |
| ... | ... | @@ -205,7 +205,7 @@ it.effect("resolves the thread baseline after a second root run replaces scope o |
| 205 | 205 | assert.equal(context.checkpointScopes.length, 1); |
| 206 | 206 | assert.equal(context.checkpointScopes[0]?.runId, secondRunId); |
| 207 | 207 | |
| 208 | const queryLayer = CheckpointDiffQuery.layer.pipe( | |
| 208 | const layerQuery = CheckpointDiffQuery.layer.pipe( | |
| 209 | 209 | Layer.provide( |
| 210 | 210 | Layer.mergeAll( |
| 211 | 211 | Layer.mock(ThreadManagement.ThreadManagementService)({ |
| ... | ... | @@ -231,7 +231,7 @@ it.effect("resolves the thread baseline after a second root run replaces scope o |
| 231 | 231 | const result = yield* Effect.gen(function* () { |
| 232 | 232 | const query = yield* CheckpointDiffQuery.CheckpointDiffQuery; |
| 233 | 233 | return yield* query.getFullThreadDiff({ threadId, toTurnCount: 2 }); |
| 234 | }).pipe(Effect.provide(queryLayer)); | |
| 234 | }).pipe(Effect.provide(layerQuery)); | |
| 235 | 235 | assert.equal(result.diff, "two-run diff"); |
| 236 | }).pipe(Effect.provide(projectionLayer)), | |
| 236 | }).pipe(Effect.provide(layerProjection)), | |
| 237 | 237 | ); |
apps/server/src/orchestration-v2/CheckpointService.test.ts+2-2| ... | ... | @@ -47,7 +47,7 @@ it.effect.each([false, true, "interrupt"] as const)( |
| 47 | 47 | ) |
| 48 | 48 | : Effect.succeed(true), |
| 49 | 49 | ); |
| 50 | const testLayer = CheckpointService.layer.pipe( | |
| 50 | const layerTest = CheckpointService.layer.pipe( | |
| 51 | 51 | Layer.provide( |
| 52 | 52 | Layer.mergeAll( |
| 53 | 53 | IdAllocator.layer, |
| ... | ... | @@ -98,6 +98,6 @@ it.effect.each([false, true, "interrupt"] as const)( |
| 98 | 98 | cwd: scope.cwd, |
| 99 | 99 | checkpointRef: baseline.ref, |
| 100 | 100 | }); |
| 101 | }).pipe(Effect.provide(testLayer)); | |
| 101 | }).pipe(Effect.provide(layerTest)); | |
| 102 | 102 | }, |
| 103 | 103 | ); |
apps/server/src/orchestration-v2/ClaudeAutomaticDelivery.integration.test.ts+9-5| ... | ... | @@ -26,7 +26,7 @@ import * as Stream from "effect/Stream"; |
| 26 | 26 | import * as ScheduledTaskService from "../scheduledTasks/ScheduledTaskService.ts"; |
| 27 | 27 | import * as SecretRequests from "../secrets/SecretRequests.ts"; |
| 28 | 28 | import * as Scheduler from "../scheduling/Scheduler.ts"; |
| 29 | import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts"; | |
| 29 | import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts"; | |
| 30 | 30 | import * as ClaudeAdapterV2 from "./Adapters/ClaudeAdapterV2.ts"; |
| 31 | 31 | import * as EffectWorker from "./EffectWorker.ts"; |
| 32 | 32 | import * as EventSink from "./EventSink.ts"; |
| ... | ... | @@ -36,7 +36,7 @@ import * as Orchestrator from "./Orchestrator.ts"; |
| 36 | 36 | import * as ProviderAdapterRegistry from "./ProviderAdapterRegistry.ts"; |
| 37 | 37 | import * as ThreadLaunchService from "./ThreadLaunchService.ts"; |
| 38 | 38 | import * as ThreadManagementService from "./ThreadManagementService.ts"; |
| 39 | import { makeOrchestratorV2ReplayLayerWithRegistry } from "./testkit/ProviderReplayHarness.ts"; | |
| 39 | import * as ProviderReplayHarness from "./testkit/ProviderReplayHarness.ts"; | |
| 40 | 40 | import { checkpointWorkspace } from "./testkit/ReplayFixtureWorkspace.ts"; |
| 41 | 41 | |
| 42 | 42 | const sessionId = "automatic-delivery-session"; |
| ... | ... | @@ -318,7 +318,11 @@ it.effect.each(["child completion", "scheduled message", "user steering"] as con |
| 318 | 318 | Layer.provide(Layer.mock(ThreadLaunchService.ThreadLaunchService)({})), |
| 319 | 319 | Layer.provide(Layer.mock(SecretRequests.SecretRequests)({})), |
| 320 | 320 | Layer.provide( |
| 321 | Layer.mergeAll(NodeCrypto.layer, Scheduler.layer, SqlitePersistenceMemory), | |
| 321 | Layer.mergeAll( | |
| 322 | NodeCrypto.layer, | |
| 323 | Scheduler.layer, | |
| 324 | SqlitePersistence.layerMemory, | |
| 325 | ), | |
| 322 | 326 | ), |
| 323 | 327 | ), |
| 324 | 328 | ), |
| ... | ... | @@ -410,9 +414,9 @@ it.effect.each(["child completion", "scheduled message", "user steering"] as con |
| 410 | 414 | assert.equal(offers.length, 2); |
| 411 | 415 | }).pipe( |
| 412 | 416 | Effect.provide( |
| 413 | makeOrchestratorV2ReplayLayerWithRegistry( | |
| 417 | ProviderReplayHarness.layerWithRegistry( | |
| 414 | 418 | { name: "claude-automatic-delivery" }, |
| 415 | ProviderAdapterRegistry.makeSingleLayer(adapter), | |
| 419 | ProviderAdapterRegistry.layerSingle(adapter), | |
| 416 | 420 | { runEffectWorker: false }, |
| 417 | 421 | ), |
| 418 | 422 | ), |
apps/server/src/orchestration-v2/CommandReceiptStore.ts+4-4| ... | ... | @@ -6,7 +6,7 @@ import * as Layer from "effect/Layer"; |
| 6 | 6 | import * as Option from "effect/Option"; |
| 7 | 7 | import * as Schema from "effect/Schema"; |
| 8 | 8 | |
| 9 | import { OrchestrationCommandReceiptRepositoryLive } from "../persistence/Layers/OrchestrationCommandReceipts.ts"; | |
| 9 | import * as OrchestrationCommandReceiptsLayer from "../persistence/Layers/OrchestrationCommandReceipts.ts"; | |
| 10 | 10 | import * as OrchestrationCommandReceipts from "../persistence/Services/OrchestrationCommandReceipts.ts"; |
| 11 | 11 | |
| 12 | 12 | /** |
| ... | ... | @@ -139,7 +139,7 @@ function toApplicationReceipt( |
| 139 | 139 | }; |
| 140 | 140 | } |
| 141 | 141 | |
| 142 | const baseLayer: Layer.Layer< | |
| 142 | const layerBase: Layer.Layer< | |
| 143 | 143 | CommandReceiptStoreV2, |
| 144 | 144 | never, |
| 145 | 145 | OrchestrationCommandReceipts.OrchestrationCommandReceiptRepository |
| ... | ... | @@ -209,6 +209,6 @@ const baseLayer: Layer.Layer< |
| 209 | 209 | }), |
| 210 | 210 | ); |
| 211 | 211 | |
| 212 | export const layer = baseLayer.pipe(Layer.provide(OrchestrationCommandReceiptRepositoryLive)); | |
| 212 | export const layer = layerBase.pipe(Layer.provide(OrchestrationCommandReceiptsLayer.layer)); | |
| 213 | 213 | |
| 214 | export const layerFromApplicationReceipts = baseLayer; | |
| 214 | export const layerFromApplicationReceipts = layerBase; |
apps/server/src/orchestration-v2/ContextHandoffService.test.ts+2-2| ... | ... | @@ -15,7 +15,7 @@ import * as Layer from "effect/Layer"; |
| 15 | 15 | import * as ContextHandoffService from "./ContextHandoffService.ts"; |
| 16 | 16 | import * as IdAllocator from "./IdAllocator.ts"; |
| 17 | 17 | |
| 18 | const TestLayer = ContextHandoffService.layer.pipe(Layer.provide(IdAllocator.layer)); | |
| 18 | const layerTest = ContextHandoffService.layer.pipe(Layer.provide(IdAllocator.layer)); | |
| 19 | 19 | |
| 20 | 20 | function importedItem( |
| 21 | 21 | input: |
| ... | ... | @@ -67,7 +67,7 @@ function importedItem( |
| 67 | 67 | }; |
| 68 | 68 | } |
| 69 | 69 | |
| 70 | it.layer(TestLayer)("ContextHandoffService legacy import", (it) => { | |
| 70 | it.layer(layerTest)("ContextHandoffService legacy import", (it) => { | |
| 71 | 71 | it.effect("prepares imported history for the first native v2 turn", () => |
| 72 | 72 | Effect.gen(function* () { |
| 73 | 73 | const service = yield* ContextHandoffService.ContextHandoffServiceV2; |
apps/server/src/orchestration-v2/CursorOrchestratorV2.live.test.ts+34-34| ... | ... | @@ -21,12 +21,12 @@ import * as CheckpointStore from "../checkpointing/CheckpointStore.ts"; |
| 21 | 21 | import * as BackgroundPolicy from "../background/BackgroundPolicy.ts"; |
| 22 | 22 | import * as HostPowerMonitor from "../background/HostPowerMonitor.ts"; |
| 23 | 23 | import * as ServerConfig from "../config.ts"; |
| 24 | import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts"; | |
| 24 | import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts"; | |
| 25 | 25 | import * as AntigravityInstallation from "../provider/AntigravityInstallation.ts"; |
| 26 | 26 | import * as CodexInstallation from "../provider/CodexInstallation.ts"; |
| 27 | 27 | import * as ServerEnvironment from "../environment/ServerEnvironment.ts"; |
| 28 | 28 | import * as ModelManifest from "../provider/ModelManifest.ts"; |
| 29 | import { ProviderInstanceRegistryHydrationLive } from "../provider/Layers/ProviderInstanceRegistryHydration.ts"; | |
| 29 | import * as ProviderInstanceRegistryHydration from "../provider/Layers/ProviderInstanceRegistryHydration.ts"; | |
| 30 | 30 | import * as ProviderEventLoggers from "../provider/Layers/ProviderEventLoggers.ts"; |
| 31 | 31 | import * as OpenCodeRuntime from "../provider/opencodeRuntime.ts"; |
| 32 | 32 | import * as OpenCodeServerLedger from "../provider/OpenCodeServerLedger.ts"; |
| ... | ... | @@ -34,54 +34,54 @@ import * as ServerSettings from "../serverSettings.ts"; |
| 34 | 34 | import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts"; |
| 35 | 35 | import * as VcsProcess from "../vcs/VcsProcess.ts"; |
| 36 | 36 | import * as Orchestrator from "./Orchestrator.ts"; |
| 37 | import { worktreeRepairDependenciesTestLayer } from "./ProviderTurnStartService.testkit.ts"; | |
| 37 | import * as ProviderTurnStartServiceTestkit from "./ProviderTurnStartService.testkit.ts"; | |
| 38 | 38 | import * as EffectWorker from "./EffectWorker.ts"; |
| 39 | import { OrchestrationV2LayerLive } from "./runtimeLayer.ts"; | |
| 39 | import * as RuntimeLayer from "./runtimeLayer.ts"; | |
| 40 | 40 | import * as McpSessionRegistryTestkit from "../mcp/McpSessionRegistry.testkit.ts"; |
| 41 | 41 | import { CURSOR_MODEL_SELECTION, SUBAGENT_PROMPT } from "./testkit/fixtures/shared.ts"; |
| 42 | 42 | |
| 43 | const PlatformTestLayer = Layer.merge( | |
| 43 | const layerPlatformTest = Layer.merge( | |
| 44 | 44 | NodeServices.layer, |
| 45 | 45 | Layer.mock(SourceControlProviderRegistry.SourceControlProviderRegistry)({ |
| 46 | 46 | resolveLink: () => Effect.die("unused title link"), |
| 47 | 47 | }), |
| 48 | 48 | ); |
| 49 | 49 | |
| 50 | const serverConfigLayer = ServerConfig.layerTest(process.cwd(), { | |
| 50 | const layerServerConfig = ServerConfig.layerTest(process.cwd(), { | |
| 51 | 51 | prefix: "t3-cursor-v2-live-", |
| 52 | 52 | }); |
| 53 | 53 | |
| 54 | const vcsDriverRegistryLayer = VcsDriverRegistry.layer.pipe( | |
| 54 | const layerVcsDriverRegistry = VcsDriverRegistry.layer.pipe( | |
| 55 | 55 | Layer.provide(VcsProcess.layer), |
| 56 | Layer.provide(serverConfigLayer), | |
| 57 | Layer.provide(PlatformTestLayer), | |
| 56 | Layer.provide(layerServerConfig), | |
| 57 | Layer.provide(layerPlatformTest), | |
| 58 | 58 | ); |
| 59 | 59 | |
| 60 | const checkpointStoreLayer = CheckpointStore.layer.pipe(Layer.provide(vcsDriverRegistryLayer)); | |
| 60 | const layerCheckpointStore = CheckpointStore.layer.pipe(Layer.provide(layerVcsDriverRegistry)); | |
| 61 | 61 | |
| 62 | const serverSettingsLayer = ServerSettings.layerTest({ | |
| 62 | const layerServerSettings = ServerSettings.layerTest({ | |
| 63 | 63 | providers: { |
| 64 | 64 | cursor: { enabled: true }, |
| 65 | 65 | }, |
| 66 | 66 | }); |
| 67 | const backgroundPolicyLayer = BackgroundPolicy.layer.pipe( | |
| 67 | const layerBackgroundPolicy = BackgroundPolicy.layer.pipe( | |
| 68 | 68 | Layer.provide(Layer.effect(HostPowerMonitor.HostPowerMonitor, HostPowerMonitor.make())), |
| 69 | Layer.provide(serverSettingsLayer), | |
| 69 | Layer.provide(layerServerSettings), | |
| 70 | 70 | ); |
| 71 | const providerInstanceRegistryLayer = ProviderInstanceRegistryHydrationLive.pipe( | |
| 71 | const layerProviderInstanceRegistry = ProviderInstanceRegistryHydration.layer.pipe( | |
| 72 | 72 | Layer.provide( |
| 73 | 73 | Layer.mergeAll( |
| 74 | serverConfigLayer.pipe(Layer.provide(PlatformTestLayer)), | |
| 75 | serverSettingsLayer, | |
| 74 | layerServerConfig.pipe(Layer.provide(layerPlatformTest)), | |
| 75 | layerServerSettings, | |
| 76 | 76 | ServerSecretStore.layer.pipe( |
| 77 | Layer.provide(serverConfigLayer), | |
| 78 | Layer.provide(PlatformTestLayer), | |
| 77 | Layer.provide(layerServerConfig), | |
| 78 | Layer.provide(layerPlatformTest), | |
| 79 | 79 | ), |
| 80 | 80 | NodeServices.layer, |
| 81 | 81 | FetchHttpClient.layer, |
| 82 | OpenCodeRuntime.OpenCodeRuntimeLive.pipe( | |
| 82 | OpenCodeRuntime.layer.pipe( | |
| 83 | 83 | Layer.provide(OpenCodeServerLedger.layerTest), |
| 84 | Layer.provide(PlatformTestLayer), | |
| 84 | Layer.provide(layerPlatformTest), | |
| 85 | 85 | ), |
| 86 | 86 | Layer.succeed( |
| 87 | 87 | ProviderEventLoggers.ProviderEventLoggers, |
| ... | ... | @@ -89,9 +89,9 @@ const providerInstanceRegistryLayer = ProviderInstanceRegistryHydrationLive.pipe |
| 89 | 89 | ), |
| 90 | 90 | ModelManifest.layerTest, |
| 91 | 91 | AntigravityInstallation.AntigravityInstallation.layer.pipe( |
| 92 | Layer.provide(serverConfigLayer.pipe(Layer.provide(PlatformTestLayer))), | |
| 92 | Layer.provide(layerServerConfig.pipe(Layer.provide(layerPlatformTest))), | |
| 93 | 93 | Layer.provide(FetchHttpClient.layer), |
| 94 | Layer.provide(PlatformTestLayer), | |
| 94 | Layer.provide(layerPlatformTest), | |
| 95 | 95 | ), |
| 96 | 96 | // The Codex driver now resolves managed ChatGPT installs; these runs never launch Codex. |
| 97 | 97 | Layer.mock(CodexInstallation.CodexInstallation)({ |
| ... | ... | @@ -106,17 +106,17 @@ const providerInstanceRegistryLayer = ProviderInstanceRegistryHydrationLive.pipe |
| 106 | 106 | ), |
| 107 | 107 | ); |
| 108 | 108 | |
| 109 | const liveLayer = OrchestrationV2LayerLive.pipe( | |
| 110 | Layer.provide(worktreeRepairDependenciesTestLayer), | |
| 109 | const layerLive = RuntimeLayer.layer.pipe( | |
| 110 | Layer.provide(ProviderTurnStartServiceTestkit.layer), | |
| 111 | 111 | Layer.provide(McpSessionRegistryTestkit.layer), |
| 112 | Layer.provide(SqlitePersistenceMemory), | |
| 113 | Layer.provide(checkpointStoreLayer), | |
| 114 | Layer.provide(serverConfigLayer), | |
| 115 | Layer.provide(serverSettingsLayer), | |
| 116 | Layer.provide(providerInstanceRegistryLayer), | |
| 112 | Layer.provide(SqlitePersistence.layerMemory), | |
| 113 | Layer.provide(layerCheckpointStore), | |
| 114 | Layer.provide(layerServerConfig), | |
| 115 | Layer.provide(layerServerSettings), | |
| 116 | Layer.provide(layerProviderInstanceRegistry), | |
| 117 | 117 | Layer.provide(ResetCreditCoordinator.layer), |
| 118 | Layer.provide(backgroundPolicyLayer), | |
| 119 | Layer.provide(PlatformTestLayer), | |
| 118 | Layer.provide(layerBackgroundPolicy), | |
| 119 | Layer.provide(layerPlatformTest), | |
| 120 | 120 | ); |
| 121 | 121 | |
| 122 | 122 | const waitForIdle = Effect.fn("CursorOrchestratorV2Live.waitForIdle")(function* ( |
| ... | ... | @@ -234,7 +234,7 @@ describe.runIf(process.env.T3_CURSOR_LIVE_ORCHESTRATOR === "1")( |
| 234 | 234 | ); |
| 235 | 235 | assert.include(targetProjection.contextHandoffs[0]?.summaryText ?? "", marker); |
| 236 | 236 | assert.include(assistantText(targetProjection), marker); |
| 237 | }).pipe(Effect.provide(liveLayer), Effect.scoped), | |
| 237 | }).pipe(Effect.provide(layerLive), Effect.scoped), | |
| 238 | 238 | 360_000, |
| 239 | 239 | ); |
| 240 | 240 | |
| ... | ... | @@ -296,7 +296,7 @@ describe.runIf(process.env.T3_CURSOR_LIVE_ORCHESTRATOR === "1")( |
| 296 | 296 | supervised.runs.map((run) => run.status), |
| 297 | 297 | ["completed"], |
| 298 | 298 | ); |
| 299 | }).pipe(Effect.provide(liveLayer), Effect.scoped), | |
| 299 | }).pipe(Effect.provide(layerLive), Effect.scoped), | |
| 300 | 300 | 360_000, |
| 301 | 301 | ); |
| 302 | 302 | |
| ... | ... | @@ -371,7 +371,7 @@ describe.runIf(process.env.T3_CURSOR_LIVE_ORCHESTRATOR === "1")( |
| 371 | 371 | `child thread ${subagent.childThreadId} should contain the subagent response`, |
| 372 | 372 | ); |
| 373 | 373 | } |
| 374 | }).pipe(Effect.provide(liveLayer), Effect.scoped), | |
| 374 | }).pipe(Effect.provide(layerLive), Effect.scoped), | |
| 375 | 375 | 360_000, |
| 376 | 376 | ); |
| 377 | 377 | }, |
apps/server/src/orchestration-v2/DelegatedCompletionDelivery.test.ts+21-25| ... | ... | @@ -23,7 +23,7 @@ import * as Stream from "effect/Stream"; |
| 23 | 23 | import * as CheckpointStore from "../checkpointing/CheckpointStore.ts"; |
| 24 | 24 | import * as ServerConfig from "../config.ts"; |
| 25 | 25 | import * as McpSessionRegistryTestkit from "../mcp/McpSessionRegistry.testkit.ts"; |
| 26 | import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts"; | |
| 26 | import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts"; | |
| 27 | 27 | import * as ProjectEnrichmentService from "../project/ProjectEnrichmentService.ts"; |
| 28 | 28 | import * as ProjectService from "../project/ProjectService.ts"; |
| 29 | 29 | import type { ProviderInstance } from "../provider/ProviderDriver.ts"; |
| ... | ... | @@ -37,21 +37,17 @@ import * as EventSink from "./EventSink.ts"; |
| 37 | 37 | import * as Orchestrator from "./Orchestrator.ts"; |
| 38 | 38 | import type { ProviderAdapterV2Shape } from "./ProviderAdapter.ts"; |
| 39 | 39 | import { continueRestartedRun } from "./RestartContinuation.ts"; |
| 40 | import { | |
| 41 | OrchestrationV2EventSinkLayerLive, | |
| 42 | OrchestrationV2LayerLive, | |
| 43 | ProjectServiceLayerLive, | |
| 44 | } from "./runtimeLayer.ts"; | |
| 45 | import { worktreeRepairDependenciesTestLayer } from "./ProviderTurnStartService.testkit.ts"; | |
| 40 | import * as RuntimeLayer from "./runtimeLayer.ts"; | |
| 41 | import * as ProviderTurnStartServiceTestkit from "./ProviderTurnStartService.testkit.ts"; | |
| 46 | 42 | |
| 47 | const PlatformTestLayer = Layer.merge( | |
| 43 | const layerPlatformTest = Layer.merge( | |
| 48 | 44 | NodeServices.layer, |
| 49 | 45 | Layer.mock(SourceControlProviderRegistry.SourceControlProviderRegistry)({ |
| 50 | 46 | resolveLink: () => Effect.die("unused title link"), |
| 51 | 47 | }), |
| 52 | 48 | ); |
| 53 | 49 | |
| 54 | const ServerConfigLayer = ServerConfig.layerTest(process.cwd(), { | |
| 50 | const layerServerConfig = ServerConfig.layerTest(process.cwd(), { | |
| 55 | 51 | prefix: "t3-orchestration-v2-delegated-completion-", |
| 56 | 52 | }); |
| 57 | 53 | |
| ... | ... | @@ -60,14 +56,14 @@ const modelSelection = { |
| 60 | 56 | model: "gpt-5.4", |
| 61 | 57 | } satisfies ModelSelection; |
| 62 | 58 | |
| 63 | const VcsDriverRegistryTestLayer = VcsDriverRegistry.layer.pipe( | |
| 59 | const layerVcsDriverRegistryTest = VcsDriverRegistry.layer.pipe( | |
| 64 | 60 | Layer.provide(VcsProcess.layer), |
| 65 | Layer.provide(ServerConfigLayer), | |
| 66 | Layer.provide(PlatformTestLayer), | |
| 61 | Layer.provide(layerServerConfig), | |
| 62 | Layer.provide(layerPlatformTest), | |
| 67 | 63 | ); |
| 68 | 64 | |
| 69 | const CheckpointStoreTestLayer = CheckpointStore.layer.pipe( | |
| 70 | Layer.provide(VcsDriverRegistryTestLayer), | |
| 65 | const layerCheckpointStoreTest = CheckpointStore.layer.pipe( | |
| 66 | Layer.provide(layerVcsDriverRegistryTest), | |
| 71 | 67 | ); |
| 72 | 68 | |
| 73 | 69 | const driver = ProviderDriverKind.make("codex"); |
| ... | ... | @@ -93,7 +89,7 @@ const providerInstance = { |
| 93 | 89 | textGeneration: {} as ProviderInstance["textGeneration"], |
| 94 | 90 | } satisfies ProviderInstance; |
| 95 | 91 | |
| 96 | const TestProviderInstanceRegistry = Layer.succeed( | |
| 92 | const layerTestProviderInstanceRegistry = Layer.succeed( | |
| 97 | 93 | ProviderInstanceRegistry.ProviderInstanceRegistry, |
| 98 | 94 | { |
| 99 | 95 | getInstance: (instanceId) => |
| ... | ... | @@ -105,14 +101,14 @@ const TestProviderInstanceRegistry = Layer.succeed( |
| 105 | 101 | }, |
| 106 | 102 | ); |
| 107 | 103 | |
| 108 | const TestLayer = Layer.mergeAll(OrchestrationV2LayerLive, OrchestrationV2EventSinkLayerLive).pipe( | |
| 109 | Layer.provideMerge(ProjectServiceLayerLive), | |
| 104 | const layerTest = Layer.mergeAll(RuntimeLayer.layer, RuntimeLayer.layerEventSink).pipe( | |
| 105 | Layer.provideMerge(RuntimeLayer.layerProjectService), | |
| 110 | 106 | Layer.provide( |
| 111 | 107 | Layer.mock(WorkspacePaths.WorkspacePaths)({ |
| 112 | 108 | normalizeWorkspaceRoot: (workspaceRoot) => Effect.succeed(workspaceRoot), |
| 113 | 109 | }), |
| 114 | 110 | ), |
| 115 | Layer.provide(worktreeRepairDependenciesTestLayer), | |
| 111 | Layer.provide(ProviderTurnStartServiceTestkit.layer), | |
| 116 | 112 | Layer.provide( |
| 117 | 113 | Layer.succeed(ProjectEnrichmentService.ProjectEnrichmentService, { |
| 118 | 114 | peek: () => |
| ... | ... | @@ -133,12 +129,12 @@ const TestLayer = Layer.mergeAll(OrchestrationV2LayerLive, OrchestrationV2EventS |
| 133 | 129 | }), |
| 134 | 130 | ), |
| 135 | 131 | Layer.provide(McpSessionRegistryTestkit.layer), |
| 136 | Layer.provide(SqlitePersistenceMemory), | |
| 137 | Layer.provide(CheckpointStoreTestLayer), | |
| 138 | Layer.provide(ServerConfigLayer), | |
| 132 | Layer.provide(SqlitePersistence.layerMemory), | |
| 133 | Layer.provide(layerCheckpointStoreTest), | |
| 134 | Layer.provide(layerServerConfig), | |
| 139 | 135 | Layer.provide(ServerSettings.layerTest()), |
| 140 | Layer.provide(TestProviderInstanceRegistry), | |
| 141 | Layer.provide(PlatformTestLayer), | |
| 136 | Layer.provide(layerTestProviderInstanceRegistry), | |
| 137 | Layer.provide(layerPlatformTest), | |
| 142 | 138 | ); |
| 143 | 139 | |
| 144 | 140 | const seedParentWithTerminalTask = (input: { |
| ... | ... | @@ -292,7 +288,7 @@ const seedParentWithTerminalTask = (input: { |
| 292 | 288 | }); |
| 293 | 289 | }); |
| 294 | 290 | |
| 295 | it.layer(TestLayer)("delegated completion delivery repairs", (it) => { | |
| 291 | it.layer(layerTest)("delegated completion delivery repairs", (it) => { | |
| 296 | 292 | it.effect("acceptance batches pending siblings without acknowledging their results", () => |
| 297 | 293 | Effect.gen(function* () { |
| 298 | 294 | const orchestrator = yield* Orchestrator.OrchestratorV2; |
| ... | ... | @@ -905,7 +901,7 @@ const seedRestartCancelledChild = (input: { |
| 905 | 901 | return { taskId, childThreadId, childRunId }; |
| 906 | 902 | }); |
| 907 | 903 | |
| 908 | it.layer(TestLayer)("delegated tasks across a server restart", (it) => { | |
| 904 | it.layer(layerTest)("delegated tasks across a server restart", (it) => { | |
| 909 | 905 | it.effect("holds a restart-cancelled child for its continuation's result", () => |
| 910 | 906 | Effect.gen(function* () { |
| 911 | 907 | const orchestrator = yield* Orchestrator.OrchestratorV2; |
apps/server/src/orchestration-v2/EffectOutbox.ts+1-1| ... | ... | @@ -690,7 +690,7 @@ export const layer: Layer.Layer<EffectOutboxV2, never, SqlClient.SqlClient> = La |
| 690 | 690 | ); |
| 691 | 691 | |
| 692 | 692 | /** Prunes settled effect rows once the server is active, then every hour. */ |
| 693 | export const pruneWorkerLive = Layer.effectDiscard( | |
| 693 | export const layerPruneWorker = Layer.effectDiscard( | |
| 694 | 694 | Effect.gen(function* () { |
| 695 | 695 | const outbox = yield* EffectOutboxV2; |
| 696 | 696 | yield* forkParked( |
apps/server/src/orchestration-v2/EffectWorker.test.ts+36-36| ... | ... | @@ -76,14 +76,14 @@ function restartEffect( |
| 76 | 76 | }; |
| 77 | 77 | } |
| 78 | 78 | |
| 79 | function makeExecutorLayer(input: { | |
| 79 | function layerExecutorFor(input: { | |
| 80 | 80 | readonly events: Ref.Ref<ReadonlyArray<string>>; |
| 81 | 81 | readonly failFirstStart?: Ref.Ref<boolean>; |
| 82 | 82 | readonly threads?: Partial<ThreadManagementService.ThreadManagementService["Service"]>; |
| 83 | 83 | readonly continueAfterRestart?: boolean; |
| 84 | 84 | }) { |
| 85 | 85 | const record = (event: string) => Ref.update(input.events, (events) => [...events, event]); |
| 86 | const dependencies = Layer.mergeAll( | |
| 86 | const layerDependencies = Layer.mergeAll( | |
| 87 | 87 | Layer.succeed( |
| 88 | 88 | ProviderTurnControlService.ProviderTurnControlServiceV2, |
| 89 | 89 | ProviderTurnControlService.ProviderTurnControlServiceV2.of({ |
| ... | ... | @@ -146,10 +146,10 @@ function makeExecutorLayer(input: { |
| 146 | 146 | }), |
| 147 | 147 | ), |
| 148 | 148 | ); |
| 149 | return EffectWorker.executorLayer.pipe( | |
| 149 | return EffectWorker.layerExecutor.pipe( | |
| 150 | 150 | Layer.provide( |
| 151 | 151 | Layer.mergeAll( |
| 152 | dependencies, | |
| 152 | layerDependencies, | |
| 153 | 153 | Layer.mock(ThreadManagementService.ThreadManagementService)(input.threads ?? {}), |
| 154 | 154 | ServerSettings.layerTest( |
| 155 | 155 | input.continueAfterRestart === true ? { continueThreadsAfterServerUpdate: true } : {}, |
| ... | ... | @@ -222,7 +222,7 @@ it.effect("requeues a claim when a pre-execution worker check fails", () => |
| 222 | 222 | }> |
| 223 | 223 | >([]); |
| 224 | 224 | const executionCount = yield* Ref.make(0); |
| 225 | const outboxLayer = Layer.mock(EffectOutbox.EffectOutboxV2)({ | |
| 225 | const layerOutbox = Layer.mock(EffectOutbox.EffectOutboxV2)({ | |
| 226 | 226 | claimNext: () => Effect.succeed(Option.some(claimedEffect)), |
| 227 | 227 | get: () => |
| 228 | 228 | Effect.fail( |
| ... | ... | @@ -235,19 +235,19 @@ it.effect("requeues a claim when a pre-execution worker check fails", () => |
| 235 | 235 | retry: (input) => |
| 236 | 236 | Ref.update(retries, (existing) => [...existing, input]).pipe(Effect.as(true)), |
| 237 | 237 | }); |
| 238 | const executorLayer = Layer.succeed( | |
| 238 | const layerExecutor = Layer.succeed( | |
| 239 | 239 | EffectWorker.OrchestrationEffectExecutorV2, |
| 240 | 240 | EffectWorker.OrchestrationEffectExecutorV2.of({ |
| 241 | 241 | execute: () => Ref.update(executionCount, (count) => count + 1), |
| 242 | 242 | }), |
| 243 | 243 | ); |
| 244 | const workerLayer = EffectWorker.layerWithOptions({ workerId }).pipe( | |
| 245 | Layer.provide(Layer.merge(outboxLayer, executorLayer)), | |
| 244 | const layerWorker = EffectWorker.layerWithOptions({ workerId }).pipe( | |
| 245 | Layer.provide(Layer.merge(layerOutbox, layerExecutor)), | |
| 246 | 246 | ); |
| 247 | 247 | |
| 248 | 248 | const exit = yield* EffectWorker.OrchestrationEffectWorkerV2.pipe( |
| 249 | 249 | Effect.flatMap((worker) => worker.runOnce), |
| 250 | Effect.provide(workerLayer), | |
| 250 | Effect.provide(layerWorker), | |
| 251 | 251 | Effect.exit, |
| 252 | 252 | ); |
| 253 | 253 | |
| ... | ... | @@ -289,7 +289,7 @@ it.effect("arms cancellation before the durable pre-execution check", () => |
| 289 | 289 | let cancellationArmed = false; |
| 290 | 290 | const executionCount = yield* Ref.make(0); |
| 291 | 291 | const settlementCount = yield* Ref.make(0); |
| 292 | const outboxLayer = Layer.mock(EffectOutbox.EffectOutboxV2)({ | |
| 292 | const layerOutbox = Layer.mock(EffectOutbox.EffectOutboxV2)({ | |
| 293 | 293 | claimNext: () => Effect.succeed(Option.some(claimedEffect)), |
| 294 | 294 | awaitCancellation: () => { |
| 295 | 295 | cancellationArmed = true; |
| ... | ... | @@ -308,20 +308,20 @@ it.effect("arms cancellation before the durable pre-execution check", () => |
| 308 | 308 | clearCancellation: () => Effect.void, |
| 309 | 309 | succeed: () => Ref.update(settlementCount, (count) => count + 1).pipe(Effect.as(true)), |
| 310 | 310 | }); |
| 311 | const executorLayer = Layer.succeed( | |
| 311 | const layerExecutor = Layer.succeed( | |
| 312 | 312 | EffectWorker.OrchestrationEffectExecutorV2, |
| 313 | 313 | EffectWorker.OrchestrationEffectExecutorV2.of({ |
| 314 | 314 | execute: () => |
| 315 | 315 | Effect.yieldNow.pipe(Effect.andThen(Ref.update(executionCount, (count) => count + 1))), |
| 316 | 316 | }), |
| 317 | 317 | ); |
| 318 | const workerLayer = EffectWorker.layerWithOptions({ workerId }).pipe( | |
| 319 | Layer.provide(Layer.merge(outboxLayer, executorLayer)), | |
| 318 | const layerWorker = EffectWorker.layerWithOptions({ workerId }).pipe( | |
| 319 | Layer.provide(Layer.merge(layerOutbox, layerExecutor)), | |
| 320 | 320 | ); |
| 321 | 321 | |
| 322 | 322 | const exit = yield* EffectWorker.OrchestrationEffectWorkerV2.pipe( |
| 323 | 323 | Effect.flatMap((worker) => worker.runOnce), |
| 324 | Effect.provide(workerLayer), | |
| 324 | Effect.provide(layerWorker), | |
| 325 | 325 | Effect.exit, |
| 326 | 326 | ); |
| 327 | 327 | |
| ... | ... | @@ -359,7 +359,7 @@ it.effect("terminalizes a process-bound claim when success settlement fails", () |
| 359 | 359 | const retries = yield* Ref.make(0); |
| 360 | 360 | const terminalErrors = yield* Ref.make<ReadonlyArray<string>>([]); |
| 361 | 361 | const executionCount = yield* Ref.make(0); |
| 362 | const outboxLayer = Layer.mock(EffectOutbox.EffectOutboxV2)({ | |
| 362 | const layerOutbox = Layer.mock(EffectOutbox.EffectOutboxV2)({ | |
| 363 | 363 | claimNext: () => Effect.succeed(Option.some(claimedEffect)), |
| 364 | 364 | get: () => Effect.succeed(Option.some(claimedEffect)), |
| 365 | 365 | awaitCancellation: () => Effect.never, |
| ... | ... | @@ -376,19 +376,19 @@ it.effect("terminalizes a process-bound claim when success settlement fails", () |
| 376 | 376 | fail: ({ error }) => |
| 377 | 377 | Ref.update(terminalErrors, (existing) => [...existing, error]).pipe(Effect.as(true)), |
| 378 | 378 | }); |
| 379 | const executorLayer = Layer.succeed( | |
| 379 | const layerExecutor = Layer.succeed( | |
| 380 | 380 | EffectWorker.OrchestrationEffectExecutorV2, |
| 381 | 381 | EffectWorker.OrchestrationEffectExecutorV2.of({ |
| 382 | 382 | execute: () => Ref.update(executionCount, (count) => count + 1), |
| 383 | 383 | }), |
| 384 | 384 | ); |
| 385 | const workerLayer = EffectWorker.layerWithOptions({ workerId }).pipe( | |
| 386 | Layer.provide(Layer.merge(outboxLayer, executorLayer)), | |
| 385 | const layerWorker = EffectWorker.layerWithOptions({ workerId }).pipe( | |
| 386 | Layer.provide(Layer.merge(layerOutbox, layerExecutor)), | |
| 387 | 387 | ); |
| 388 | 388 | |
| 389 | 389 | const exit = yield* EffectWorker.OrchestrationEffectWorkerV2.pipe( |
| 390 | 390 | Effect.flatMap((worker) => worker.runOnce), |
| 391 | Effect.provide(workerLayer), | |
| 391 | Effect.provide(layerWorker), | |
| 392 | 392 | Effect.exit, |
| 393 | 393 | ); |
| 394 | 394 | |
| ... | ... | @@ -424,7 +424,7 @@ it.effect("requeues a replay-safe claim when success settlement fails", () => |
| 424 | 424 | }; |
| 425 | 425 | const retries = yield* Ref.make(0); |
| 426 | 426 | const terminalizations = yield* Ref.make(0); |
| 427 | const outboxLayer = Layer.mock(EffectOutbox.EffectOutboxV2)({ | |
| 427 | const layerOutbox = Layer.mock(EffectOutbox.EffectOutboxV2)({ | |
| 428 | 428 | claimNext: () => Effect.succeed(Option.some(claimedEffect)), |
| 429 | 429 | get: () => Effect.succeed(Option.some(claimedEffect)), |
| 430 | 430 | awaitCancellation: () => Effect.never, |
| ... | ... | @@ -440,17 +440,17 @@ it.effect("requeues a replay-safe claim when success settlement fails", () => |
| 440 | 440 | retry: () => Ref.update(retries, (count) => count + 1).pipe(Effect.as(true)), |
| 441 | 441 | fail: () => Ref.update(terminalizations, (count) => count + 1).pipe(Effect.as(true)), |
| 442 | 442 | }); |
| 443 | const executorLayer = Layer.succeed( | |
| 443 | const layerExecutor = Layer.succeed( | |
| 444 | 444 | EffectWorker.OrchestrationEffectExecutorV2, |
| 445 | 445 | EffectWorker.OrchestrationEffectExecutorV2.of({ execute: () => Effect.void }), |
| 446 | 446 | ); |
| 447 | const workerLayer = EffectWorker.layerWithOptions({ workerId }).pipe( | |
| 448 | Layer.provide(Layer.merge(outboxLayer, executorLayer)), | |
| 447 | const layerWorker = EffectWorker.layerWithOptions({ workerId }).pipe( | |
| 448 | Layer.provide(Layer.merge(layerOutbox, layerExecutor)), | |
| 449 | 449 | ); |
| 450 | 450 | |
| 451 | 451 | const exit = yield* EffectWorker.OrchestrationEffectWorkerV2.pipe( |
| 452 | 452 | Effect.flatMap((worker) => worker.runOnce), |
| 453 | Effect.provide(workerLayer), | |
| 453 | Effect.provide(layerWorker), | |
| 454 | 454 | Effect.exit, |
| 455 | 455 | ); |
| 456 | 456 | |
| ... | ... | @@ -485,7 +485,7 @@ it.effect("keeps a process-bound executor failure retryable when retry settlemen |
| 485 | 485 | }; |
| 486 | 486 | const retryAttempts = yield* Ref.make(0); |
| 487 | 487 | const terminalizations = yield* Ref.make(0); |
| 488 | const outboxLayer = Layer.mock(EffectOutbox.EffectOutboxV2)({ | |
| 488 | const layerOutbox = Layer.mock(EffectOutbox.EffectOutboxV2)({ | |
| 489 | 489 | claimNext: () => Effect.succeed(Option.some(claimedEffect)), |
| 490 | 490 | get: () => Effect.succeed(Option.some(claimedEffect)), |
| 491 | 491 | awaitCancellation: () => Effect.never, |
| ... | ... | @@ -506,7 +506,7 @@ it.effect("keeps a process-bound executor failure retryable when retry settlemen |
| 506 | 506 | ), |
| 507 | 507 | fail: () => Ref.update(terminalizations, (count) => count + 1).pipe(Effect.as(true)), |
| 508 | 508 | }); |
| 509 | const executorLayer = Layer.succeed( | |
| 509 | const layerExecutor = Layer.succeed( | |
| 510 | 510 | EffectWorker.OrchestrationEffectExecutorV2, |
| 511 | 511 | EffectWorker.OrchestrationEffectExecutorV2.of({ |
| 512 | 512 | execute: () => |
| ... | ... | @@ -519,13 +519,13 @@ it.effect("keeps a process-bound executor failure retryable when retry settlemen |
| 519 | 519 | ), |
| 520 | 520 | }), |
| 521 | 521 | ); |
| 522 | const workerLayer = EffectWorker.layerWithOptions({ workerId }).pipe( | |
| 523 | Layer.provide(Layer.merge(outboxLayer, executorLayer)), | |
| 522 | const layerWorker = EffectWorker.layerWithOptions({ workerId }).pipe( | |
| 523 | Layer.provide(Layer.merge(layerOutbox, layerExecutor)), | |
| 524 | 524 | ); |
| 525 | 525 | |
| 526 | 526 | const exit = yield* EffectWorker.OrchestrationEffectWorkerV2.pipe( |
| 527 | 527 | Effect.flatMap((worker) => worker.runOnce), |
| 528 | Effect.provide(workerLayer), | |
| 528 | Effect.provide(layerWorker), | |
| 529 | 529 | Effect.exit, |
| 530 | 530 | ); |
| 531 | 531 | |
| ... | ... | @@ -557,7 +557,7 @@ it.effect("keeps a max-attempt replay-safe failure terminal when fail settlement |
| 557 | 557 | }; |
| 558 | 558 | const failAttempts = yield* Ref.make(0); |
| 559 | 559 | const retries = yield* Ref.make(0); |
| 560 | const outboxLayer = Layer.mock(EffectOutbox.EffectOutboxV2)({ | |
| 560 | const layerOutbox = Layer.mock(EffectOutbox.EffectOutboxV2)({ | |
| 561 | 561 | claimNext: () => Effect.succeed(Option.some(claimedEffect)), |
| 562 | 562 | get: () => Effect.succeed(Option.some(claimedEffect)), |
| 563 | 563 | awaitCancellation: () => Effect.never, |
| ... | ... | @@ -578,7 +578,7 @@ it.effect("keeps a max-attempt replay-safe failure terminal when fail settlement |
| 578 | 578 | ), |
| 579 | 579 | retry: () => Ref.update(retries, (count) => count + 1).pipe(Effect.as(true)), |
| 580 | 580 | }); |
| 581 | const executorLayer = Layer.succeed( | |
| 581 | const layerExecutor = Layer.succeed( | |
| 582 | 582 | EffectWorker.OrchestrationEffectExecutorV2, |
| 583 | 583 | EffectWorker.OrchestrationEffectExecutorV2.of({ |
| 584 | 584 | execute: () => |
| ... | ... | @@ -591,13 +591,13 @@ it.effect("keeps a max-attempt replay-safe failure terminal when fail settlement |
| 591 | 591 | ), |
| 592 | 592 | }), |
| 593 | 593 | ); |
| 594 | const workerLayer = EffectWorker.layerWithOptions({ workerId, maxAttempts: 5 }).pipe( | |
| 595 | Layer.provide(Layer.merge(outboxLayer, executorLayer)), | |
| 594 | const layerWorker = EffectWorker.layerWithOptions({ workerId, maxAttempts: 5 }).pipe( | |
| 595 | Layer.provide(Layer.merge(layerOutbox, layerExecutor)), | |
| 596 | 596 | ); |
| 597 | 597 | |
| 598 | 598 | const exit = yield* EffectWorker.OrchestrationEffectWorkerV2.pipe( |
| 599 | 599 | Effect.flatMap((worker) => worker.runOnce), |
| 600 | Effect.provide(workerLayer), | |
| 600 | Effect.provide(layerWorker), | |
| 601 | 601 | Effect.exit, |
| 602 | 602 | ); |
| 603 | 603 | |
| ... | ... | @@ -734,7 +734,7 @@ it.effect("safely retries after replacement cleanup succeeds and start fails", ( |
| 734 | 734 | type: "replace", |
| 735 | 735 | replacementProviderSessionId: replacementSessionId, |
| 736 | 736 | }); |
| 737 | const layer = makeExecutorLayer({ events, failFirstStart }); | |
| 737 | const layer = layerExecutorFor({ events, failFirstStart }); | |
| 738 | 738 | |
| 739 | 739 | const first = yield* Effect.gen(function* () { |
| 740 | 740 | const executor = yield* EffectWorker.OrchestrationEffectExecutorV2; |
| ... | ... | @@ -763,7 +763,7 @@ it.effect("settles a delegated child once its restart continuation fails for goo |
| 763 | 763 | const timestamp = DateTime.formatIso(yield* DateTime.now); |
| 764 | 764 | const events = yield* Ref.make<ReadonlyArray<string>>([]); |
| 765 | 765 | const recovered = yield* Ref.make<ReadonlyArray<ThreadId>>([]); |
| 766 | const layer = makeExecutorLayer({ | |
| 766 | const layer = layerExecutorFor({ | |
| 767 | 767 | events, |
| 768 | 768 | continueAfterRestart: true, |
| 769 | 769 | threads: { |
apps/server/src/orchestration-v2/EffectWorker.ts+2-2| ... | ... | @@ -78,7 +78,7 @@ export class OrchestrationEffectExecutorV2 extends Context.Service< |
| 78 | 78 | OrchestrationEffectExecutorV2Shape |
| 79 | 79 | >()("t3/orchestration-v2/EffectWorker/OrchestrationEffectExecutorV2") {} |
| 80 | 80 | |
| 81 | export const executorLayer: Layer.Layer< | |
| 81 | export const layerExecutor: Layer.Layer< | |
| 82 | 82 | OrchestrationEffectExecutorV2, |
| 83 | 83 | never, |
| 84 | 84 | | ProviderSessionManager.ProviderSessionManagerV2 |
| ... | ... | @@ -846,6 +846,6 @@ export const runDaemonWithOptions = (options: OrchestrationEffectDaemonOptions = |
| 846 | 846 | |
| 847 | 847 | export const runDaemon = runDaemonWithOptions(); |
| 848 | 848 | |
| 849 | const daemonLayer: Layer.Layer<never, never, OrchestrationEffectWorkerV2> = Layer.effectDiscard( | |
| 849 | const layerDaemon: Layer.Layer<never, never, OrchestrationEffectWorkerV2> = Layer.effectDiscard( | |
| 850 | 850 | runDaemon.pipe(Effect.forkScoped), |
| 851 | 851 | ); |
apps/server/src/orchestration-v2/EventSink.ts+3-3| ... | ... | @@ -190,7 +190,7 @@ export class EventSinkV2 extends Context.Service<EventSinkV2, EventSinkV2Shape>( |
| 190 | 190 | /** |
| 191 | 191 | * IMPLEMENTATIONS |
| 192 | 192 | */ |
| 193 | const baseLayer: Layer.Layer< | |
| 193 | const layerBase: Layer.Layer< | |
| 194 | 194 | EventSinkV2, |
| 195 | 195 | never, |
| 196 | 196 | | CommandReceiptStore.CommandReceiptStoreV2 |
| ... | ... | @@ -873,13 +873,13 @@ const baseLayer: Layer.Layer< |
| 873 | 873 | * important because enqueue notifications are in-memory wakeups backed by the |
| 874 | 874 | * durable SQL queue. |
| 875 | 875 | */ |
| 876 | export const layerFromStores = baseLayer; | |
| 876 | export const layerFromStores = layerBase; | |
| 877 | 877 | |
| 878 | 878 | export const layer: Layer.Layer< |
| 879 | 879 | EventSinkV2, |
| 880 | 880 | never, |
| 881 | 881 | EventStore.EventStoreV2 | ProjectionStore.ProjectionStoreV2 | SqlClient.SqlClient |
| 882 | > = baseLayer.pipe( | |
| 882 | > = layerBase.pipe( | |
| 883 | 883 | Layer.provide( |
| 884 | 884 | Layer.mergeAll( |
| 885 | 885 | CommandReceiptStore.layer, |
apps/server/src/orchestration-v2/EventStore.ts+5-5| ... | ... | @@ -13,7 +13,7 @@ import * as Schema from "effect/Schema"; |
| 13 | 13 | import * as Stream from "effect/Stream"; |
| 14 | 14 | import type * as SqlClient from "effect/sql/SqlClient"; |
| 15 | 15 | |
| 16 | import { OrchestrationEventStoreLive } from "../persistence/Layers/OrchestrationEventStore.ts"; | |
| 16 | import * as OrchestrationEventStoreLayer from "../persistence/Layers/OrchestrationEventStore.ts"; | |
| 17 | 17 | import * as OrchestrationEventStore from "../persistence/Services/OrchestrationEventStore.ts"; |
| 18 | 18 | |
| 19 | 19 | export class EventStoreAppendEventsError extends Schema.TaggedError<EventStoreAppendEventsError>()( |
| ... | ... | @@ -79,7 +79,7 @@ export class EventStoreV2 extends Context.Service<EventStoreV2, EventStoreV2Shap |
| 79 | 79 | "t3/orchestration-v2/EventStore/EventStoreV2", |
| 80 | 80 | ) {} |
| 81 | 81 | |
| 82 | const baseLayer: Layer.Layer<EventStoreV2, never, OrchestrationEventStore.OrchestrationEventStore> = | |
| 82 | const layerBase: Layer.Layer<EventStoreV2, never, OrchestrationEventStore.OrchestrationEventStore> = | |
| 83 | 83 | Layer.effect( |
| 84 | 84 | EventStoreV2, |
| 85 | 85 | Effect.gen(function* () { |
| ... | ... | @@ -149,8 +149,8 @@ const baseLayer: Layer.Layer<EventStoreV2, never, OrchestrationEventStore.Orches |
| 149 | 149 | }), |
| 150 | 150 | ); |
| 151 | 151 | |
| 152 | export const layer: Layer.Layer<EventStoreV2, never, SqlClient.SqlClient> = baseLayer.pipe( | |
| 153 | Layer.provide(OrchestrationEventStoreLive), | |
| 152 | export const layer: Layer.Layer<EventStoreV2, never, SqlClient.SqlClient> = layerBase.pipe( | |
| 153 | Layer.provide(OrchestrationEventStoreLayer.layer), | |
| 154 | 154 | ); |
| 155 | 155 | |
| 156 | export const layerFromOrchestrationEventStore = baseLayer; | |
| 156 | export const layerFromOrchestrationEventStore = layerBase; |
apps/server/src/orchestration-v2/FoundationPersistence.test.ts+83-75| ... | ... | @@ -43,7 +43,7 @@ import * as SqlClient from "effect/sql/SqlClient"; |
| 43 | 43 | import * as Statement from "effect/sql/Statement"; |
| 44 | 44 | |
| 45 | 45 | import { LIVE_STREAM_MAX_ITEMS, LiveStreamBufferError } from "./LiveStreamBudget.ts"; |
| 46 | import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts"; | |
| 46 | import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts"; | |
| 47 | 47 | import { CodexProviderCapabilitiesV2 } from "./Adapters/CodexAdapterV2.ts"; |
| 48 | 48 | import * as CommandReceiptStore from "./CommandReceiptStore.ts"; |
| 49 | 49 | import * as EffectOutbox from "./EffectOutbox.ts"; |
| ... | ... | @@ -60,25 +60,31 @@ import * as TurnItemPositionStore from "./TurnItemPositionStore.ts"; |
| 60 | 60 | const isLiveStreamBufferError = Schema.is(LiveStreamBufferError); |
| 61 | 61 | const encodeJson = Schema.encodeEffect(Schema.fromJsonString(Schema.Unknown)); |
| 62 | 62 | |
| 63 | const databaseLayer = SqlitePersistenceMemory; | |
| 64 | const eventStoreProvided = EventStore.layer.pipe(Layer.provideMerge(databaseLayer)); | |
| 65 | const projectionStoreProvided = ProjectionStore.layer.pipe(Layer.provideMerge(databaseLayer)); | |
| 66 | const storesProvided = Layer.mergeAll(databaseLayer, eventStoreProvided, projectionStoreProvided); | |
| 67 | const eventSinkProvided = EventSink.layer.pipe(Layer.provide(storesProvided)); | |
| 68 | const effectOutboxProvided = EffectOutbox.layer.pipe(Layer.provide(databaseLayer)); | |
| 69 | const commandReceiptStoreProvided = CommandReceiptStore.layer.pipe(Layer.provide(databaseLayer)); | |
| 63 | const layerDatabase = SqlitePersistence.layerMemory; | |
| 64 | const layerEventStoreProvided = EventStore.layer.pipe(Layer.provideMerge(layerDatabase)); | |
| 65 | const layerProjectionStoreProvided = ProjectionStore.layer.pipe(Layer.provideMerge(layerDatabase)); | |
| 66 | const layerStoresProvided = Layer.mergeAll( | |
| 67 | layerDatabase, | |
| 68 | layerEventStoreProvided, | |
| 69 | layerProjectionStoreProvided, | |
| 70 | ); | |
| 71 | const layerEventSinkProvided = EventSink.layer.pipe(Layer.provide(layerStoresProvided)); | |
| 72 | const layerEffectOutboxProvided = EffectOutbox.layer.pipe(Layer.provide(layerDatabase)); | |
| 73 | const layerCommandReceiptStoreProvided = CommandReceiptStore.layer.pipe( | |
| 74 | Layer.provide(layerDatabase), | |
| 75 | ); | |
| 70 | 76 | // Its own database, for tests that act on every row in the outbox table. |
| 71 | const isolatedOutboxLayer = Layer.fresh(EffectOutbox.layer.pipe(Layer.provideMerge(databaseLayer))); | |
| 72 | const projectionMaintenanceProvided = ProjectionMaintenance.layer.pipe( | |
| 73 | Layer.provide(storesProvided), | |
| 77 | const layerIsolatedOutbox = Layer.fresh(EffectOutbox.layer.pipe(Layer.provideMerge(layerDatabase))); | |
| 78 | const layerProjectionMaintenanceProvided = ProjectionMaintenance.layer.pipe( | |
| 79 | Layer.provide(layerStoresProvided), | |
| 74 | 80 | ); |
| 75 | const TestLayer = Layer.mergeAll( | |
| 76 | storesProvided, | |
| 77 | eventSinkProvided, | |
| 78 | effectOutboxProvided, | |
| 79 | commandReceiptStoreProvided, | |
| 81 | const layerTest = Layer.mergeAll( | |
| 82 | layerStoresProvided, | |
| 83 | layerEventSinkProvided, | |
| 84 | layerEffectOutboxProvided, | |
| 85 | layerCommandReceiptStoreProvided, | |
| 80 | 86 | IdAllocator.layer, |
| 81 | projectionMaintenanceProvided, | |
| 87 | layerProjectionMaintenanceProvided, | |
| 82 | 88 | ); |
| 83 | 89 | |
| 84 | 90 | const providerInstanceId = ProviderInstanceId.make("codex"); |
| ... | ... | @@ -156,7 +162,7 @@ it.effect("rebuilds event history one bounded page at a time", () => |
| 156 | 162 | let applied = 0; |
| 157 | 163 | let failAfterFirstPage = false; |
| 158 | 164 | const appliedAtRead: Array<number> = []; |
| 159 | const observedStores = Layer.mergeAll( | |
| 165 | const layerObservedStores = Layer.mergeAll( | |
| 160 | 166 | Layer.succeed(EventStore.EventStoreV2, { |
| 161 | 167 | ...eventStore, |
| 162 | 168 | read: (input) => |
| ... | ... | @@ -186,7 +192,9 @@ it.effect("rebuilds event history one bounded page at a time", () => |
| 186 | 192 | const maintenance = yield* ProjectionMaintenance.ProjectionMaintenanceV2; |
| 187 | 193 | return yield* maintenance.rebuild; |
| 188 | 194 | }).pipe( |
| 189 | Effect.provide(Layer.fresh(ProjectionMaintenance.layer.pipe(Layer.provide(observedStores)))), | |
| 195 | Effect.provide( | |
| 196 | Layer.fresh(ProjectionMaintenance.layer.pipe(Layer.provide(layerObservedStores))), | |
| 197 | ), | |
| 190 | 198 | ); |
| 191 | 199 | const rebuilt = yield* rebuild; |
| 192 | 200 | assert.isTrue(rebuilt.valid); |
| ... | ... | @@ -207,7 +215,7 @@ it.effect("rebuilds event history one bounded page at a time", () => |
| 207 | 215 | ); |
| 208 | 216 | const maintenance = yield* ProjectionMaintenance.ProjectionMaintenanceV2; |
| 209 | 217 | assert.isTrue((yield* maintenance.verify).valid); |
| 210 | }).pipe(Effect.provide(TestLayer)), | |
| 218 | }).pipe(Effect.provide(layerTest)), | |
| 211 | 219 | ); |
| 212 | 220 | |
| 213 | 221 | it.effect("verifies thread membership using only the thread-created partial index", () => |
| ... | ... | @@ -253,7 +261,7 @@ it.effect("verifies thread membership using only the thread-created partial inde |
| 253 | 261 | const details = plan.map((row) => row.detail).join("\n"); |
| 254 | 262 | assert.match(details, /USING (?:COVERING )?INDEX orchestration_events_v2_created_threads_idx/); |
| 255 | 263 | assert.notMatch(details, /idx_orch_events_stream_sequence|TEMP B-TREE/); |
| 256 | }).pipe(Effect.provide(TestLayer)), | |
| 264 | }).pipe(Effect.provide(layerTest)), | |
| 257 | 265 | ); |
| 258 | 266 | |
| 259 | 267 | it.effect("keeps other database work runnable while discovering compaction candidates", () => |
| ... | ... | @@ -339,10 +347,10 @@ it.effect("keeps other database work runnable while discovering compaction candi |
| 339 | 347 | assert.isBelow(result.queriesAtProbe, queries[result.table]); |
| 340 | 348 | } |
| 341 | 349 | }), |
| 342 | ).pipe(Effect.provide(TestLayer)), | |
| 350 | ).pipe(Effect.provide(layerTest)), | |
| 343 | 351 | ); |
| 344 | 352 | |
| 345 | it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => { | |
| 353 | it.layer(layerTest)("orchestration V2 foundation persistence", (it) => { | |
| 346 | 354 | it.effect("projects oversized tool bodies before both replay and live RPC retention", () => |
| 347 | 355 | Effect.scoped( |
| 348 | 356 | Effect.gen(function* () { |
| ... | ... | @@ -1448,22 +1456,22 @@ it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => { |
| 1448 | 1456 | assert.lengthOf(yield* outbox.listByCommandId(commandId), 1); |
| 1449 | 1457 | |
| 1450 | 1458 | const executionCount = yield* Ref.make(0); |
| 1451 | const executorLayer = Layer.succeed( | |
| 1459 | const layerExecutor = Layer.succeed( | |
| 1452 | 1460 | EffectWorker.OrchestrationEffectExecutorV2, |
| 1453 | 1461 | EffectWorker.OrchestrationEffectExecutorV2.of({ |
| 1454 | 1462 | execute: () => Ref.update(executionCount, (count) => count + 1), |
| 1455 | 1463 | }), |
| 1456 | 1464 | ); |
| 1457 | const workerLayer = EffectWorker.layerWithOptions({ workerId: "recovery-worker" }).pipe( | |
| 1465 | const layerWorker = EffectWorker.layerWithOptions({ workerId: "recovery-worker" }).pipe( | |
| 1458 | 1466 | Layer.provide( |
| 1459 | Layer.merge(Layer.succeed(EffectOutbox.EffectOutboxV2, outbox), executorLayer), | |
| 1467 | Layer.merge(Layer.succeed(EffectOutbox.EffectOutboxV2, outbox), layerExecutor), | |
| 1460 | 1468 | ), |
| 1461 | 1469 | ); |
| 1462 | 1470 | yield* Effect.gen(function* () { |
| 1463 | 1471 | const worker = yield* EffectWorker.OrchestrationEffectWorkerV2; |
| 1464 | 1472 | assert.isTrue(yield* worker.runOnce); |
| 1465 | 1473 | assert.isFalse(yield* worker.runOnce); |
| 1466 | }).pipe(Effect.provide(workerLayer)); | |
| 1474 | }).pipe(Effect.provide(layerWorker)); | |
| 1467 | 1475 | |
| 1468 | 1476 | assert.equal(yield* Ref.get(executionCount), 1); |
| 1469 | 1477 | const storedEffect = yield* outbox.get(effect.id); |
| ... | ... | @@ -1510,7 +1518,7 @@ it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => { |
| 1510 | 1518 | const unexpectedWake = yield* outbox.awaitAvailable.pipe(Effect.forkChild); |
| 1511 | 1519 | yield* Effect.yieldNow; |
| 1512 | 1520 | assert.isUndefined(unexpectedWake.pollUnsafe()); |
| 1513 | }).pipe(Effect.provide(Layer.fresh(TestLayer))), | |
| 1521 | }).pipe(Effect.provide(Layer.fresh(layerTest))), | |
| 1514 | 1522 | ); |
| 1515 | 1523 | |
| 1516 | 1524 | it.effect("does not publish a stale provider start after an interrupt wins", () => |
| ... | ... | @@ -1861,7 +1869,7 @@ it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => { |
| 1861 | 1869 | }, |
| 1862 | 1870 | ]); |
| 1863 | 1871 | |
| 1864 | const executorLayer = Layer.succeed( | |
| 1872 | const layerExecutor = Layer.succeed( | |
| 1865 | 1873 | EffectWorker.OrchestrationEffectExecutorV2, |
| 1866 | 1874 | EffectWorker.OrchestrationEffectExecutorV2.of({ |
| 1867 | 1875 | execute: () => |
| ... | ... | @@ -1873,11 +1881,11 @@ it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => { |
| 1873 | 1881 | ), |
| 1874 | 1882 | }), |
| 1875 | 1883 | ); |
| 1876 | const workerLayer = EffectWorker.layerWithOptions({ | |
| 1884 | const layerWorker = EffectWorker.layerWithOptions({ | |
| 1877 | 1885 | workerId: "cancellation-worker", |
| 1878 | 1886 | }).pipe( |
| 1879 | 1887 | Layer.provide( |
| 1880 | Layer.merge(Layer.succeed(EffectOutbox.EffectOutboxV2, outbox), executorLayer), | |
| 1888 | Layer.merge(Layer.succeed(EffectOutbox.EffectOutboxV2, outbox), layerExecutor), | |
| 1881 | 1889 | ), |
| 1882 | 1890 | ); |
| 1883 | 1891 | |
| ... | ... | @@ -1894,7 +1902,7 @@ it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => { |
| 1894 | 1902 | yield* outbox.signalCancellations(cancelledEffectIds); |
| 1895 | 1903 | assert.isTrue(yield* Fiber.join(workerFiber)); |
| 1896 | 1904 | yield* Deferred.await(interrupted); |
| 1897 | }).pipe(Effect.provide(workerLayer)); | |
| 1905 | }).pipe(Effect.provide(layerWorker)); | |
| 1898 | 1906 | |
| 1899 | 1907 | const cancelled = yield* outbox.get(effectId); |
| 1900 | 1908 | assert.isTrue(Option.isSome(cancelled)); |
| ... | ... | @@ -1923,7 +1931,7 @@ it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => { |
| 1923 | 1931 | completedAt: null, |
| 1924 | 1932 | lastError: null, |
| 1925 | 1933 | }; |
| 1926 | const outboxLayer = Layer.mock(EffectOutbox.EffectOutboxV2)({ | |
| 1934 | const layerOutbox = Layer.mock(EffectOutbox.EffectOutboxV2)({ | |
| 1927 | 1935 | claimNext: () => Effect.succeed(Option.some(claimedEffect)), |
| 1928 | 1936 | awaitCancellation: () => Effect.never, |
| 1929 | 1937 | clearCancellation: () => Effect.void, |
| ... | ... | @@ -1939,18 +1947,18 @@ it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => { |
| 1939 | 1947 | }), |
| 1940 | 1948 | ), |
| 1941 | 1949 | }); |
| 1942 | const executorLayer = Layer.succeed( | |
| 1950 | const layerExecutor = Layer.succeed( | |
| 1943 | 1951 | EffectWorker.OrchestrationEffectExecutorV2, |
| 1944 | 1952 | EffectWorker.OrchestrationEffectExecutorV2.of({ execute: () => Effect.void }), |
| 1945 | 1953 | ); |
| 1946 | const workerLayer = EffectWorker.layerWithOptions({ | |
| 1954 | const layerWorker = EffectWorker.layerWithOptions({ | |
| 1947 | 1955 | workerId: "settlement-race-worker", |
| 1948 | }).pipe(Layer.provide(Layer.merge(outboxLayer, executorLayer))); | |
| 1956 | }).pipe(Layer.provide(Layer.merge(layerOutbox, layerExecutor))); | |
| 1949 | 1957 | |
| 1950 | 1958 | assert.isTrue( |
| 1951 | 1959 | yield* EffectWorker.OrchestrationEffectWorkerV2.pipe( |
| 1952 | 1960 | Effect.flatMap((worker) => worker.runOnce), |
| 1953 | Effect.provide(workerLayer), | |
| 1961 | Effect.provide(layerWorker), | |
| 1954 | 1962 | ), |
| 1955 | 1963 | ); |
| 1956 | 1964 | }), |
| ... | ... | @@ -1978,7 +1986,7 @@ it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => { |
| 1978 | 1986 | lastError: null, |
| 1979 | 1987 | }; |
| 1980 | 1988 | const executionCount = yield* Ref.make(0); |
| 1981 | const outboxLayer = Layer.mock(EffectOutbox.EffectOutboxV2)({ | |
| 1989 | const layerOutbox = Layer.mock(EffectOutbox.EffectOutboxV2)({ | |
| 1982 | 1990 | claimNext: () => Effect.succeed(Option.some(claimedEffect)), |
| 1983 | 1991 | get: () => |
| 1984 | 1992 | Effect.succeed( |
| ... | ... | @@ -1993,20 +2001,20 @@ it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => { |
| 1993 | 2001 | clearCancellation: () => Effect.void, |
| 1994 | 2002 | awaitCancellation: () => Effect.never, |
| 1995 | 2003 | }); |
| 1996 | const executorLayer = Layer.succeed( | |
| 2004 | const layerExecutor = Layer.succeed( | |
| 1997 | 2005 | EffectWorker.OrchestrationEffectExecutorV2, |
| 1998 | 2006 | EffectWorker.OrchestrationEffectExecutorV2.of({ |
| 1999 | 2007 | execute: () => Ref.update(executionCount, (count) => count + 1), |
| 2000 | 2008 | }), |
| 2001 | 2009 | ); |
| 2002 | const workerLayer = EffectWorker.layerWithOptions({ | |
| 2010 | const layerWorker = EffectWorker.layerWithOptions({ | |
| 2003 | 2011 | workerId: "claim-cancellation-worker", |
| 2004 | }).pipe(Layer.provide(Layer.merge(outboxLayer, executorLayer))); | |
| 2012 | }).pipe(Layer.provide(Layer.merge(layerOutbox, layerExecutor))); | |
| 2005 | 2013 | |
| 2006 | 2014 | assert.isTrue( |
| 2007 | 2015 | yield* EffectWorker.OrchestrationEffectWorkerV2.pipe( |
| 2008 | 2016 | Effect.flatMap((worker) => worker.runOnce), |
| 2009 | Effect.provide(workerLayer), | |
| 2017 | Effect.provide(layerWorker), | |
| 2010 | 2018 | ), |
| 2011 | 2019 | ); |
| 2012 | 2020 | assert.equal(yield* Ref.get(executionCount), 0); |
| ... | ... | @@ -2275,7 +2283,7 @@ it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => { |
| 2275 | 2283 | ["failed-old:1", "pending:1", "running:1", "succeeded-recent:1"], |
| 2276 | 2284 | ); |
| 2277 | 2285 | assert.equal(yield* outbox.pruneSettled, 0); |
| 2278 | }).pipe(Effect.provide(isolatedOutboxLayer)), | |
| 2286 | }).pipe(Effect.provide(layerIsolatedOutbox)), | |
| 2279 | 2287 | ); |
| 2280 | 2288 | |
| 2281 | 2289 | it.effect("prunes settled effects hourly from the layer-owned worker", () => |
| ... | ... | @@ -2303,7 +2311,7 @@ it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => { |
| 2303 | 2311 | Duration.subtract(EffectOutbox.SETTLED_EFFECT_RETENTION, Duration.minutes(30)), |
| 2304 | 2312 | ); |
| 2305 | 2313 | yield* Layer.build( |
| 2306 | EffectOutbox.pruneWorkerLive.pipe( | |
| 2314 | EffectOutbox.layerPruneWorker.pipe( | |
| 2307 | 2315 | Layer.provide(Layer.succeed(EffectOutbox.EffectOutboxV2, observed)), |
| 2308 | 2316 | ), |
| 2309 | 2317 | ); |
| ... | ... | @@ -2313,7 +2321,7 @@ it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => { |
| 2313 | 2321 | yield* TestClock.adjust("1 hour"); |
| 2314 | 2322 | assert.equal(yield* Queue.take(runs), 1); |
| 2315 | 2323 | assert.deepEqual(yield* ids, ["effect:prune-worker:pending"]); |
| 2316 | }).pipe(Effect.scoped, Effect.provide(isolatedOutboxLayer)), | |
| 2324 | }).pipe(Effect.scoped, Effect.provide(layerIsolatedOutbox)), | |
| 2317 | 2325 | ); |
| 2318 | 2326 | |
| 2319 | 2327 | it.effect("does not emit a SQL span for an empty safety claim", () => |
| ... | ... | @@ -2338,7 +2346,7 @@ it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => { |
| 2338 | 2346 | |
| 2339 | 2347 | assert.isTrue(Option.isNone(claim)); |
| 2340 | 2348 | assert.notInclude(spans, "sql.execute"); |
| 2341 | }).pipe(Effect.provide(Layer.fresh(effectOutboxProvided))), | |
| 2349 | }).pipe(Effect.provide(Layer.fresh(layerEffectOutboxProvided))), | |
| 2342 | 2350 | ); |
| 2343 | 2351 | |
| 2344 | 2352 | it.effect("wakes claimers when cancellation unblocks same-thread work", () => |
| ... | ... | @@ -2395,7 +2403,7 @@ it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => { |
| 2395 | 2403 | workerId: "cancellation-wakeup-worker", |
| 2396 | 2404 | }); |
| 2397 | 2405 | } |
| 2398 | }).pipe(Effect.provide(Layer.fresh(effectOutboxProvided))), | |
| 2406 | }).pipe(Effect.provide(Layer.fresh(layerEffectOutboxProvided))), | |
| 2399 | 2407 | ); |
| 2400 | 2408 | |
| 2401 | 2409 | it.effect("keeps later thread effects behind an earlier effect waiting to retry", () => |
| ... | ... | @@ -2465,7 +2473,7 @@ it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => { |
| 2465 | 2473 | assert.equal(Option.getOrUndefined(unblocked)?.id, "effect:foundation-retry-order:a-start"); |
| 2466 | 2474 | yield* outbox.succeed({ effectId: "effect:foundation-retry-order:a-start", workerId }); |
| 2467 | 2475 | yield* outbox.succeed({ effectId: "effect:foundation-retry-order:b-title", workerId }); |
| 2468 | }).pipe(Effect.provide(Layer.fresh(effectOutboxProvided))), | |
| 2476 | }).pipe(Effect.provide(Layer.fresh(layerEffectOutboxProvided))), | |
| 2469 | 2477 | ); |
| 2470 | 2478 | |
| 2471 | 2479 | it.effect("executes a retry at its durable deadline instead of the liveness interval", () => |
| ... | ... | @@ -2476,7 +2484,7 @@ it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => { |
| 2476 | 2484 | const threadId = ThreadId.make("thread:foundation-durable-retry-deadline"); |
| 2477 | 2485 | const executions = yield* Ref.make(0); |
| 2478 | 2486 | const completed = yield* Deferred.make<void>(); |
| 2479 | const executorLayer = Layer.succeed( | |
| 2487 | const layerExecutor = Layer.succeed( | |
| 2480 | 2488 | EffectWorker.OrchestrationEffectExecutorV2, |
| 2481 | 2489 | EffectWorker.OrchestrationEffectExecutorV2.of({ |
| 2482 | 2490 | execute: () => |
| ... | ... | @@ -2493,11 +2501,11 @@ it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => { |
| 2493 | 2501 | }), |
| 2494 | 2502 | }), |
| 2495 | 2503 | ); |
| 2496 | const workerLayer = EffectWorker.layerWithOptions({ | |
| 2504 | const layerWorker = EffectWorker.layerWithOptions({ | |
| 2497 | 2505 | workerId: "durable-retry-deadline-worker", |
| 2498 | 2506 | }).pipe( |
| 2499 | 2507 | Layer.provide( |
| 2500 | Layer.merge(Layer.succeed(EffectOutbox.EffectOutboxV2, outbox), executorLayer), | |
| 2508 | Layer.merge(Layer.succeed(EffectOutbox.EffectOutboxV2, outbox), layerExecutor), | |
| 2501 | 2509 | ), |
| 2502 | 2510 | ); |
| 2503 | 2511 | |
| ... | ... | @@ -2531,7 +2539,7 @@ it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => { |
| 2531 | 2539 | yield* TestClock.adjust("1 millis"); |
| 2532 | 2540 | yield* Deferred.await(completed); |
| 2533 | 2541 | assert.equal(yield* Ref.get(executions), 2); |
| 2534 | }).pipe(Effect.provide(workerLayer), Effect.scoped); | |
| 2542 | }).pipe(Effect.provide(layerWorker), Effect.scoped); | |
| 2535 | 2543 | }).pipe(Effect.provide(TestClock.layer())), |
| 2536 | 2544 | ); |
| 2537 | 2545 | |
| ... | ... | @@ -2555,7 +2563,7 @@ it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => { |
| 2555 | 2563 | [effectA2, { started: startedA2, release: releaseA2 }], |
| 2556 | 2564 | [effectB1, { started: startedB1, release: releaseB1 }], |
| 2557 | 2565 | ]); |
| 2558 | const executorLayer = Layer.succeed( | |
| 2566 | const layerExecutor = Layer.succeed( | |
| 2559 | 2567 | EffectWorker.OrchestrationEffectExecutorV2, |
| 2560 | 2568 | EffectWorker.OrchestrationEffectExecutorV2.of({ |
| 2561 | 2569 | execute: (effect) => { |
| ... | ... | @@ -2567,11 +2575,11 @@ it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => { |
| 2567 | 2575 | }, |
| 2568 | 2576 | }), |
| 2569 | 2577 | ); |
| 2570 | const workerLayer = EffectWorker.layerWithOptions({ | |
| 2578 | const layerWorker = EffectWorker.layerWithOptions({ | |
| 2571 | 2579 | workerId: "concurrency-worker", |
| 2572 | 2580 | }).pipe( |
| 2573 | 2581 | Layer.provide( |
| 2574 | Layer.merge(Layer.succeed(EffectOutbox.EffectOutboxV2, outbox), executorLayer), | |
| 2582 | Layer.merge(Layer.succeed(EffectOutbox.EffectOutboxV2, outbox), layerExecutor), | |
| 2575 | 2583 | ), |
| 2576 | 2584 | ); |
| 2577 | 2585 | |
| ... | ... | @@ -2625,7 +2633,7 @@ it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => { |
| 2625 | 2633 | ); |
| 2626 | 2634 | if (!settled) yield* Effect.yieldNow; |
| 2627 | 2635 | } |
| 2628 | }).pipe(Effect.provide(workerLayer), Effect.scoped); | |
| 2636 | }).pipe(Effect.provide(layerWorker), Effect.scoped); | |
| 2629 | 2637 | }), |
| 2630 | 2638 | ); |
| 2631 | 2639 | |
| ... | ... | @@ -2655,7 +2663,7 @@ it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => { |
| 2655 | 2663 | }, |
| 2656 | 2664 | ]); |
| 2657 | 2665 | |
| 2658 | const executorLayer = Layer.succeed( | |
| 2666 | const layerExecutor = Layer.succeed( | |
| 2659 | 2667 | EffectWorker.OrchestrationEffectExecutorV2, |
| 2660 | 2668 | EffectWorker.OrchestrationEffectExecutorV2.of({ |
| 2661 | 2669 | execute: (effect) => |
| ... | ... | @@ -2670,12 +2678,12 @@ it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => { |
| 2670 | 2678 | ), |
| 2671 | 2679 | }), |
| 2672 | 2680 | ); |
| 2673 | const workerLayer = EffectWorker.layerWithOptions({ | |
| 2681 | const layerWorker = EffectWorker.layerWithOptions({ | |
| 2674 | 2682 | workerId: "no-live-reclaim-worker", |
| 2675 | 2683 | leaseDurationMs: 1, |
| 2676 | 2684 | }).pipe( |
| 2677 | 2685 | Layer.provide( |
| 2678 | Layer.merge(Layer.succeed(EffectOutbox.EffectOutboxV2, outbox), executorLayer), | |
| 2686 | Layer.merge(Layer.succeed(EffectOutbox.EffectOutboxV2, outbox), layerExecutor), | |
| 2679 | 2687 | ), |
| 2680 | 2688 | ); |
| 2681 | 2689 | |
| ... | ... | @@ -2696,7 +2704,7 @@ it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => { |
| 2696 | 2704 | assert.isTrue(yield* Fiber.join(firstFiber)); |
| 2697 | 2705 | assert.isTrue(yield* worker.runOnce); |
| 2698 | 2706 | assert.deepEqual(yield* Ref.get(executions), [firstEffectId, secondEffectId]); |
| 2699 | }).pipe(Effect.provide(workerLayer)); | |
| 2707 | }).pipe(Effect.provide(layerWorker)); | |
| 2700 | 2708 | }), |
| 2701 | 2709 | ); |
| 2702 | 2710 | |
| ... | ... | @@ -3348,7 +3356,7 @@ it.live("keeps claiming new work after repeated idle periods", () => |
| 3348 | 3356 | Effect.gen(function* () { |
| 3349 | 3357 | const outbox = yield* EffectOutbox.EffectOutboxV2; |
| 3350 | 3358 | const completed = new Map<string, Deferred.Deferred<void>>(); |
| 3351 | const executorLayer = Layer.succeed( | |
| 3359 | const layerExecutor = Layer.succeed( | |
| 3352 | 3360 | EffectWorker.OrchestrationEffectExecutorV2, |
| 3353 | 3361 | EffectWorker.OrchestrationEffectExecutorV2.of({ |
| 3354 | 3362 | execute: (effect) => { |
| ... | ... | @@ -3359,10 +3367,10 @@ it.live("keeps claiming new work after repeated idle periods", () => |
| 3359 | 3367 | }, |
| 3360 | 3368 | }), |
| 3361 | 3369 | ); |
| 3362 | const workerLayer = EffectWorker.layerWithOptions({ | |
| 3370 | const layerWorker = EffectWorker.layerWithOptions({ | |
| 3363 | 3371 | workerId: "idle-wave-worker", |
| 3364 | 3372 | }).pipe( |
| 3365 | Layer.provide(Layer.merge(Layer.succeed(EffectOutbox.EffectOutboxV2, outbox), executorLayer)), | |
| 3373 | Layer.provide(Layer.merge(Layer.succeed(EffectOutbox.EffectOutboxV2, outbox), layerExecutor)), | |
| 3366 | 3374 | ); |
| 3367 | 3375 | |
| 3368 | 3376 | yield* Effect.gen(function* () { |
| ... | ... | @@ -3384,8 +3392,8 @@ it.live("keeps claiming new work after repeated idle periods", () => |
| 3384 | 3392 | const observed = yield* Deferred.await(completion).pipe(Effect.timeoutOption("2 seconds")); |
| 3385 | 3393 | assert.isTrue(Option.isSome(observed), `worker stopped before idle wave ${wave}`); |
| 3386 | 3394 | } |
| 3387 | }).pipe(Effect.provide(workerLayer), Effect.scoped); | |
| 3388 | }).pipe(Effect.provide(TestLayer)), | |
| 3395 | }).pipe(Effect.provide(layerWorker), Effect.scoped); | |
| 3396 | }).pipe(Effect.provide(layerTest)), | |
| 3389 | 3397 | ); |
| 3390 | 3398 | |
| 3391 | 3399 | it.effect("publishes live events in commit order across concurrent writers", () => |
| ... | ... | @@ -3394,7 +3402,7 @@ it.effect("publishes live events in commit order across concurrent writers", () |
| 3394 | 3402 | const releaseFirst = yield* Deferred.make<void>(); |
| 3395 | 3403 | // The first writer's post-commit wakeup stands in for any scheduler yield |
| 3396 | 3404 | // between its commit and its publish. |
| 3397 | const pausingOutbox = Layer.effect( | |
| 3405 | const layerPausingOutbox = Layer.effect( | |
| 3398 | 3406 | EffectOutbox.EffectOutboxV2, |
| 3399 | 3407 | Effect.gen(function* () { |
| 3400 | 3408 | const delegate = yield* EffectOutbox.EffectOutboxV2; |
| ... | ... | @@ -3407,15 +3415,15 @@ it.effect("publishes live events in commit order across concurrent writers", () |
| 3407 | 3415 | ), |
| 3408 | 3416 | }); |
| 3409 | 3417 | }), |
| 3410 | ).pipe(Layer.provide(effectOutboxProvided)); | |
| 3411 | const eventSinkLayer = EventSink.layerFromStores.pipe( | |
| 3418 | ).pipe(Layer.provide(layerEffectOutboxProvided)); | |
| 3419 | const layerEventSink = EventSink.layerFromStores.pipe( | |
| 3412 | 3420 | Layer.provide( |
| 3413 | 3421 | Layer.mergeAll( |
| 3414 | storesProvided, | |
| 3415 | pausingOutbox, | |
| 3416 | commandReceiptStoreProvided, | |
| 3417 | ProjectStore.layer.pipe(Layer.provide(databaseLayer)), | |
| 3418 | TurnItemPositionStore.layer.pipe(Layer.provide(databaseLayer)), | |
| 3422 | layerStoresProvided, | |
| 3423 | layerPausingOutbox, | |
| 3424 | layerCommandReceiptStoreProvided, | |
| 3425 | ProjectStore.layer.pipe(Layer.provide(layerDatabase)), | |
| 3426 | TurnItemPositionStore.layer.pipe(Layer.provide(layerDatabase)), | |
| 3419 | 3427 | ), |
| 3420 | 3428 | ), |
| 3421 | 3429 | ); |
| ... | ... | @@ -3471,6 +3479,6 @@ it.effect("publishes live events in commit order across concurrent writers", () |
| 3471 | 3479 | sequences, |
| 3472 | 3480 | [...sequences].sort((left, right) => left - right), |
| 3473 | 3481 | ); |
| 3474 | }).pipe(Effect.provide(eventSinkLayer)); | |
| 3475 | }).pipe(Effect.provide(databaseLayer)), | |
| 3482 | }).pipe(Effect.provide(layerEventSink)); | |
| 3483 | }).pipe(Effect.provide(layerDatabase)), | |
| 3476 | 3484 | ); |
apps/server/src/orchestration-v2/GrokOrchestratorV2.live.test.ts+32-32| ... | ... | @@ -21,12 +21,12 @@ import * as CheckpointStore from "../checkpointing/CheckpointStore.ts"; |
| 21 | 21 | import * as BackgroundPolicy from "../background/BackgroundPolicy.ts"; |
| 22 | 22 | import * as HostPowerMonitor from "../background/HostPowerMonitor.ts"; |
| 23 | 23 | import * as ServerConfig from "../config.ts"; |
| 24 | import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts"; | |
| 24 | import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts"; | |
| 25 | 25 | import * as AntigravityInstallation from "../provider/AntigravityInstallation.ts"; |
| 26 | 26 | import * as CodexInstallation from "../provider/CodexInstallation.ts"; |
| 27 | 27 | import * as ServerEnvironment from "../environment/ServerEnvironment.ts"; |
| 28 | 28 | import * as ModelManifest from "../provider/ModelManifest.ts"; |
| 29 | import { ProviderInstanceRegistryHydrationLive } from "../provider/Layers/ProviderInstanceRegistryHydration.ts"; | |
| 29 | import * as ProviderInstanceRegistryHydration from "../provider/Layers/ProviderInstanceRegistryHydration.ts"; | |
| 30 | 30 | import * as ProviderEventLoggers from "../provider/Layers/ProviderEventLoggers.ts"; |
| 31 | 31 | import * as OpenCodeRuntime from "../provider/opencodeRuntime.ts"; |
| 32 | 32 | import * as OpenCodeServerLedger from "../provider/OpenCodeServerLedger.ts"; |
| ... | ... | @@ -34,53 +34,53 @@ import * as ServerSettings from "../serverSettings.ts"; |
| 34 | 34 | import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts"; |
| 35 | 35 | import * as VcsProcess from "../vcs/VcsProcess.ts"; |
| 36 | 36 | import * as Orchestrator from "./Orchestrator.ts"; |
| 37 | import { worktreeRepairDependenciesTestLayer } from "./ProviderTurnStartService.testkit.ts"; | |
| 38 | import { OrchestrationV2LayerLive } from "./runtimeLayer.ts"; | |
| 37 | import * as ProviderTurnStartServiceTestkit from "./ProviderTurnStartService.testkit.ts"; | |
| 38 | import * as RuntimeLayer from "./runtimeLayer.ts"; | |
| 39 | 39 | import * as McpSessionRegistryTestkit from "../mcp/McpSessionRegistry.testkit.ts"; |
| 40 | 40 | import { GROK_MODEL_SELECTION } from "./testkit/fixtures/shared.ts"; |
| 41 | 41 | |
| 42 | const PlatformTestLayer = Layer.merge( | |
| 42 | const layerPlatformTest = Layer.merge( | |
| 43 | 43 | NodeServices.layer, |
| 44 | 44 | Layer.mock(SourceControlProviderRegistry.SourceControlProviderRegistry)({ |
| 45 | 45 | resolveLink: () => Effect.die("unused title link"), |
| 46 | 46 | }), |
| 47 | 47 | ); |
| 48 | 48 | |
| 49 | const serverConfigLayer = ServerConfig.layerTest(process.cwd(), { | |
| 49 | const layerServerConfig = ServerConfig.layerTest(process.cwd(), { | |
| 50 | 50 | prefix: "t3-grok-v2-live-", |
| 51 | 51 | }); |
| 52 | 52 | |
| 53 | const vcsDriverRegistryLayer = VcsDriverRegistry.layer.pipe( | |
| 53 | const layerVcsDriverRegistry = VcsDriverRegistry.layer.pipe( | |
| 54 | 54 | Layer.provide(VcsProcess.layer), |
| 55 | Layer.provide(serverConfigLayer), | |
| 56 | Layer.provide(PlatformTestLayer), | |
| 55 | Layer.provide(layerServerConfig), | |
| 56 | Layer.provide(layerPlatformTest), | |
| 57 | 57 | ); |
| 58 | 58 | |
| 59 | const checkpointStoreLayer = CheckpointStore.layer.pipe(Layer.provide(vcsDriverRegistryLayer)); | |
| 59 | const layerCheckpointStore = CheckpointStore.layer.pipe(Layer.provide(layerVcsDriverRegistry)); | |
| 60 | 60 | |
| 61 | const serverSettingsLayer = ServerSettings.layerTest({ | |
| 61 | const layerServerSettings = ServerSettings.layerTest({ | |
| 62 | 62 | providers: { |
| 63 | 63 | grok: { enabled: true }, |
| 64 | 64 | }, |
| 65 | 65 | }); |
| 66 | const backgroundPolicyLayer = BackgroundPolicy.layer.pipe( | |
| 66 | const layerBackgroundPolicy = BackgroundPolicy.layer.pipe( | |
| 67 | 67 | Layer.provide(Layer.effect(HostPowerMonitor.HostPowerMonitor, HostPowerMonitor.make())), |
| 68 | Layer.provide(serverSettingsLayer), | |
| 68 | Layer.provide(layerServerSettings), | |
| 69 | 69 | ); |
| 70 | const providerInstanceRegistryLayer = ProviderInstanceRegistryHydrationLive.pipe( | |
| 70 | const layerProviderInstanceRegistry = ProviderInstanceRegistryHydration.layer.pipe( | |
| 71 | 71 | Layer.provide( |
| 72 | 72 | Layer.mergeAll( |
| 73 | serverConfigLayer.pipe(Layer.provide(PlatformTestLayer)), | |
| 74 | serverSettingsLayer, | |
| 73 | layerServerConfig.pipe(Layer.provide(layerPlatformTest)), | |
| 74 | layerServerSettings, | |
| 75 | 75 | ServerSecretStore.layer.pipe( |
| 76 | Layer.provide(serverConfigLayer), | |
| 77 | Layer.provide(PlatformTestLayer), | |
| 76 | Layer.provide(layerServerConfig), | |
| 77 | Layer.provide(layerPlatformTest), | |
| 78 | 78 | ), |
| 79 | 79 | NodeServices.layer, |
| 80 | 80 | FetchHttpClient.layer, |
| 81 | OpenCodeRuntime.OpenCodeRuntimeLive.pipe( | |
| 81 | OpenCodeRuntime.layer.pipe( | |
| 82 | 82 | Layer.provide(OpenCodeServerLedger.layerTest), |
| 83 | Layer.provide(PlatformTestLayer), | |
| 83 | Layer.provide(layerPlatformTest), | |
| 84 | 84 | ), |
| 85 | 85 | Layer.succeed( |
| 86 | 86 | ProviderEventLoggers.ProviderEventLoggers, |
| ... | ... | @@ -88,9 +88,9 @@ const providerInstanceRegistryLayer = ProviderInstanceRegistryHydrationLive.pipe |
| 88 | 88 | ), |
| 89 | 89 | ModelManifest.layerTest, |
| 90 | 90 | AntigravityInstallation.AntigravityInstallation.layer.pipe( |
| 91 | Layer.provide(serverConfigLayer.pipe(Layer.provide(PlatformTestLayer))), | |
| 91 | Layer.provide(layerServerConfig.pipe(Layer.provide(layerPlatformTest))), | |
| 92 | 92 | Layer.provide(FetchHttpClient.layer), |
| 93 | Layer.provide(PlatformTestLayer), | |
| 93 | Layer.provide(layerPlatformTest), | |
| 94 | 94 | ), |
| 95 | 95 | // The Codex driver now resolves managed ChatGPT installs; these runs never launch Codex. |
| 96 | 96 | Layer.mock(CodexInstallation.CodexInstallation)({ |
| ... | ... | @@ -105,17 +105,17 @@ const providerInstanceRegistryLayer = ProviderInstanceRegistryHydrationLive.pipe |
| 105 | 105 | ), |
| 106 | 106 | ); |
| 107 | 107 | |
| 108 | const liveLayer = OrchestrationV2LayerLive.pipe( | |
| 109 | Layer.provide(worktreeRepairDependenciesTestLayer), | |
| 108 | const layerLive = RuntimeLayer.layer.pipe( | |
| 109 | Layer.provide(ProviderTurnStartServiceTestkit.layer), | |
| 110 | 110 | Layer.provide(McpSessionRegistryTestkit.layer), |
| 111 | Layer.provide(SqlitePersistenceMemory), | |
| 112 | Layer.provide(checkpointStoreLayer), | |
| 113 | Layer.provide(serverConfigLayer), | |
| 114 | Layer.provide(serverSettingsLayer), | |
| 115 | Layer.provide(providerInstanceRegistryLayer), | |
| 111 | Layer.provide(SqlitePersistence.layerMemory), | |
| 112 | Layer.provide(layerCheckpointStore), | |
| 113 | Layer.provide(layerServerConfig), | |
| 114 | Layer.provide(layerServerSettings), | |
| 115 | Layer.provide(layerProviderInstanceRegistry), | |
| 116 | 116 | Layer.provide(ResetCreditCoordinator.layer), |
| 117 | Layer.provide(backgroundPolicyLayer), | |
| 118 | Layer.provide(PlatformTestLayer), | |
| 117 | Layer.provide(layerBackgroundPolicy), | |
| 118 | Layer.provide(layerPlatformTest), | |
| 119 | 119 | ); |
| 120 | 120 | |
| 121 | 121 | const waitForIdle = Effect.fn("GrokOrchestratorV2Live.waitForIdle")(function* (threadId: ThreadId) { |
| ... | ... | @@ -230,7 +230,7 @@ describe.runIf(process.env.T3_GROK_LIVE_ORCHESTRATOR === "1")("Grok V2 live orch |
| 230 | 230 | ); |
| 231 | 231 | assert.include(targetProjection.contextHandoffs[0]?.summaryText ?? "", marker); |
| 232 | 232 | assert.include(assistantText(targetProjection), marker); |
| 233 | }).pipe(Effect.provide(liveLayer), Effect.scoped), | |
| 233 | }).pipe(Effect.provide(layerLive), Effect.scoped), | |
| 234 | 234 | 360_000, |
| 235 | 235 | ); |
| 236 | 236 | }); |
apps/server/src/orchestration-v2/OpenCode2OrchestratorV2.live.test.ts+44-44| ... | ... | @@ -48,12 +48,12 @@ import * as CheckpointStore from "../checkpointing/CheckpointStore.ts"; |
| 48 | 48 | import * as BackgroundPolicy from "../background/BackgroundPolicy.ts"; |
| 49 | 49 | import * as HostPowerMonitor from "../background/HostPowerMonitor.ts"; |
| 50 | 50 | import * as ServerConfig from "../config.ts"; |
| 51 | import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts"; | |
| 51 | import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts"; | |
| 52 | 52 | import * as AntigravityInstallation from "../provider/AntigravityInstallation.ts"; |
| 53 | 53 | import * as CodexInstallation from "../provider/CodexInstallation.ts"; |
| 54 | 54 | import * as ServerEnvironment from "../environment/ServerEnvironment.ts"; |
| 55 | 55 | import * as ModelManifest from "../provider/ModelManifest.ts"; |
| 56 | import { ProviderInstanceRegistryHydrationLive } from "../provider/Layers/ProviderInstanceRegistryHydration.ts"; | |
| 56 | import * as ProviderInstanceRegistryHydration from "../provider/Layers/ProviderInstanceRegistryHydration.ts"; | |
| 57 | 57 | import * as ProviderEventLoggers from "../provider/Layers/ProviderEventLoggers.ts"; |
| 58 | 58 | import * as OpenCode2Client from "../provider/opencode2/OpenCode2Client.ts"; |
| 59 | 59 | import * as OpenCodeRuntime from "../provider/opencodeRuntime.ts"; |
| ... | ... | @@ -64,8 +64,8 @@ import * as VcsProcess from "../vcs/VcsProcess.ts"; |
| 64 | 64 | import * as EffectWorker from "./EffectWorker.ts"; |
| 65 | 65 | import * as Orchestrator from "./Orchestrator.ts"; |
| 66 | 66 | import * as ProviderInstanceRegistry from "../provider/Services/ProviderInstanceRegistry.ts"; |
| 67 | import { worktreeRepairDependenciesTestLayer } from "./ProviderTurnStartService.testkit.ts"; | |
| 68 | import { OrchestrationV2LayerLive } from "./runtimeLayer.ts"; | |
| 67 | import * as ProviderTurnStartServiceTestkit from "./ProviderTurnStartService.testkit.ts"; | |
| 68 | import * as RuntimeLayer from "./runtimeLayer.ts"; | |
| 69 | 69 | import * as IdAllocator from "./IdAllocator.ts"; |
| 70 | 70 | import * as ProviderContinuationRequests from "./ProviderContinuationRequests.ts"; |
| 71 | 71 | import * as ProviderContinuationService from "./ProviderContinuationService.ts"; |
| ... | ... | @@ -84,7 +84,7 @@ const SWITCHED_MODEL = "opencode/mimo-v2.6-flash-free"; |
| 84 | 84 | |
| 85 | 85 | /** The OpenCode servers the driver spawned, newest last, so a test can kill one by its own PID. */ |
| 86 | 86 | const spawnedPids: Array<number> = []; |
| 87 | const spawnedServers = Layer.succeed( | |
| 87 | const layerSpawnedServers = Layer.succeed( | |
| 88 | 88 | OpenCodeServerLedger.OpenCodeServerLedger, |
| 89 | 89 | OpenCodeServerLedger.OpenCodeServerLedger.of({ |
| 90 | 90 | track: ({ pid }) => |
| ... | ... | @@ -100,7 +100,7 @@ const spawnedServers = Layer.succeed( |
| 100 | 100 | * MCP server the run can see called; without it they point nowhere, as in replay. |
| 101 | 101 | */ |
| 102 | 102 | const MCP_URL = process.env.OPENCODE2_MCP_URL ?? "http://127.0.0.1/mcp"; |
| 103 | const mcpRegistryLayer = Layer.succeed( | |
| 103 | const layerMcpRegistry = Layer.succeed( | |
| 104 | 104 | McpSessionRegistry.McpSessionRegistry, |
| 105 | 105 | McpSessionRegistry.McpSessionRegistry.of({ |
| 106 | 106 | issue: ({ threadId, providerInstanceId }) => |
| ... | ... | @@ -123,20 +123,20 @@ const mcpRegistryLayer = Layer.succeed( |
| 123 | 123 | }), |
| 124 | 124 | ); |
| 125 | 125 | |
| 126 | const PlatformTestLayer = Layer.merge( | |
| 126 | const layerPlatformTest = Layer.merge( | |
| 127 | 127 | NodeServices.layer, |
| 128 | 128 | Layer.mock(SourceControlProviderRegistry.SourceControlProviderRegistry)({ |
| 129 | 129 | resolveLink: () => Effect.die("unused title link"), |
| 130 | 130 | }), |
| 131 | 131 | ); |
| 132 | const serverConfigLayer = ServerConfig.layerTest(`${ROOT}/work`, { prefix: "t3-opencode2-live-" }); | |
| 133 | const vcsDriverRegistryLayer = VcsDriverRegistry.layer.pipe( | |
| 132 | const layerServerConfig = ServerConfig.layerTest(`${ROOT}/work`, { prefix: "t3-opencode2-live-" }); | |
| 133 | const layerVcsDriverRegistry = VcsDriverRegistry.layer.pipe( | |
| 134 | 134 | Layer.provide(VcsProcess.layer), |
| 135 | Layer.provide(serverConfigLayer), | |
| 136 | Layer.provide(PlatformTestLayer), | |
| 135 | Layer.provide(layerServerConfig), | |
| 136 | Layer.provide(layerPlatformTest), | |
| 137 | 137 | ); |
| 138 | 138 | // Isolated OpenCode state: the server never touches the developer's own data. |
| 139 | const serverSettingsLayer = ServerSettings.layerTest({ | |
| 139 | const layerServerSettings = ServerSettings.layerTest({ | |
| 140 | 140 | providerInstances: { |
| 141 | 141 | [INSTANCE]: { |
| 142 | 142 | driver: ProviderDriverKind.make("opencode"), |
| ... | ... | @@ -162,24 +162,24 @@ const serverSettingsLayer = ServerSettings.layerTest({ |
| 162 | 162 | }, |
| 163 | 163 | }, |
| 164 | 164 | }); |
| 165 | const backgroundPolicyLayer = BackgroundPolicy.layer.pipe( | |
| 165 | const layerBackgroundPolicy = BackgroundPolicy.layer.pipe( | |
| 166 | 166 | Layer.provide(Layer.effect(HostPowerMonitor.HostPowerMonitor, HostPowerMonitor.make())), |
| 167 | Layer.provide(serverSettingsLayer), | |
| 167 | Layer.provide(layerServerSettings), | |
| 168 | 168 | ); |
| 169 | const providerInstanceRegistryLayer = ProviderInstanceRegistryHydrationLive.pipe( | |
| 169 | const layerProviderInstanceRegistry = ProviderInstanceRegistryHydration.layer.pipe( | |
| 170 | 170 | Layer.provide( |
| 171 | 171 | Layer.mergeAll( |
| 172 | serverConfigLayer.pipe(Layer.provide(PlatformTestLayer)), | |
| 173 | serverSettingsLayer, | |
| 172 | layerServerConfig.pipe(Layer.provide(layerPlatformTest)), | |
| 173 | layerServerSettings, | |
| 174 | 174 | ServerSecretStore.layer.pipe( |
| 175 | Layer.provide(serverConfigLayer), | |
| 176 | Layer.provide(PlatformTestLayer), | |
| 175 | Layer.provide(layerServerConfig), | |
| 176 | Layer.provide(layerPlatformTest), | |
| 177 | 177 | ), |
| 178 | 178 | NodeServices.layer, |
| 179 | 179 | FetchHttpClient.layer, |
| 180 | OpenCodeRuntime.OpenCodeRuntimeLive.pipe( | |
| 181 | Layer.provide(spawnedServers), | |
| 182 | Layer.provide(PlatformTestLayer), | |
| 180 | OpenCodeRuntime.layer.pipe( | |
| 181 | Layer.provide(layerSpawnedServers), | |
| 182 | Layer.provide(layerPlatformTest), | |
| 183 | 183 | ), |
| 184 | 184 | Layer.succeed( |
| 185 | 185 | ProviderEventLoggers.ProviderEventLoggers, |
| ... | ... | @@ -187,9 +187,9 @@ const providerInstanceRegistryLayer = ProviderInstanceRegistryHydrationLive.pipe |
| 187 | 187 | ), |
| 188 | 188 | ModelManifest.layerTest, |
| 189 | 189 | AntigravityInstallation.AntigravityInstallation.layer.pipe( |
| 190 | Layer.provide(serverConfigLayer.pipe(Layer.provide(PlatformTestLayer))), | |
| 190 | Layer.provide(layerServerConfig.pipe(Layer.provide(layerPlatformTest))), | |
| 191 | 191 | Layer.provide(FetchHttpClient.layer), |
| 192 | Layer.provide(PlatformTestLayer), | |
| 192 | Layer.provide(layerPlatformTest), | |
| 193 | 193 | ), |
| 194 | 194 | // The Codex driver now resolves managed ChatGPT installs; these runs never launch Codex. |
| 195 | 195 | Layer.mock(CodexInstallation.CodexInstallation)({ |
| ... | ... | @@ -203,22 +203,22 @@ const providerInstanceRegistryLayer = ProviderInstanceRegistryHydrationLive.pipe |
| 203 | 203 | ), |
| 204 | 204 | ), |
| 205 | 205 | ); |
| 206 | const orchestrationLayer = OrchestrationV2LayerLive.pipe( | |
| 207 | Layer.provide(worktreeRepairDependenciesTestLayer), | |
| 208 | Layer.provide(mcpRegistryLayer), | |
| 209 | Layer.provide(SqlitePersistenceMemory), | |
| 210 | Layer.provide(CheckpointStore.layer.pipe(Layer.provide(vcsDriverRegistryLayer))), | |
| 211 | Layer.provide(serverConfigLayer), | |
| 212 | Layer.provide(serverSettingsLayer), | |
| 206 | const layerOrchestration = RuntimeLayer.layer.pipe( | |
| 207 | Layer.provide(ProviderTurnStartServiceTestkit.layer), | |
| 208 | Layer.provide(layerMcpRegistry), | |
| 209 | Layer.provide(SqlitePersistence.layerMemory), | |
| 210 | Layer.provide(CheckpointStore.layer.pipe(Layer.provide(layerVcsDriverRegistry))), | |
| 211 | Layer.provide(layerServerConfig), | |
| 212 | Layer.provide(layerServerSettings), | |
| 213 | 213 | // Merged, not only provided: the test reads the same instance the orchestrator uses. |
| 214 | Layer.provideMerge(providerInstanceRegistryLayer), | |
| 214 | Layer.provideMerge(layerProviderInstanceRegistry), | |
| 215 | 215 | Layer.provide(ResetCreditCoordinator.layer), |
| 216 | Layer.provide(backgroundPolicyLayer), | |
| 217 | Layer.provide(PlatformTestLayer), | |
| 216 | Layer.provide(layerBackgroundPolicy), | |
| 217 | Layer.provide(layerPlatformTest), | |
| 218 | 218 | ); |
| 219 | 219 | |
| 220 | 220 | // Starts the continuation run a provider wake asks for, as the production layer does. |
| 221 | const continuationWorkerLayer = ProviderContinuationService.workerLive.pipe( | |
| 221 | const layerContinuationWorker = ProviderContinuationService.layer.pipe( | |
| 222 | 222 | Layer.provide( |
| 223 | 223 | Layer.unwrap( |
| 224 | 224 | Effect.gen(function* () { |
| ... | ... | @@ -234,7 +234,7 @@ const continuationWorkerLayer = ProviderContinuationService.workerLive.pipe( |
| 234 | 234 | Layer.provide(IdAllocator.layer), |
| 235 | 235 | Layer.provide(ProviderContinuationRequests.layer), |
| 236 | 236 | ); |
| 237 | const liveLayer = continuationWorkerLayer.pipe(Layer.provideMerge(orchestrationLayer)); | |
| 237 | const layerLive = layerContinuationWorker.pipe(Layer.provideMerge(layerOrchestration)); | |
| 238 | 238 | |
| 239 | 239 | // How long one step may take, in seconds. A free model sometimes takes minutes |
| 240 | 240 | // before its first tool call; `OPENCODE2_STEP_WAIT` raises it for such runs. |
| ... | ... | @@ -432,7 +432,7 @@ describe.runIf(binaryPath !== undefined && ROOT !== "")("OpenCode 2 live orchest |
| 432 | 432 | const models = yield* assistantModels(sessionId!); |
| 433 | 433 | assert.equal(models.at(-1), SWITCHED_MODEL); |
| 434 | 434 | assert.notEqual(models[0], SWITCHED_MODEL); |
| 435 | }).pipe(Effect.provide(Layer.merge(liveLayer, NodeServices.layer)), Effect.scoped), | |
| 435 | }).pipe(Effect.provide(Layer.merge(layerLive, NodeServices.layer)), Effect.scoped), | |
| 436 | 436 | 360_000, |
| 437 | 437 | ); |
| 438 | 438 | |
| ... | ... | @@ -542,7 +542,7 @@ describe.runIf(binaryPath !== undefined && ROOT !== "")("OpenCode 2 live orchest |
| 542 | 542 | assert.equal(answered.runs.at(-1)?.status, "completed"); |
| 543 | 543 | const reply = answered.messages.findLast((message) => message.role === "assistant"); |
| 544 | 544 | assert.match(reply?.text ?? "", /blue/i); |
| 545 | }).pipe(Effect.provide(Layer.merge(liveLayer, NodeServices.layer)), Effect.scoped), | |
| 545 | }).pipe(Effect.provide(Layer.merge(layerLive, NodeServices.layer)), Effect.scoped), | |
| 546 | 546 | 600_000, |
| 547 | 547 | ); |
| 548 | 548 | |
| ... | ... | @@ -582,7 +582,7 @@ describe.runIf(binaryPath !== undefined && ROOT !== "")("OpenCode 2 live orchest |
| 582 | 582 | assert.equal(planned.runs.at(-1)?.status, "completed"); |
| 583 | 583 | assert.isFalse(yield* fs.exists(path.join(ROOT, "work", "plan_write_probe.txt"))); |
| 584 | 584 | assert.isTrue(yield* fs.exists(path.join(planDir, "probe-plan.md"))); |
| 585 | }).pipe(Effect.provide(Layer.merge(liveLayer, NodeServices.layer)), Effect.scoped), | |
| 585 | }).pipe(Effect.provide(Layer.merge(layerLive, NodeServices.layer)), Effect.scoped), | |
| 586 | 586 | 360_000, |
| 587 | 587 | ); |
| 588 | 588 | |
| ... | ... | @@ -718,7 +718,7 @@ describe.runIf(binaryPath !== undefined && ROOT !== "")("OpenCode 2 live orchest |
| 718 | 718 | stoppedChild.turnItems.some((item) => item.status === "running"), |
| 719 | 719 | "the stopped child shows nothing running", |
| 720 | 720 | ); |
| 721 | }).pipe(Effect.provide(Layer.merge(liveLayer, NodeServices.layer)), Effect.scoped), | |
| 721 | }).pipe(Effect.provide(Layer.merge(layerLive, NodeServices.layer)), Effect.scoped), | |
| 722 | 722 | 480_000, |
| 723 | 723 | ); |
| 724 | 724 | |
| ... | ... | @@ -927,7 +927,7 @@ describe.runIf(binaryPath !== undefined && ROOT !== "")("OpenCode 2 live orchest |
| 927 | 927 | .map((row) => String(row.id)); |
| 928 | 928 | db.close(); |
| 929 | 929 | assert.lengthOf(userMessages, 2); |
| 930 | }).pipe(Effect.provide(Layer.merge(liveLayer, NodeServices.layer)), Effect.scoped), | |
| 930 | }).pipe(Effect.provide(Layer.merge(layerLive, NodeServices.layer)), Effect.scoped), | |
| 931 | 931 | 600_000, |
| 932 | 932 | ); |
| 933 | 933 | |
| ... | ... | @@ -1028,7 +1028,7 @@ describe.runIf(binaryPath !== undefined && ROOT !== "")("OpenCode 2 live orchest |
| 1028 | 1028 | compaction?.type === "compaction" ? (compaction.summary ?? "").length : 0, |
| 1029 | 1029 | 0, |
| 1030 | 1030 | ); |
| 1031 | }).pipe(Effect.provide(Layer.merge(liveLayer, NodeServices.layer)), Effect.scoped), | |
| 1031 | }).pipe(Effect.provide(Layer.merge(layerLive, NodeServices.layer)), Effect.scoped), | |
| 1032 | 1032 | 360_000, |
| 1033 | 1033 | ); |
| 1034 | 1034 | |
| ... | ... | @@ -1114,7 +1114,7 @@ describe.runIf(binaryPath !== undefined && ROOT !== "")("OpenCode 2 live orchest |
| 1114 | 1114 | const answer = next.turnItems.findLast((item) => item.type === "assistant_message"); |
| 1115 | 1115 | assert.include(answer?.type === "assistant_message" ? answer.text : "", "sleep 60"); |
| 1116 | 1116 | assert.lengthOf(next.providerThreads, 1); |
| 1117 | }).pipe(Effect.provide(Layer.merge(liveLayer, NodeServices.layer)), Effect.scoped), | |
| 1117 | }).pipe(Effect.provide(Layer.merge(layerLive, NodeServices.layer)), Effect.scoped), | |
| 1118 | 1118 | 360_000, |
| 1119 | 1119 | ); |
| 1120 | 1120 | |
| ... | ... | @@ -1175,7 +1175,7 @@ describe.runIf(binaryPath !== undefined && ROOT !== "")("OpenCode 2 live orchest |
| 1175 | 1175 | [], |
| 1176 | 1176 | "an external server gets no T3 MCP server, as with 1.x", |
| 1177 | 1177 | ); |
| 1178 | }).pipe(Effect.provide(Layer.merge(liveLayer, NodeServices.layer)), Effect.scoped), | |
| 1178 | }).pipe(Effect.provide(Layer.merge(layerLive, NodeServices.layer)), Effect.scoped), | |
| 1179 | 1179 | 360_000, |
| 1180 | 1180 | ); |
| 1181 | 1181 | }); |
apps/server/src/orchestration-v2/Orchestrator.control-reads.test.ts+13-13| ... | ... | @@ -21,13 +21,13 @@ import * as DateTime from "effect/DateTime"; |
| 21 | 21 | import * as Effect from "effect/Effect"; |
| 22 | 22 | import * as Layer from "effect/Layer"; |
| 23 | 23 | import * as SqlClient from "effect/sql/SqlClient"; |
| 24 | import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts"; | |
| 24 | import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts"; | |
| 25 | 25 | import { CodexProviderCapabilitiesV2 } from "./Adapters/CodexAdapterV2.ts"; |
| 26 | 26 | import * as Orchestrator from "./Orchestrator.ts"; |
| 27 | 27 | import * as ProjectionStore from "./ProjectionStore.ts"; |
| 28 | 28 | import type { ProviderAdapterV2Shape } from "./ProviderAdapter.ts"; |
| 29 | 29 | import * as ProviderAdapterRegistry from "./ProviderAdapterRegistry.ts"; |
| 30 | import { makeOrchestratorV2ReplayLayerWithRegistry } from "./testkit/ProviderReplayHarness.ts"; | |
| 30 | import * as ProviderReplayHarness from "./testkit/ProviderReplayHarness.ts"; | |
| 31 | 31 | |
| 32 | 32 | const instanceId = ProviderInstanceId.make("codex"); |
| 33 | 33 | const modelSelection = { instanceId, model: "gpt-5.1-codex" }; |
| ... | ... | @@ -38,14 +38,14 @@ const adapter = { |
| 38 | 38 | planSelectionTransition: () => Effect.succeed({ type: "apply_on_next_turn" as const }), |
| 39 | 39 | openSession: () => Effect.die("No provider process needed for metadata controls"), |
| 40 | 40 | } as ProviderAdapterV2Shape; |
| 41 | const database = SqlitePersistenceMemory; | |
| 42 | const testLayer = Layer.mergeAll( | |
| 43 | database, | |
| 44 | ProjectionStore.layer.pipe(Layer.provide(database)), | |
| 45 | makeOrchestratorV2ReplayLayerWithRegistry( | |
| 41 | const layerDatabase = SqlitePersistence.layerMemory; | |
| 42 | const layerTest = Layer.mergeAll( | |
| 43 | layerDatabase, | |
| 44 | ProjectionStore.layer.pipe(Layer.provide(layerDatabase)), | |
| 45 | ProviderReplayHarness.layerWithRegistry( | |
| 46 | 46 | { name: "control-reads" }, |
| 47 | ProviderAdapterRegistry.makeLayer([adapter]), | |
| 48 | { databaseLayer: database, runEffectWorker: false }, | |
| 47 | ProviderAdapterRegistry.layerFromAdapters([adapter]), | |
| 48 | { databaseLayer: layerDatabase, runEffectWorker: false }, | |
| 49 | 49 | ), |
| 50 | 50 | ); |
| 51 | 51 | |
| ... | ... | @@ -277,7 +277,7 @@ it.effect( |
| 277 | 277 | threadId, |
| 278 | 278 | }); |
| 279 | 279 | assert.isNotNull((yield* projections.getThread(threadId)).deletedAt); |
| 280 | }).pipe(Effect.provide(testLayer)), | |
| 280 | }).pipe(Effect.provide(layerTest)), | |
| 281 | 281 | ); |
| 282 | 282 | |
| 283 | 283 | it.effect("implements a proposed plan that the command projection leaves out", () => |
| ... | ... | @@ -331,7 +331,7 @@ it.effect("implements a proposed plan that the command projection leaves out", ( |
| 331 | 331 | }); |
| 332 | 332 | |
| 333 | 333 | assert.equal((yield* projections.getPlan(threadId, planId))?.status, "completed"); |
| 334 | }).pipe(Effect.provide(testLayer)), | |
| 334 | }).pipe(Effect.provide(layerTest)), | |
| 335 | 335 | ); |
| 336 | 336 | |
| 337 | 337 | // Stop's settle follow-up runs after the provider interrupt returns, possibly |
| ... | ... | @@ -532,7 +532,7 @@ it.effect("settles only the stopped run's background work, once", () => |
| 532 | 532 | `${commandItem(2)}:running`, |
| 533 | 533 | `${commandItem(3)}:running`, |
| 534 | 534 | ]); |
| 535 | }).pipe(Effect.provide(testLayer)), | |
| 535 | }).pipe(Effect.provide(layerTest)), | |
| 536 | 536 | ); |
| 537 | 537 | |
| 538 | 538 | it.effect("keeps delegated child pull-request links independent of the parent", () => |
| ... | ... | @@ -622,5 +622,5 @@ it.effect("keeps delegated child pull-request links independent of the parent", |
| 622 | 622 | const parentAfterChildLink = yield* projections.getThreadProjection(parentThreadId); |
| 623 | 623 | assert.deepEqual(parentAfterChildLink.thread.linkedPullRequest, parentPullRequest); |
| 624 | 624 | assert.deepEqual(parentAfterChildLink.thread.pullRequests, parent.thread.pullRequests); |
| 625 | }).pipe(Effect.provide(testLayer)), | |
| 625 | }).pipe(Effect.provide(layerTest)), | |
| 626 | 626 | ); |
apps/server/src/orchestration-v2/ProjectSettingsUpgrade.integration.test.ts+15-15| ... | ... | @@ -15,7 +15,7 @@ import * as GitWorkflow from "../git/GitWorkflowService.ts"; |
| 15 | 15 | import * as NodeSqliteClient from "@t3tools/shared/nodeSqliteClient"; |
| 16 | 16 | |
| 17 | 17 | import * as McpSessionRegistryTestkit from "../mcp/McpSessionRegistry.testkit.ts"; |
| 18 | import { makeSqlitePersistenceLive } from "../persistence/Layers/Sqlite.ts"; | |
| 18 | import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts"; | |
| 19 | 19 | import { runMigrations } from "../persistence/Migrations.ts"; |
| 20 | 20 | import * as ProjectEnrichmentService from "../project/ProjectEnrichmentService.ts"; |
| 21 | 21 | import * as ProjectService from "../project/ProjectService.ts"; |
| ... | ... | @@ -26,7 +26,7 @@ import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts"; |
| 26 | 26 | import * as VcsProcess from "../vcs/VcsProcess.ts"; |
| 27 | 27 | import * as WorkspacePaths from "../workspace/WorkspacePaths.ts"; |
| 28 | 28 | import * as LegacyV1ThreadImporter from "./legacy/LegacyV1ThreadImporter.ts"; |
| 29 | import { OrchestrationV2LayerLive, ProjectServiceLayerLive } from "./runtimeLayer.ts"; | |
| 29 | import * as RuntimeLayer from "./runtimeLayer.ts"; | |
| 30 | 30 | |
| 31 | 31 | const projectId = ProjectId.make("project:upgrade"); |
| 32 | 32 | const icon = { kind: "emoji", emoji: "🦊" } as const; |
| ... | ... | @@ -116,28 +116,28 @@ const unusedEnrichment = { |
| 116 | 116 | }; |
| 117 | 117 | |
| 118 | 118 | /** The production V2 runtime and project service against one file-backed database. */ |
| 119 | const makeRuntimeLayer = (dbPath: string) => { | |
| 120 | const platform = Layer.merge( | |
| 119 | const layerRuntime = (dbPath: string) => { | |
| 120 | const layerPlatform = Layer.merge( | |
| 121 | 121 | NodeServices.layer, |
| 122 | 122 | Layer.mock(SourceControlProviderRegistry.SourceControlProviderRegistry)({ |
| 123 | 123 | resolveLink: () => Effect.die("unused"), |
| 124 | 124 | }), |
| 125 | 125 | ); |
| 126 | const serverConfig = ServerConfig.layerTest(process.cwd(), { | |
| 126 | const layerServerConfig = ServerConfig.layerTest(process.cwd(), { | |
| 127 | 127 | prefix: "t3-project-upgrade-", |
| 128 | 128 | }); |
| 129 | const checkpointStore = CheckpointStore.layer.pipe( | |
| 129 | const layerCheckpointStore = CheckpointStore.layer.pipe( | |
| 130 | 130 | Layer.provide( |
| 131 | 131 | VcsDriverRegistry.layer.pipe( |
| 132 | 132 | Layer.provide(VcsProcess.layer), |
| 133 | Layer.provide(serverConfig), | |
| 134 | Layer.provide(platform), | |
| 133 | Layer.provide(layerServerConfig), | |
| 134 | Layer.provide(layerPlatform), | |
| 135 | 135 | ), |
| 136 | 136 | ), |
| 137 | 137 | ); |
| 138 | 138 | return Layer.mergeAll( |
| 139 | OrchestrationV2LayerLive.pipe(Layer.provide(ProjectServiceLayerLive)), | |
| 140 | ProjectServiceLayerLive, | |
| 139 | RuntimeLayer.layer.pipe(Layer.provide(RuntimeLayer.layerProjectService)), | |
| 140 | RuntimeLayer.layerProjectService, | |
| 141 | 141 | ).pipe( |
| 142 | 142 | Layer.provide( |
| 143 | 143 | Layer.mock(ProjectEnrichmentService.ProjectEnrichmentService)({ |
| ... | ... | @@ -152,9 +152,9 @@ const makeRuntimeLayer = (dbPath: string) => { |
| 152 | 152 | }), |
| 153 | 153 | ), |
| 154 | 154 | Layer.provide(McpSessionRegistryTestkit.layer), |
| 155 | Layer.provideMerge(makeSqlitePersistenceLive(dbPath)), | |
| 156 | Layer.provide(checkpointStore), | |
| 157 | Layer.provide(serverConfig), | |
| 155 | Layer.provideMerge(SqlitePersistence.layerFromPath(dbPath)), | |
| 156 | Layer.provide(layerCheckpointStore), | |
| 157 | Layer.provide(layerServerConfig), | |
| 158 | 158 | Layer.provide(ServerSettings.layerTest()), |
| 159 | 159 | Layer.provide( |
| 160 | 160 | Layer.succeed(ProviderInstanceRegistry.ProviderInstanceRegistry, { |
| ... | ... | @@ -170,7 +170,7 @@ const makeRuntimeLayer = (dbPath: string) => { |
| 170 | 170 | pruneWorktrees: () => Effect.void, |
| 171 | 171 | }), |
| 172 | 172 | ), |
| 173 | Layer.provide(platform), | |
| 173 | Layer.provide(layerPlatform), | |
| 174 | 174 | ); |
| 175 | 175 | }; |
| 176 | 176 | |
| ... | ... | @@ -189,7 +189,7 @@ it.live("keeps project settings through the V2 migrations and the first V2 boot" |
| 189 | 189 | assert.deepEqual(yield* readSettings, expectedSettings); |
| 190 | 190 | const project = yield* (yield* ProjectService.ProjectService).getById(projectId); |
| 191 | 191 | assert.equal(project._tag, "Some"); |
| 192 | }).pipe(Effect.provide(makeRuntimeLayer(dbPath))); | |
| 192 | }).pipe(Effect.provide(layerRuntime(dbPath))); | |
| 193 | 193 | }), |
| 194 | 194 | ).pipe(Effect.provide(NodeServices.layer)), |
| 195 | 195 | ); |
apps/server/src/orchestration-v2/ProjectStore.test.ts+2-2| ... | ... | @@ -5,10 +5,10 @@ import * as Layer from "effect/Layer"; |
| 5 | 5 | import * as Option from "effect/Option"; |
| 6 | 6 | import * as SqlClient from "effect/sql/SqlClient"; |
| 7 | 7 | |
| 8 | import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts"; | |
| 8 | import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts"; | |
| 9 | 9 | import * as ProjectStore from "./ProjectStore.ts"; |
| 10 | 10 | |
| 11 | it.layer(ProjectStore.layer.pipe(Layer.provideMerge(SqlitePersistenceMemory)))( | |
| 11 | it.layer(ProjectStore.layer.pipe(Layer.provideMerge(SqlitePersistence.layerMemory)))( | |
| 12 | 12 | "ProjectStoreV2", |
| 13 | 13 | (it) => { |
| 14 | 14 | it.effect("stores a model selection without options as JSON without an options key", () => |
apps/server/src/orchestration-v2/ProjectionControlReads.test.ts+5-5| ... | ... | @@ -20,7 +20,7 @@ import * as Effect from "effect/Effect"; |
| 20 | 20 | import * as Layer from "effect/Layer"; |
| 21 | 21 | import * as Option from "effect/Option"; |
| 22 | 22 | import * as SqlClient from "effect/sql/SqlClient"; |
| 23 | import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts"; | |
| 23 | import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts"; | |
| 24 | 24 | import * as ProjectionStore from "./ProjectionStore.ts"; |
| 25 | 25 | import * as ProviderSessionManager from "./ProviderSessionManager.ts"; |
| 26 | 26 | import * as ProviderTurnControlService from "./ProviderTurnControlService.ts"; |
| ... | ... | @@ -190,7 +190,7 @@ const storageCases = (["sqlite", "memory"] as const).map((storage) => ({ |
| 190 | 190 | storage, |
| 191 | 191 | storeLayer: |
| 192 | 192 | storage === "sqlite" |
| 193 | ? ProjectionStore.layer.pipe(Layer.provideMerge(SqlitePersistenceMemory)) | |
| 193 | ? ProjectionStore.layer.pipe(Layer.provideMerge(SqlitePersistence.layerMemory)) | |
| 194 | 194 | : ProjectionStore.layerMemory, |
| 195 | 195 | })); |
| 196 | 196 | |
| ... | ... | @@ -274,7 +274,7 @@ it.effect.each(storageCases)( |
| 274 | 274 | assert.instanceOf(missingThread, ProjectionStore.ProjectionStoreThreadNotFoundError); |
| 275 | 275 | |
| 276 | 276 | const calls: string[] = []; |
| 277 | const sessions = Layer.mock(ProviderSessionManager.ProviderSessionManagerV2)({ | |
| 277 | const layerSessions = Layer.mock(ProviderSessionManager.ProviderSessionManagerV2)({ | |
| 278 | 278 | get: () => |
| 279 | 279 | Effect.succeed( |
| 280 | 280 | Option.some({ |
| ... | ... | @@ -346,9 +346,9 @@ it.effect.each(storageCases)( |
| 346 | 346 | }).pipe( |
| 347 | 347 | Effect.provide( |
| 348 | 348 | Layer.merge(ProviderTurnControlService.layer, RuntimeRequestService.layer).pipe( |
| 349 | Layer.provide(sessions), | |
| 349 | Layer.provide(layerSessions), | |
| 350 | 350 | ), |
| 351 | 351 | ), |
| 352 | 352 | ); |
| 353 | }).pipe(Effect.provide(Layer.merge(storeLayer, SqlitePersistenceMemory))), | |
| 353 | }).pipe(Effect.provide(Layer.merge(storeLayer, SqlitePersistence.layerMemory))), | |
| 354 | 354 | ); |
apps/server/src/orchestration-v2/ProjectionRecovery.test.ts+8-8| ... | ... | @@ -22,14 +22,14 @@ import * as Effect from "effect/Effect"; |
| 22 | 22 | import * as Layer from "effect/Layer"; |
| 23 | 23 | import * as SqlClient from "effect/sql/SqlClient"; |
| 24 | 24 | |
| 25 | import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts"; | |
| 25 | import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts"; | |
| 26 | 26 | import { CodexProviderCapabilitiesV2 } from "./Adapters/CodexAdapterV2.ts"; |
| 27 | 27 | import * as EffectOutbox from "./EffectOutbox.ts"; |
| 28 | 28 | import * as ProjectionStore from "./ProjectionStore.ts"; |
| 29 | 29 | import { restartContinuationRun } from "./RestartContinuation.ts"; |
| 30 | 30 | |
| 31 | const TestLayer = Layer.mergeAll(ProjectionStore.layer, EffectOutbox.layer).pipe( | |
| 32 | Layer.provideMerge(SqlitePersistenceMemory), | |
| 31 | const layerTest = Layer.mergeAll(ProjectionStore.layer, EffectOutbox.layer).pipe( | |
| 32 | Layer.provideMerge(SqlitePersistence.layerMemory), | |
| 33 | 33 | ); |
| 34 | 34 | const providerInstanceId = ProviderInstanceId.make("codex"); |
| 35 | 35 | const modelSelection = { instanceId: providerInstanceId, model: "gpt-5.4" }; |
| ... | ... | @@ -221,7 +221,7 @@ it.effect("selects unfinished recovery work without reading settled thread histo |
| 221 | 221 | queued, |
| 222 | 222 | ThreadId.make("thread:recovery:settled-599"), |
| 223 | 223 | ]); |
| 224 | }).pipe(Effect.provide(TestLayer)), | |
| 224 | }).pipe(Effect.provide(layerTest)), | |
| 225 | 225 | ); |
| 226 | 226 | |
| 227 | 227 | it.effect("recovers terminal subagent results until their cross-thread transfer exists", () => |
| ... | ... | @@ -283,7 +283,7 @@ it.effect("recovers terminal subagent results until their cross-thread transfer |
| 283 | 283 | new Set(yield* projections.getUnreadableThreadIds()), |
| 284 | 284 | new Set([parent, completed]), |
| 285 | 285 | ); |
| 286 | }).pipe(Effect.provide(TestLayer)), | |
| 286 | }).pipe(Effect.provide(layerTest)), | |
| 287 | 287 | ); |
| 288 | 288 | |
| 289 | 289 | it.effect("includes shared sessions and provider-owned background rosters in recovery", () => |
| ... | ... | @@ -417,7 +417,7 @@ it.effect("includes shared sessions and provider-owned background rosters in rec |
| 417 | 417 | new Set(yield* projections.getUnreadableThreadIds()), |
| 418 | 418 | new Set([first, second]), |
| 419 | 419 | ); |
| 420 | }).pipe(Effect.provide(TestLayer)), | |
| 420 | }).pipe(Effect.provide(layerTest)), | |
| 421 | 421 | ); |
| 422 | 422 | |
| 423 | 423 | it.effect("reads the run that owns a background roster, not a queued or resumed one", () => |
| ... | ... | @@ -484,7 +484,7 @@ it.effect("reads the run that owns a background roster, not a queued or resumed |
| 484 | 484 | yield* createRun(quiet, "completed"); |
| 485 | 485 | const quietQueued = yield* createRun(quiet, "queued", { ordinal: 2, completedAt: null }); |
| 486 | 486 | assert.deepEqual(yield* runIds(quiet), [quietQueued]); |
| 487 | }).pipe(Effect.provide(TestLayer)), | |
| 487 | }).pipe(Effect.provide(layerTest)), | |
| 488 | 488 | ); |
| 489 | 489 | |
| 490 | 490 | it.effect("marks fork descendants unreadable when their source is missing or corrupt", () => |
| ... | ... | @@ -513,5 +513,5 @@ it.effect("marks fork descendants unreadable when their source is missing or cor |
| 513 | 513 | new Set(yield* projections.getUnreadableThreadIds()), |
| 514 | 514 | new Set([fork, descendant]), |
| 515 | 515 | ); |
| 516 | }).pipe(Effect.provide(TestLayer)), | |
| 516 | }).pipe(Effect.provide(layerTest)), | |
| 517 | 517 | ); |
apps/server/src/orchestration-v2/ProjectionSettlement.test.ts+8-8| ... | ... | @@ -21,11 +21,11 @@ import * as Schema from "effect/Schema"; |
| 21 | 21 | import * as SqlClient from "effect/sql/SqlClient"; |
| 22 | 22 | import * as Statement from "effect/sql/Statement"; |
| 23 | 23 | |
| 24 | import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts"; | |
| 24 | import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts"; | |
| 25 | 25 | import * as ProjectionStore from "./ProjectionStore.ts"; |
| 26 | 26 | import { isAutoSettlementCandidate, resolveAutoSettlementAt } from "./ThreadSettlementService.ts"; |
| 27 | 27 | |
| 28 | const SqlLayer = ProjectionStore.layer.pipe(Layer.provideMerge(SqlitePersistenceMemory)); | |
| 28 | const layerSql = ProjectionStore.layer.pipe(Layer.provideMerge(SqlitePersistence.layerMemory)); | |
| 29 | 29 | const now = DateTime.makeUnsafe("2026-09-04T12:00:00Z"); |
| 30 | 30 | const old = DateTime.subtract(now, { days: 10 }); |
| 31 | 31 | const providerInstanceId = ProviderInstanceId.make("codex"); |
| ... | ... | @@ -141,7 +141,7 @@ const createItem = Effect.fn(function* ( |
| 141 | 141 | }); |
| 142 | 142 | |
| 143 | 143 | it.effect.each([ |
| 144 | ["sql", SqlLayer], | |
| 144 | ["sql", layerSql], | |
| 145 | 145 | ["memory", ProjectionStore.layerMemory], |
| 146 | 146 | ] as const)( |
| 147 | 147 | "%s: discovers settlement work with the same activity and background semantics as the shell", |
| ... | ... | @@ -299,7 +299,7 @@ it.effect.each([ |
| 299 | 299 | ); |
| 300 | 300 | |
| 301 | 301 | it.effect.each([ |
| 302 | ["sql", SqlLayer], | |
| 302 | ["sql", layerSql], | |
| 303 | 303 | ["memory", ProjectionStore.layerMemory], |
| 304 | 304 | ] as const)("%s: the user-authored message time ignores agent notifications", ([, testLayer]) => |
| 305 | 305 | Effect.gen(function* () { |
| ... | ... | @@ -366,7 +366,7 @@ const pullRequestLink = (number: number) => ({ |
| 366 | 366 | }); |
| 367 | 367 | |
| 368 | 368 | it.effect.each([ |
| 369 | ["sql", SqlLayer], | |
| 369 | ["sql", layerSql], | |
| 370 | 370 | ["memory", ProjectionStore.layerMemory], |
| 371 | 371 | ] as const)( |
| 372 | 372 | "%s: lists only active threads with pull request links, oldest first", |
| ... | ... | @@ -405,7 +405,7 @@ it.effect.each([ |
| 405 | 405 | ); |
| 406 | 406 | |
| 407 | 407 | it.effect.each([ |
| 408 | ["sql", SqlLayer], | |
| 408 | ["sql", layerSql], | |
| 409 | 409 | ["memory", ProjectionStore.layerMemory], |
| 410 | 410 | ] as const)("%s: an unsettled-only shell read skips settled threads", ([, testLayer]) => |
| 411 | 411 | Effect.gen(function* () { |
| ... | ... | @@ -486,7 +486,7 @@ it.effect( |
| 486 | 486 | WHERE thread_id = ${threadId} AND role = 'user' ORDER BY updated_at DESC, message_id DESC LIMIT 1`; |
| 487 | 487 | assert.isTrue(messagePlan.some((row) => row.detail.includes("messages_latest_user_idx"))); |
| 488 | 488 | assert.isFalse(messagePlan.some((row) => row.detail.includes("TEMP B-TREE"))); |
| 489 | }).pipe(Effect.provide(SqlLayer)), | |
| 489 | }).pipe(Effect.provide(layerSql)), | |
| 490 | 490 | ); |
| 491 | 491 | |
| 492 | 492 | it.effect("shell failure lookups stay on the thread's own turn items", () => |
| ... | ... | @@ -525,5 +525,5 @@ it.effect("shell failure lookups stay on the thread's own turn items", () => |
| 525 | 525 | const secretLookups = plan.filter((row) => row.detail.startsWith("SEARCH secret ")); |
| 526 | 526 | assert.lengthOf(secretLookups, 1); |
| 527 | 527 | assert.include(secretLookups[0]!.detail, "turn_items_thread_run_idx"); |
| 528 | }).pipe(Effect.provide(SqlLayer)), | |
| 528 | }).pipe(Effect.provide(layerSql)), | |
| 529 | 529 | ); |
apps/server/src/orchestration-v2/ProjectionStore.test.ts+5-5| ... | ... | @@ -30,7 +30,7 @@ import * as Schema from "effect/Schema"; |
| 30 | 30 | import * as SqlClient from "effect/sql/SqlClient"; |
| 31 | 31 | import { projectThreadAwarenessV2 } from "@t3tools/shared/agentAwareness"; |
| 32 | 32 | |
| 33 | import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts"; | |
| 33 | import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts"; | |
| 34 | 34 | import { CodexProviderCapabilitiesV2 } from "./Adapters/CodexAdapterV2.ts"; |
| 35 | 35 | import * as ProjectionStore from "./ProjectionStore.ts"; |
| 36 | 36 | import { |
| ... | ... | @@ -40,9 +40,9 @@ import { |
| 40 | 40 | THREAD_HISTORY_PAGE_POLICY, |
| 41 | 41 | } from "./threadHistoryPaging.ts"; |
| 42 | 42 | |
| 43 | const TestLayer = Layer.mergeAll( | |
| 44 | ProjectionStore.layer.pipe(Layer.provideMerge(SqlitePersistenceMemory)), | |
| 45 | SqlitePersistenceMemory, | |
| 43 | const layerTest = Layer.mergeAll( | |
| 44 | ProjectionStore.layer.pipe(Layer.provideMerge(SqlitePersistence.layerMemory)), | |
| 45 | SqlitePersistence.layerMemory, | |
| 46 | 46 | ); |
| 47 | 47 | const modelSelection = { |
| 48 | 48 | instanceId: ProviderInstanceId.make("codex"), |
| ... | ... | @@ -330,7 +330,7 @@ it.effect("memory recovery selection includes unfinished items from missing runs |
| 330 | 330 | }).pipe(Effect.provide(ProjectionStore.layerMemory)), |
| 331 | 331 | ); |
| 332 | 332 | |
| 333 | it.layer(TestLayer)("ProjectionStoreV2", (it) => { | |
| 333 | it.layer(layerTest)("ProjectionStoreV2", (it) => { | |
| 334 | 334 | it.effect( |
| 335 | 335 | "keeps restart-cancelled work through a stale run.updated", |
| 336 | 336 | () => restartCancelledWorkSurvivesStaleRunUpdate, |
apps/server/src/orchestration-v2/ProviderAdapterRegistry.test.ts+4-4| ... | ... | @@ -65,7 +65,7 @@ const instances = [ |
| 65 | 65 | makeInstance(personalId, personalAdapter), |
| 66 | 66 | makeInstance(workId, workAdapter), |
| 67 | 67 | ] as const; |
| 68 | const instanceRegistryLayer = Layer.succeed(ProviderInstanceRegistry.ProviderInstanceRegistry, { | |
| 68 | const layerInstanceRegistry = Layer.succeed(ProviderInstanceRegistry.ProviderInstanceRegistry, { | |
| 69 | 69 | getInstance: (instanceId) => |
| 70 | 70 | Effect.succeed(instances.find((instance) => instance.instanceId === instanceId)), |
| 71 | 71 | listInstances: Effect.succeed(instances), |
| ... | ... | @@ -73,8 +73,8 @@ const instanceRegistryLayer = Layer.succeed(ProviderInstanceRegistry.ProviderIns |
| 73 | 73 | streamChanges: Stream.empty, |
| 74 | 74 | subscribeChanges: Effect.never, |
| 75 | 75 | }); |
| 76 | const TestLayer = ProviderAdapterRegistry.layerFromProviderInstanceRegistry.pipe( | |
| 77 | Layer.provide(instanceRegistryLayer), | |
| 76 | const layerTest = ProviderAdapterRegistry.layerFromProviderInstanceRegistry.pipe( | |
| 77 | Layer.provide(layerInstanceRegistry), | |
| 78 | 78 | ); |
| 79 | 79 | |
| 80 | 80 | it.effect("routes two configured instances of the same driver independently", () => |
| ... | ... | @@ -84,7 +84,7 @@ it.effect("routes two configured instances of the same driver independently", () |
| 84 | 84 | assert.strictEqual(yield* registry.get(personalId), personalAdapter); |
| 85 | 85 | assert.strictEqual(yield* registry.get(workId), workAdapter); |
| 86 | 86 | assert.deepEqual(yield* registry.list(), [personalId, workId]); |
| 87 | }).pipe(Effect.provide(TestLayer)), | |
| 87 | }).pipe(Effect.provide(layerTest)), | |
| 88 | 88 | ); |
| 89 | 89 | |
| 90 | 90 | const lifecycleDriver = ProviderDriverKind.make("lifecycle-test"); |
apps/server/src/orchestration-v2/ProviderAdapterRegistry.ts+5-5| ... | ... | @@ -184,7 +184,7 @@ function makeRegistry( |
| 184 | 184 | }; |
| 185 | 185 | } |
| 186 | 186 | |
| 187 | export function makeLayer( | |
| 187 | export function layerFromAdapters( | |
| 188 | 188 | adapters: ReadonlyArray<ProviderAdapter.ProviderAdapterV2Shape>, |
| 189 | 189 | ): Layer.Layer<ProviderAdapterRegistryV2> { |
| 190 | 190 | return Layer.succeed( |
| ... | ... | @@ -193,7 +193,7 @@ export function makeLayer( |
| 193 | 193 | ); |
| 194 | 194 | } |
| 195 | 195 | |
| 196 | export function makeLayerEffect<R, E>( | |
| 196 | export function layerFromAdaptersEffect<R, E>( | |
| 197 | 197 | adapters: Effect.Effect<ReadonlyArray<ProviderAdapter.ProviderAdapterV2Shape>, E, R>, |
| 198 | 198 | ): Layer.Layer<ProviderAdapterRegistryV2, E, R> { |
| 199 | 199 | return Layer.effect( |
| ... | ... | @@ -202,10 +202,10 @@ export function makeLayerEffect<R, E>( |
| 202 | 202 | ); |
| 203 | 203 | } |
| 204 | 204 | |
| 205 | export function makeSingleLayer( | |
| 205 | export function layerSingle( | |
| 206 | 206 | adapter: ProviderAdapter.ProviderAdapterV2Shape, |
| 207 | 207 | ): Layer.Layer<ProviderAdapterRegistryV2> { |
| 208 | return makeLayer([adapter]); | |
| 208 | return layerFromAdapters([adapter]); | |
| 209 | 209 | } |
| 210 | 210 | |
| 211 | 211 | const decodedConfigEnabled = (config: unknown): boolean | undefined => { |
| ... | ... | @@ -338,7 +338,7 @@ export function makeRegistryFromConfigMap<R>(input: { |
| 338 | 338 | }); |
| 339 | 339 | } |
| 340 | 340 | |
| 341 | export function makeDriverLayer<R>(input: { | |
| 341 | export function layerFromDrivers<R>(input: { | |
| 342 | 342 | readonly drivers: ReadonlyArray<AnyProviderAdapterDriver<R>>; |
| 343 | 343 | readonly configMap: ProviderInstanceConfigMap; |
| 344 | 344 | }): Layer.Layer<ProviderAdapterRegistryV2, ProviderAdapterRegistryBuildError, R> { |
apps/server/src/orchestration-v2/ProviderContinuationService.test.ts+37-35| ... | ... | @@ -88,18 +88,20 @@ const makeGuard = Effect.fnUntraced(function* (completed?: Deferred.Deferred<voi |
| 88 | 88 | }; |
| 89 | 89 | }); |
| 90 | 90 | |
| 91 | function testLayer(input: { | |
| 91 | function layerTest(input: { | |
| 92 | 92 | readonly dispatched: Queue.Queue<unknown>; |
| 93 | 93 | readonly getThreadRecords: () => Effect.Effect<OrchestrationV2ThreadProjection>; |
| 94 | 94 | }) { |
| 95 | const threads = Layer.mock(ThreadManagementService.ThreadManagementService)({ | |
| 95 | const layerThreads = Layer.mock(ThreadManagementService.ThreadManagementService)({ | |
| 96 | 96 | getThreadRecords: input.getThreadRecords, |
| 97 | 97 | dispatch: (command) => Queue.offer(input.dispatched, command).pipe(Effect.as({} as never)), |
| 98 | 98 | }); |
| 99 | const worker = ProviderContinuationService.workerLive.pipe( | |
| 100 | Layer.provide(Layer.mergeAll(IdAllocator.layer, ProviderContinuationRequests.layer, threads)), | |
| 99 | const layerWorker = ProviderContinuationService.layer.pipe( | |
| 100 | Layer.provide( | |
| 101 | Layer.mergeAll(IdAllocator.layer, ProviderContinuationRequests.layer, layerThreads), | |
| 102 | ), | |
| 101 | 103 | ); |
| 102 | return Layer.merge(ProviderContinuationRequests.layer, worker); | |
| 104 | return Layer.merge(ProviderContinuationRequests.layer, layerWorker); | |
| 103 | 105 | } |
| 104 | 106 | |
| 105 | 107 | describe("ProviderContinuationService", () => { |
| ... | ... | @@ -145,7 +147,7 @@ describe("ProviderContinuationService", () => { |
| 145 | 147 | assert.equal(command.delegatedCompletion.taskIds.length, 2); |
| 146 | 148 | }).pipe( |
| 147 | 149 | Effect.provide( |
| 148 | testLayer({ dispatched, getThreadRecords: () => Effect.succeed(recovered) }), | |
| 150 | layerTest({ dispatched, getThreadRecords: () => Effect.succeed(recovered) }), | |
| 149 | 151 | ), |
| 150 | 152 | Effect.scoped, |
| 151 | 153 | ); |
| ... | ... | @@ -171,7 +173,7 @@ describe("ProviderContinuationService", () => { |
| 171 | 173 | }); |
| 172 | 174 | }).pipe( |
| 173 | 175 | Effect.provide( |
| 174 | testLayer({ dispatched, getThreadRecords: () => Effect.succeed(projection) }), | |
| 176 | layerTest({ dispatched, getThreadRecords: () => Effect.succeed(projection) }), | |
| 175 | 177 | ), |
| 176 | 178 | Effect.scoped, |
| 177 | 179 | ); |
| ... | ... | @@ -213,7 +215,7 @@ describe("ProviderContinuationService", () => { |
| 213 | 215 | }); |
| 214 | 216 | }).pipe( |
| 215 | 217 | Effect.provide( |
| 216 | testLayer({ dispatched, getThreadRecords: () => Effect.succeed(projection) }), | |
| 218 | layerTest({ dispatched, getThreadRecords: () => Effect.succeed(projection) }), | |
| 217 | 219 | ), |
| 218 | 220 | Effect.scoped, |
| 219 | 221 | ); |
| ... | ... | @@ -262,7 +264,7 @@ describe("ProviderContinuationService", () => { |
| 262 | 264 | assert.include(command.text, "task_status"); |
| 263 | 265 | }).pipe( |
| 264 | 266 | Effect.provide( |
| 265 | testLayer({ | |
| 267 | layerTest({ | |
| 266 | 268 | dispatched, |
| 267 | 269 | getThreadRecords: () => Effect.succeed(delegatedProjection()), |
| 268 | 270 | }), |
| ... | ... | @@ -276,7 +278,7 @@ describe("ProviderContinuationService", () => { |
| 276 | 278 | return Effect.gen(function* () { |
| 277 | 279 | const attempts = yield* Ref.make(0); |
| 278 | 280 | const dispatched = yield* Queue.unbounded<unknown>(); |
| 279 | const threads = Layer.mock(ThreadManagementService.ThreadManagementService)({ | |
| 281 | const layerThreads = Layer.mock(ThreadManagementService.ThreadManagementService)({ | |
| 280 | 282 | getThreadRecords: () => Effect.succeed(delegatedProjection()), |
| 281 | 283 | dispatch: (command) => |
| 282 | 284 | Ref.getAndUpdate(attempts, (count) => count + 1).pipe( |
| ... | ... | @@ -287,9 +289,9 @@ describe("ProviderContinuationService", () => { |
| 287 | 289 | ), |
| 288 | 290 | ), |
| 289 | 291 | }); |
| 290 | const worker = ProviderContinuationService.workerLive.pipe( | |
| 292 | const layerWorker = ProviderContinuationService.layer.pipe( | |
| 291 | 293 | Layer.provide( |
| 292 | Layer.mergeAll(IdAllocator.layer, ProviderContinuationRequests.layer, threads), | |
| 294 | Layer.mergeAll(IdAllocator.layer, ProviderContinuationRequests.layer, layerThreads), | |
| 293 | 295 | ), |
| 294 | 296 | ); |
| 295 | 297 | |
| ... | ... | @@ -318,7 +320,7 @@ describe("ProviderContinuationService", () => { |
| 318 | 320 | assert.equal(command.messageId, delegatedMessageId); |
| 319 | 321 | assert.equal(yield* Ref.get(attempts), 2); |
| 320 | 322 | }).pipe( |
| 321 | Effect.provide(Layer.merge(ProviderContinuationRequests.layer, worker)), | |
| 323 | Effect.provide(Layer.merge(ProviderContinuationRequests.layer, layerWorker)), | |
| 322 | 324 | Effect.scoped, |
| 323 | 325 | ); |
| 324 | 326 | }); |
| ... | ... | @@ -328,7 +330,7 @@ describe("ProviderContinuationService", () => { |
| 328 | 330 | return Effect.gen(function* () { |
| 329 | 331 | const attempts = yield* Ref.make(0); |
| 330 | 332 | const disposition = yield* Ref.make<"open" | "disposed">("open"); |
| 331 | const threads = Layer.mock(ThreadManagementService.ThreadManagementService)({ | |
| 333 | const layerThreads = Layer.mock(ThreadManagementService.ThreadManagementService)({ | |
| 332 | 334 | getThreadRecords: () => |
| 333 | 335 | Ref.get(disposition).pipe(Effect.map((state) => delegatedProjection(state))), |
| 334 | 336 | dispatch: () => |
| ... | ... | @@ -336,9 +338,9 @@ describe("ProviderContinuationService", () => { |
| 336 | 338 | Effect.andThen(Effect.fail(new Error("simulated persistent failure") as never)), |
| 337 | 339 | ), |
| 338 | 340 | }); |
| 339 | const worker = ProviderContinuationService.workerLive.pipe( | |
| 341 | const layerWorker = ProviderContinuationService.layer.pipe( | |
| 340 | 342 | Layer.provide( |
| 341 | Layer.mergeAll(IdAllocator.layer, ProviderContinuationRequests.layer, threads), | |
| 343 | Layer.mergeAll(IdAllocator.layer, ProviderContinuationRequests.layer, layerThreads), | |
| 342 | 344 | ), |
| 343 | 345 | ); |
| 344 | 346 | |
| ... | ... | @@ -372,7 +374,7 @@ describe("ProviderContinuationService", () => { |
| 372 | 374 | yield* Ref.set(disposition, "disposed"); |
| 373 | 375 | yield* TestClock.adjust("400 millis"); |
| 374 | 376 | }).pipe( |
| 375 | Effect.provide(Layer.merge(ProviderContinuationRequests.layer, worker)), | |
| 377 | Effect.provide(Layer.merge(ProviderContinuationRequests.layer, layerWorker)), | |
| 376 | 378 | Effect.scoped, |
| 377 | 379 | ); |
| 378 | 380 | }); |
| ... | ... | @@ -385,7 +387,7 @@ describe("ProviderContinuationService", () => { |
| 385 | 387 | const blockedProjectionReads = yield* Ref.make(0); |
| 386 | 388 | const blockedRequestDropped = yield* Deferred.make<void>(); |
| 387 | 389 | const state = yield* Ref.make<"open" | "blocked" | "disposed">("open"); |
| 388 | const threads = Layer.mock(ThreadManagementService.ThreadManagementService)({ | |
| 390 | const layerThreads = Layer.mock(ThreadManagementService.ThreadManagementService)({ | |
| 389 | 391 | getThreadRecords: () => |
| 390 | 392 | Effect.gen(function* () { |
| 391 | 393 | const currentState = yield* Ref.get(state); |
| ... | ... | @@ -411,9 +413,9 @@ describe("ProviderContinuationService", () => { |
| 411 | 413 | Effect.andThen(Effect.fail(new Error("simulated persistent failure") as never)), |
| 412 | 414 | ), |
| 413 | 415 | }); |
| 414 | const worker = ProviderContinuationService.workerLive.pipe( | |
| 416 | const layerWorker = ProviderContinuationService.layer.pipe( | |
| 415 | 417 | Layer.provide( |
| 416 | Layer.mergeAll(IdAllocator.layer, ProviderContinuationRequests.layer, threads), | |
| 418 | Layer.mergeAll(IdAllocator.layer, ProviderContinuationRequests.layer, layerThreads), | |
| 417 | 419 | ), |
| 418 | 420 | ); |
| 419 | 421 | const completionRequest = { |
| ... | ... | @@ -455,7 +457,7 @@ describe("ProviderContinuationService", () => { |
| 455 | 457 | yield* Ref.set(state, "disposed"); |
| 456 | 458 | yield* TestClock.adjust("200 millis"); |
| 457 | 459 | }).pipe( |
| 458 | Effect.provide(Layer.merge(ProviderContinuationRequests.layer, worker)), | |
| 460 | Effect.provide(Layer.merge(ProviderContinuationRequests.layer, layerWorker)), | |
| 459 | 461 | Effect.scoped, |
| 460 | 462 | ); |
| 461 | 463 | } |
| ... | ... | @@ -466,7 +468,7 @@ describe("ProviderContinuationService", () => { |
| 466 | 468 | return Effect.gen(function* () { |
| 467 | 469 | const attempts = yield* Ref.make(0); |
| 468 | 470 | const disposition = yield* Ref.make<"open" | "disposed">("open"); |
| 469 | const threads = Layer.mock(ThreadManagementService.ThreadManagementService)({ | |
| 471 | const layerThreads = Layer.mock(ThreadManagementService.ThreadManagementService)({ | |
| 470 | 472 | getThreadRecords: () => |
| 471 | 473 | Ref.get(disposition).pipe(Effect.map((state) => delegatedProjection(state))), |
| 472 | 474 | dispatch: () => |
| ... | ... | @@ -474,9 +476,9 @@ describe("ProviderContinuationService", () => { |
| 474 | 476 | Effect.andThen(Effect.fail(new Error("simulated dispatch failure") as never)), |
| 475 | 477 | ), |
| 476 | 478 | }); |
| 477 | const worker = ProviderContinuationService.workerLive.pipe( | |
| 479 | const layerWorker = ProviderContinuationService.layer.pipe( | |
| 478 | 480 | Layer.provide( |
| 479 | Layer.mergeAll(IdAllocator.layer, ProviderContinuationRequests.layer, threads), | |
| 481 | Layer.mergeAll(IdAllocator.layer, ProviderContinuationRequests.layer, layerThreads), | |
| 480 | 482 | ), |
| 481 | 483 | ); |
| 482 | 484 | |
| ... | ... | @@ -502,7 +504,7 @@ describe("ProviderContinuationService", () => { |
| 502 | 504 | yield* Effect.yieldNow; |
| 503 | 505 | assert.equal(yield* Ref.get(attempts), 1); |
| 504 | 506 | }).pipe( |
| 505 | Effect.provide(Layer.merge(ProviderContinuationRequests.layer, worker)), | |
| 507 | Effect.provide(Layer.merge(ProviderContinuationRequests.layer, layerWorker)), | |
| 506 | 508 | Effect.scoped, |
| 507 | 509 | ); |
| 508 | 510 | }); |
| ... | ... | @@ -531,7 +533,7 @@ describe("ProviderContinuationService", () => { |
| 531 | 533 | assert.deepEqual(command.dispatchMode, { type: "queue_after_active" }); |
| 532 | 534 | }).pipe( |
| 533 | 535 | Effect.provide( |
| 534 | testLayer({ | |
| 536 | layerTest({ | |
| 535 | 537 | dispatched, |
| 536 | 538 | getThreadRecords: () => Effect.succeed(delegatedProjection()), |
| 537 | 539 | }), |
| ... | ... | @@ -566,7 +568,7 @@ describe("ProviderContinuationService", () => { |
| 566 | 568 | assert.isTrue(Option.isNone(yield* Queue.poll(dispatched))); |
| 567 | 569 | }).pipe( |
| 568 | 570 | Effect.provide( |
| 569 | testLayer({ | |
| 571 | layerTest({ | |
| 570 | 572 | dispatched, |
| 571 | 573 | getThreadRecords: () => Effect.succeed(delegatedProjection(disposition)), |
| 572 | 574 | }), |
| ... | ... | @@ -600,7 +602,7 @@ describe("ProviderContinuationService", () => { |
| 600 | 602 | assert.isTrue(Option.isNone(yield* Queue.poll(dispatched))); |
| 601 | 603 | }).pipe( |
| 602 | 604 | Effect.provide( |
| 603 | testLayer({ | |
| 605 | layerTest({ | |
| 604 | 606 | dispatched, |
| 605 | 607 | getThreadRecords: () => |
| 606 | 608 | Effect.succeed({ |
| ... | ... | @@ -637,7 +639,7 @@ describe("ProviderContinuationService", () => { |
| 637 | 639 | assert.isTrue(Option.isNone(yield* Queue.poll(dispatched))); |
| 638 | 640 | }).pipe( |
| 639 | 641 | Effect.provide( |
| 640 | testLayer({ | |
| 642 | layerTest({ | |
| 641 | 643 | dispatched, |
| 642 | 644 | getThreadRecords: () => |
| 643 | 645 | Effect.succeed({ |
| ... | ... | @@ -666,7 +668,7 @@ describe("ProviderContinuationService", () => { |
| 666 | 668 | assert.isTrue(Option.isNone(yield* Queue.poll(dispatched))); |
| 667 | 669 | }).pipe( |
| 668 | 670 | Effect.provide( |
| 669 | testLayer({ dispatched, getThreadRecords: () => Effect.succeed(projection) }), | |
| 671 | layerTest({ dispatched, getThreadRecords: () => Effect.succeed(projection) }), | |
| 670 | 672 | ), |
| 671 | 673 | Effect.scoped, |
| 672 | 674 | ); |
| ... | ... | @@ -685,7 +687,7 @@ describe("ProviderContinuationService", () => { |
| 685 | 687 | }); |
| 686 | 688 | }).pipe( |
| 687 | 689 | Effect.provide( |
| 688 | testLayer({ dispatched, getThreadRecords: () => Effect.succeed(projection) }), | |
| 690 | layerTest({ dispatched, getThreadRecords: () => Effect.succeed(projection) }), | |
| 689 | 691 | ), |
| 690 | 692 | Effect.scoped, |
| 691 | 693 | ); |
| ... | ... | @@ -706,7 +708,7 @@ describe("ProviderContinuationService", () => { |
| 706 | 708 | assert.isTrue(Option.isNone(yield* Queue.poll(dispatched))); |
| 707 | 709 | }).pipe( |
| 708 | 710 | Effect.provide( |
| 709 | testLayer({ dispatched, getThreadRecords: () => Effect.succeed(projection) }), | |
| 711 | layerTest({ dispatched, getThreadRecords: () => Effect.succeed(projection) }), | |
| 710 | 712 | ), |
| 711 | 713 | Effect.scoped, |
| 712 | 714 | ); |
| ... | ... | @@ -731,7 +733,7 @@ describe("ProviderContinuationService", () => { |
| 731 | 733 | assert.isTrue(Option.isNone(yield* Queue.poll(dispatched))); |
| 732 | 734 | }).pipe( |
| 733 | 735 | Effect.provide( |
| 734 | testLayer({ | |
| 736 | layerTest({ | |
| 735 | 737 | dispatched, |
| 736 | 738 | getThreadRecords: () => |
| 737 | 739 | Deferred.succeed(projectionEntered, undefined).pipe( |
| ... | ... | @@ -766,7 +768,7 @@ describe("ProviderContinuationService", () => { |
| 766 | 768 | assert.isTrue(Option.isNone(yield* Queue.poll(dispatched))); |
| 767 | 769 | }).pipe( |
| 768 | 770 | Effect.provide( |
| 769 | testLayer({ | |
| 771 | layerTest({ | |
| 770 | 772 | dispatched, |
| 771 | 773 | getThreadRecords: () => { |
| 772 | 774 | projectionCalls += 1; |
| ... | ... | @@ -801,7 +803,7 @@ describe("ProviderContinuationService", () => { |
| 801 | 803 | assert.isTrue(Option.isNone(yield* Queue.poll(dispatched))); |
| 802 | 804 | }).pipe( |
| 803 | 805 | Effect.provide( |
| 804 | testLayer({ | |
| 806 | layerTest({ | |
| 805 | 807 | dispatched, |
| 806 | 808 | getThreadRecords: () => |
| 807 | 809 | Effect.succeed({ |
apps/server/src/orchestration-v2/ProviderContinuationService.ts+1-1| ... | ... | @@ -57,7 +57,7 @@ function delegatedCompletionRetryKey( |
| 57 | 57 | * the orchestrator selects native steering or queued delivery under its thread |
| 58 | 58 | * lock. Adapter-buffered continuations still queue behind active work. |
| 59 | 59 | */ |
| 60 | export const workerLive = Layer.effectDiscard( | |
| 60 | export const layer = Layer.effectDiscard( | |
| 61 | 61 | Effect.gen(function* () { |
| 62 | 62 | const ids = yield* IdAllocator.IdAllocatorV2; |
| 63 | 63 | const requests = yield* ProviderContinuationRequests.ProviderContinuationRequests; |
apps/server/src/orchestration-v2/ProviderEventIngestor.test.ts+14-14| ... | ... | @@ -27,7 +27,7 @@ import * as Layer from "effect/Layer"; |
| 27 | 27 | import * as Stream from "effect/Stream"; |
| 28 | 28 | import * as TestClock from "effect/testing/TestClock"; |
| 29 | 29 | |
| 30 | import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts"; | |
| 30 | import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts"; | |
| 31 | 31 | import * as EventSink from "./EventSink.ts"; |
| 32 | 32 | import * as EventStore from "./EventStore.ts"; |
| 33 | 33 | import * as IdAllocator from "./IdAllocator.ts"; |
| ... | ... | @@ -42,25 +42,25 @@ import { |
| 42 | 42 | selectInheritedBackgroundTurnItems, |
| 43 | 43 | } from "./RunExecutionService.ts"; |
| 44 | 44 | |
| 45 | const TestDatabaseLayer = SqlitePersistenceMemory; | |
| 46 | const TestStoresLayer = Layer.merge(EventStore.layer, ProjectionStore.layer).pipe( | |
| 47 | Layer.provide(TestDatabaseLayer), | |
| 45 | const layerTestDatabase = SqlitePersistence.layerMemory; | |
| 46 | const layerTestStores = Layer.merge(EventStore.layer, ProjectionStore.layer).pipe( | |
| 47 | Layer.provide(layerTestDatabase), | |
| 48 | 48 | ); |
| 49 | 49 | |
| 50 | const TestEventSinkLayer = EventSink.layer.pipe( | |
| 51 | Layer.provide(Layer.mergeAll(TestStoresLayer, TestDatabaseLayer)), | |
| 50 | const layerTestEventSink = EventSink.layer.pipe( | |
| 51 | Layer.provide(Layer.mergeAll(layerTestStores, layerTestDatabase)), | |
| 52 | 52 | ); |
| 53 | 53 | |
| 54 | const TestLayer = Layer.mergeAll( | |
| 55 | TestStoresLayer, | |
| 56 | TestEventSinkLayer, | |
| 54 | const layerTest = Layer.mergeAll( | |
| 55 | layerTestStores, | |
| 56 | layerTestEventSink, | |
| 57 | 57 | IdAllocator.layer, |
| 58 | 58 | ThreadCommandExecutor.layer, |
| 59 | 59 | ProviderEventIngestor.layer.pipe( |
| 60 | 60 | Layer.provide( |
| 61 | 61 | Layer.mergeAll( |
| 62 | TestStoresLayer, | |
| 63 | TestEventSinkLayer, | |
| 62 | layerTestStores, | |
| 63 | layerTestEventSink, | |
| 64 | 64 | IdAllocator.layer, |
| 65 | 65 | ThreadCommandExecutor.layer, |
| 66 | 66 | ), |
| ... | ... | @@ -133,11 +133,11 @@ function threadCreatedEvent( |
| 133 | 133 | }); |
| 134 | 134 | } |
| 135 | 135 | |
| 136 | const layer = it.layer(TestLayer); | |
| 136 | const layer = it.layer(layerTest); | |
| 137 | 137 | |
| 138 | 138 | it.effect("records accepted billed turn usage once without billing the context window", () => { |
| 139 | 139 | const recorded: Array<Readonly<Record<string, unknown>>> = []; |
| 140 | const analytics = Layer.succeed(ProviderEventIngestor.ProviderTurnAnalytics, { | |
| 140 | const layerAnalytics = Layer.succeed(ProviderEventIngestor.ProviderTurnAnalytics, { | |
| 141 | 141 | record: (properties: Readonly<Record<string, unknown>>) => |
| 142 | 142 | Effect.sync(() => { |
| 143 | 143 | recorded.push(properties); |
| ... | ... | @@ -232,7 +232,7 @@ it.effect("records accepted billed turn usage once without billing the context w |
| 232 | 232 | interactionMode: "default", |
| 233 | 233 | durationMs: 120, |
| 234 | 234 | }); |
| 235 | }).pipe(Effect.provide(TestLayer.pipe(Layer.provide(analytics)))); | |
| 235 | }).pipe(Effect.provide(layerTest.pipe(Layer.provide(layerAnalytics)))); | |
| 236 | 236 | }); |
| 237 | 237 | |
| 238 | 238 | layer("ProviderEventIngestorV2", (it) => { |
apps/server/src/orchestration-v2/ProviderEventIngestor.ts+1-1| ... | ... | @@ -79,7 +79,7 @@ export class ProviderTurnAnalytics extends Context.Reference<{ |
| 79 | 79 | defaultValue: () => ({ record: () => Effect.void }), |
| 80 | 80 | }) {} |
| 81 | 81 | |
| 82 | export const analyticsLive = Layer.effect( | |
| 82 | export const layerAnalytics = Layer.effect( | |
| 83 | 83 | ProviderTurnAnalytics, |
| 84 | 84 | Effect.gen(function* () { |
| 85 | 85 | const analytics = yield* AnalyticsService.AnalyticsService; |
apps/server/src/orchestration-v2/ProviderRuntimeRecoveryPerformance.test.ts+7-7| ... | ... | @@ -25,7 +25,7 @@ import * as Effect from "effect/Effect"; |
| 25 | 25 | import * as Layer from "effect/Layer"; |
| 26 | 26 | import * as SqlClient from "effect/sql/SqlClient"; |
| 27 | 27 | |
| 28 | import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts"; | |
| 28 | import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts"; | |
| 29 | 29 | import * as ServerSettings from "../serverSettings.ts"; |
| 30 | 30 | import { CodexProviderCapabilitiesV2 } from "./Adapters/CodexAdapterV2.ts"; |
| 31 | 31 | import * as EffectOutbox from "./EffectOutbox.ts"; |
| ... | ... | @@ -40,14 +40,14 @@ import * as ProviderRuntimeRecovery from "./ProviderRuntimeRecoveryService.ts"; |
| 40 | 40 | // `it.live` keeps a real clock: each reconcile gets a fresh command id, so the |
| 41 | 41 | // second recover cannot hide behind command receipt dedup. |
| 42 | 42 | |
| 43 | const stores = Layer.mergeAll( | |
| 43 | const layerStores = Layer.mergeAll( | |
| 44 | 44 | EventStore.layer, |
| 45 | 45 | ProjectionStore.layer, |
| 46 | 46 | EffectOutbox.layer, |
| 47 | 47 | IdAllocator.layer, |
| 48 | ).pipe(Layer.provideMerge(SqlitePersistenceMemory)); | |
| 49 | const TestLayer = ProviderRuntimeRecovery.layer.pipe( | |
| 50 | Layer.provideMerge(EventSink.layer.pipe(Layer.provideMerge(stores))), | |
| 48 | ).pipe(Layer.provideMerge(SqlitePersistence.layerMemory)); | |
| 49 | const layerTest = ProviderRuntimeRecovery.layer.pipe( | |
| 50 | Layer.provideMerge(EventSink.layer.pipe(Layer.provideMerge(layerStores))), | |
| 51 | 51 | Layer.provideMerge(ServerSettings.layerTest({ continueThreadsAfterServerUpdate: true })), |
| 52 | 52 | ); |
| 53 | 53 | |
| ... | ... | @@ -440,8 +440,8 @@ const measureCrash = Effect.fn(function* (scenario: Scenario) { |
| 440 | 440 | }); |
| 441 | 441 | |
| 442 | 442 | const measure = Effect.fn(function* (scenario: Scenario) { |
| 443 | const graceful = yield* measureGraceful(scenario).pipe(Effect.provide(Layer.fresh(TestLayer))); | |
| 444 | const crash = yield* measureCrash(scenario).pipe(Effect.provide(Layer.fresh(TestLayer))); | |
| 443 | const graceful = yield* measureGraceful(scenario).pipe(Effect.provide(Layer.fresh(layerTest))); | |
| 444 | const crash = yield* measureCrash(scenario).pipe(Effect.provide(Layer.fresh(layerTest))); | |
| 445 | 445 | return { ...scenario, ...graceful, ...crash } satisfies Measurement; |
| 446 | 446 | }); |
| 447 | 447 |
apps/server/src/orchestration-v2/ProviderSessionManager.test.ts+69-71| ... | ... | @@ -36,7 +36,7 @@ import * as ServerEnvironment from "../environment/ServerEnvironment.ts"; |
| 36 | 36 | import * as ProjectService from "../project/ProjectService.ts"; |
| 37 | 37 | import * as McpProviderSession from "../mcp/McpProviderSession.ts"; |
| 38 | 38 | import * as McpSessionRegistry from "../mcp/McpSessionRegistry.ts"; |
| 39 | import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts"; | |
| 39 | import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts"; | |
| 40 | 40 | import * as ServerSettings from "../serverSettings.ts"; |
| 41 | 41 | import { CodexProviderCapabilitiesV2 } from "./Adapters/CodexAdapterV2.ts"; |
| 42 | 42 | import * as EventSink from "./EventSink.ts"; |
| ... | ... | @@ -56,14 +56,14 @@ import * as ProviderEventIngestor from "./ProviderEventIngestor.ts"; |
| 56 | 56 | import * as ThreadCommandExecutor from "./ThreadCommandExecutor.ts"; |
| 57 | 57 | import * as ProviderSessionManager from "./ProviderSessionManager.ts"; |
| 58 | 58 | |
| 59 | const TestDatabaseLayer = SqlitePersistenceMemory; | |
| 60 | const TestStoresLayer = Layer.merge(EventStore.layer, ProjectionStore.layer).pipe( | |
| 61 | Layer.provide(TestDatabaseLayer), | |
| 59 | const layerTestDatabase = SqlitePersistence.layerMemory; | |
| 60 | const layerTestStores = Layer.merge(EventStore.layer, ProjectionStore.layer).pipe( | |
| 61 | Layer.provide(layerTestDatabase), | |
| 62 | 62 | ); |
| 63 | const TestEventSinkLayer = EventSink.layer.pipe( | |
| 64 | Layer.provide(Layer.mergeAll(TestStoresLayer, TestDatabaseLayer)), | |
| 63 | const layerTestEventSink = EventSink.layer.pipe( | |
| 64 | Layer.provide(Layer.mergeAll(layerTestStores, layerTestDatabase)), | |
| 65 | 65 | ); |
| 66 | const FailingReleaseEventSinkLayer = Layer.effect( | |
| 66 | const layerFailingReleaseEventSink = Layer.effect( | |
| 67 | 67 | EventSink.EventSinkV2, |
| 68 | 68 | Effect.gen(function* () { |
| 69 | 69 | const delegate = yield* EventSink.EventSinkV2; |
| ... | ... | @@ -79,7 +79,7 @@ const FailingReleaseEventSinkLayer = Layer.effect( |
| 79 | 79 | : delegate.write(input), |
| 80 | 80 | }); |
| 81 | 81 | }), |
| 82 | ).pipe(Layer.provide(TestEventSinkLayer)); | |
| 82 | ).pipe(Layer.provide(layerTestEventSink)); | |
| 83 | 83 | |
| 84 | 84 | interface FlakyReleaseWrites { |
| 85 | 85 | /** Which release writes fail right now. */ |
| ... | ... | @@ -94,7 +94,7 @@ interface FlakyReleaseWrites { |
| 94 | 94 | } |
| 95 | 95 | |
| 96 | 96 | // Fails release writes with a defect, the way a failed SQL commit surfaces. |
| 97 | const makeFlakyReleaseEventSinkLayer = (flaky: FlakyReleaseWrites) => | |
| 97 | const layerFlakyReleaseEventSink = (flaky: FlakyReleaseWrites) => | |
| 98 | 98 | Layer.effect( |
| 99 | 99 | EventSink.EventSinkV2, |
| 100 | 100 | Effect.gen(function* () { |
| ... | ... | @@ -125,7 +125,7 @@ const makeFlakyReleaseEventSinkLayer = (flaky: FlakyReleaseWrites) => |
| 125 | 125 | }), |
| 126 | 126 | }); |
| 127 | 127 | }), |
| 128 | ).pipe(Layer.provide(TestEventSinkLayer)); | |
| 128 | ).pipe(Layer.provide(layerTestEventSink)); | |
| 129 | 129 | |
| 130 | 130 | const CodexCapabilities: OrchestrationV2ProviderCapabilities = CodexProviderCapabilitiesV2; |
| 131 | 131 | const ExclusiveCapabilities: OrchestrationV2ProviderCapabilities = { |
| ... | ... | @@ -392,7 +392,7 @@ function makeProviderAdapter( |
| 392 | 392 | }; |
| 393 | 393 | } |
| 394 | 394 | |
| 395 | function makeTestLayer(input: { | |
| 395 | function layerTest(input: { | |
| 396 | 396 | readonly state: Ref.Ref<TestProviderRuntimeState>; |
| 397 | 397 | readonly idleTimeoutMs: number; |
| 398 | 398 | readonly maxIdlePinMs?: number; |
| ... | ... | @@ -413,13 +413,13 @@ function makeTestLayer(input: { |
| 413 | 413 | readonly serverSettingsLayer?: ReturnType<typeof ServerSettings.layerTest>; |
| 414 | 414 | readonly projectServiceLayer?: Layer.Layer<ProjectService.ProjectService>; |
| 415 | 415 | }) { |
| 416 | const configuredEventSinkLayer = | |
| 416 | const layerConfiguredEventSink = | |
| 417 | 417 | input.flakyReleaseWrites !== undefined |
| 418 | ? makeFlakyReleaseEventSinkLayer(input.flakyReleaseWrites) | |
| 418 | ? layerFlakyReleaseEventSink(input.flakyReleaseWrites) | |
| 419 | 419 | : input.failReleaseEventWrites |
| 420 | ? FailingReleaseEventSinkLayer | |
| 421 | : TestEventSinkLayer; | |
| 422 | const registryLayer = ProviderAdapterRegistry.makeSingleLayer( | |
| 420 | ? layerFailingReleaseEventSink | |
| 421 | : layerTestEventSink; | |
| 422 | const layerRegistry = ProviderAdapterRegistry.layerSingle( | |
| 423 | 423 | makeProviderAdapter(input.state, { |
| 424 | 424 | failEventStream: input.failEventStream ?? false, |
| 425 | 425 | ...(input.capabilities === undefined ? {} : { capabilities: input.capabilities }), |
| ... | ... | @@ -434,33 +434,33 @@ function makeTestLayer(input: { |
| 434 | 434 | ...(input.beforeUnload === undefined ? {} : { beforeUnload: input.beforeUnload }), |
| 435 | 435 | }), |
| 436 | 436 | ); |
| 437 | const providerEventIngestorTestLayer = ProviderEventIngestor.layer.pipe( | |
| 437 | const layerProviderEventIngestorTest = ProviderEventIngestor.layer.pipe( | |
| 438 | 438 | Layer.provide( |
| 439 | 439 | Layer.mergeAll( |
| 440 | configuredEventSinkLayer, | |
| 440 | layerConfiguredEventSink, | |
| 441 | 441 | IdAllocator.layer, |
| 442 | TestStoresLayer, | |
| 442 | layerTestStores, | |
| 443 | 443 | ThreadCommandExecutor.layer, |
| 444 | 444 | ), |
| 445 | 445 | ), |
| 446 | 446 | ); |
| 447 | 447 | return Layer.mergeAll( |
| 448 | TestStoresLayer, | |
| 449 | configuredEventSinkLayer, | |
| 448 | layerTestStores, | |
| 449 | layerConfiguredEventSink, | |
| 450 | 450 | IdAllocator.layer, |
| 451 | TestMcpRegistryLayer, | |
| 451 | layerTestMcpRegistry, | |
| 452 | 452 | ProviderSessionManager.layerWithOptions({ |
| 453 | 453 | idleTimeoutMs: input.idleTimeoutMs, |
| 454 | 454 | ...(input.maxIdlePinMs === undefined ? {} : { maxIdlePinMs: input.maxIdlePinMs }), |
| 455 | 455 | }).pipe( |
| 456 | 456 | Layer.provide( |
| 457 | 457 | Layer.mergeAll( |
| 458 | registryLayer, | |
| 459 | configuredEventSinkLayer, | |
| 458 | layerRegistry, | |
| 459 | layerConfiguredEventSink, | |
| 460 | 460 | IdAllocator.layer, |
| 461 | providerEventIngestorTestLayer, | |
| 462 | TestMcpRegistryLayer, | |
| 463 | TestStoresLayer, | |
| 461 | layerProviderEventIngestorTest, | |
| 462 | layerTestMcpRegistry, | |
| 463 | layerTestStores, | |
| 464 | 464 | ...(input.serverSettingsLayer === undefined ? [] : [input.serverSettingsLayer]), |
| 465 | 465 | ...(input.projectServiceLayer === undefined ? [] : [input.projectServiceLayer]), |
| 466 | 466 | ), |
| ... | ... | @@ -479,7 +479,7 @@ const fakeEnvironment = ServerEnvironment.ServerEnvironment.of({ |
| 479 | 479 | getDescriptor: Effect.die("unused"), |
| 480 | 480 | }); |
| 481 | 481 | |
| 482 | const TestMcpRegistryLayer = Layer.effect( | |
| 482 | const layerTestMcpRegistry = Layer.effect( | |
| 483 | 483 | McpSessionRegistry.McpSessionRegistry, |
| 484 | 484 | McpSessionRegistry.__testing.make(), |
| 485 | 485 | ).pipe( |
| ... | ... | @@ -520,7 +520,7 @@ function runBrowserAccessScenario(input: { |
| 520 | 520 | >([]); |
| 521 | 521 | const projectId = ProjectId.make("project-provider-session-manager-browser-access"); |
| 522 | 522 | const threadId = ThreadId.make("thread-provider-session-manager-browser-access"); |
| 523 | const projectServiceLayer = Layer.mock(ProjectService.ProjectService)({ | |
| 523 | const layerProjectService = Layer.mock(ProjectService.ProjectService)({ | |
| 524 | 524 | getById: (requestedProjectId) => |
| 525 | 525 | Effect.succeed( |
| 526 | 526 | input.projectExists === false |
| ... | ... | @@ -548,11 +548,11 @@ function runBrowserAccessScenario(input: { |
| 548 | 548 | .pipe(Effect.ignore); |
| 549 | 549 | }).pipe( |
| 550 | 550 | Effect.provide( |
| 551 | makeTestLayer({ | |
| 551 | layerTest({ | |
| 552 | 552 | state, |
| 553 | 553 | idleTimeoutMs: 1_000, |
| 554 | 554 | mcpConfigs, |
| 555 | projectServiceLayer, | |
| 555 | projectServiceLayer: layerProjectService, | |
| 556 | 556 | serverSettingsLayer: ServerSettings.layerTest({ |
| 557 | 557 | enableAgentBrowserAccess: input.enableAgentBrowserAccess, |
| 558 | 558 | projectSettingsOverrides: { |
| ... | ... | @@ -766,7 +766,7 @@ it.effect("ProviderSessionManagerV2 opens independent sessions concurrently", () |
| 766 | 766 | |
| 767 | 767 | yield* effect.pipe( |
| 768 | 768 | Effect.provide( |
| 769 | makeTestLayer({ | |
| 769 | layerTest({ | |
| 770 | 770 | state, |
| 771 | 771 | idleTimeoutMs: 60_000, |
| 772 | 772 | beforeOpen, |
| ... | ... | @@ -823,7 +823,7 @@ it.effect("ProviderSessionManagerV2 closes every live session for a provider ins |
| 823 | 823 | |
| 824 | 824 | yield* effect.pipe( |
| 825 | 825 | Effect.provide( |
| 826 | makeTestLayer({ | |
| 826 | layerTest({ | |
| 827 | 827 | state, |
| 828 | 828 | idleTimeoutMs: 60_000, |
| 829 | 829 | }), |
| ... | ... | @@ -882,7 +882,7 @@ it.effect("ProviderSessionManagerV2 opens a duplicate session only once", () => |
| 882 | 882 | |
| 883 | 883 | yield* effect.pipe( |
| 884 | 884 | Effect.provide( |
| 885 | makeTestLayer({ | |
| 885 | layerTest({ | |
| 886 | 886 | state, |
| 887 | 887 | idleTimeoutMs: 60_000, |
| 888 | 888 | beforeOpen, |
| ... | ... | @@ -923,7 +923,7 @@ it.effect("ProviderSessionManagerV2 releases live sessions when its layer shuts |
| 923 | 923 | |
| 924 | 924 | yield* effect.pipe( |
| 925 | 925 | Effect.provide( |
| 926 | makeTestLayer({ | |
| 926 | layerTest({ | |
| 927 | 927 | state, |
| 928 | 928 | idleTimeoutMs: 60_000, |
| 929 | 929 | }), |
| ... | ... | @@ -972,7 +972,7 @@ it.effect("ProviderSessionManagerV2 closes event subscriptions normally on serve |
| 972 | 972 | assert.isEmpty(yield* bufferedSubscription.events.pipe(Stream.runCollect)); |
| 973 | 973 | }); |
| 974 | 974 | |
| 975 | yield* effect.pipe(Effect.provide(makeTestLayer({ state, idleTimeoutMs: 60_000 }))); | |
| 975 | yield* effect.pipe(Effect.provide(layerTest({ state, idleTimeoutMs: 60_000 }))); | |
| 976 | 976 | }), |
| 977 | 977 | ); |
| 978 | 978 | |
| ... | ... | @@ -1040,7 +1040,7 @@ it.effect("ProviderSessionManagerV2 drains subscribers when the provider stops", |
| 1040 | 1040 | assert.equal((yield* Ref.get(state)).closeCount, 1); |
| 1041 | 1041 | }); |
| 1042 | 1042 | |
| 1043 | yield* effect.pipe(Effect.provide(makeTestLayer({ state, idleTimeoutMs: 60_000 }))); | |
| 1043 | yield* effect.pipe(Effect.provide(layerTest({ state, idleTimeoutMs: 60_000 }))); | |
| 1044 | 1044 | }), |
| 1045 | 1045 | ); |
| 1046 | 1046 | |
| ... | ... | @@ -1095,7 +1095,7 @@ it.effect( |
| 1095 | 1095 | |
| 1096 | 1096 | yield* effect.pipe( |
| 1097 | 1097 | Effect.provide( |
| 1098 | makeTestLayer({ | |
| 1098 | layerTest({ | |
| 1099 | 1099 | state, |
| 1100 | 1100 | idleTimeoutMs: 1_000, |
| 1101 | 1101 | mcpConfigs, |
| ... | ... | @@ -1150,7 +1150,7 @@ it.effect( |
| 1150 | 1150 | |
| 1151 | 1151 | yield* effect.pipe( |
| 1152 | 1152 | Effect.provide( |
| 1153 | makeTestLayer({ | |
| 1153 | layerTest({ | |
| 1154 | 1154 | state, |
| 1155 | 1155 | idleTimeoutMs: 1_000, |
| 1156 | 1156 | mcpConfigs, |
| ... | ... | @@ -1252,7 +1252,7 @@ it.effect("ProviderSessionManagerV2 revokes MCP credentials when release persist |
| 1252 | 1252 | |
| 1253 | 1253 | yield* effect.pipe( |
| 1254 | 1254 | Effect.provide( |
| 1255 | makeTestLayer({ | |
| 1255 | layerTest({ | |
| 1256 | 1256 | state, |
| 1257 | 1257 | idleTimeoutMs: 1_000, |
| 1258 | 1258 | mcpConfigs, |
| ... | ... | @@ -1322,7 +1322,7 @@ it.effect("ProviderSessionManagerV2 duplicate detach preserves replacement MCP c |
| 1322 | 1322 | |
| 1323 | 1323 | yield* effect.pipe( |
| 1324 | 1324 | Effect.provide( |
| 1325 | makeTestLayer({ | |
| 1325 | layerTest({ | |
| 1326 | 1326 | state, |
| 1327 | 1327 | idleTimeoutMs: 1_000, |
| 1328 | 1328 | capabilities: ExclusiveCapabilities, |
| ... | ... | @@ -1398,7 +1398,7 @@ it.effect( |
| 1398 | 1398 | |
| 1399 | 1399 | yield* effect.pipe( |
| 1400 | 1400 | Effect.provide( |
| 1401 | makeTestLayer({ | |
| 1401 | layerTest({ | |
| 1402 | 1402 | state, |
| 1403 | 1403 | idleTimeoutMs: 1_000, |
| 1404 | 1404 | capabilities: ExclusiveCapabilities, |
| ... | ... | @@ -1477,7 +1477,7 @@ it.effect( |
| 1477 | 1477 | |
| 1478 | 1478 | yield* effect.pipe( |
| 1479 | 1479 | Effect.provide( |
| 1480 | makeTestLayer({ | |
| 1480 | layerTest({ | |
| 1481 | 1481 | state, |
| 1482 | 1482 | idleTimeoutMs: 1_000, |
| 1483 | 1483 | mcpConfigs, |
| ... | ... | @@ -1537,9 +1537,7 @@ it.effect( |
| 1537 | 1537 | yield* manager.close(s1); |
| 1538 | 1538 | }); |
| 1539 | 1539 | |
| 1540 | yield* effect.pipe( | |
| 1541 | Effect.provide(makeTestLayer({ state, idleTimeoutMs: 1_000, mcpConfigs })), | |
| 1542 | ); | |
| 1540 | yield* effect.pipe(Effect.provide(layerTest({ state, idleTimeoutMs: 1_000, mcpConfigs }))); | |
| 1543 | 1541 | }), |
| 1544 | 1542 | ); |
| 1545 | 1543 | |
| ... | ... | @@ -1601,7 +1599,7 @@ it.effect( |
| 1601 | 1599 | |
| 1602 | 1600 | yield* effect.pipe( |
| 1603 | 1601 | Effect.provide( |
| 1604 | makeTestLayer({ | |
| 1602 | layerTest({ | |
| 1605 | 1603 | state, |
| 1606 | 1604 | idleTimeoutMs: 1_000, |
| 1607 | 1605 | mcpConfigs, |
| ... | ... | @@ -1656,7 +1654,7 @@ it.effect("ProviderSessionManagerV2 terminal detach revokes the thread's MCP cre |
| 1656 | 1654 | assert.isUndefined(McpProviderSession.readMcpProviderSession(threadId)); |
| 1657 | 1655 | }); |
| 1658 | 1656 | |
| 1659 | yield* effect.pipe(Effect.provide(makeTestLayer({ state, idleTimeoutMs: 1_000, mcpConfigs }))); | |
| 1657 | yield* effect.pipe(Effect.provide(layerTest({ state, idleTimeoutMs: 1_000, mcpConfigs }))); | |
| 1660 | 1658 | }), |
| 1661 | 1659 | ); |
| 1662 | 1660 | |
| ... | ... | @@ -1704,7 +1702,7 @@ it.effect("ProviderSessionManagerV2 releases idle sessions without sweeping all |
| 1704 | 1702 | assert.equal(projection.providerSessions.at(-1)?.status, "stopped"); |
| 1705 | 1703 | }); |
| 1706 | 1704 | |
| 1707 | yield* effect.pipe(Effect.provide(makeTestLayer({ state, idleTimeoutMs: 1000 }))); | |
| 1705 | yield* effect.pipe(Effect.provide(layerTest({ state, idleTimeoutMs: 1000 }))); | |
| 1708 | 1706 | }), |
| 1709 | 1707 | ); |
| 1710 | 1708 | |
| ... | ... | @@ -1750,7 +1748,7 @@ it.effect("ProviderSessionManagerV2 persists release when session scope close ha |
| 1750 | 1748 | }); |
| 1751 | 1749 | |
| 1752 | 1750 | yield* effect.pipe( |
| 1753 | Effect.provide(makeTestLayer({ state, idleTimeoutMs: 1000, hangSessionScopeClose: true })), | |
| 1751 | Effect.provide(layerTest({ state, idleTimeoutMs: 1000, hangSessionScopeClose: true })), | |
| 1754 | 1752 | ); |
| 1755 | 1753 | }), |
| 1756 | 1754 | ); |
| ... | ... | @@ -1799,7 +1797,7 @@ it.effect("ProviderSessionManagerV2 defers idle release while background work is |
| 1799 | 1797 | |
| 1800 | 1798 | yield* effect.pipe( |
| 1801 | 1799 | Effect.provide( |
| 1802 | makeTestLayer({ | |
| 1800 | layerTest({ | |
| 1803 | 1801 | state, |
| 1804 | 1802 | idleTimeoutMs: 1000, |
| 1805 | 1803 | hasPendingBackgroundWork: Ref.get(pendingWork), |
| ... | ... | @@ -1850,7 +1848,7 @@ it.effect("ProviderSessionManagerV2 releases pinned idle sessions once the pin c |
| 1850 | 1848 | |
| 1851 | 1849 | yield* effect.pipe( |
| 1852 | 1850 | Effect.provide( |
| 1853 | makeTestLayer({ | |
| 1851 | layerTest({ | |
| 1854 | 1852 | state, |
| 1855 | 1853 | idleTimeoutMs: 1000, |
| 1856 | 1854 | maxIdlePinMs: 3000, |
| ... | ... | @@ -1969,7 +1967,7 @@ it.effect( |
| 1969 | 1967 | |
| 1970 | 1968 | yield* effect.pipe( |
| 1971 | 1969 | Effect.provide( |
| 1972 | makeTestLayer({ | |
| 1970 | layerTest({ | |
| 1973 | 1971 | state, |
| 1974 | 1972 | idleTimeoutMs: 1000, |
| 1975 | 1973 | // Uninterruptible so the markBusy-triggered interrupt cannot land |
| ... | ... | @@ -2063,7 +2061,7 @@ it.effect("ProviderSessionManagerV2 does not apply a stale idle pin to a replace |
| 2063 | 2061 | |
| 2064 | 2062 | yield* effect.pipe( |
| 2065 | 2063 | Effect.provide( |
| 2066 | makeTestLayer({ | |
| 2064 | layerTest({ | |
| 2067 | 2065 | state, |
| 2068 | 2066 | idleTimeoutMs: 1000, |
| 2069 | 2067 | maxIdlePinMs: 60_000, |
| ... | ... | @@ -2176,7 +2174,7 @@ it.effect( |
| 2176 | 2174 | assert.equal(projection.providerSessions.at(-1)?.status, "stopped"); |
| 2177 | 2175 | }); |
| 2178 | 2176 | |
| 2179 | yield* effect.pipe(Effect.provide(makeTestLayer({ state, idleTimeoutMs: 1000 }))); | |
| 2177 | yield* effect.pipe(Effect.provide(layerTest({ state, idleTimeoutMs: 1000 }))); | |
| 2180 | 2178 | }), |
| 2181 | 2179 | ); |
| 2182 | 2180 | |
| ... | ... | @@ -2226,7 +2224,7 @@ it.effect("ProviderSessionManagerV2 uses the same release path for runtime failu |
| 2226 | 2224 | assert.equal(projection.providerSessions.at(-1)?.lastError, "process exited"); |
| 2227 | 2225 | }); |
| 2228 | 2226 | |
| 2229 | yield* effect.pipe(Effect.provide(makeTestLayer({ state, idleTimeoutMs: 1000 }))); | |
| 2227 | yield* effect.pipe(Effect.provide(layerTest({ state, idleTimeoutMs: 1000 }))); | |
| 2230 | 2228 | }), |
| 2231 | 2229 | ); |
| 2232 | 2230 | |
| ... | ... | @@ -2274,7 +2272,7 @@ it.effect("ProviderSessionManagerV2 releases sessions when provider event stream |
| 2274 | 2272 | |
| 2275 | 2273 | yield* effect.pipe( |
| 2276 | 2274 | Effect.provide( |
| 2277 | makeTestLayer({ | |
| 2275 | layerTest({ | |
| 2278 | 2276 | state, |
| 2279 | 2277 | idleTimeoutMs: 1000, |
| 2280 | 2278 | failEventStream: true, |
| ... | ... | @@ -2347,7 +2345,7 @@ it.effect("ProviderSessionManagerV2 marks pending runtime requests non-live on r |
| 2347 | 2345 | assert.equal(requestTurnItem?.status, "failed"); |
| 2348 | 2346 | }); |
| 2349 | 2347 | |
| 2350 | yield* effect.pipe(Effect.provide(makeTestLayer({ state, idleTimeoutMs: 1000 }))); | |
| 2348 | yield* effect.pipe(Effect.provide(layerTest({ state, idleTimeoutMs: 1000 }))); | |
| 2351 | 2349 | }), |
| 2352 | 2350 | ); |
| 2353 | 2351 | it.effect("ProviderSessionManagerV2 retries release records that failed to persist", () => |
| ... | ... | @@ -2408,7 +2406,7 @@ it.effect("ProviderSessionManagerV2 retries release records that failed to persi |
| 2408 | 2406 | }); |
| 2409 | 2407 | |
| 2410 | 2408 | yield* effect.pipe( |
| 2411 | Effect.provide(makeTestLayer({ state, idleTimeoutMs: 60_000, flakyReleaseWrites: flaky })), | |
| 2409 | Effect.provide(layerTest({ state, idleTimeoutMs: 60_000, flakyReleaseWrites: flaky })), | |
| 2412 | 2410 | ); |
| 2413 | 2411 | }), |
| 2414 | 2412 | ); |
| ... | ... | @@ -2481,7 +2479,7 @@ it.effect("ProviderSessionManagerV2 release retries leave a replacement session |
| 2481 | 2479 | }); |
| 2482 | 2480 | |
| 2483 | 2481 | yield* effect.pipe( |
| 2484 | Effect.provide(makeTestLayer({ state, idleTimeoutMs: 60_000, flakyReleaseWrites: flaky })), | |
| 2482 | Effect.provide(layerTest({ state, idleTimeoutMs: 60_000, flakyReleaseWrites: flaky })), | |
| 2485 | 2483 | ); |
| 2486 | 2484 | }), |
| 2487 | 2485 | ); |
| ... | ... | @@ -2572,7 +2570,7 @@ it.effect("ProviderSessionManagerV2 keeps each failed release's cleanup", () => |
| 2572 | 2570 | }); |
| 2573 | 2571 | |
| 2574 | 2572 | yield* effect.pipe( |
| 2575 | Effect.provide(makeTestLayer({ state, idleTimeoutMs: 60_000, flakyReleaseWrites: flaky })), | |
| 2573 | Effect.provide(layerTest({ state, idleTimeoutMs: 60_000, flakyReleaseWrites: flaky })), | |
| 2576 | 2574 | ); |
| 2577 | 2575 | }), |
| 2578 | 2576 | ); |
| ... | ... | @@ -2638,7 +2636,7 @@ it.effect("ProviderSessionManagerV2 settles a request the event pump persists du |
| 2638 | 2636 | }); |
| 2639 | 2637 | |
| 2640 | 2638 | yield* effect.pipe( |
| 2641 | Effect.provide(makeTestLayer({ state, idleTimeoutMs: 60_000, flakyReleaseWrites: flaky })), | |
| 2639 | Effect.provide(layerTest({ state, idleTimeoutMs: 60_000, flakyReleaseWrites: flaky })), | |
| 2642 | 2640 | ); |
| 2643 | 2641 | }), |
| 2644 | 2642 | ); |
| ... | ... | @@ -2712,7 +2710,7 @@ it.effect("ProviderSessionManagerV2 terminalizes a pending input transcript item |
| 2712 | 2710 | assert.equal(requestTurnItem?.status, "failed"); |
| 2713 | 2711 | }); |
| 2714 | 2712 | |
| 2715 | yield* effect.pipe(Effect.provide(makeTestLayer({ state, idleTimeoutMs: 1000 }))); | |
| 2713 | yield* effect.pipe(Effect.provide(layerTest({ state, idleTimeoutMs: 1000 }))); | |
| 2716 | 2714 | }), |
| 2717 | 2715 | ); |
| 2718 | 2716 | |
| ... | ... | @@ -2797,7 +2795,7 @@ it.effect("ProviderSessionManagerV2 persists session-scoped runtime requests wit |
| 2797 | 2795 | assert.equal(turnItem?.status, "waiting"); |
| 2798 | 2796 | }); |
| 2799 | 2797 | |
| 2800 | yield* effect.pipe(Effect.provide(makeTestLayer({ state, idleTimeoutMs: 1000 }))); | |
| 2798 | yield* effect.pipe(Effect.provide(layerTest({ state, idleTimeoutMs: 1000 }))); | |
| 2801 | 2799 | }), |
| 2802 | 2800 | ); |
| 2803 | 2801 | |
| ... | ... | @@ -2871,7 +2869,7 @@ it.effect( |
| 2871 | 2869 | |
| 2872 | 2870 | yield* effect.pipe( |
| 2873 | 2871 | Effect.provide( |
| 2874 | makeTestLayer({ | |
| 2872 | layerTest({ | |
| 2875 | 2873 | state, |
| 2876 | 2874 | idleTimeoutMs: 1000, |
| 2877 | 2875 | beforeOpen: (input) => |
| ... | ... | @@ -3025,7 +3023,7 @@ it.effect( |
| 3025 | 3023 | assert.equal((yield* Ref.get(state)).closeCount, 1); |
| 3026 | 3024 | }); |
| 3027 | 3025 | |
| 3028 | yield* effect.pipe(Effect.provide(makeTestLayer({ state, idleTimeoutMs: 1000 }))); | |
| 3026 | yield* effect.pipe(Effect.provide(layerTest({ state, idleTimeoutMs: 1000 }))); | |
| 3029 | 3027 | }), |
| 3030 | 3028 | ); |
| 3031 | 3029 | |
| ... | ... | @@ -3190,7 +3188,7 @@ it.effect( |
| 3190 | 3188 | assert.equal((yield* Ref.get(state)).closeCount, 1); |
| 3191 | 3189 | }); |
| 3192 | 3190 | |
| 3193 | yield* effect.pipe(Effect.provide(makeTestLayer({ state, idleTimeoutMs: 1000 }))); | |
| 3191 | yield* effect.pipe(Effect.provide(layerTest({ state, idleTimeoutMs: 1000 }))); | |
| 3194 | 3192 | }), |
| 3195 | 3193 | ); |
| 3196 | 3194 | |
| ... | ... | @@ -3294,7 +3292,7 @@ it.effect( |
| 3294 | 3292 | }); |
| 3295 | 3293 | |
| 3296 | 3294 | yield* effect.pipe( |
| 3297 | Effect.provide(makeTestLayer({ state, idleTimeoutMs: 1000, beforeUnload })), | |
| 3295 | Effect.provide(layerTest({ state, idleTimeoutMs: 1000, beforeUnload })), | |
| 3298 | 3296 | Effect.scoped, |
| 3299 | 3297 | ); |
| 3300 | 3298 | }), |
| ... | ... | @@ -3353,7 +3351,7 @@ it.effect( |
| 3353 | 3351 | |
| 3354 | 3352 | yield* effect.pipe( |
| 3355 | 3353 | Effect.provide( |
| 3356 | makeTestLayer({ state, idleTimeoutMs: 1000, capabilities: ExclusiveCapabilities }), | |
| 3354 | layerTest({ state, idleTimeoutMs: 1000, capabilities: ExclusiveCapabilities }), | |
| 3357 | 3355 | ), |
| 3358 | 3356 | ); |
| 3359 | 3357 | }), |
| ... | ... | @@ -3404,7 +3402,7 @@ it.effect.each(["missing", "file"] as const)( |
| 3404 | 3402 | (yield* projectionStore.getThreadProjection(threadId)).providerSessions, |
| 3405 | 3403 | [], |
| 3406 | 3404 | ); |
| 3407 | }).pipe(Effect.provide(makeTestLayer({ state, idleTimeoutMs: 60_000 }))); | |
| 3405 | }).pipe(Effect.provide(layerTest({ state, idleTimeoutMs: 60_000 }))); | |
| 3408 | 3406 | }).pipe(Effect.provide(NodeServices.layer)), |
| 3409 | 3407 | ); |
| 3410 | 3408 | |
| ... | ... | @@ -3454,7 +3452,7 @@ it.effect( |
| 3454 | 3452 | (yield* projectionStore.getThreadProjection(threadId)).providerSessions, |
| 3455 | 3453 | before.providerSessions, |
| 3456 | 3454 | ); |
| 3457 | }).pipe(Effect.provide(makeTestLayer({ state, idleTimeoutMs: 60_000 }))); | |
| 3455 | }).pipe(Effect.provide(layerTest({ state, idleTimeoutMs: 60_000 }))); | |
| 3458 | 3456 | }).pipe(Effect.provide(NodeServices.layer)), |
| 3459 | 3457 | ); |
| 3460 | 3458 |
apps/server/src/orchestration-v2/ProviderSwitchService.test.ts+12-12| ... | ... | @@ -65,7 +65,7 @@ function deadSessionRecord( |
| 65 | 65 | }; |
| 66 | 66 | } |
| 67 | 67 | |
| 68 | function testLayer( | |
| 68 | function layerTest( | |
| 69 | 69 | metadata: Readonly<Record<string, { continuationKey: string }>>, |
| 70 | 70 | planSelectionTransition: ProviderAdapterV2Shape["planSelectionTransition"] = () => |
| 71 | 71 | Effect.succeed({ type: "restart_session" }), |
| ... | ... | @@ -77,7 +77,7 @@ function testLayer( |
| 77 | 77 | planSelectionTransition, |
| 78 | 78 | openSession: () => Effect.die("ProviderSwitchService tests do not open sessions."), |
| 79 | 79 | }); |
| 80 | const registry = Layer.mock(ProviderAdapterRegistry.ProviderAdapterRegistryV2)({ | |
| 80 | const layerRegistry = Layer.mock(ProviderAdapterRegistry.ProviderAdapterRegistryV2)({ | |
| 81 | 81 | get: (instanceId) => |
| 82 | 82 | metadata[instanceId] === undefined |
| 83 | 83 | ? Effect.fail( |
| ... | ... | @@ -99,7 +99,7 @@ function testLayer( |
| 99 | 99 | }); |
| 100 | 100 | }, |
| 101 | 101 | }); |
| 102 | return ProviderSwitch.layer.pipe(Layer.provide(registry)); | |
| 102 | return ProviderSwitch.layer.pipe(Layer.provide(layerRegistry)); | |
| 103 | 103 | } |
| 104 | 104 | |
| 105 | 105 | it.effect( |
| ... | ... | @@ -115,7 +115,7 @@ it.effect( |
| 115 | 115 | assert.deepEqual(result.releaseProviderSessionIds, [currentSessionId]); |
| 116 | 116 | }).pipe( |
| 117 | 117 | Effect.provide( |
| 118 | testLayer({ [currentInstanceId]: { continuationKey: "codex:account:primary" } }), | |
| 118 | layerTest({ [currentInstanceId]: { continuationKey: "codex:account:primary" } }), | |
| 119 | 119 | ), |
| 120 | 120 | ), |
| 121 | 121 | ); |
| ... | ... | @@ -140,7 +140,7 @@ it.effect.each(["stopped", "error"] as const)( |
| 140 | 140 | assert.deepEqual(result.releaseProviderSessionIds, [currentSessionId]); |
| 141 | 141 | }).pipe( |
| 142 | 142 | Effect.provide( |
| 143 | testLayer({ [currentInstanceId]: { continuationKey: "codex:account:primary" } }), | |
| 143 | layerTest({ [currentInstanceId]: { continuationKey: "codex:account:primary" } }), | |
| 144 | 144 | ), |
| 145 | 145 | ), |
| 146 | 146 | ); |
| ... | ... | @@ -169,7 +169,7 @@ it.effect("releases the newest live session, not the newest record overall", () |
| 169 | 169 | assert.deepEqual(result.releaseProviderSessionIds, [newerLiveSessionId]); |
| 170 | 170 | }).pipe( |
| 171 | 171 | Effect.provide( |
| 172 | testLayer({ [currentInstanceId]: { continuationKey: "codex:account:primary" } }), | |
| 172 | layerTest({ [currentInstanceId]: { continuationKey: "codex:account:primary" } }), | |
| 173 | 173 | ), |
| 174 | 174 | ), |
| 175 | 175 | ); |
| ... | ... | @@ -192,7 +192,7 @@ it.effect("creates a fresh session with handoff when every recorded session is d |
| 192 | 192 | assert.deepEqual(result.releaseProviderSessionIds, []); |
| 193 | 193 | }).pipe( |
| 194 | 194 | Effect.provide( |
| 195 | testLayer({ [currentInstanceId]: { continuationKey: "codex:account:primary" } }), | |
| 195 | layerTest({ [currentInstanceId]: { continuationKey: "codex:account:primary" } }), | |
| 196 | 196 | ), |
| 197 | 197 | ), |
| 198 | 198 | ); |
| ... | ... | @@ -246,7 +246,7 @@ it.effect("falls back to the native provider thread when every recorded session |
| 246 | 246 | assert.deepEqual(result.releaseProviderSessionIds, []); |
| 247 | 247 | }).pipe( |
| 248 | 248 | Effect.provide( |
| 249 | testLayer({ [currentInstanceId]: { continuationKey: "codex:account:primary" } }), | |
| 249 | layerTest({ [currentInstanceId]: { continuationKey: "codex:account:primary" } }), | |
| 250 | 250 | ), |
| 251 | 251 | ), |
| 252 | 252 | ); |
| ... | ... | @@ -265,7 +265,7 @@ it.effect("hands off from a native provider thread after its session detaches", |
| 265 | 265 | assert.deepEqual(result.releaseProviderSessionIds, []); |
| 266 | 266 | }).pipe( |
| 267 | 267 | Effect.provide( |
| 268 | testLayer({ | |
| 268 | layerTest({ | |
| 269 | 269 | [currentInstanceId]: { continuationKey: "codex:account:primary" }, |
| 270 | 270 | codex_other: { continuationKey: "codex:account:other" }, |
| 271 | 271 | }), |
| ... | ... | @@ -290,7 +290,7 @@ it.effect.each(["stopped", "error"] as const)( |
| 290 | 290 | assert.deepEqual(result.releaseProviderSessionIds, []); |
| 291 | 291 | }).pipe( |
| 292 | 292 | Effect.provide( |
| 293 | testLayer({ [currentInstanceId]: { continuationKey: "codex:account:primary" } }, (input) => | |
| 293 | layerTest({ [currentInstanceId]: { continuationKey: "codex:account:primary" } }, (input) => | |
| 294 | 294 | Effect.succeed(acpSelectionTransition(input)), |
| 295 | 295 | ), |
| 296 | 296 | ), |
| ... | ... | @@ -309,7 +309,7 @@ it.effect("rejects a model change the dead record never negotiated support for", |
| 309 | 309 | assert.instanceOf(result, ProviderSwitch.ProviderSwitchPlanError); |
| 310 | 310 | }).pipe( |
| 311 | 311 | Effect.provide( |
| 312 | testLayer({ [currentInstanceId]: { continuationKey: "codex:account:primary" } }, (input) => | |
| 312 | layerTest({ [currentInstanceId]: { continuationKey: "codex:account:primary" } }, (input) => | |
| 313 | 313 | Effect.succeed(acpSelectionTransition(input)), |
| 314 | 314 | ), |
| 315 | 315 | ), |
| ... | ... | @@ -333,7 +333,7 @@ it.effect("distinguishes compatible and incompatible instances of the same drive |
| 333 | 333 | assert.equal(incompatible.transition.type, "create_with_handoff"); |
| 334 | 334 | }).pipe( |
| 335 | 335 | Effect.provide( |
| 336 | testLayer({ | |
| 336 | layerTest({ | |
| 337 | 337 | [currentInstanceId]: { continuationKey: "codex:account:primary" }, |
| 338 | 338 | codex_compatible: { continuationKey: "codex:account:primary" }, |
| 339 | 339 | codex_incompatible: { continuationKey: "codex:account:other" }, |
apps/server/src/orchestration-v2/ProviderTurnControlService.test.ts+6-6| ... | ... | @@ -207,7 +207,7 @@ it.effect( |
| 207 | 207 | rollbackThread: () => Effect.die("unused rollbackThread"), |
| 208 | 208 | forkThread: () => Effect.die("unused forkThread"), |
| 209 | 209 | }; |
| 210 | const projectionLayer = Layer.succeed( | |
| 210 | const layerProjection = Layer.succeed( | |
| 211 | 211 | ProjectionStore.ProjectionStoreV2, |
| 212 | 212 | ProjectionStore.ProjectionStoreV2.of({ |
| 213 | 213 | apply: () => Effect.void, |
| ... | ... | @@ -258,7 +258,7 @@ it.effect( |
| 258 | 258 | getThreadSnapshotWindow: () => Effect.die("unused getThreadSnapshotWindow"), |
| 259 | 259 | }), |
| 260 | 260 | ); |
| 261 | const sessionManagerLayer = Layer.succeed( | |
| 261 | const layerSessionManager = Layer.succeed( | |
| 262 | 262 | ProviderSessionManager.ProviderSessionManagerV2, |
| 263 | 263 | ProviderSessionManager.ProviderSessionManagerV2.of({ |
| 264 | 264 | shutdown: Effect.void, |
| ... | ... | @@ -273,8 +273,8 @@ it.effect( |
| 273 | 273 | detach: () => Effect.void, |
| 274 | 274 | }), |
| 275 | 275 | ); |
| 276 | const controlLayer = ProviderTurnControlService.layer.pipe( | |
| 277 | Layer.provide(Layer.merge(projectionLayer, sessionManagerLayer)), | |
| 276 | const layerControl = ProviderTurnControlService.layer.pipe( | |
| 277 | Layer.provide(Layer.merge(layerProjection, layerSessionManager)), | |
| 278 | 278 | ); |
| 279 | 279 | |
| 280 | 280 | const [ordinaryInterrupt, unrelatedRestart] = yield* Effect.gen(function* () { |
| ... | ... | @@ -298,7 +298,7 @@ it.effect( |
| 298 | 298 | }), |
| 299 | 299 | ); |
| 300 | 300 | return [ordinary, unrelated] as const; |
| 301 | }).pipe(Effect.provide(controlLayer)); | |
| 301 | }).pipe(Effect.provide(layerControl)); | |
| 302 | 302 | |
| 303 | 303 | assert.isTrue(Exit.isFailure(ordinaryInterrupt)); |
| 304 | 304 | assert.isTrue(Exit.isFailure(unrelatedRestart)); |
| ... | ... | @@ -314,7 +314,7 @@ it.effect( |
| 314 | 314 | providerTurnId, |
| 315 | 315 | interruptedAttemptId: attemptId, |
| 316 | 316 | }); |
| 317 | }).pipe(Effect.provide(controlLayer)); | |
| 317 | }).pipe(Effect.provide(layerControl)); | |
| 318 | 318 | |
| 319 | 319 | const interrupted = yield* Ref.get(interruptedThread); |
| 320 | 320 | assert.isNotNull(interrupted); |
apps/server/src/orchestration-v2/ProviderTurnStartService.testkit.ts+1-1| ... | ... | @@ -5,7 +5,7 @@ import * as Option from "effect/Option"; |
| 5 | 5 | import * as GitWorkflow from "../git/GitWorkflowService.ts"; |
| 6 | 6 | import * as ProjectService from "../project/ProjectService.ts"; |
| 7 | 7 | |
| 8 | export const worktreeRepairDependenciesTestLayer = Layer.merge( | |
| 8 | export const layer = Layer.merge( | |
| 9 | 9 | Layer.mock(GitWorkflow.GitWorkflowService)({ |
| 10 | 10 | pruneWorktrees: () => Effect.void, |
| 11 | 11 | createWorktree: () => Effect.succeed({} as never), |
apps/server/src/orchestration-v2/PullRequestSyncReactor.test.ts+2-2| ... | ... | @@ -213,7 +213,7 @@ const makeHarness = Effect.fn("makePullRequestSyncHarness")(function* (options: |
| 213 | 213 | return Effect.die(new Error(`Unexpected command: ${command.type}`)); |
| 214 | 214 | }; |
| 215 | 215 | |
| 216 | const dependencies = Layer.mergeAll( | |
| 216 | const layerDependencies = Layer.mergeAll( | |
| 217 | 217 | Layer.mock(PullRequestService.PullRequestService)({ |
| 218 | 218 | summary, |
| 219 | 219 | stack, |
| ... | ... | @@ -269,7 +269,7 @@ const makeHarness = Effect.fn("makePullRequestSyncHarness")(function* (options: |
| 269 | 269 | summaryCalls, |
| 270 | 270 | stackCalls, |
| 271 | 271 | domainEvents, |
| 272 | layer: PullRequestSyncReactor.layer.pipe(Layer.provide(dependencies)), | |
| 272 | layer: PullRequestSyncReactor.layer.pipe(Layer.provide(layerDependencies)), | |
| 273 | 273 | }; |
| 274 | 274 | }); |
| 275 | 275 |
apps/server/src/orchestration-v2/ResourceCleanupService.ts+1-1| ... | ... | @@ -30,7 +30,7 @@ export class ResourceCleanupService extends Context.Reference<{ |
| 30 | 30 | }), |
| 31 | 31 | }) {} |
| 32 | 32 | |
| 33 | export const live = Layer.effect( | |
| 33 | export const layer = Layer.effect( | |
| 34 | 34 | ResourceCleanupService, |
| 35 | 35 | Effect.gen(function* () { |
| 36 | 36 | const terminals = yield* TerminalManager.TerminalManager; |
apps/server/src/orchestration-v2/RestartContinuation.test.ts+16-7| ... | ... | @@ -319,7 +319,7 @@ it.effect("does not duplicate delivery and yields to newer user work or opt-out" |
| 319 | 319 | const commands: Parameters< |
| 320 | 320 | ThreadManagementService.ThreadManagementService["Service"]["dispatch"] |
| 321 | 321 | >[0][] = []; |
| 322 | const threads = Layer.mock(ThreadManagementService.ThreadManagementService)({ | |
| 322 | const layerThreads = Layer.mock(ThreadManagementService.ThreadManagementService)({ | |
| 323 | 323 | getThreadRecords: () => Effect.succeed(projection), |
| 324 | 324 | recoverDelegatedTask: () => Effect.void, |
| 325 | 325 | dispatch: (command) => { |
| ... | ... | @@ -329,12 +329,16 @@ it.effect("does not duplicate delivery and yields to newer user work or opt-out" |
| 329 | 329 | return Effect.succeed({} as never); |
| 330 | 330 | }, |
| 331 | 331 | }); |
| 332 | const enabled = Layer.merge( | |
| 333 | threads, | |
| 332 | const layerEnabled = Layer.merge( | |
| 333 | layerThreads, | |
| 334 | 334 | ServerSettings.layerTest({ continueThreadsAfterServerUpdate: true }), |
| 335 | 335 | ); |
| 336 | yield* continueRestartedRun({ threadId, sourceRunId: runId }).pipe(Effect.provide(enabled)); | |
| 337 | yield* continueRestartedRun({ threadId, sourceRunId: runId }).pipe(Effect.provide(enabled)); | |
| 336 | yield* continueRestartedRun({ threadId, sourceRunId: runId }).pipe( | |
| 337 | Effect.provide(layerEnabled), | |
| 338 | ); | |
| 339 | yield* continueRestartedRun({ threadId, sourceRunId: runId }).pipe( | |
| 340 | Effect.provide(layerEnabled), | |
| 341 | ); | |
| 338 | 342 | assert.lengthOf(commands, 1); |
| 339 | 343 | assert.match(String(commands[0]!.commandId), /run:restart$/); |
| 340 | 344 | if (commands[0]!.type === "message.dispatch") |
| ... | ... | @@ -352,12 +356,17 @@ it.effect("does not duplicate delivery and yields to newer user work or opt-out" |
| 352 | 356 | }, |
| 353 | 357 | ], |
| 354 | 358 | }; |
| 355 | yield* continueRestartedRun({ threadId, sourceRunId: runId }).pipe(Effect.provide(enabled)); | |
| 359 | yield* continueRestartedRun({ threadId, sourceRunId: runId }).pipe( | |
| 360 | Effect.provide(layerEnabled), | |
| 361 | ); | |
| 356 | 362 | assert.lengthOf(commands, 1); |
| 357 | 363 | projection = { ...projection, runs: [projection.runs[0]!] }; |
| 358 | 364 | yield* continueRestartedRun({ threadId, sourceRunId: runId }).pipe( |
| 359 | 365 | Effect.provide( |
| 360 | Layer.merge(threads, ServerSettings.layerTest({ continueThreadsAfterServerUpdate: false })), | |
| 366 | Layer.merge( | |
| 367 | layerThreads, | |
| 368 | ServerSettings.layerTest({ continueThreadsAfterServerUpdate: false }), | |
| 369 | ), | |
| 361 | 370 | ), |
| 362 | 371 | ); |
| 363 | 372 | assert.lengthOf(commands, 1); |
apps/server/src/orchestration-v2/RunCompletionReads.test.ts+6-6| ... | ... | @@ -13,12 +13,12 @@ import * as DateTime from "effect/DateTime"; |
| 13 | 13 | import * as Effect from "effect/Effect"; |
| 14 | 14 | import * as Layer from "effect/Layer"; |
| 15 | 15 | import * as SqlClient from "effect/sql/SqlClient"; |
| 16 | import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts"; | |
| 16 | import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts"; | |
| 17 | 17 | import * as ProjectionStore from "./ProjectionStore.ts"; |
| 18 | 18 | |
| 19 | const databaseLayer = Layer.mergeAll( | |
| 20 | SqlitePersistenceMemory, | |
| 21 | ProjectionStore.layer.pipe(Layer.provide(SqlitePersistenceMemory)), | |
| 19 | const layerDatabase = Layer.mergeAll( | |
| 20 | SqlitePersistence.layerMemory, | |
| 21 | ProjectionStore.layer.pipe(Layer.provide(SqlitePersistence.layerMemory)), | |
| 22 | 22 | ); |
| 23 | 23 | |
| 24 | 24 | it.effect.each(["sqlite", "memory"] as const)( |
| ... | ... | @@ -155,8 +155,8 @@ it.effect.each(["sqlite", "memory"] as const)( |
| 155 | 155 | }).pipe( |
| 156 | 156 | Effect.provide( |
| 157 | 157 | storage === "sqlite" |
| 158 | ? databaseLayer | |
| 159 | : Layer.merge(SqlitePersistenceMemory, ProjectionStore.layerMemory), | |
| 158 | ? layerDatabase | |
| 159 | : Layer.merge(SqlitePersistence.layerMemory, ProjectionStore.layerMemory), | |
| 160 | 160 | ), |
| 161 | 161 | ), |
| 162 | 162 | ); |
apps/server/src/orchestration-v2/RunExecutionService.test.ts+28-28| ... | ... | @@ -57,7 +57,7 @@ import * as RunFinalizationService from "./RunFinalizationService.ts"; |
| 57 | 57 | |
| 58 | 58 | const driver = ProviderDriverKind.make("codex"); |
| 59 | 59 | |
| 60 | const RunExecutionTestLayer = RunExecutionService.layer.pipe( | |
| 60 | const layerRunExecutionTest = RunExecutionService.layer.pipe( | |
| 61 | 61 | Layer.provide( |
| 62 | 62 | Layer.mergeAll( |
| 63 | 63 | Layer.mock(CheckpointService.CheckpointServiceV2)({ captureBaseline: () => Effect.void }), |
| ... | ... | @@ -588,7 +588,7 @@ it.effect("rechecks run ownership immediately before calling the provider", () = |
| 588 | 588 | |
| 589 | 589 | assert.equal(yield* Ref.get(guardCalls), 2); |
| 590 | 590 | assert.equal(yield* Ref.get(providerStarts), 0); |
| 591 | }).pipe(Effect.provide(RunExecutionTestLayer)), | |
| 591 | }).pipe(Effect.provide(layerRunExecutionTest)), | |
| 592 | 592 | ); |
| 593 | 593 | |
| 594 | 594 | it.effect("fails the run when its ownership check cannot be read before calling the provider", () => |
| ... | ... | @@ -600,7 +600,7 @@ it.effect("fails the run when its ownership check cannot be read before calling |
| 600 | 600 | const runId = RunId.make("run:run-execution-start-guard-read"); |
| 601 | 601 | const attemptId = RunAttemptId.make("attempt:run-execution-start-guard-read"); |
| 602 | 602 | const providerInstanceId = ProviderInstanceId.make("codex"); |
| 603 | const testLayer = RunExecutionService.layer.pipe( | |
| 603 | const layerTest = RunExecutionService.layer.pipe( | |
| 604 | 604 | Layer.provide( |
| 605 | 605 | Layer.mergeAll( |
| 606 | 606 | Layer.mock(CheckpointService.CheckpointServiceV2)({ captureBaseline: () => Effect.void }), |
| ... | ... | @@ -686,7 +686,7 @@ it.effect("fails the run when its ownership check cannot be read before calling |
| 686 | 686 | }, |
| 687 | 687 | }, |
| 688 | 688 | }); |
| 689 | }).pipe(Effect.provide(testLayer)); | |
| 689 | }).pipe(Effect.provide(layerTest)); | |
| 690 | 690 | |
| 691 | 691 | assert.equal(yield* Ref.get(guardCalls), 2); |
| 692 | 692 | assert.equal(yield* Ref.get(providerStarts), 0); |
| ... | ... | @@ -778,7 +778,7 @@ it.effect( |
| 778 | 778 | calls, |
| 779 | 779 | cases.map((testCase) => testCase.expected), |
| 780 | 780 | ); |
| 781 | }).pipe(Effect.provide(RunExecutionTestLayer)), | |
| 781 | }).pipe(Effect.provide(layerRunExecutionTest)), | |
| 782 | 782 | ); |
| 783 | 783 | |
| 784 | 784 | it.effect("refreshes MCP credential liveness before calling the provider", () => |
| ... | ... | @@ -849,7 +849,7 @@ it.effect("refreshes MCP credential liveness before calling the provider", () => |
| 849 | 849 | .pipe(Effect.ensuring(Effect.sync(() => touchActiveMcpThread.mockRestore()))); |
| 850 | 850 | |
| 851 | 851 | assert.deepEqual(yield* Ref.get(order), [`touch:${threadId}`, "start-turn"]); |
| 852 | }).pipe(Effect.provide(RunExecutionTestLayer)), | |
| 852 | }).pipe(Effect.provide(layerRunExecutionTest)), | |
| 853 | 853 | ); |
| 854 | 854 | |
| 855 | 855 | it.effect("starts the provider when checkpoint baseline capture fails", () => |
| ... | ... | @@ -873,7 +873,7 @@ it.effect("starts the provider when checkpoint baseline capture fails", () => |
| 873 | 873 | effects: ReadonlyArray<PendingOrchestrationEffectV2>; |
| 874 | 874 | }> |
| 875 | 875 | >([]); |
| 876 | const testLayer = RunExecutionService.layer.pipe( | |
| 876 | const layerTest = RunExecutionService.layer.pipe( | |
| 877 | 877 | Layer.provide( |
| 878 | 878 | Layer.mergeAll( |
| 879 | 879 | Layer.mock(CheckpointService.CheckpointServiceV2)({ |
| ... | ... | @@ -950,7 +950,7 @@ it.effect("starts the provider when checkpoint baseline capture fails", () => |
| 950 | 950 | }, |
| 951 | 951 | }, |
| 952 | 952 | }); |
| 953 | }).pipe(Effect.provide(testLayer)); | |
| 953 | }).pipe(Effect.provide(layerTest)); | |
| 954 | 954 | |
| 955 | 955 | assert.equal(yield* Ref.get(providerStarts), 1); |
| 956 | 956 | |
| ... | ... | @@ -990,7 +990,7 @@ it.effect.each(["failure", "interruption", "stale-attempt", "start-guard"] as co |
| 990 | 990 | const refreshes = yield* Ref.make(0); |
| 991 | 991 | const guardedWrites = yield* Ref.make(0); |
| 992 | 992 | const writes = yield* Ref.make<ReadonlyArray<ReadonlyArray<OrchestrationV2DomainEvent>>>([]); |
| 993 | const testLayer = RunExecutionService.layer.pipe( | |
| 993 | const layerTest = RunExecutionService.layer.pipe( | |
| 994 | 994 | Layer.provide( |
| 995 | 995 | Layer.mergeAll( |
| 996 | 996 | Layer.mock(CheckpointService.CheckpointServiceV2)({ |
| ... | ... | @@ -1092,7 +1092,7 @@ it.effect.each(["failure", "interruption", "stale-attempt", "start-guard"] as co |
| 1092 | 1092 | }, |
| 1093 | 1093 | }, |
| 1094 | 1094 | }); |
| 1095 | }).pipe(Effect.provide(testLayer), Effect.exit); | |
| 1095 | }).pipe(Effect.provide(layerTest), Effect.exit); | |
| 1096 | 1096 | |
| 1097 | 1097 | assert.equal(yield* Ref.get(providerStarts), 0); |
| 1098 | 1098 | const events = (yield* Ref.get(writes)).flat(); |
| ... | ... | @@ -1160,7 +1160,7 @@ it.effect("keeps ingesting owned child events after the root turn terminalizes", |
| 1160 | 1160 | const subagentNodeId = NodeId.make("node:run-execution-late-child:subagent"); |
| 1161 | 1161 | const childMessageIngested = yield* Deferred.make<void>(); |
| 1162 | 1162 | const order = yield* Ref.make<ReadonlyArray<string>>([]); |
| 1163 | const testLayer = RunExecutionService.layer.pipe( | |
| 1163 | const layerTest = RunExecutionService.layer.pipe( | |
| 1164 | 1164 | Layer.provide( |
| 1165 | 1165 | Layer.mergeAll( |
| 1166 | 1166 | Layer.mock(CheckpointService.CheckpointServiceV2)({ captureBaseline: () => Effect.void }), |
| ... | ... | @@ -1337,7 +1337,7 @@ it.effect("keeps ingesting owned child events after the root turn terminalizes", |
| 1337 | 1337 | }, |
| 1338 | 1338 | }, |
| 1339 | 1339 | }); |
| 1340 | }).pipe(Effect.provide(testLayer)); | |
| 1340 | }).pipe(Effect.provide(layerTest)); | |
| 1341 | 1341 | |
| 1342 | 1342 | const observed = yield* Deferred.await(childMessageIngested).pipe( |
| 1343 | 1343 | Effect.timeoutOption("2 seconds"), |
| ... | ... | @@ -1585,7 +1585,7 @@ it.effect( |
| 1585 | 1585 | }> |
| 1586 | 1586 | >([]); |
| 1587 | 1587 | const ingestionDone = yield* Deferred.make<void>(); |
| 1588 | const testLayer = RunExecutionService.layer.pipe( | |
| 1588 | const layerTest = RunExecutionService.layer.pipe( | |
| 1589 | 1589 | Layer.provide( |
| 1590 | 1590 | Layer.mergeAll( |
| 1591 | 1591 | Layer.mock(CheckpointService.CheckpointServiceV2)({ |
| ... | ... | @@ -1750,7 +1750,7 @@ it.effect( |
| 1750 | 1750 | }, |
| 1751 | 1751 | }, |
| 1752 | 1752 | }); |
| 1753 | }).pipe(Effect.provide(testLayer)); | |
| 1753 | }).pipe(Effect.provide(layerTest)); | |
| 1754 | 1754 | |
| 1755 | 1755 | const closed = yield* Deferred.await(ingestionDone).pipe(Effect.timeoutOption("2 seconds")); |
| 1756 | 1756 | assert.isTrue(Option.isSome(closed), "event subscription did not release"); |
| ... | ... | @@ -1802,7 +1802,7 @@ it.effect("drops late root provider-thread writes from a superseded attempt", () |
| 1802 | 1802 | readonly committed: boolean; |
| 1803 | 1803 | }> |
| 1804 | 1804 | >([]); |
| 1805 | const testLayer = RunExecutionService.layer.pipe( | |
| 1805 | const layerTest = RunExecutionService.layer.pipe( | |
| 1806 | 1806 | Layer.provide( |
| 1807 | 1807 | Layer.mergeAll( |
| 1808 | 1808 | Layer.mock(CheckpointService.CheckpointServiceV2)({ captureBaseline: () => Effect.void }), |
| ... | ... | @@ -1966,7 +1966,7 @@ it.effect("drops late root provider-thread writes from a superseded attempt", () |
| 1966 | 1966 | }, |
| 1967 | 1967 | }, |
| 1968 | 1968 | }); |
| 1969 | }).pipe(Effect.provide(testLayer)); | |
| 1969 | }).pipe(Effect.provide(layerTest)); | |
| 1970 | 1970 | |
| 1971 | 1971 | const closed = yield* Deferred.await(ingestionDone).pipe(Effect.timeoutOption("2 seconds")); |
| 1972 | 1972 | assert.isTrue( |
| ... | ... | @@ -2006,7 +2006,7 @@ it.effect( |
| 2006 | 2006 | // Probe stays true forever; open background items must pin the stream |
| 2007 | 2007 | // past ownership-loss so late turn_item completions still land. |
| 2008 | 2008 | const ingestionDone = yield* Deferred.make<void>(); |
| 2009 | const testLayer = RunExecutionService.layer.pipe( | |
| 2009 | const layerTest = RunExecutionService.layer.pipe( | |
| 2010 | 2010 | Layer.provide( |
| 2011 | 2011 | Layer.mergeAll( |
| 2012 | 2012 | Layer.mock(CheckpointService.CheckpointServiceV2)({ |
| ... | ... | @@ -2149,7 +2149,7 @@ it.effect( |
| 2149 | 2149 | }, |
| 2150 | 2150 | }, |
| 2151 | 2151 | }); |
| 2152 | }).pipe(Effect.provide(testLayer)); | |
| 2152 | }).pipe(Effect.provide(layerTest)); | |
| 2153 | 2153 | |
| 2154 | 2154 | const closed = yield* Deferred.await(ingestionDone).pipe(Effect.timeoutOption("2 seconds")); |
| 2155 | 2155 | assert.isTrue( |
| ... | ... | @@ -2177,7 +2177,7 @@ it.effect( |
| 2177 | 2177 | const observed = yield* Ref.make<ReadonlyArray<string>>([]); |
| 2178 | 2178 | const ingestionDone = yield* Deferred.make<void>(); |
| 2179 | 2179 | const scopedProbeArgs = yield* Ref.make<ReadonlyArray<ProviderThreadId>>([]); |
| 2180 | const testLayer = RunExecutionService.layer.pipe( | |
| 2180 | const layerTest = RunExecutionService.layer.pipe( | |
| 2181 | 2181 | Layer.provide( |
| 2182 | 2182 | Layer.mergeAll( |
| 2183 | 2183 | Layer.mock(CheckpointService.CheckpointServiceV2)({ |
| ... | ... | @@ -2319,7 +2319,7 @@ it.effect( |
| 2319 | 2319 | }, |
| 2320 | 2320 | }, |
| 2321 | 2321 | }); |
| 2322 | }).pipe(Effect.provide(testLayer)); | |
| 2322 | }).pipe(Effect.provide(layerTest)); | |
| 2323 | 2323 | |
| 2324 | 2324 | const closed = yield* Deferred.await(ingestionDone).pipe(Effect.timeoutOption("2 seconds")); |
| 2325 | 2325 | assert.isTrue(Option.isSome(closed), "event subscription did not release"); |
| ... | ... | @@ -2346,7 +2346,7 @@ it.effect( |
| 2346 | 2346 | const written = yield* Ref.make<ReadonlyArray<OrchestrationV2DomainEvent>>([]); |
| 2347 | 2347 | const ingested = yield* Ref.make<ReadonlyArray<ProviderAdapterV2Event>>([]); |
| 2348 | 2348 | const ingestionDone = yield* Deferred.make<void>(); |
| 2349 | const testLayer = RunExecutionService.layer.pipe( | |
| 2349 | const layerTest = RunExecutionService.layer.pipe( | |
| 2350 | 2350 | Layer.provide( |
| 2351 | 2351 | Layer.mergeAll( |
| 2352 | 2352 | Layer.mock(CheckpointService.CheckpointServiceV2)({ |
| ... | ... | @@ -2559,7 +2559,7 @@ it.effect( |
| 2559 | 2559 | }, |
| 2560 | 2560 | }, |
| 2561 | 2561 | }); |
| 2562 | }).pipe(Effect.provide(testLayer)); | |
| 2562 | }).pipe(Effect.provide(layerTest)); | |
| 2563 | 2563 | |
| 2564 | 2564 | const closed = yield* Deferred.await(ingestionDone).pipe(Effect.timeoutOption("2 seconds")); |
| 2565 | 2565 | assert.isTrue(Option.isSome(closed), "event ingestion fiber did not finish"); |
| ... | ... | @@ -2719,7 +2719,7 @@ it.effect( |
| 2719 | 2719 | const providerInstanceId = ProviderInstanceId.make("codex"); |
| 2720 | 2720 | const written = yield* Ref.make<ReadonlyArray<OrchestrationV2DomainEvent>>([]); |
| 2721 | 2721 | const ingestionDone = yield* Deferred.make<void>(); |
| 2722 | const testLayer = RunExecutionService.layer.pipe( | |
| 2722 | const layerTest = RunExecutionService.layer.pipe( | |
| 2723 | 2723 | Layer.provide( |
| 2724 | 2724 | Layer.mergeAll( |
| 2725 | 2725 | Layer.mock(CheckpointService.CheckpointServiceV2)({ |
| ... | ... | @@ -2889,7 +2889,7 @@ it.effect( |
| 2889 | 2889 | }, |
| 2890 | 2890 | }, |
| 2891 | 2891 | }); |
| 2892 | }).pipe(Effect.provide(testLayer)); | |
| 2892 | }).pipe(Effect.provide(layerTest)); | |
| 2893 | 2893 | |
| 2894 | 2894 | const closed = yield* Deferred.await(ingestionDone).pipe(Effect.timeoutOption("2 seconds")); |
| 2895 | 2895 | assert.isTrue(Option.isSome(closed), "event ingestion fiber did not finish"); |
| ... | ... | @@ -3416,7 +3416,7 @@ function captureRootRunTermination(input: { |
| 3416 | 3416 | } |
| 3417 | 3417 | } |
| 3418 | 3418 | }); |
| 3419 | const testLayer = RunExecutionService.layer.pipe( | |
| 3419 | const layerTest = RunExecutionService.layer.pipe( | |
| 3420 | 3420 | Layer.provide( |
| 3421 | 3421 | Layer.mergeAll( |
| 3422 | 3422 | Layer.mock(CheckpointService.CheckpointServiceV2)({ captureBaseline: () => Effect.void }), |
| ... | ... | @@ -3540,7 +3540,7 @@ function captureRootRunTermination(input: { |
| 3540 | 3540 | }, |
| 3541 | 3541 | }, |
| 3542 | 3542 | }); |
| 3543 | }).pipe(Effect.provide(testLayer)); | |
| 3543 | }).pipe(Effect.provide(layerTest)); | |
| 3544 | 3544 | |
| 3545 | 3545 | yield* Deferred.await(ingestionDone); |
| 3546 | 3546 | return { written: yield* Ref.get(writtenItems), observed: yield* Ref.get(observed) }; |
| ... | ... | @@ -3876,7 +3876,7 @@ function runBackgroundItemScenario( |
| 3876 | 3876 | const providerInstanceId = ProviderInstanceId.make("codex"); |
| 3877 | 3877 | const observed = yield* Ref.make<ReadonlyArray<string>>([]); |
| 3878 | 3878 | const ingestionDone = yield* Deferred.make<void>(); |
| 3879 | const testLayer = RunExecutionService.layer.pipe( | |
| 3879 | const layerTest = RunExecutionService.layer.pipe( | |
| 3880 | 3880 | Layer.provide( |
| 3881 | 3881 | Layer.mergeAll( |
| 3882 | 3882 | Layer.mock(CheckpointService.CheckpointServiceV2)({ captureBaseline: () => Effect.void }), |
| ... | ... | @@ -3986,7 +3986,7 @@ function runBackgroundItemScenario( |
| 3986 | 3986 | }, |
| 3987 | 3987 | }, |
| 3988 | 3988 | }); |
| 3989 | }).pipe(Effect.provide(testLayer)); | |
| 3989 | }).pipe(Effect.provide(layerTest)); | |
| 3990 | 3990 | |
| 3991 | 3991 | const closed = yield* Deferred.await(ingestionDone).pipe(Effect.timeoutOption("2 seconds")); |
| 3992 | 3992 | assert.isTrue(Option.isSome(closed), "event ingestion fiber did not finish"); |
apps/server/src/orchestration-v2/RunFinalizationService.test.ts+1-1| ... | ... | @@ -87,7 +87,7 @@ it.effect.each( |
| 87 | 87 | const refreshed: string[] = []; |
| 88 | 88 | const threadId = ThreadId.make("thread-pr-refresh"); |
| 89 | 89 | const runId = RunId.make("completed-run"); |
| 90 | const layer = RunFinalization.observerLive.pipe( | |
| 90 | const layer = RunFinalization.layerObserver.pipe( | |
| 91 | 91 | Layer.provide( |
| 92 | 92 | Layer.mergeAll( |
| 93 | 93 | Layer.mock(WorkspaceEntries.WorkspaceEntries)({ refresh: () => Effect.void }), |
apps/server/src/orchestration-v2/RunFinalizationService.ts+1-1| ... | ... | @@ -87,7 +87,7 @@ const make = Effect.gen(function* () { |
| 87 | 87 | |
| 88 | 88 | export const layer = Layer.effect(RunFinalizationService, make); |
| 89 | 89 | |
| 90 | export const observerLive = Layer.effect( | |
| 90 | export const layerObserver = Layer.effect( | |
| 91 | 91 | RunFinalizationObserver, |
| 92 | 92 | Effect.gen(function* () { |
| 93 | 93 | const workspaceEntries = yield* WorkspaceEntries.WorkspaceEntries; |
apps/server/src/orchestration-v2/RuntimePolicy.test.ts+2-2| ... | ... | @@ -76,7 +76,7 @@ const providerInstanceFor = (instanceId: ProviderInstanceId) => |
| 76 | 76 | }, |
| 77 | 77 | }) as ProviderInstance; |
| 78 | 78 | |
| 79 | const TestLayer = RuntimePolicy.layerFromProjectStore.pipe( | |
| 79 | const layerTest = RuntimePolicy.layerFromProjectStore.pipe( | |
| 80 | 80 | Layer.provide( |
| 81 | 81 | Layer.succeed(ProviderInstanceRegistry.ProviderInstanceRegistry, { |
| 82 | 82 | getInstance: (instanceId) => Effect.succeed(providerInstanceFor(instanceId)), |
| ... | ... | @@ -109,7 +109,7 @@ const TestLayer = RuntimePolicy.layerFromProjectStore.pipe( |
| 109 | 109 | ), |
| 110 | 110 | ); |
| 111 | 111 | |
| 112 | it.layer(TestLayer)("RuntimePolicyV2", (it) => { | |
| 112 | it.layer(layerTest)("RuntimePolicyV2", (it) => { | |
| 113 | 113 | it.effect("uses the project root for local-checkout threads", () => |
| 114 | 114 | Effect.gen(function* () { |
| 115 | 115 | const policy = yield* RuntimePolicy.RuntimePolicyV2; |
apps/server/src/orchestration-v2/RuntimeRequestService.test.ts+13-13| ... | ... | @@ -45,7 +45,7 @@ function resolvedRuntimeRequest( |
| 45 | 45 | }; |
| 46 | 46 | } |
| 47 | 47 | |
| 48 | function runtimeRequestTestLayer( | |
| 48 | function layerRuntimeRequestTest( | |
| 49 | 49 | projection: OrchestrationV2ThreadProjection, |
| 50 | 50 | getSession: ProviderSessionManager.ProviderSessionManagerV2Shape["get"], |
| 51 | 51 | ) { |
| ... | ... | @@ -96,7 +96,7 @@ it.effect("forwards orchestrator-resolved runtime requests to the live adapter", |
| 96 | 96 | }, |
| 97 | 97 | ], |
| 98 | 98 | } as unknown as OrchestrationV2ThreadProjection; |
| 99 | const testLayer = RuntimeRequestService.layer.pipe( | |
| 99 | const layerTest = RuntimeRequestService.layer.pipe( | |
| 100 | 100 | Layer.provide( |
| 101 | 101 | Layer.mergeAll( |
| 102 | 102 | Layer.mock(ProjectionStore.ProjectionStoreV2)({ |
| ... | ... | @@ -125,7 +125,7 @@ it.effect("forwards orchestrator-resolved runtime requests to the live adapter", |
| 125 | 125 | requestId, |
| 126 | 126 | decision: "accept", |
| 127 | 127 | }); |
| 128 | }).pipe(Effect.provide(testLayer)); | |
| 128 | }).pipe(Effect.provide(layerTest)); | |
| 129 | 129 | }); |
| 130 | 130 | |
| 131 | 131 | it.effect("rejects expired runtime requests before invoking the live adapter", () => { |
| ... | ... | @@ -160,7 +160,7 @@ it.effect("rejects expired runtime requests before invoking the live adapter", ( |
| 160 | 160 | }, |
| 161 | 161 | ], |
| 162 | 162 | } as unknown as OrchestrationV2ThreadProjection; |
| 163 | const testLayer = RuntimeRequestService.layer.pipe( | |
| 163 | const layerTest = RuntimeRequestService.layer.pipe( | |
| 164 | 164 | Layer.provide( |
| 165 | 165 | Layer.mergeAll( |
| 166 | 166 | Layer.mock(ProjectionStore.ProjectionStoreV2)({ |
| ... | ... | @@ -193,7 +193,7 @@ it.effect("rejects expired runtime requests before invoking the live adapter", ( |
| 193 | 193 | assert.isUndefined(error.cause); |
| 194 | 194 | assert.equal(getSession.mock.calls.length, 0); |
| 195 | 195 | assert.equal(respondToRuntimeRequest.mock.calls.length, 0); |
| 196 | }).pipe(Effect.provide(testLayer)); | |
| 196 | }).pipe(Effect.provide(layerTest)); | |
| 197 | 197 | }); |
| 198 | 198 | |
| 199 | 199 | it.effect("classifies runtime request response validation failures", () => { |
| ... | ... | @@ -204,15 +204,15 @@ it.effect("classifies runtime request response validation failures", () => { |
| 204 | 204 | ); |
| 205 | 205 | const requestId = RuntimeRequestId.make("request-validation"); |
| 206 | 206 | const unexpectedGetSession = vi.fn(() => Effect.succeed(Option.none())); |
| 207 | const missingLayer = runtimeRequestTestLayer( | |
| 207 | const layerMissing = layerRuntimeRequestTest( | |
| 208 | 208 | projectionWithRuntimeRequest(), |
| 209 | 209 | unexpectedGetSession, |
| 210 | 210 | ); |
| 211 | const notResumableLayer = runtimeRequestTestLayer( | |
| 211 | const layerNotResumable = layerRuntimeRequestTest( | |
| 212 | 212 | projectionWithRuntimeRequest(resolvedRuntimeRequest(requestId, otherProviderSessionId)), |
| 213 | 213 | unexpectedGetSession, |
| 214 | 214 | ); |
| 215 | const inactiveLayer = runtimeRequestTestLayer( | |
| 215 | const layerInactive = layerRuntimeRequestTest( | |
| 216 | 216 | projectionWithRuntimeRequest(resolvedRuntimeRequest(requestId, providerSessionId)), |
| 217 | 217 | () => Effect.succeed(Option.none()), |
| 218 | 218 | ); |
| ... | ... | @@ -229,7 +229,7 @@ it.effect("classifies runtime request response validation failures", () => { |
| 229 | 229 | }); |
| 230 | 230 | |
| 231 | 231 | return Effect.gen(function* () { |
| 232 | const missing = yield* respond.pipe(Effect.provide(missingLayer)); | |
| 232 | const missing = yield* respond.pipe(Effect.provide(layerMissing)); | |
| 233 | 233 | assert.equal(missing.reason, "request-missing"); |
| 234 | 234 | assert.equal( |
| 235 | 235 | missing.message, |
| ... | ... | @@ -237,7 +237,7 @@ it.effect("classifies runtime request response validation failures", () => { |
| 237 | 237 | ); |
| 238 | 238 | assert.isUndefined(missing.cause); |
| 239 | 239 | |
| 240 | const notResumable = yield* respond.pipe(Effect.provide(notResumableLayer)); | |
| 240 | const notResumable = yield* respond.pipe(Effect.provide(layerNotResumable)); | |
| 241 | 241 | assert.equal(notResumable.reason, "request-not-resumable"); |
| 242 | 242 | assert.equal( |
| 243 | 243 | notResumable.message, |
| ... | ... | @@ -245,7 +245,7 @@ it.effect("classifies runtime request response validation failures", () => { |
| 245 | 245 | ); |
| 246 | 246 | assert.isUndefined(notResumable.cause); |
| 247 | 247 | |
| 248 | const inactive = yield* respond.pipe(Effect.provide(inactiveLayer)); | |
| 248 | const inactive = yield* respond.pipe(Effect.provide(layerInactive)); | |
| 249 | 249 | assert.equal(inactive.reason, "provider-session-not-active"); |
| 250 | 250 | assert.equal( |
| 251 | 251 | inactive.message, |
| ... | ... | @@ -266,7 +266,7 @@ it.effect("preserves genuine provider session lookup failures as the cause", () |
| 266 | 266 | providerSessionId, |
| 267 | 267 | cause: "lookup failed", |
| 268 | 268 | }); |
| 269 | const testLayer = runtimeRequestTestLayer( | |
| 269 | const layerTest = layerRuntimeRequestTest( | |
| 270 | 270 | projectionWithRuntimeRequest(resolvedRuntimeRequest(requestId, providerSessionId)), |
| 271 | 271 | () => Effect.fail(lookupFailure), |
| 272 | 272 | ); |
| ... | ... | @@ -289,5 +289,5 @@ it.effect("preserves genuine provider session lookup failures as the cause", () |
| 289 | 289 | `Failed to respond to runtime request ${requestId} on thread ${threadId} via provider session ${providerSessionId}.`, |
| 290 | 290 | ); |
| 291 | 291 | assert.strictEqual(error.cause, lookupFailure); |
| 292 | }).pipe(Effect.provide(testLayer)); | |
| 292 | }).pipe(Effect.provide(layerTest)); | |
| 293 | 293 | }); |
apps/server/src/orchestration-v2/SelectionRestart.integration.test.ts+11-11| ... | ... | @@ -35,7 +35,7 @@ import { |
| 35 | 35 | type ProviderAdapterV2TurnInput, |
| 36 | 36 | } from "./ProviderAdapter.ts"; |
| 37 | 37 | import * as ProviderAdapterRegistry from "./ProviderAdapterRegistry.ts"; |
| 38 | import { makeOrchestratorV2ReplayLayerWithRegistry } from "./testkit/ProviderReplayHarness.ts"; | |
| 38 | import * as ProviderReplayHarness from "./testkit/ProviderReplayHarness.ts"; | |
| 39 | 39 | import { checkpointWorkspace } from "./testkit/ReplayFixtureWorkspace.ts"; |
| 40 | 40 | |
| 41 | 41 | const driver = ProviderDriverKind.make("codex"); |
| ... | ... | @@ -403,7 +403,7 @@ it.live("restarts selection as a new attempt and retries after old-session clean |
| 403 | 403 | closedSessionCount: 0, |
| 404 | 404 | failedReplacementOpen: false, |
| 405 | 405 | }); |
| 406 | const registry = ProviderAdapterRegistry.makeSingleLayer(makeRestartAdapter(state)); | |
| 406 | const layerRegistry = ProviderAdapterRegistry.layerSingle(makeRestartAdapter(state)); | |
| 407 | 407 | |
| 408 | 408 | const result = yield* Effect.gen(function* () { |
| 409 | 409 | const orchestrator = yield* Orchestrator.OrchestratorV2; |
| ... | ... | @@ -480,9 +480,9 @@ it.live("restarts selection as a new attempt and retries after old-session clean |
| 480 | 480 | return yield* Effect.die("selection restart did not complete"); |
| 481 | 481 | }).pipe( |
| 482 | 482 | Effect.provide( |
| 483 | makeOrchestratorV2ReplayLayerWithRegistry( | |
| 483 | ProviderReplayHarness.layerWithRegistry( | |
| 484 | 484 | { name: "selection-restart-lifecycle" }, |
| 485 | registry, | |
| 485 | layerRegistry, | |
| 486 | 486 | ), |
| 487 | 487 | ), |
| 488 | 488 | ); |
| ... | ... | @@ -554,7 +554,7 @@ it.live.each(["stopped", "error"] as const)( |
| 554 | 554 | // simulated replacement-open failure is skipped. |
| 555 | 555 | failedReplacementOpen: true, |
| 556 | 556 | }); |
| 557 | const registry = ProviderAdapterRegistry.makeSingleLayer( | |
| 557 | const layerRegistry = ProviderAdapterRegistry.layerSingle( | |
| 558 | 558 | makeRestartAdapter(state, exclusiveCapabilities), |
| 559 | 559 | ); |
| 560 | 560 | |
| ... | ... | @@ -677,7 +677,7 @@ it.live.each(["stopped", "error"] as const)( |
| 677 | 677 | liveSessionId: liveSession.id, |
| 678 | 678 | detachedSessionIds, |
| 679 | 679 | }; |
| 680 | }).pipe(Effect.provide(makeOrchestratorV2ReplayLayerWithRegistry({ name }, registry))); | |
| 680 | }).pipe(Effect.provide(ProviderReplayHarness.layerWithRegistry({ name }, layerRegistry))); | |
| 681 | 681 | |
| 682 | 682 | const { projection, captured } = result; |
| 683 | 683 | assert.lengthOf(projection.runs, 2); |
| ... | ... | @@ -719,7 +719,7 @@ it.live("detaches the old provider session after an active provider handoff", () |
| 719 | 719 | failedReplacementOpen: false, |
| 720 | 720 | }); |
| 721 | 721 | const targetStartCount = yield* Ref.make(0); |
| 722 | const registry = ProviderAdapterRegistry.makeLayer([ | |
| 722 | const layerRegistry = ProviderAdapterRegistry.layerFromAdapters([ | |
| 723 | 723 | makeRestartAdapter(state, exclusiveCapabilities), |
| 724 | 724 | makeCompletingHandoffAdapter(targetStartCount), |
| 725 | 725 | ]); |
| ... | ... | @@ -803,9 +803,9 @@ it.live("detaches the old provider session after an active provider handoff", () |
| 803 | 803 | return yield* Effect.die("active provider handoff did not complete"); |
| 804 | 804 | }).pipe( |
| 805 | 805 | Effect.provide( |
| 806 | makeOrchestratorV2ReplayLayerWithRegistry( | |
| 806 | ProviderReplayHarness.layerWithRegistry( | |
| 807 | 807 | { name: "selection-provider-handoff-lifecycle" }, |
| 808 | registry, | |
| 808 | layerRegistry, | |
| 809 | 809 | ), |
| 810 | 810 | ), |
| 811 | 811 | ); |
| ... | ... | @@ -867,7 +867,7 @@ it.live.each(["active", "idle", "selection-command", "pooled", "separate-home"] |
| 867 | 867 | ), |
| 868 | 868 | } satisfies ProviderAdapterV2Shape; |
| 869 | 869 | }); |
| 870 | const registry = Layer.succeed(ProviderAdapterRegistry.ProviderAdapterRegistryV2, { | |
| 870 | const layerRegistry = Layer.succeed(ProviderAdapterRegistry.ProviderAdapterRegistryV2, { | |
| 871 | 871 | get: (instanceId) => |
| 872 | 872 | Effect.succeed(adapters.find((adapter) => adapter.instanceId === instanceId)!), |
| 873 | 873 | list: () => Effect.succeed([providerInstanceId, targetId]), |
| ... | ... | @@ -990,7 +990,7 @@ it.live.each(["active", "idle", "selection-command", "pooled", "separate-home"] |
| 990 | 990 | assert.equal(returnedThread.providerInstanceId, providerInstanceId); |
| 991 | 991 | assert.isEmpty(third.contextHandoffs); |
| 992 | 992 | assert.deepEqual(messages, ["first", "second", "third"]); |
| 993 | }).pipe(Effect.provide(makeOrchestratorV2ReplayLayerWithRegistry({ name }, registry))); | |
| 993 | }).pipe(Effect.provide(ProviderReplayHarness.layerWithRegistry({ name }, layerRegistry))); | |
| 994 | 994 | }), |
| 995 | 995 | ), |
| 996 | 996 | ); |
apps/server/src/orchestration-v2/SteeringCompletion.integration.test.ts+5-5| ... | ... | @@ -30,7 +30,7 @@ import { |
| 30 | 30 | type ProviderAdapterV2TurnInput, |
| 31 | 31 | } from "./ProviderAdapter.ts"; |
| 32 | 32 | import * as ProviderAdapterRegistry from "./ProviderAdapterRegistry.ts"; |
| 33 | import { makeOrchestratorV2ReplayLayerWithRegistry } from "./testkit/ProviderReplayHarness.ts"; | |
| 33 | import * as ProviderReplayHarness from "./testkit/ProviderReplayHarness.ts"; | |
| 34 | 34 | import { checkpointWorkspace } from "./testkit/ReplayFixtureWorkspace.ts"; |
| 35 | 35 | |
| 36 | 36 | const driver = ProviderDriverKind.make("codex"); |
| ... | ... | @@ -399,9 +399,9 @@ it.effect.each( |
| 399 | 399 | assert.equal(started.length, 2); |
| 400 | 400 | }).pipe( |
| 401 | 401 | Effect.provide( |
| 402 | makeOrchestratorV2ReplayLayerWithRegistry( | |
| 402 | ProviderReplayHarness.layerWithRegistry( | |
| 403 | 403 | { name: `steering-completion-${timing}` }, |
| 404 | ProviderAdapterRegistry.makeSingleLayer(adapter), | |
| 404 | ProviderAdapterRegistry.layerSingle(adapter), | |
| 405 | 405 | { runEffectWorker: false }, |
| 406 | 406 | ), |
| 407 | 407 | ), |
| ... | ... | @@ -514,9 +514,9 @@ const nextTurnSelectionHarness = Effect.fn("nextTurnSelectionHarness")(function* |
| 514 | 514 | }; |
| 515 | 515 | }), |
| 516 | 516 | }; |
| 517 | const layer = makeOrchestratorV2ReplayLayerWithRegistry( | |
| 517 | const layer = ProviderReplayHarness.layerWithRegistry( | |
| 518 | 518 | { name }, |
| 519 | ProviderAdapterRegistry.makeSingleLayer(adapter), | |
| 519 | ProviderAdapterRegistry.layerSingle(adapter), | |
| 520 | 520 | { runEffectWorker: false }, |
| 521 | 521 | ); |
| 522 | 522 | // Creates the thread and starts its first turn on `runSelection`. |
apps/server/src/orchestration-v2/ThreadFork.execution.test.ts+3-3| ... | ... | @@ -23,7 +23,7 @@ import * as EventSink from "./EventSink.ts"; |
| 23 | 23 | import * as Orchestrator from "./Orchestrator.ts"; |
| 24 | 24 | import type { ProviderAdapterV2Shape } from "./ProviderAdapter.ts"; |
| 25 | 25 | import * as ProviderAdapterRegistry from "./ProviderAdapterRegistry.ts"; |
| 26 | import { makeOrchestratorV2ReplayLayerWithRegistry } from "./testkit/ProviderReplayHarness.ts"; | |
| 26 | import * as ProviderReplayHarness from "./testkit/ProviderReplayHarness.ts"; | |
| 27 | 27 | |
| 28 | 28 | const forkCases = (["codex", "claudeAgent"] as const).flatMap((driverName) => { |
| 29 | 29 | const driver = ProviderDriverKind.make(driverName); |
| ... | ... | @@ -39,9 +39,9 @@ const forkCases = (["codex", "claudeAgent"] as const).flatMap((driverName) => { |
| 39 | 39 | planSelectionTransition: () => Effect.succeed({ type: "apply_on_next_turn" }), |
| 40 | 40 | openSession: () => Effect.die("Execution is paused after dispatch for handoff inspection"), |
| 41 | 41 | }; |
| 42 | const layer = makeOrchestratorV2ReplayLayerWithRegistry( | |
| 42 | const layer = ProviderReplayHarness.layerWithRegistry( | |
| 43 | 43 | { name: `fork-boundary-${driver}` }, |
| 44 | ProviderAdapterRegistry.makeLayer([adapter]), | |
| 44 | ProviderAdapterRegistry.layerFromAdapters([adapter]), | |
| 45 | 45 | { runEffectWorker: false }, |
| 46 | 46 | ); |
| 47 | 47 |
apps/server/src/orchestration-v2/ThreadLaunchService.test.ts+37-28| ... | ... | @@ -40,12 +40,12 @@ import * as Schema from "effect/Schema"; |
| 40 | 40 | import * as TestClock from "effect/testing/TestClock"; |
| 41 | 41 | |
| 42 | 42 | import * as GitWorkflow from "../git/GitWorkflowService.ts"; |
| 43 | import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts"; | |
| 43 | import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts"; | |
| 44 | 44 | import * as ProjectStore from "./ProjectStore.ts"; |
| 45 | 45 | import * as ProjectService from "../project/ProjectService.ts"; |
| 46 | 46 | import * as ProjectSetupScriptRunner from "../project/ProjectSetupScriptRunner.ts"; |
| 47 | 47 | import * as ManagedProjectFolders from "../project/ManagedProjectFolders.ts"; |
| 48 | import { makeProviderRegistryLayer } from "../provider/testUtils/providerRegistryMock.ts"; | |
| 48 | import * as ProviderRegistryMock from "../provider/testUtils/providerRegistryMock.ts"; | |
| 49 | 49 | import * as ServerSettings from "../serverSettings.ts"; |
| 50 | 50 | import * as ScheduledTasks from "../scheduledTasks/ScheduledTaskService.ts"; |
| 51 | 51 | import * as SecretRequests from "../secrets/SecretRequests.ts"; |
| ... | ... | @@ -59,7 +59,7 @@ import * as ProviderAdapterRegistry from "./ProviderAdapterRegistry.ts"; |
| 59 | 59 | import * as ThreadLaunch from "./ThreadLaunchService.ts"; |
| 60 | 60 | import * as ThreadManagement from "./ThreadManagementService.ts"; |
| 61 | 61 | import * as ThreadTitleRegeneration from "./ThreadTitleRegenerationService.ts"; |
| 62 | import { makeOrchestratorV2ReplayLayerWithRegistry } from "./testkit/ProviderReplayHarness.ts"; | |
| 62 | import * as ProviderReplayHarness from "./testkit/ProviderReplayHarness.ts"; | |
| 63 | 63 | |
| 64 | 64 | const projectId = ProjectId.make("project:launch-test"); |
| 65 | 65 | const otherProjectId = ProjectId.make("project:launch-other"); |
| ... | ... | @@ -110,16 +110,16 @@ interface HarnessOptions { |
| 110 | 110 | } |
| 111 | 111 | |
| 112 | 112 | function makeHarness(options: HarnessOptions = {}) { |
| 113 | const database = SqlitePersistenceMemory; | |
| 114 | const registry = ProviderAdapterRegistry.makeLayer([adapter]); | |
| 115 | const orchestrator = makeOrchestratorV2ReplayLayerWithRegistry( | |
| 113 | const layerDatabase = SqlitePersistence.layerMemory; | |
| 114 | const layerRegistry = ProviderAdapterRegistry.layerFromAdapters([adapter]); | |
| 115 | const layerOrchestrator = ProviderReplayHarness.layerWithRegistry( | |
| 116 | 116 | { name: "thread-launch" }, |
| 117 | registry, | |
| 118 | { databaseLayer: database, runEffectWorker: false }, | |
| 117 | layerRegistry, | |
| 118 | { databaseLayer: layerDatabase, runEffectWorker: false }, | |
| 119 | 119 | ); |
| 120 | const threadManagement = ThreadManagement.layer.pipe(Layer.provide(orchestrator)); | |
| 121 | const receipts = CommandReceiptStore.layer.pipe(Layer.provide(database)); | |
| 122 | const outbox = EffectOutbox.layer.pipe(Layer.provide(database)); | |
| 120 | const layerThreadManagement = ThreadManagement.layer.pipe(Layer.provide(layerOrchestrator)); | |
| 121 | const layerReceipts = CommandReceiptStore.layer.pipe(Layer.provide(layerDatabase)); | |
| 122 | const layerOutbox = EffectOutbox.layer.pipe(Layer.provide(layerDatabase)); | |
| 123 | 123 | const createWorktree = vi.fn( |
| 124 | 124 | options.createWorktree ?? |
| 125 | 125 | ((input) => |
| ... | ... | @@ -143,7 +143,7 @@ function makeHarness(options: HarnessOptions = {}) { |
| 143 | 143 | const generateThreadTitle = vi.fn( |
| 144 | 144 | options.generateTitle ?? (() => Effect.succeed({ title: "Generated title" })), |
| 145 | 145 | ); |
| 146 | const externalServices = Layer.mergeAll( | |
| 146 | const layerExternalServices = Layer.mergeAll( | |
| 147 | 147 | WorktreeSetupTracker.layer, |
| 148 | 148 | Layer.mock(ProjectCloneTracker.ProjectCloneTracker)({ get: () => Effect.succeed(null) }), |
| 149 | 149 | Layer.mock(TerminalManager.TerminalManager)({ close: () => Effect.void }), |
| ... | ... | @@ -184,17 +184,24 @@ function makeHarness(options: HarnessOptions = {}) { |
| 184 | 184 | generateBranchName, |
| 185 | 185 | }), |
| 186 | 186 | ServerSettings.layerTest(options.serverSettings), |
| 187 | makeProviderRegistryLayer(options.providers), | |
| 187 | ProviderRegistryMock.layer(options.providers), | |
| 188 | 188 | options.managedFolders ?? |
| 189 | 189 | Layer.mock(ManagedProjectFolders.ManagedProjectFolders)({ |
| 190 | 190 | namedProjectsRoot: "/projects", |
| 191 | 191 | folderForThread: () => Effect.succeed(Option.none()), |
| 192 | 192 | }), |
| 193 | 193 | ); |
| 194 | const launch = ThreadLaunch.layer.pipe( | |
| 195 | Layer.provide(Layer.mergeAll(externalServices, threadManagement, receipts, IdAllocator.layer)), | |
| 194 | const layerLaunch = ThreadLaunch.layer.pipe( | |
| 195 | Layer.provide( | |
| 196 | Layer.mergeAll( | |
| 197 | layerExternalServices, | |
| 198 | layerThreadManagement, | |
| 199 | layerReceipts, | |
| 200 | IdAllocator.layer, | |
| 201 | ), | |
| 202 | ), | |
| 196 | 203 | ); |
| 197 | const projectedProjects = Layer.mock(ProjectStore.ProjectStoreV2)({ | |
| 204 | const layerProjectedProjects = Layer.mock(ProjectStore.ProjectStoreV2)({ | |
| 198 | 205 | get: (requestedProjectId) => |
| 199 | 206 | Effect.succeed( |
| 200 | 207 | requestedProjectId === projectId |
| ... | ... | @@ -215,17 +222,19 @@ function makeHarness(options: HarnessOptions = {}) { |
| 215 | 222 | : Option.none(), |
| 216 | 223 | ), |
| 217 | 224 | }); |
| 218 | const titleRegeneration = ThreadTitleRegeneration.layer.pipe( | |
| 219 | Layer.provide(Layer.mergeAll(threadManagement, projectedProjects, externalServices)), | |
| 225 | const layerTitleRegeneration = ThreadTitleRegeneration.layer.pipe( | |
| 226 | Layer.provide( | |
| 227 | Layer.mergeAll(layerThreadManagement, layerProjectedProjects, layerExternalServices), | |
| 228 | ), | |
| 220 | 229 | ); |
| 221 | 230 | return { |
| 222 | 231 | layer: Layer.mergeAll( |
| 223 | launch, | |
| 224 | threadManagement, | |
| 225 | titleRegeneration, | |
| 226 | outbox, | |
| 227 | database, | |
| 228 | externalServices, | |
| 232 | layerLaunch, | |
| 233 | layerThreadManagement, | |
| 234 | layerTitleRegeneration, | |
| 235 | layerOutbox, | |
| 236 | layerDatabase, | |
| 237 | layerExternalServices, | |
| 229 | 238 | ), |
| 230 | 239 | createWorktree, |
| 231 | 240 | removeWorktree, |
| ... | ... | @@ -288,7 +297,7 @@ it.effect.each( |
| 288 | 297 | "attributes $createdBy-configured automations in $target threads without changing their prompt", |
| 289 | 298 | ({ target, createdBy }) => { |
| 290 | 299 | const harness = makeHarness(); |
| 291 | const scheduledTasks = ScheduledTasks.layer.pipe( | |
| 300 | const layerScheduledTasks = ScheduledTasks.layer.pipe( | |
| 292 | 301 | Layer.provide( |
| 293 | 302 | Layer.mergeAll( |
| 294 | 303 | harness.layer, |
| ... | ... | @@ -345,7 +354,7 @@ it.effect.each( |
| 345 | 354 | ); |
| 346 | 355 | assert.equal(turnItem?.text, task.prompt); |
| 347 | 356 | assert.equal(turnItem?.scheduledTaskId, task.id); |
| 348 | }).pipe(Effect.provide(Layer.mergeAll(harness.layer, scheduledTasks))); | |
| 357 | }).pipe(Effect.provide(Layer.mergeAll(harness.layer, layerScheduledTasks))); | |
| 349 | 358 | }, |
| 350 | 359 | ); |
| 351 | 360 | |
| ... | ... | @@ -1955,7 +1964,7 @@ it.effect("creates a strong provider-thread mapping for an imported native sessi |
| 1955 | 1964 | |
| 1956 | 1965 | it.effect("shared intake preserves durable attachment bytes after a lost launch result", () => { |
| 1957 | 1966 | const harness = makeHarness(); |
| 1958 | const files = ServerConfig.layerTest(process.cwd(), { prefix: "t3-message-intake-" }).pipe( | |
| 1967 | const layerFiles = ServerConfig.layerTest(process.cwd(), { prefix: "t3-message-intake-" }).pipe( | |
| 1959 | 1968 | Layer.provideMerge(NodeServices.layer), |
| 1960 | 1969 | ); |
| 1961 | 1970 | return Effect.gen(function* () { |
| ... | ... | @@ -2171,7 +2180,7 @@ it.effect("shared intake preserves durable attachment bytes after a lost launch |
| 2171 | 2180 | assert.isNotNull(path); |
| 2172 | 2181 | assert.deepEqual(yield* fs.readFile(path), new Uint8Array([1, 2, 3, 4])); |
| 2173 | 2182 | } |
| 2174 | }).pipe(Effect.provide(Layer.mergeAll(harness.layer, files))); | |
| 2183 | }).pipe(Effect.provide(Layer.mergeAll(harness.layer, layerFiles))); | |
| 2175 | 2184 | }); |
| 2176 | 2185 | |
| 2177 | 2186 | it.effect("cancels tracked setup before provider work is released", () => |
apps/server/src/orchestration-v2/ThreadManagementService.test.ts+10-10| ... | ... | @@ -268,7 +268,7 @@ it.effect("classifies projection infrastructure failures separately from a missi |
| 268 | 268 | threadId, |
| 269 | 269 | cause: infrastructureCause, |
| 270 | 270 | }); |
| 271 | const testLayer = ThreadManagementService.layer.pipe( | |
| 271 | const layerTest = ThreadManagementService.layer.pipe( | |
| 272 | 272 | Layer.provide( |
| 273 | 273 | Layer.mock(Orchestrator.OrchestratorV2)({ |
| 274 | 274 | getThreadProjection: () => Effect.fail(projectionError), |
| ... | ... | @@ -287,7 +287,7 @@ it.effect("classifies projection infrastructure failures separately from a missi |
| 287 | 287 | cause: projectionError, |
| 288 | 288 | }); |
| 289 | 289 | expect(error.message).toBe(`Unable to load thread ${threadId} in project ${projectId}.`); |
| 290 | }).pipe(Effect.provide(testLayer)); | |
| 290 | }).pipe(Effect.provide(layerTest)); | |
| 291 | 291 | }); |
| 292 | 292 | |
| 293 | 293 | it.effect("uses thread-not-found only after a projection loads outside the project", () => { |
| ... | ... | @@ -301,7 +301,7 @@ it.effect("uses thread-not-found only after a projection loads outside the proje |
| 301 | 301 | deletedAt: null, |
| 302 | 302 | }, |
| 303 | 303 | } as OrchestrationV2ThreadProjection; |
| 304 | const testLayer = ThreadManagementService.layer.pipe( | |
| 304 | const layerTest = ThreadManagementService.layer.pipe( | |
| 305 | 305 | Layer.provide( |
| 306 | 306 | Layer.mock(Orchestrator.OrchestratorV2)({ |
| 307 | 307 | getThreadProjection: () => Effect.succeed(projection), |
| ... | ... | @@ -316,7 +316,7 @@ it.effect("uses thread-not-found only after a projection loads outside the proje |
| 316 | 316 | expect(error).toBeInstanceOf(ThreadManagementService.ThreadManagementThreadNotFoundError); |
| 317 | 317 | expect(error).toMatchObject({ projectId, threadId }); |
| 318 | 318 | expect("cause" in error).toBe(false); |
| 319 | }).pipe(Effect.provide(testLayer)); | |
| 319 | }).pipe(Effect.provide(layerTest)); | |
| 320 | 320 | }); |
| 321 | 321 | |
| 322 | 322 | it.effect("preserves failed legacy materialization when reading checkpoint context", () => { |
| ... | ... | @@ -326,7 +326,7 @@ it.effect("preserves failed legacy materialization when reading checkpoint conte |
| 326 | 326 | operation: "hydrate transcript for", |
| 327 | 327 | cause: new Error("checkpoint import failed"), |
| 328 | 328 | }); |
| 329 | const testLayer = ThreadManagementService.layerWithLegacyImporter.pipe( | |
| 329 | const layerTest = ThreadManagementService.layerWithLegacyImporter.pipe( | |
| 330 | 330 | Layer.provide( |
| 331 | 331 | Layer.mergeAll( |
| 332 | 332 | Layer.mock(Orchestrator.OrchestratorV2)({ |
| ... | ... | @@ -344,7 +344,7 @@ it.effect("preserves failed legacy materialization when reading checkpoint conte |
| 344 | 344 | const error = yield* service.getCheckpointContext(threadId).pipe(Effect.flip); |
| 345 | 345 | expect(error).toBeInstanceOf(Orchestrator.OrchestratorProjectionError); |
| 346 | 346 | expect(error).toMatchObject({ threadId, cause: importError }); |
| 347 | }).pipe(Effect.provide(testLayer)); | |
| 347 | }).pipe(Effect.provide(layerTest)); | |
| 348 | 348 | }); |
| 349 | 349 | |
| 350 | 350 | it.effect.each([ |
| ... | ... | @@ -367,7 +367,7 @@ it.effect.each([ |
| 367 | 367 | thread: { id: threadId, projectId, deletedAt: null }, |
| 368 | 368 | runs: status === "missing" ? [] : [{ id: runId, status }], |
| 369 | 369 | }) as unknown as OrchestrationV2ThreadProjection; |
| 370 | const testLayer = ThreadManagementService.layer.pipe( | |
| 370 | const layerTest = ThreadManagementService.layer.pipe( | |
| 371 | 371 | Layer.provide( |
| 372 | 372 | Layer.mock(Orchestrator.OrchestratorV2)({ |
| 373 | 373 | getThreadEventSequence: () => Effect.succeed(0), |
| ... | ... | @@ -387,7 +387,7 @@ it.effect.each([ |
| 387 | 387 | ), |
| 388 | 388 | ); |
| 389 | 389 | const service = yield* ThreadManagementService.ThreadManagementService.pipe( |
| 390 | Effect.provide(testLayer), | |
| 390 | Effect.provide(layerTest), | |
| 391 | 391 | ); |
| 392 | 392 | const fiber = yield* service |
| 393 | 393 | .waitForThread({ |
| ... | ... | @@ -433,7 +433,7 @@ it.effect("waitForThread reads the run again only when the run updates", () => |
| 433 | 433 | let reads = 0; |
| 434 | 434 | const stored = (sequence: number, event: object) => |
| 435 | 435 | ({ sequence, event: { threadId, ...event } }) as unknown as OrchestrationV2StoredEvent; |
| 436 | const testLayer = ThreadManagementService.layer.pipe( | |
| 436 | const layerTest = ThreadManagementService.layer.pipe( | |
| 437 | 437 | Layer.provide( |
| 438 | 438 | Layer.mock(Orchestrator.OrchestratorV2)({ |
| 439 | 439 | getThreadEventSequence: () => Effect.succeed(0), |
| ... | ... | @@ -457,7 +457,7 @@ it.effect("waitForThread reads the run again only when the run updates", () => |
| 457 | 457 | ), |
| 458 | 458 | ); |
| 459 | 459 | const service = yield* ThreadManagementService.ThreadManagementService.pipe( |
| 460 | Effect.provide(testLayer), | |
| 460 | Effect.provide(layerTest), | |
| 461 | 461 | ); |
| 462 | 462 | const fiber = yield* service |
| 463 | 463 | .waitForThread({ projectId, threadId, runId, timeoutMs: 60 * 60 * 1_000 }) |
apps/server/src/orchestration-v2/ThreadManagementService.ts+2-2| ... | ... | @@ -833,7 +833,7 @@ const make = Effect.gen(function* () { |
| 833 | 833 | }); |
| 834 | 834 | }); |
| 835 | 835 | |
| 836 | const legacyV1ThreadImporterNoopLayer = Layer.succeed( | |
| 836 | const layerLegacyV1ThreadImporterNoop = Layer.succeed( | |
| 837 | 837 | LegacyV1ThreadImporter.LegacyV1ThreadImporter, |
| 838 | 838 | LegacyV1ThreadImporter.LegacyV1ThreadImporter.of({ |
| 839 | 839 | pendingThreadCount: Effect.succeed(0), |
| ... | ... | @@ -844,7 +844,7 @@ const legacyV1ThreadImporterNoopLayer = Layer.succeed( |
| 844 | 844 | ); |
| 845 | 845 | |
| 846 | 846 | export const layer: Layer.Layer<ThreadManagementService, never, Orchestrator.OrchestratorV2> = |
| 847 | Layer.effect(ThreadManagementService, make).pipe(Layer.provide(legacyV1ThreadImporterNoopLayer)); | |
| 847 | Layer.effect(ThreadManagementService, make).pipe(Layer.provide(layerLegacyV1ThreadImporterNoop)); | |
| 848 | 848 | |
| 849 | 849 | export const layerWithLegacyImporter: Layer.Layer< |
| 850 | 850 | ThreadManagementService, |
apps/server/src/orchestration-v2/ThreadMessageIntake.test.ts+21-21| ... | ... | @@ -27,11 +27,11 @@ import { |
| 27 | 27 | import * as ThreadManagementService from "./ThreadManagementService.ts"; |
| 28 | 28 | import { dispatchCommand } from "./ThreadMessageIntake.ts"; |
| 29 | 29 | |
| 30 | const intakeTestLayer = ServerConfig.layerTest(process.cwd(), { | |
| 30 | const layerIntakeTest = ServerConfig.layerTest(process.cwd(), { | |
| 31 | 31 | prefix: "t3-question-intake-", |
| 32 | 32 | }).pipe(Layer.provideMerge(NodeServices.layer)); |
| 33 | 33 | |
| 34 | const failingDispatch = (captured: OrchestrationV2ServerCommand[]) => | |
| 34 | const layerFailingDispatch = (captured: OrchestrationV2ServerCommand[]) => | |
| 35 | 35 | Layer.mock(ThreadManagementService.ThreadManagementService)({ |
| 36 | 36 | dispatch: (command) => { |
| 37 | 37 | captured.push(command); |
| ... | ... | @@ -143,7 +143,7 @@ it.effect("claims question uploads and passes readable paths through the V2 requ |
| 143 | 143 | attachmentsByQuestionId: { |
| 144 | 144 | q: [{ type: "image", id, name: "screen.png", mimeType: "image/png", sizeBytes: 3 }], |
| 145 | 145 | }, |
| 146 | }).pipe(Effect.provide(failingDispatch(captured)), Effect.result); | |
| 146 | }).pipe(Effect.provide(layerFailingDispatch(captured)), Effect.result); | |
| 147 | 147 | expect(captured).toHaveLength(1); |
| 148 | 148 | const command = captured[0]!; |
| 149 | 149 | expect(command.type).toBe("runtime-request.respond"); |
| ... | ... | @@ -160,7 +160,7 @@ it.effect("claims question uploads and passes readable paths through the V2 requ |
| 160 | 160 | "Selected option", |
| 161 | 161 | `Attached image "screen.png": "${path}"`, |
| 162 | 162 | ]); |
| 163 | }).pipe(Effect.provide(intakeTestLayer)), | |
| 163 | }).pipe(Effect.provide(layerIntakeTest)), | |
| 164 | 164 | ); |
| 165 | 165 | |
| 166 | 166 | it.effect("rolls back earlier question claims when a later pending upload is missing", () => |
| ... | ... | @@ -211,7 +211,7 @@ it.effect("rolls back earlier question claims when a later pending upload is mis |
| 211 | 211 | }, |
| 212 | 212 | ], |
| 213 | 213 | }, |
| 214 | }).pipe(Effect.provide(failingDispatch(captured)), Effect.result); | |
| 214 | }).pipe(Effect.provide(layerFailingDispatch(captured)), Effect.result); | |
| 215 | 215 | expect(result._tag).toBe("Failure"); |
| 216 | 216 | if (result._tag === "Failure") { |
| 217 | 217 | expect(result.failure._tag).toBe("AttachmentClaimError"); |
| ... | ... | @@ -227,7 +227,7 @@ it.effect("rolls back earlier question claims when a later pending upload is mis |
| 227 | 227 | entry.startsWith("thread-q-fail-"), |
| 228 | 228 | ), |
| 229 | 229 | ).toEqual([`${existingId}.png`]); |
| 230 | }).pipe(Effect.provide(intakeTestLayer)), | |
| 230 | }).pipe(Effect.provide(layerIntakeTest)), | |
| 231 | 231 | ); |
| 232 | 232 | |
| 233 | 233 | it.effect("rolls back earlier question claims when attachment path preparation fails", () => |
| ... | ... | @@ -265,7 +265,7 @@ it.effect("rolls back earlier question claims when attachment path preparation f |
| 265 | 265 | }, |
| 266 | 266 | ], |
| 267 | 267 | }, |
| 268 | }).pipe(Effect.provide(failingDispatch(captured)), Effect.result); | |
| 268 | }).pipe(Effect.provide(layerFailingDispatch(captured)), Effect.result); | |
| 269 | 269 | expect(result._tag).toBe("Failure"); |
| 270 | 270 | if (result._tag === "Failure") { |
| 271 | 271 | expect(result.failure._tag).toBe("AttachmentClaimError"); |
| ... | ... | @@ -279,7 +279,7 @@ it.effect("rolls back earlier question claims when attachment path preparation f |
| 279 | 279 | entry.startsWith("thread-path-fail-"), |
| 280 | 280 | ), |
| 281 | 281 | ).toEqual([]); |
| 282 | }).pipe(Effect.provide(intakeTestLayer)), | |
| 282 | }).pipe(Effect.provide(layerIntakeTest)), | |
| 283 | 283 | ); |
| 284 | 284 | |
| 285 | 285 | it.effect("retains claimed copies when dispatch failure may have been accepted", () => |
| ... | ... | @@ -308,7 +308,7 @@ it.effect("retains claimed copies when dispatch failure may have been accepted", |
| 308 | 308 | }, |
| 309 | 309 | ], |
| 310 | 310 | }, |
| 311 | }).pipe(Effect.provide(failingDispatch(captured)), Effect.result); | |
| 311 | }).pipe(Effect.provide(layerFailingDispatch(captured)), Effect.result); | |
| 312 | 312 | expect(result._tag).toBe("Failure"); |
| 313 | 313 | expect(captured).toHaveLength(1); |
| 314 | 314 | // The response may have been accepted before the error, so the copy that |
| ... | ... | @@ -318,7 +318,7 @@ it.effect("retains claimed copies when dispatch failure may have been accepted", |
| 318 | 318 | ); |
| 319 | 319 | expect(threadFiles).toHaveLength(1); |
| 320 | 320 | expect(NodeFS.existsSync(NodePath.join(config.attachmentsDir, `${pendingId}.png`))).toBe(true); |
| 321 | }).pipe(Effect.provide(intakeTestLayer)), | |
| 321 | }).pipe(Effect.provide(layerIntakeTest)), | |
| 322 | 322 | ); |
| 323 | 323 | |
| 324 | 324 | it.effect("releases claimed copies when the command was already rejected", () => |
| ... | ... | @@ -370,7 +370,7 @@ it.effect("releases claimed copies when the command was already rejected", () => |
| 370 | 370 | ), |
| 371 | 371 | ).toEqual([]); |
| 372 | 372 | expect(NodeFS.existsSync(NodePath.join(config.attachmentsDir, `${pendingId}.png`))).toBe(true); |
| 373 | }).pipe(Effect.provide(intakeTestLayer)), | |
| 373 | }).pipe(Effect.provide(layerIntakeTest)), | |
| 374 | 374 | ); |
| 375 | 375 | |
| 376 | 376 | it.effect("releases claimed copies when dispatch replays an earlier accepted response", () => |
| ... | ... | @@ -438,7 +438,7 @@ it.effect("releases claimed copies when dispatch replays an earlier accepted res |
| 438 | 438 | NodeFS.readFileSync(NodePath.join(config.attachmentsDir, `${earlierAttachment.id}.png`)), |
| 439 | 439 | ).toEqual(Buffer.from([4, 4, 4])); |
| 440 | 440 | expect(NodeFS.existsSync(NodePath.join(config.attachmentsDir, `${pendingId}.png`))).toBe(true); |
| 441 | }).pipe(Effect.provide(intakeTestLayer)), | |
| 441 | }).pipe(Effect.provide(layerIntakeTest)), | |
| 442 | 442 | ); |
| 443 | 443 | |
| 444 | 444 | it.effect("releases claimed copies when the recorded answer has no attachments", () => |
| ... | ... | @@ -489,7 +489,7 @@ it.effect("releases claimed copies when the recorded answer has no attachments", |
| 489 | 489 | ), |
| 490 | 490 | ).toEqual([]); |
| 491 | 491 | expect(NodeFS.existsSync(NodePath.join(config.attachmentsDir, `${pendingId}.png`))).toBe(true); |
| 492 | }).pipe(Effect.provide(intakeTestLayer)), | |
| 492 | }).pipe(Effect.provide(layerIntakeTest)), | |
| 493 | 493 | ); |
| 494 | 494 | |
| 495 | 495 | it.effect("releases claimed copies when the recorded answer was text-only", () => |
| ... | ... | @@ -540,7 +540,7 @@ it.effect("releases claimed copies when the recorded answer was text-only", () = |
| 540 | 540 | ), |
| 541 | 541 | ).toEqual([]); |
| 542 | 542 | expect(NodeFS.existsSync(NodePath.join(config.attachmentsDir, `${pendingId}.png`))).toBe(true); |
| 543 | }).pipe(Effect.provide(intakeTestLayer)), | |
| 543 | }).pipe(Effect.provide(layerIntakeTest)), | |
| 544 | 544 | ); |
| 545 | 545 | |
| 546 | 546 | it.effect("retains claimed copies referenced by a fresh recorded answer", () => |
| ... | ... | @@ -609,7 +609,7 @@ it.effect("retains claimed copies referenced by a fresh recorded answer", () => |
| 609 | 609 | entry.startsWith("thread-fresh-answer-"), |
| 610 | 610 | ), |
| 611 | 611 | ).toHaveLength(2); |
| 612 | }).pipe(Effect.provide(intakeTestLayer)), | |
| 612 | }).pipe(Effect.provide(layerIntakeTest)), | |
| 613 | 613 | ); |
| 614 | 614 | |
| 615 | 615 | it.effect("releases claimed copies when preparation dies with a defect", () => |
| ... | ... | @@ -644,7 +644,7 @@ it.effect("releases claimed copies when preparation dies with a defect", () => |
| 644 | 644 | ], |
| 645 | 645 | q2: [poisoned] as never, |
| 646 | 646 | }, |
| 647 | }).pipe(Effect.provide(failingDispatch(captured)), Effect.exit); | |
| 647 | }).pipe(Effect.provide(layerFailingDispatch(captured)), Effect.exit); | |
| 648 | 648 | expect(result._tag).toBe("Failure"); |
| 649 | 649 | expect(captured).toHaveLength(0); |
| 650 | 650 | expect( |
| ... | ... | @@ -653,7 +653,7 @@ it.effect("releases claimed copies when preparation dies with a defect", () => |
| 653 | 653 | ), |
| 654 | 654 | ).toEqual([]); |
| 655 | 655 | expect(NodeFS.existsSync(NodePath.join(config.attachmentsDir, `${pendingId}.png`))).toBe(true); |
| 656 | }).pipe(Effect.provide(intakeTestLayer)), | |
| 656 | }).pipe(Effect.provide(layerIntakeTest)), | |
| 657 | 657 | ); |
| 658 | 658 | |
| 659 | 659 | it.effect("a retried response re-claims the preserved pending uploads", () => |
| ... | ... | @@ -695,7 +695,7 @@ it.effect("a retried response re-claims the preserved pending uploads", () => |
| 695 | 695 | }; |
| 696 | 696 | const captured: OrchestrationV2ServerCommand[] = []; |
| 697 | 697 | const first = yield* dispatchCommand(command).pipe( |
| 698 | Effect.provide(failingDispatch(captured)), | |
| 698 | Effect.provide(layerFailingDispatch(captured)), | |
| 699 | 699 | Effect.result, |
| 700 | 700 | ); |
| 701 | 701 | expect(first._tag).toBe("Failure"); |
| ... | ... | @@ -736,7 +736,7 @@ it.effect("a retried response re-claims the preserved pending uploads", () => |
| 736 | 736 | entry.startsWith("thread-retry-"), |
| 737 | 737 | ), |
| 738 | 738 | ).toHaveLength(2); |
| 739 | }).pipe(Effect.provide(intakeTestLayer)), | |
| 739 | }).pipe(Effect.provide(layerIntakeTest)), | |
| 740 | 740 | ); |
| 741 | 741 | |
| 742 | 742 | it.effect("applies the image budget across all questions before dispatch", () => |
| ... | ... | @@ -757,10 +757,10 @@ it.effect("applies the image budget across all questions before dispatch", () => |
| 757 | 757 | requestId: RuntimeRequestId.make("question-image-budget"), |
| 758 | 758 | answers: {}, |
| 759 | 759 | attachmentsByQuestionId: { q1: attachments, q2: attachments }, |
| 760 | }).pipe(Effect.provide(failingDispatch(captured))), | |
| 760 | }).pipe(Effect.provide(layerFailingDispatch(captured))), | |
| 761 | 761 | ); |
| 762 | 762 | expect(result._tag).toBe("Failure"); |
| 763 | 763 | if (result._tag === "Failure") expect(String(result.cause)).toContain("80 MiB"); |
| 764 | 764 | expect(captured).toEqual([]); |
| 765 | }).pipe(Effect.provide(intakeTestLayer)), | |
| 765 | }).pipe(Effect.provide(layerIntakeTest)), | |
| 766 | 766 | ); |
apps/server/src/orchestration-v2/ThreadPullRequestService.test.ts+4-4| ... | ... | @@ -180,7 +180,7 @@ describe("ThreadPullRequestServiceV2 reads", () => { |
| 180 | 180 | const reads = yield* Queue.unbounded< |
| 181 | 181 | ThreadId | { readonly location?: string; readonly unsettledOnly?: boolean } |
| 182 | 182 | >(); |
| 183 | const dependencies = Layer.mergeAll( | |
| 183 | const layerDependencies = Layer.mergeAll( | |
| 184 | 184 | Layer.mock(Orchestrator.OrchestratorV2)({ |
| 185 | 185 | streamDomainEvents: Stream.fromPubSub(events), |
| 186 | 186 | getShellSnapshot: (options) => |
| ... | ... | @@ -254,7 +254,7 @@ describe("ThreadPullRequestServiceV2 reads", () => { |
| 254 | 254 | expect(yield* Queue.take(reads)).toBe(thread.id); |
| 255 | 255 | yield* service.drain; |
| 256 | 256 | expect(yield* Queue.size(reads)).toBe(0); |
| 257 | }).pipe(Effect.provide(dependencies)); | |
| 257 | }).pipe(Effect.provide(layerDependencies)); | |
| 258 | 258 | }), |
| 259 | 259 | ), |
| 260 | 260 | ); |
| ... | ... | @@ -273,7 +273,7 @@ describe("ThreadPullRequestServiceV2 reads", () => { |
| 273 | 273 | readonly location?: string; |
| 274 | 274 | readonly unsettledOnly?: boolean; |
| 275 | 275 | }>(); |
| 276 | const dependencies = Layer.mergeAll( | |
| 276 | const layerDependencies = Layer.mergeAll( | |
| 277 | 277 | Layer.mock(Orchestrator.OrchestratorV2)({ |
| 278 | 278 | streamDomainEvents: Stream.never, |
| 279 | 279 | getShellSnapshot: (options) => |
| ... | ... | @@ -315,7 +315,7 @@ describe("ThreadPullRequestServiceV2 reads", () => { |
| 315 | 315 | yield* TestClock.adjust("1 minute"); |
| 316 | 316 | expect(yield* Queue.take(reads)).toEqual({ location: "active", unsettledOnly: true }); |
| 317 | 317 | yield* service.drain; |
| 318 | }).pipe(Effect.provide(dependencies)); | |
| 318 | }).pipe(Effect.provide(layerDependencies)); | |
| 319 | 319 | }), |
| 320 | 320 | ), |
| 321 | 321 | ); |
apps/server/src/orchestration-v2/ThreadSearch.test.ts+4-4| ... | ... | @@ -12,16 +12,16 @@ import * as Effect from "effect/Effect"; |
| 12 | 12 | import * as Layer from "effect/Layer"; |
| 13 | 13 | import * as SqlClient from "effect/sql/SqlClient"; |
| 14 | 14 | |
| 15 | import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts"; | |
| 15 | import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts"; | |
| 16 | 16 | import * as ProjectionStore from "./ProjectionStore.ts"; |
| 17 | 17 | import * as ProjectStore from "./ProjectStore.ts"; |
| 18 | 18 | import * as ThreadSearch from "./ThreadSearch.ts"; |
| 19 | 19 | |
| 20 | const TestLayer = Layer.mergeAll( | |
| 20 | const layerTest = Layer.mergeAll( | |
| 21 | 21 | ThreadSearch.layer, |
| 22 | 22 | ProjectionStore.layer, |
| 23 | 23 | ProjectStore.layer, |
| 24 | ).pipe(Layer.provideMerge(SqlitePersistenceMemory)); | |
| 24 | ).pipe(Layer.provideMerge(SqlitePersistence.layerMemory)); | |
| 25 | 25 | |
| 26 | 26 | const providerInstanceId = ProviderInstanceId.make("codex"); |
| 27 | 27 | const at = (minute: number) => DateTime.makeUnsafe(Date.UTC(2026, 8, 27, 0, minute)); |
| ... | ... | @@ -114,7 +114,7 @@ const message = ( |
| 114 | 114 | }, |
| 115 | 115 | }); |
| 116 | 116 | |
| 117 | it.layer(TestLayer)("ThreadSearch", (it) => { | |
| 117 | it.layer(layerTest)("ThreadSearch", (it) => { | |
| 118 | 118 | it.effect("returns one finished user or assistant match per active thread", () => |
| 119 | 119 | Effect.gen(function* () { |
| 120 | 120 | const projections = yield* ProjectionStore.ProjectionStoreV2; |
apps/server/src/orchestration-v2/ThreadSettlementService.test.ts+2-2| ... | ... | @@ -571,7 +571,7 @@ const makeHarness = Effect.fn("makeThreadSettlementHarness")(function* (options: |
| 571 | 571 | ), |
| 572 | 572 | }); |
| 573 | 573 | |
| 574 | const dependencies = Layer.mergeAll( | |
| 574 | const layerDependencies = Layer.mergeAll( | |
| 575 | 575 | Layer.mock(ProjectStore.ProjectStoreV2)({ |
| 576 | 576 | listShells: () => Ref.get(snapshots).pipe(Effect.map((snapshot) => snapshot.projects)), |
| 577 | 577 | }), |
| ... | ... | @@ -639,7 +639,7 @@ const makeHarness = Effect.fn("makeThreadSettlementHarness")(function* (options: |
| 639 | 639 | number: 42, |
| 640 | 640 | mergedAt: NOW, |
| 641 | 641 | }), |
| 642 | layer: ThreadSettlementService.layer.pipe(Layer.provide(dependencies)), | |
| 642 | layer: ThreadSettlementService.layer.pipe(Layer.provide(layerDependencies)), | |
| 643 | 643 | }; |
| 644 | 644 | }); |
| 645 | 645 |
apps/server/src/orchestration-v2/ThreadStop.test.ts+17-17| ... | ... | @@ -19,7 +19,7 @@ import * as Schema from "effect/Schema"; |
| 19 | 19 | import * as SqlClient from "effect/sql/SqlClient"; |
| 20 | 20 | import * as TestClock from "effect/testing/TestClock"; |
| 21 | 21 | |
| 22 | import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts"; | |
| 22 | import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts"; | |
| 23 | 23 | import { CodexProviderCapabilitiesV2 } from "./Adapters/CodexAdapterV2.ts"; |
| 24 | 24 | import { OrchestrationEffectRequestV2 } from "./EffectOutbox.ts"; |
| 25 | 25 | import * as Orchestrator from "./Orchestrator.ts"; |
| ... | ... | @@ -27,7 +27,7 @@ import * as ProjectionStore from "./ProjectionStore.ts"; |
| 27 | 27 | import type { ProviderAdapterV2Shape } from "./ProviderAdapter.ts"; |
| 28 | 28 | import * as ProviderAdapterRegistry from "./ProviderAdapterRegistry.ts"; |
| 29 | 29 | import * as ThreadManagementService from "./ThreadManagementService.ts"; |
| 30 | import { makeOrchestratorV2ReplayLayerWithRegistry } from "./testkit/ProviderReplayHarness.ts"; | |
| 30 | import * as ProviderReplayHarness from "./testkit/ProviderReplayHarness.ts"; | |
| 31 | 31 | |
| 32 | 32 | const instanceId = ProviderInstanceId.make("codex"); |
| 33 | 33 | const modelSelection = { instanceId, model: "gpt-5.1-codex" }; |
| ... | ... | @@ -38,17 +38,17 @@ const adapter = { |
| 38 | 38 | planSelectionTransition: () => Effect.succeed({ type: "apply_on_next_turn" as const }), |
| 39 | 39 | openSession: () => Effect.die("Runs here never reach a provider"), |
| 40 | 40 | } as ProviderAdapterV2Shape; |
| 41 | const database = SqlitePersistenceMemory; | |
| 41 | const layerDatabase = SqlitePersistence.layerMemory; | |
| 42 | 42 | // No effect worker: runs stay unstarted, so Stop ends them without a provider. |
| 43 | const testLayer = ThreadManagementService.layer.pipe( | |
| 43 | const layerTest = ThreadManagementService.layer.pipe( | |
| 44 | 44 | Layer.provideMerge( |
| 45 | 45 | Layer.mergeAll( |
| 46 | database, | |
| 47 | ProjectionStore.layer.pipe(Layer.provide(database)), | |
| 48 | makeOrchestratorV2ReplayLayerWithRegistry( | |
| 46 | layerDatabase, | |
| 47 | ProjectionStore.layer.pipe(Layer.provide(layerDatabase)), | |
| 48 | ProviderReplayHarness.layerWithRegistry( | |
| 49 | 49 | { name: "thread-stop" }, |
| 50 | ProviderAdapterRegistry.makeLayer([adapter]), | |
| 51 | { databaseLayer: database, runEffectWorker: false }, | |
| 50 | ProviderAdapterRegistry.layerFromAdapters([adapter]), | |
| 51 | { databaseLayer: layerDatabase, runEffectWorker: false }, | |
| 52 | 52 | ), |
| 53 | 53 | ), |
| 54 | 54 | ), |
| ... | ... | @@ -210,7 +210,7 @@ it.effect("Stop ends watches, holds queues, and stops the delegated tasks under |
| 210 | 210 | // A retried effect stops nothing twice. |
| 211 | 211 | yield* threads.stopDelegatedTasks({ threadId: parentThreadId, commandId: stopCommandId }); |
| 212 | 212 | assert.deepEqual((yield* threadState(childThreadId)).runs, ["interrupted", "queued:held"]); |
| 213 | }).pipe(Effect.provide(testLayer)), | |
| 213 | }).pipe(Effect.provide(layerTest)), | |
| 214 | 214 | ); |
| 215 | 215 | |
| 216 | 216 | it.effect( |
| ... | ... | @@ -234,7 +234,7 @@ it.effect( |
| 234 | 234 | threadId, |
| 235 | 235 | }); |
| 236 | 236 | assert.lengthOf(again.storedEvents, 0); |
| 237 | }).pipe(Effect.provide(testLayer)), | |
| 237 | }).pipe(Effect.provide(layerTest)), | |
| 238 | 238 | ); |
| 239 | 239 | |
| 240 | 240 | it.effect("a run Stop reached cannot delegate or start a watch, even after it ends", () => |
| ... | ... | @@ -319,7 +319,7 @@ it.effect("a run Stop reached cannot delegate or start a watch, even after it en |
| 319 | 319 | }); |
| 320 | 320 | } |
| 321 | 321 | assert.deepEqual((yield* threadState(threadId)).watched, [4]); |
| 322 | }).pipe(Effect.provide(testLayer)), | |
| 322 | }).pipe(Effect.provide(layerTest)), | |
| 323 | 323 | ); |
| 324 | 324 | |
| 325 | 325 | it.effect("thread.stop keeps a restart continuation of the stopped run from starting", () => |
| ... | ... | @@ -372,7 +372,7 @@ it.effect("thread.stop keeps a restart continuation of the stopped run from star |
| 372 | 372 | restartContinuationOfRunId: run.id, |
| 373 | 373 | }); |
| 374 | 374 | assert.deepEqual(yield* threadState(threadId), { runs: ["cancelled"], watched: [] }); |
| 375 | }).pipe(Effect.provide(testLayer)), | |
| 375 | }).pipe(Effect.provide(layerTest)), | |
| 376 | 376 | ); |
| 377 | 377 | |
| 378 | 378 | it.effect.each(["thread.stop", "run.interrupt"] as const)( |
| ... | ... | @@ -468,7 +468,7 @@ it.effect.each(["thread.stop", "run.interrupt"] as const)( |
| 468 | 468 | runs: ["completed", "interrupted", "cancelled"], |
| 469 | 469 | watched: [], |
| 470 | 470 | }); |
| 471 | }).pipe(Effect.provide(testLayer.pipe(Layer.provideMerge(TestClock.layer())))), | |
| 471 | }).pipe(Effect.provide(layerTest.pipe(Layer.provideMerge(TestClock.layer())))), | |
| 472 | 472 | ); |
| 473 | 473 | |
| 474 | 474 | it.effect("a delegated task that cannot be stopped fails the walk after its siblings stop", () => |
| ... | ... | @@ -506,7 +506,7 @@ it.effect("a delegated task that cannot be stopped fails the walk after its sibl |
| 506 | 506 | ); |
| 507 | 507 | assert.isTrue(Exit.isFailure(walked)); |
| 508 | 508 | assert.deepEqual((yield* threadState(childThreadId)).runs, ["interrupted"]); |
| 509 | }).pipe(Effect.provide(testLayer)), | |
| 509 | }).pipe(Effect.provide(layerTest)), | |
| 510 | 510 | ); |
| 511 | 511 | |
| 512 | 512 | it.effect("thread.stop on a finished thread refuses a late agent watch", () => |
| ... | ... | @@ -534,7 +534,7 @@ it.effect("thread.stop on a finished thread refuses a late agent watch", () => |
| 534 | 534 | }); |
| 535 | 535 | assert.isTrue(Exit.isFailure(yield* Effect.exit(watch(threadId, 10)))); |
| 536 | 536 | assert.deepEqual(yield* threadState(threadId), { runs: ["completed"], watched: [] }); |
| 537 | }).pipe(Effect.provide(testLayer)), | |
| 537 | }).pipe(Effect.provide(layerTest)), | |
| 538 | 538 | ); |
| 539 | 539 | |
| 540 | 540 | it.effect("thread.stop marks a turn it cannot interrupt so a late agent watch is refused", () => |
| ... | ... | @@ -580,5 +580,5 @@ it.effect("thread.stop marks a turn it cannot interrupt so a late agent watch is |
| 580 | 580 | }); |
| 581 | 581 | assert.isTrue(Exit.isFailure(yield* Effect.exit(watch(threadId, 12)))); |
| 582 | 582 | assert.deepEqual(yield* threadState(threadId), { runs: ["running"], watched: [] }); |
| 583 | }).pipe(Effect.provide(testLayer)), | |
| 583 | }).pipe(Effect.provide(layerTest)), | |
| 584 | 584 | ); |
apps/server/src/orchestration-v2/ThreadTitleRegenerationService.test.ts+19-14| ... | ... | @@ -16,7 +16,7 @@ import * as TestClock from "effect/testing/TestClock"; |
| 16 | 16 | import * as Layer from "effect/Layer"; |
| 17 | 17 | import * as Option from "effect/Option"; |
| 18 | 18 | |
| 19 | import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts"; | |
| 19 | import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts"; | |
| 20 | 20 | import * as ProjectStore from "./ProjectStore.ts"; |
| 21 | 21 | import * as ServerSettings from "../serverSettings.ts"; |
| 22 | 22 | import * as TextGeneration from "../textGeneration/TextGeneration.ts"; |
| ... | ... | @@ -26,7 +26,7 @@ import type { ProviderAdapterV2Shape } from "./ProviderAdapter.ts"; |
| 26 | 26 | import * as ProviderAdapterRegistry from "./ProviderAdapterRegistry.ts"; |
| 27 | 27 | import * as ThreadManagement from "./ThreadManagementService.ts"; |
| 28 | 28 | import * as ThreadTitleRegeneration from "./ThreadTitleRegenerationService.ts"; |
| 29 | import { makeOrchestratorV2ReplayLayerWithRegistry } from "./testkit/ProviderReplayHarness.ts"; | |
| 29 | import * as ProviderReplayHarness from "./testkit/ProviderReplayHarness.ts"; | |
| 30 | 30 | |
| 31 | 31 | const projectId = ProjectId.make("project:title-regeneration"); |
| 32 | 32 | const modelSelection = { |
| ... | ... | @@ -47,19 +47,19 @@ function makeHarness( |
| 47 | 47 | readonly generateTitle?: TextGeneration.TextGeneration["Service"]["generateThreadTitle"]; |
| 48 | 48 | } = {}, |
| 49 | 49 | ) { |
| 50 | const database = SqlitePersistenceMemory; | |
| 51 | const registry = ProviderAdapterRegistry.makeLayer([adapter]); | |
| 52 | const orchestrator = makeOrchestratorV2ReplayLayerWithRegistry( | |
| 50 | const layerDatabase = SqlitePersistence.layerMemory; | |
| 51 | const layerRegistry = ProviderAdapterRegistry.layerFromAdapters([adapter]); | |
| 52 | const layerOrchestrator = ProviderReplayHarness.layerWithRegistry( | |
| 53 | 53 | { name: "thread-title-regeneration" }, |
| 54 | registry, | |
| 55 | { databaseLayer: database, runEffectWorker: false }, | |
| 54 | layerRegistry, | |
| 55 | { databaseLayer: layerDatabase, runEffectWorker: false }, | |
| 56 | 56 | ); |
| 57 | const threadManagement = ThreadManagement.layer.pipe(Layer.provide(orchestrator)); | |
| 58 | const outbox = EffectOutbox.layer.pipe(Layer.provide(database)); | |
| 57 | const layerThreadManagement = ThreadManagement.layer.pipe(Layer.provide(layerOrchestrator)); | |
| 58 | const layerOutbox = EffectOutbox.layer.pipe(Layer.provide(layerDatabase)); | |
| 59 | 59 | const generateThreadTitle = vi.fn( |
| 60 | 60 | options.generateTitle ?? (() => Effect.succeed({ title: "Generated title" })), |
| 61 | 61 | ); |
| 62 | const projectedProjects = Layer.mock(ProjectStore.ProjectStoreV2)({ | |
| 62 | const layerProjectedProjects = Layer.mock(ProjectStore.ProjectStoreV2)({ | |
| 63 | 63 | get: (requestedProjectId) => |
| 64 | 64 | Effect.succeed( |
| 65 | 65 | requestedProjectId === projectId |
| ... | ... | @@ -80,18 +80,23 @@ function makeHarness( |
| 80 | 80 | : Option.none(), |
| 81 | 81 | ), |
| 82 | 82 | }); |
| 83 | const titleRegeneration = ThreadTitleRegeneration.layer.pipe( | |
| 83 | const layerTitleRegeneration = ThreadTitleRegeneration.layer.pipe( | |
| 84 | 84 | Layer.provide( |
| 85 | 85 | Layer.mergeAll( |
| 86 | threadManagement, | |
| 87 | projectedProjects, | |
| 86 | layerThreadManagement, | |
| 87 | layerProjectedProjects, | |
| 88 | 88 | Layer.mock(TextGeneration.TextGeneration)({ generateThreadTitle }), |
| 89 | 89 | ServerSettings.layerTest({}), |
| 90 | 90 | ), |
| 91 | 91 | ), |
| 92 | 92 | ); |
| 93 | 93 | return { |
| 94 | layer: Layer.mergeAll(threadManagement, titleRegeneration, outbox, database), | |
| 94 | layer: Layer.mergeAll( | |
| 95 | layerThreadManagement, | |
| 96 | layerTitleRegeneration, | |
| 97 | layerOutbox, | |
| 98 | layerDatabase, | |
| 99 | ), | |
| 95 | 100 | generateThreadTitle, |
| 96 | 101 | }; |
| 97 | 102 | } |
apps/server/src/orchestration-v2/TurnStartReads.test.ts+6-6| ... | ... | @@ -16,12 +16,12 @@ import * as DateTime from "effect/DateTime"; |
| 16 | 16 | import * as Effect from "effect/Effect"; |
| 17 | 17 | import * as Layer from "effect/Layer"; |
| 18 | 18 | import * as SqlClient from "effect/sql/SqlClient"; |
| 19 | import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts"; | |
| 19 | import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts"; | |
| 20 | 20 | import * as ProjectionStore from "./ProjectionStore.ts"; |
| 21 | 21 | |
| 22 | const databaseLayer = Layer.mergeAll( | |
| 23 | SqlitePersistenceMemory, | |
| 24 | ProjectionStore.layer.pipe(Layer.provide(SqlitePersistenceMemory)), | |
| 22 | const layerDatabase = Layer.mergeAll( | |
| 23 | SqlitePersistence.layerMemory, | |
| 24 | ProjectionStore.layer.pipe(Layer.provide(SqlitePersistence.layerMemory)), | |
| 25 | 25 | ); |
| 26 | 26 | |
| 27 | 27 | it.effect.each(["sqlite", "memory"] as const)( |
| ... | ... | @@ -362,8 +362,8 @@ it.effect.each(["sqlite", "memory"] as const)( |
| 362 | 362 | }).pipe( |
| 363 | 363 | Effect.provide( |
| 364 | 364 | storage === "sqlite" |
| 365 | ? databaseLayer | |
| 366 | : Layer.merge(SqlitePersistenceMemory, ProjectionStore.layerMemory), | |
| 365 | ? layerDatabase | |
| 366 | : Layer.merge(SqlitePersistence.layerMemory, ProjectionStore.layerMemory), | |
| 367 | 367 | ), |
| 368 | 368 | ), |
| 369 | 369 | ); |
apps/server/src/orchestration-v2/UsageLimitRecoveryWorker.ts+1-1| ... | ... | @@ -106,7 +106,7 @@ const makeSweep = Effect.gen(function* () { |
| 106 | 106 | |
| 107 | 107 | // The shared scheduler derives due work from persisted failures and recovery |
| 108 | 108 | // choices, so restarts need no timer restoration or connected client. |
| 109 | export const workerLive = Layer.effectDiscard( | |
| 109 | export const layer = Layer.effectDiscard( | |
| 110 | 110 | Effect.gen(function* () { |
| 111 | 111 | const sweep = yield* makeSweep; |
| 112 | 112 | const scheduler = yield* Scheduler.Scheduler; |
apps/server/src/orchestration-v2/http.ts+1-1| ... | ... | @@ -63,7 +63,7 @@ function selectHistoryPageFromCursorOrError( |
| 63 | 63 | * compressible and cacheable — and then resume the WebSocket subscription via |
| 64 | 64 | * `afterSequence`. |
| 65 | 65 | */ |
| 66 | export const orchestrationHttpApiLayer = HttpApiBuilder.group( | |
| 66 | export const layer = HttpApiBuilder.group( | |
| 67 | 67 | EnvironmentHttpApi, |
| 68 | 68 | "orchestration", |
| 69 | 69 | Effect.fnUntraced(function* (handlers) { |
apps/server/src/orchestration-v2/legacy/LegacyV1Cutover.integration.test.ts+29-21| ... | ... | @@ -27,7 +27,7 @@ import * as Stream from "effect/Stream"; |
| 27 | 27 | import * as SqlClient from "effect/sql/SqlClient"; |
| 28 | 28 | |
| 29 | 29 | import { runMigrations } from "../../persistence/Migrations.ts"; |
| 30 | import { makeSqlitePersistenceLive } from "../../persistence/Layers/Sqlite.ts"; | |
| 30 | import * as SqlitePersistence from "../../persistence/Layers/Sqlite.ts"; | |
| 31 | 31 | import Migration0042 from "../../persistence/Migrations/042_ProjectionThreadLinkedPullRequest.ts"; |
| 32 | 32 | import Migration0043 from "../../persistence/Migrations/043_ProjectionThreadsUnsettledAt.ts"; |
| 33 | 33 | import Migration0044 from "../../persistence/Migrations/044_ClearAutomaticProjectModelDefaults.ts"; |
| ... | ... | @@ -50,7 +50,7 @@ import { |
| 50 | 50 | type ProviderAdapterV2Shape, |
| 51 | 51 | } from "../ProviderAdapter.ts"; |
| 52 | 52 | import * as ProviderAdapterRegistry from "../ProviderAdapterRegistry.ts"; |
| 53 | import { makeOrchestratorV2ReplayLayerWithRegistry } from "../testkit/ProviderReplayHarness.ts"; | |
| 53 | import * as ProviderReplayHarness from "../testkit/ProviderReplayHarness.ts"; | |
| 54 | 54 | import { checkpointWorkspace } from "../testkit/ReplayFixtureWorkspace.ts"; |
| 55 | 55 | |
| 56 | 56 | const PROJECT_ID = "project:cutover"; |
| ... | ... | @@ -579,24 +579,32 @@ const waitForIdle = Effect.fn("LegacyV1Cutover.waitForIdle")(function* (threadId |
| 579 | 579 | return yield* Effect.die(new Error("Cutover test timed out waiting for idle")); |
| 580 | 580 | }); |
| 581 | 581 | |
| 582 | const makeBootLayer = (input: { | |
| 582 | const layerBoot = (input: { | |
| 583 | 583 | readonly name: string; |
| 584 | 584 | readonly dbPath: string; |
| 585 | 585 | readonly workspace: string; |
| 586 | 586 | readonly capturedTurns: Ref.Ref<ReadonlyArray<CapturedTurn>>; |
| 587 | 587 | }) => { |
| 588 | const databaseLayer = makeSqlitePersistenceLive(input.dbPath).pipe( | |
| 588 | const layerDatabase = SqlitePersistence.layerFromPath(input.dbPath).pipe( | |
| 589 | 589 | Layer.provide(NodeServices.layer), |
| 590 | 590 | ); |
| 591 | const eventStoreProvided = EventStore.layer.pipe(Layer.provideMerge(databaseLayer)); | |
| 592 | const projectionStoreProvided = ProjectionStore.layer.pipe(Layer.provideMerge(databaseLayer)); | |
| 593 | const storesProvided = Layer.mergeAll(databaseLayer, eventStoreProvided, projectionStoreProvided); | |
| 594 | const eventSinkProvided = EventSink.layer.pipe(Layer.provide(storesProvided)); | |
| 595 | const importerProvided = LegacyV1ThreadImporter.layer.pipe( | |
| 596 | Layer.provide(Layer.mergeAll(storesProvided, eventSinkProvided)), | |
| 591 | const layerEventStoreProvided = EventStore.layer.pipe(Layer.provideMerge(layerDatabase)); | |
| 592 | const layerProjectionStoreProvided = ProjectionStore.layer.pipe( | |
| 593 | Layer.provideMerge(layerDatabase), | |
| 597 | 594 | ); |
| 598 | const maintenanceProvided = ProjectionMaintenance.layer.pipe(Layer.provide(storesProvided)); | |
| 599 | const orchestratorProvided = makeOrchestratorV2ReplayLayerWithRegistry( | |
| 595 | const layerStoresProvided = Layer.mergeAll( | |
| 596 | layerDatabase, | |
| 597 | layerEventStoreProvided, | |
| 598 | layerProjectionStoreProvided, | |
| 599 | ); | |
| 600 | const layerEventSinkProvided = EventSink.layer.pipe(Layer.provide(layerStoresProvided)); | |
| 601 | const layerImporterProvided = LegacyV1ThreadImporter.layer.pipe( | |
| 602 | Layer.provide(Layer.mergeAll(layerStoresProvided, layerEventSinkProvided)), | |
| 603 | ); | |
| 604 | const layerMaintenanceProvided = ProjectionMaintenance.layer.pipe( | |
| 605 | Layer.provide(layerStoresProvided), | |
| 606 | ); | |
| 607 | const layerOrchestratorProvided = ProviderReplayHarness.layerWithRegistry( | |
| 600 | 608 | { |
| 601 | 609 | name: input.name, |
| 602 | 610 | runtimePolicyOverride: { |
| ... | ... | @@ -609,15 +617,15 @@ const makeBootLayer = (input: { |
| 609 | 617 | }, |
| 610 | 618 | }, |
| 611 | 619 | }, |
| 612 | ProviderAdapterRegistry.makeSingleLayer(makeCodexAdapter(input.capturedTurns)), | |
| 613 | { databaseLayer }, | |
| 620 | ProviderAdapterRegistry.layerSingle(makeCodexAdapter(input.capturedTurns)), | |
| 621 | { databaseLayer: layerDatabase }, | |
| 614 | 622 | ); |
| 615 | 623 | return Layer.mergeAll( |
| 616 | storesProvided, | |
| 617 | eventSinkProvided, | |
| 618 | importerProvided, | |
| 619 | maintenanceProvided, | |
| 620 | orchestratorProvided, | |
| 624 | layerStoresProvided, | |
| 625 | layerEventSinkProvided, | |
| 626 | layerImporterProvided, | |
| 627 | layerMaintenanceProvided, | |
| 628 | layerOrchestratorProvided, | |
| 621 | 629 | ); |
| 622 | 630 | }; |
| 623 | 631 | |
| ... | ... | @@ -909,7 +917,7 @@ describe("orchestration v2 legacy v1 cutover", () => { |
| 909 | 917 | }; |
| 910 | 918 | }).pipe( |
| 911 | 919 | Effect.provide( |
| 912 | makeBootLayer({ | |
| 920 | layerBoot({ | |
| 913 | 921 | name: "legacy-v1-cutover-first", |
| 914 | 922 | dbPath: copyPath, |
| 915 | 923 | workspace, |
| ... | ... | @@ -1020,7 +1028,7 @@ describe("orchestration v2 legacy v1 cutover", () => { |
| 1020 | 1028 | ); |
| 1021 | 1029 | }).pipe( |
| 1022 | 1030 | Effect.provide( |
| 1023 | makeBootLayer({ | |
| 1031 | layerBoot({ | |
| 1024 | 1032 | name: "legacy-v1-cutover-restart", |
| 1025 | 1033 | dbPath: copyPath, |
| 1026 | 1034 | workspace, |
apps/server/src/orchestration-v2/legacy/LegacyV1ThreadImporter.test.ts+20-16| ... | ... | @@ -6,7 +6,7 @@ import * as Layer from "effect/Layer"; |
| 6 | 6 | import * as Tracer from "effect/Tracer"; |
| 7 | 7 | import * as SqlClient from "effect/sql/SqlClient"; |
| 8 | 8 | |
| 9 | import { SqlitePersistenceMemory } from "../../persistence/Layers/Sqlite.ts"; | |
| 9 | import * as SqlitePersistence from "../../persistence/Layers/Sqlite.ts"; | |
| 10 | 10 | import { listLinkedPullRequestThreads } from "../../pullRequest/linkedThreads.ts"; |
| 11 | 11 | import * as EventSink from "../EventSink.ts"; |
| 12 | 12 | import * as EventStore from "../EventStore.ts"; |
| ... | ... | @@ -14,25 +14,29 @@ import * as LegacyV1ThreadImporter from "./LegacyV1ThreadImporter.ts"; |
| 14 | 14 | import * as ProjectionMaintenance from "../ProjectionMaintenance.ts"; |
| 15 | 15 | import * as ProjectionStore from "../ProjectionStore.ts"; |
| 16 | 16 | |
| 17 | const databaseLayer = SqlitePersistenceMemory; | |
| 18 | const eventStoreProvided = EventStore.layer.pipe(Layer.provideMerge(databaseLayer)); | |
| 19 | const projectionStoreProvided = ProjectionStore.layer.pipe(Layer.provideMerge(databaseLayer)); | |
| 20 | const storesProvided = Layer.mergeAll(databaseLayer, eventStoreProvided, projectionStoreProvided); | |
| 21 | const eventSinkProvided = EventSink.layer.pipe(Layer.provide(storesProvided)); | |
| 22 | const importerProvided = LegacyV1ThreadImporter.layer.pipe( | |
| 23 | Layer.provide(Layer.mergeAll(storesProvided, eventSinkProvided)), | |
| 17 | const layerDatabase = SqlitePersistence.layerMemory; | |
| 18 | const layerEventStoreProvided = EventStore.layer.pipe(Layer.provideMerge(layerDatabase)); | |
| 19 | const layerProjectionStoreProvided = ProjectionStore.layer.pipe(Layer.provideMerge(layerDatabase)); | |
| 20 | const layerStoresProvided = Layer.mergeAll( | |
| 21 | layerDatabase, | |
| 22 | layerEventStoreProvided, | |
| 23 | layerProjectionStoreProvided, | |
| 24 | 24 | ); |
| 25 | const projectionMaintenanceProvided = ProjectionMaintenance.layer.pipe( | |
| 26 | Layer.provide(storesProvided), | |
| 25 | const layerEventSinkProvided = EventSink.layer.pipe(Layer.provide(layerStoresProvided)); | |
| 26 | const layerImporterProvided = LegacyV1ThreadImporter.layer.pipe( | |
| 27 | Layer.provide(Layer.mergeAll(layerStoresProvided, layerEventSinkProvided)), | |
| 27 | 28 | ); |
| 28 | const TestLayer = Layer.mergeAll( | |
| 29 | storesProvided, | |
| 30 | eventSinkProvided, | |
| 31 | importerProvided, | |
| 32 | projectionMaintenanceProvided, | |
| 29 | const layerProjectionMaintenanceProvided = ProjectionMaintenance.layer.pipe( | |
| 30 | Layer.provide(layerStoresProvided), | |
| 31 | ); | |
| 32 | const layerTest = Layer.mergeAll( | |
| 33 | layerStoresProvided, | |
| 34 | layerEventSinkProvided, | |
| 35 | layerImporterProvided, | |
| 36 | layerProjectionMaintenanceProvided, | |
| 33 | 37 | ); |
| 34 | 38 | |
| 35 | it.layer(TestLayer)("LegacyV1ThreadImporter", (it) => { | |
| 39 | it.layer(layerTest)("LegacyV1ThreadImporter", (it) => { | |
| 36 | 40 | it.effect("uses the created-thread index for startup migration checks", () => |
| 37 | 41 | Effect.gen(function* () { |
| 38 | 42 | const sql = yield* SqlClient.SqlClient; |
apps/server/src/orchestration-v2/runtimeLayer.test.ts+61-66| ... | ... | @@ -43,7 +43,7 @@ import * as SqlClient from "effect/sql/SqlClient"; |
| 43 | 43 | import * as CheckpointStore from "../checkpointing/CheckpointStore.ts"; |
| 44 | 44 | import * as GitWorkflow from "../git/GitWorkflowService.ts"; |
| 45 | 45 | import * as ServerConfig from "../config.ts"; |
| 46 | import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts"; | |
| 46 | import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts"; | |
| 47 | 47 | import * as OrchestrationEventStore from "../persistence/Services/OrchestrationEventStore.ts"; |
| 48 | 48 | import * as ProjectEnrichmentService from "../project/ProjectEnrichmentService.ts"; |
| 49 | 49 | import * as ProjectService from "../project/ProjectService.ts"; |
| ... | ... | @@ -69,25 +69,20 @@ import * as PullRequestService from "../pullRequest/PullRequestService.ts"; |
| 69 | 69 | import * as ProjectStore from "./ProjectStore.ts"; |
| 70 | 70 | import type { ProviderAdapterV2SessionRuntime, ProviderAdapterV2Shape } from "./ProviderAdapter.ts"; |
| 71 | 71 | import * as ProviderSessionManager from "./ProviderSessionManager.ts"; |
| 72 | import { | |
| 73 | OrchestrationEventInfrastructureLayerLive, | |
| 74 | OrchestrationV2EventSinkLayerLive, | |
| 75 | OrchestrationV2LayerLive, | |
| 76 | ProjectServiceLayerLive, | |
| 77 | } from "./runtimeLayer.ts"; | |
| 72 | import * as RuntimeLayer from "./runtimeLayer.ts"; | |
| 78 | 73 | import { shellStreamItemFromThreadShell } from "./ShellStream.ts"; |
| 79 | 74 | import { CodexProviderCapabilitiesV2 } from "./Adapters/CodexAdapterV2.ts"; |
| 80 | 75 | import * as ThreadManagementService from "./ThreadManagementService.ts"; |
| 81 | 76 | import * as ThreadCommandExecutor from "./ThreadCommandExecutor.ts"; |
| 82 | 77 | |
| 83 | const PlatformTestLayer = Layer.merge( | |
| 78 | const layerPlatformTest = Layer.merge( | |
| 84 | 79 | NodeServices.layer, |
| 85 | 80 | Layer.mock(SourceControlProviderRegistry.SourceControlProviderRegistry)({ |
| 86 | 81 | resolveLink: () => Effect.die("unused title link"), |
| 87 | 82 | }), |
| 88 | 83 | ); |
| 89 | 84 | |
| 90 | const ServerConfigLayer = ServerConfig.layerTest(process.cwd(), { | |
| 85 | const layerServerConfig = ServerConfig.layerTest(process.cwd(), { | |
| 91 | 86 | prefix: "t3-orchestration-v2-runtime-layer-", |
| 92 | 87 | }); |
| 93 | 88 | |
| ... | ... | @@ -97,20 +92,20 @@ const modelSelection = { |
| 97 | 92 | } satisfies ModelSelection; |
| 98 | 93 | const alternateInstanceId = ProviderInstanceId.make("codex_alternate"); |
| 99 | 94 | |
| 100 | const VcsDriverRegistryTestLayer = VcsDriverRegistry.layer.pipe( | |
| 95 | const layerVcsDriverRegistryTest = VcsDriverRegistry.layer.pipe( | |
| 101 | 96 | Layer.provide(VcsProcess.layer), |
| 102 | Layer.provide(ServerConfigLayer), | |
| 103 | Layer.provide(PlatformTestLayer), | |
| 97 | Layer.provide(layerServerConfig), | |
| 98 | Layer.provide(layerPlatformTest), | |
| 104 | 99 | ); |
| 105 | 100 | |
| 106 | const CheckpointStoreTestLayer = CheckpointStore.layer.pipe( | |
| 107 | Layer.provide(VcsDriverRegistryTestLayer), | |
| 101 | const layerCheckpointStoreTest = CheckpointStore.layer.pipe( | |
| 102 | Layer.provide(layerVcsDriverRegistryTest), | |
| 108 | 103 | ); |
| 109 | const GitWorkflowTestLayer = Layer.mock(GitWorkflow.GitWorkflowService)({ | |
| 104 | const layerGitWorkflowTest = Layer.mock(GitWorkflow.GitWorkflowService)({ | |
| 110 | 105 | pruneWorktrees: () => Effect.void, |
| 111 | 106 | createWorktree: () => Effect.succeed({} as never), |
| 112 | 107 | }); |
| 113 | const ProjectServiceTestLayer = Layer.mock(ProjectService.ProjectService)({ | |
| 108 | const layerProjectServiceTest = Layer.mock(ProjectService.ProjectService)({ | |
| 114 | 109 | getById: () => Effect.succeed(Option.none()), |
| 115 | 110 | }); |
| 116 | 111 | |
| ... | ... | @@ -150,7 +145,7 @@ const alternateProviderInstance = { |
| 150 | 145 | }, |
| 151 | 146 | } satisfies ProviderInstance; |
| 152 | 147 | |
| 153 | const TestProviderInstanceRegistry = Layer.succeed( | |
| 148 | const layerTestProviderInstanceRegistry = Layer.succeed( | |
| 154 | 149 | ProviderInstanceRegistry.ProviderInstanceRegistry, |
| 155 | 150 | { |
| 156 | 151 | getInstance: (instanceId) => |
| ... | ... | @@ -268,41 +263,41 @@ const moveProject = (projectId: ProjectId, workspaceRoot: string, updatedAt: str |
| 268 | 263 | }), |
| 269 | 264 | ); |
| 270 | 265 | |
| 271 | const TestLayer = Layer.mergeAll( | |
| 272 | OrchestrationV2LayerLive, | |
| 273 | OrchestrationV2EventSinkLayerLive, | |
| 266 | const layerTest = Layer.mergeAll( | |
| 267 | RuntimeLayer.layer, | |
| 268 | RuntimeLayer.layerEventSink, | |
| 274 | 269 | ProjectStore.layer, |
| 275 | 270 | ProjectionStore.layer, |
| 276 | 271 | EffectOutbox.layer, |
| 277 | 272 | ThreadCommandExecutor.layer, |
| 278 | 273 | ).pipe( |
| 279 | 274 | Layer.provide(McpSessionRegistryTestkit.layer), |
| 280 | Layer.provide(SqlitePersistenceMemory), | |
| 281 | Layer.provide(CheckpointStoreTestLayer), | |
| 282 | Layer.provide(ServerConfigLayer), | |
| 275 | Layer.provide(SqlitePersistence.layerMemory), | |
| 276 | Layer.provide(layerCheckpointStoreTest), | |
| 277 | Layer.provide(layerServerConfig), | |
| 283 | 278 | Layer.provide(ServerSettings.layerTest()), |
| 284 | Layer.provide(TestProviderInstanceRegistry), | |
| 285 | Layer.provide(GitWorkflowTestLayer), | |
| 286 | Layer.provide(ProjectServiceTestLayer), | |
| 287 | Layer.provide(PlatformTestLayer), | |
| 279 | Layer.provide(layerTestProviderInstanceRegistry), | |
| 280 | Layer.provide(layerGitWorkflowTest), | |
| 281 | Layer.provide(layerProjectServiceTest), | |
| 282 | Layer.provide(layerPlatformTest), | |
| 288 | 283 | ); |
| 289 | 284 | |
| 290 | const LegacyImportTestLayer = OrchestrationV2LayerLive.pipe( | |
| 285 | const layerLegacyImportTest = RuntimeLayer.layer.pipe( | |
| 291 | 286 | Layer.provide(McpSessionRegistryTestkit.layer), |
| 292 | Layer.provideMerge(SqlitePersistenceMemory), | |
| 293 | Layer.provide(CheckpointStoreTestLayer), | |
| 294 | Layer.provide(ServerConfigLayer), | |
| 287 | Layer.provideMerge(SqlitePersistence.layerMemory), | |
| 288 | Layer.provide(layerCheckpointStoreTest), | |
| 289 | Layer.provide(layerServerConfig), | |
| 295 | 290 | Layer.provide(ServerSettings.layerTest()), |
| 296 | Layer.provide(TestProviderInstanceRegistry), | |
| 297 | Layer.provide(GitWorkflowTestLayer), | |
| 298 | Layer.provide(ProjectServiceTestLayer), | |
| 299 | Layer.provide(PlatformTestLayer), | |
| 291 | Layer.provide(layerTestProviderInstanceRegistry), | |
| 292 | Layer.provide(layerGitWorkflowTest), | |
| 293 | Layer.provide(layerProjectServiceTest), | |
| 294 | Layer.provide(layerPlatformTest), | |
| 300 | 295 | ); |
| 301 | 296 | |
| 302 | const ProjectDeletionTestLayer = Layer.mergeAll( | |
| 303 | OrchestrationV2LayerLive.pipe(Layer.provide(ProjectServiceLayerLive)), | |
| 304 | ProjectServiceLayerLive, | |
| 305 | OrchestrationV2EventSinkLayerLive, | |
| 297 | const layerProjectDeletionTest = Layer.mergeAll( | |
| 298 | RuntimeLayer.layer.pipe(Layer.provide(RuntimeLayer.layerProjectService)), | |
| 299 | RuntimeLayer.layerProjectService, | |
| 300 | RuntimeLayer.layerEventSink, | |
| 306 | 301 | ThreadCommandExecutor.layer, |
| 307 | 302 | ).pipe( |
| 308 | 303 | Layer.provide( |
| ... | ... | @@ -328,16 +323,16 @@ const ProjectDeletionTestLayer = Layer.mergeAll( |
| 328 | 323 | }), |
| 329 | 324 | ), |
| 330 | 325 | Layer.provide(McpSessionRegistryTestkit.layer), |
| 331 | Layer.provide(SqlitePersistenceMemory), | |
| 332 | Layer.provide(CheckpointStoreTestLayer), | |
| 333 | Layer.provide(ServerConfigLayer), | |
| 326 | Layer.provide(SqlitePersistence.layerMemory), | |
| 327 | Layer.provide(layerCheckpointStoreTest), | |
| 328 | Layer.provide(layerServerConfig), | |
| 334 | 329 | Layer.provide(ServerSettings.layerTest()), |
| 335 | Layer.provide(TestProviderInstanceRegistry), | |
| 336 | Layer.provide(GitWorkflowTestLayer), | |
| 337 | Layer.provide(PlatformTestLayer), | |
| 330 | Layer.provide(layerTestProviderInstanceRegistry), | |
| 331 | Layer.provide(layerGitWorkflowTest), | |
| 332 | Layer.provide(layerPlatformTest), | |
| 338 | 333 | ); |
| 339 | 334 | |
| 340 | it.layer(ProjectDeletionTestLayer)("project deletion during thread commands", (it) => { | |
| 335 | it.layer(layerProjectDeletionTest)("project deletion during thread commands", (it) => { | |
| 341 | 336 | it.effect("waits for an in-flight thread update before planning deletion", () => |
| 342 | 337 | Effect.gen(function* () { |
| 343 | 338 | const projects = yield* ProjectService.ProjectService; |
| ... | ... | @@ -441,11 +436,11 @@ it.layer(ProjectDeletionTestLayer)("project deletion during thread commands", (i |
| 441 | 436 | ); |
| 442 | 437 | }); |
| 443 | 438 | |
| 444 | const SharedApplicationDataPlaneTestLayer = Layer.mergeAll( | |
| 445 | OrchestrationV2LayerLive.pipe(Layer.provide(ProjectServiceLayerLive)), | |
| 446 | ProjectServiceLayerLive, | |
| 447 | OrchestrationV2EventSinkLayerLive, | |
| 448 | OrchestrationEventInfrastructureLayerLive, | |
| 439 | const layerSharedApplicationDataPlaneTest = Layer.mergeAll( | |
| 440 | RuntimeLayer.layer.pipe(Layer.provide(RuntimeLayer.layerProjectService)), | |
| 441 | RuntimeLayer.layerProjectService, | |
| 442 | RuntimeLayer.layerEventSink, | |
| 443 | RuntimeLayer.layerEventInfrastructure, | |
| 449 | 444 | ).pipe( |
| 450 | 445 | Layer.provide( |
| 451 | 446 | Layer.mock(WorkspacePaths.WorkspacePaths)({ |
| ... | ... | @@ -472,16 +467,16 @@ const SharedApplicationDataPlaneTestLayer = Layer.mergeAll( |
| 472 | 467 | }), |
| 473 | 468 | ), |
| 474 | 469 | Layer.provide(McpSessionRegistryTestkit.layer), |
| 475 | Layer.provideMerge(SqlitePersistenceMemory), | |
| 476 | Layer.provide(CheckpointStoreTestLayer), | |
| 477 | Layer.provide(ServerConfigLayer), | |
| 470 | Layer.provideMerge(SqlitePersistence.layerMemory), | |
| 471 | Layer.provide(layerCheckpointStoreTest), | |
| 472 | Layer.provide(layerServerConfig), | |
| 478 | 473 | Layer.provide(ServerSettings.layerTest()), |
| 479 | Layer.provide(TestProviderInstanceRegistry), | |
| 480 | Layer.provide(GitWorkflowTestLayer), | |
| 481 | Layer.provide(PlatformTestLayer), | |
| 474 | Layer.provide(layerTestProviderInstanceRegistry), | |
| 475 | Layer.provide(layerGitWorkflowTest), | |
| 476 | Layer.provide(layerPlatformTest), | |
| 482 | 477 | ); |
| 483 | 478 | |
| 484 | it.layer(TestLayer)("OrchestrationV2LayerLive", (it) => { | |
| 479 | it.layer(layerTest)("OrchestrationV2LayerLive", (it) => { | |
| 485 | 480 | it.effect("emits model updates separately from provider switches", () => |
| 486 | 481 | Effect.gen(function* () { |
| 487 | 482 | const orchestrator = yield* Orchestrator.OrchestratorV2; |
| ... | ... | @@ -837,7 +832,7 @@ it.layer(TestLayer)("OrchestrationV2LayerLive", (it) => { |
| 837 | 832 | assert.deepEqual(yield* outbox.listByCommandId(commandId), []); |
| 838 | 833 | } |
| 839 | 834 | } |
| 840 | }).pipe(Effect.provide(Layer.fresh(TestLayer))), | |
| 835 | }).pipe(Effect.provide(Layer.fresh(layerTest))), | |
| 841 | 836 | ); |
| 842 | 837 | |
| 843 | 838 | it.effect("resolves delivery intent against the active run and starts after it completes", () => |
| ... | ... | @@ -1478,7 +1473,7 @@ it.layer(TestLayer)("OrchestrationV2LayerLive", (it) => { |
| 1478 | 1473 | ); |
| 1479 | 1474 | }); |
| 1480 | 1475 | |
| 1481 | it.layer(LegacyImportTestLayer)("OrchestrationV2 legacy import", (it) => { | |
| 1476 | it.layer(layerLegacyImportTest)("OrchestrationV2 legacy import", (it) => { | |
| 1482 | 1477 | it.effect("hydrates imported transcripts before commands and propagates hydration failures", () => |
| 1483 | 1478 | Effect.gen(function* () { |
| 1484 | 1479 | const sql = yield* SqlClient.SqlClient; |
| ... | ... | @@ -1678,7 +1673,7 @@ it.layer(LegacyImportTestLayer)("OrchestrationV2 legacy import", (it) => { |
| 1678 | 1673 | ); |
| 1679 | 1674 | }); |
| 1680 | 1675 | |
| 1681 | it.layer(TestLayer)("OrchestrationV2LayerLive lifecycle", (it) => { | |
| 1676 | it.layer(layerTest)("OrchestrationV2LayerLive lifecycle", (it) => { | |
| 1682 | 1677 | it.effect("applies lifecycle commands idempotently and emits archive/removal shell deltas", () => |
| 1683 | 1678 | Effect.gen(function* () { |
| 1684 | 1679 | const orchestrator = yield* Orchestrator.OrchestratorV2; |
| ... | ... | @@ -4349,7 +4344,7 @@ it.layer(TestLayer)("OrchestrationV2LayerLive lifecycle", (it) => { |
| 4349 | 4344 | ); |
| 4350 | 4345 | }); |
| 4351 | 4346 | |
| 4352 | it.layer(SharedApplicationDataPlaneTestLayer)("pending provider interruption", (it) => { | |
| 4347 | it.layer(layerSharedApplicationDataPlaneTest)("pending provider interruption", (it) => { | |
| 4353 | 4348 | it.effect("interrupts a pending provider start without launching provider work", () => |
| 4354 | 4349 | Effect.gen(function* () { |
| 4355 | 4350 | const projects = yield* ProjectService.ProjectService; |
| ... | ... | @@ -4423,7 +4418,7 @@ it.layer(SharedApplicationDataPlaneTestLayer)("pending provider interruption", ( |
| 4423 | 4418 | ); |
| 4424 | 4419 | }); |
| 4425 | 4420 | |
| 4426 | it.layer(SharedApplicationDataPlaneTestLayer)("snooze projection", (it) => { | |
| 4421 | it.layer(layerSharedApplicationDataPlaneTest)("snooze projection", (it) => { | |
| 4427 | 4422 | it.effect("carries snooze state through the V2 shell projection", () => |
| 4428 | 4423 | Effect.gen(function* () { |
| 4429 | 4424 | const projects = yield* ProjectService.ProjectService; |
| ... | ... | @@ -4498,7 +4493,7 @@ it.layer(SharedApplicationDataPlaneTestLayer)("snooze projection", (it) => { |
| 4498 | 4493 | ); |
| 4499 | 4494 | }); |
| 4500 | 4495 | |
| 4501 | it.layer(SharedApplicationDataPlaneTestLayer)("visited projection", (it) => { | |
| 4496 | it.layer(layerSharedApplicationDataPlaneTest)("visited projection", (it) => { | |
| 4502 | 4497 | it.effect("carries the visited watermark through the V2 shell projection", () => |
| 4503 | 4498 | Effect.gen(function* () { |
| 4504 | 4499 | const projects = yield* ProjectService.ProjectService; |
| ... | ... | @@ -4606,7 +4601,7 @@ it.layer(SharedApplicationDataPlaneTestLayer)("visited projection", (it) => { |
| 4606 | 4601 | ); |
| 4607 | 4602 | }); |
| 4608 | 4603 | |
| 4609 | it.layer(SharedApplicationDataPlaneTestLayer)("shared application data plane", (it) => { | |
| 4604 | it.layer(layerSharedApplicationDataPlaneTest)("shared application data plane", (it) => { | |
| 4610 | 4605 | it.effect("orders retained project transactions and V2 thread transactions in one source", () => |
| 4611 | 4606 | Effect.gen(function* () { |
| 4612 | 4607 | const projects = yield* ProjectService.ProjectService; |
| ... | ... | @@ -4681,7 +4676,7 @@ it.layer(SharedApplicationDataPlaneTestLayer)("shared application data plane", ( |
| 4681 | 4676 | ); |
| 4682 | 4677 | }); |
| 4683 | 4678 | |
| 4684 | it.layer(TestLayer)("usage-limit recovery", (it) => { | |
| 4679 | it.layer(layerTest)("usage-limit recovery", (it) => { | |
| 4685 | 4680 | it.effect.each(["interrupted", "usage_limit"] as const)( |
| 4686 | 4681 | "manually resumes an %s run ahead of its queued message only once", |
| 4687 | 4682 | (reason) => |
apps/server/src/orchestration-v2/runtimeLayer.ts+150-138| ... | ... | @@ -5,7 +5,7 @@ import * as OrchestrationCommandReceipts from "../persistence/Layers/Orchestrati |
| 5 | 5 | import * as OrchestrationEventStore from "../persistence/Layers/OrchestrationEventStore.ts"; |
| 6 | 6 | import * as ProviderSessionRuntime from "../persistence/ProviderSessionRuntime.ts"; |
| 7 | 7 | import * as TextGeneration from "../textGeneration/TextGeneration.ts"; |
| 8 | import { ProviderAuthServiceLive } from "../provider/Layers/ProviderAuthService.ts"; | |
| 8 | import * as ProviderAuthService from "../provider/Layers/ProviderAuthService.ts"; | |
| 9 | 9 | import * as AgentSessionImporter from "../project/AgentSessionImporter.ts"; |
| 10 | 10 | import * as AgentSessionScanner from "../project/AgentSessionScanner.ts"; |
| 11 | 11 | import * as ProjectService from "../project/ProjectService.ts"; |
| ... | ... | @@ -51,57 +51,57 @@ import * as ScheduledTaskService from "../scheduledTasks/ScheduledTaskService.ts |
| 51 | 51 | import * as SecretRequests from "../secrets/SecretRequests.ts"; |
| 52 | 52 | |
| 53 | 53 | /** The shared application event log and its command receipts. */ |
| 54 | export const OrchestrationEventInfrastructureLayerLive = Layer.mergeAll( | |
| 55 | OrchestrationEventStore.OrchestrationEventStoreLive, | |
| 56 | OrchestrationCommandReceipts.OrchestrationCommandReceiptRepositoryLive, | |
| 54 | export const layerEventInfrastructure = Layer.mergeAll( | |
| 55 | OrchestrationEventStore.layer, | |
| 56 | OrchestrationCommandReceipts.layer, | |
| 57 | 57 | ); |
| 58 | 58 | |
| 59 | const runtimePolicyProvided = RuntimePolicy.layerFromProjectStore.pipe( | |
| 59 | const layerRuntimePolicyProvided = RuntimePolicy.layerFromProjectStore.pipe( | |
| 60 | 60 | Layer.provide(ProjectStore.layer), |
| 61 | 61 | ); |
| 62 | 62 | |
| 63 | const eventStoreProvided = EventStore.layerFromOrchestrationEventStore.pipe( | |
| 64 | Layer.provide(OrchestrationEventInfrastructureLayerLive), | |
| 63 | const layerEventStoreProvided = EventStore.layerFromOrchestrationEventStore.pipe( | |
| 64 | Layer.provide(layerEventInfrastructure), | |
| 65 | 65 | ); |
| 66 | const commandReceiptStoreProvided = CommandReceiptStore.layerFromApplicationReceipts.pipe( | |
| 67 | Layer.provide(OrchestrationEventInfrastructureLayerLive), | |
| 66 | const layerCommandReceiptStoreProvided = CommandReceiptStore.layerFromApplicationReceipts.pipe( | |
| 67 | Layer.provide(layerEventInfrastructure), | |
| 68 | 68 | ); |
| 69 | 69 | |
| 70 | const storesLayer = Layer.mergeAll( | |
| 71 | OrchestrationEventInfrastructureLayerLive, | |
| 72 | eventStoreProvided, | |
| 70 | const layerStores = Layer.mergeAll( | |
| 71 | layerEventInfrastructure, | |
| 72 | layerEventStoreProvided, | |
| 73 | 73 | ProjectionStore.layer, |
| 74 | 74 | ProjectStore.layer, |
| 75 | commandReceiptStoreProvided, | |
| 75 | layerCommandReceiptStoreProvided, | |
| 76 | 76 | EffectOutbox.layer, |
| 77 | 77 | TurnItemPositionStore.layer, |
| 78 | 78 | ); |
| 79 | 79 | |
| 80 | export const OrchestrationV2EventSinkLayerLive = EventSink.layerFromStores.pipe( | |
| 81 | Layer.provide(storesLayer), | |
| 80 | export const layerEventSink = EventSink.layerFromStores.pipe(Layer.provide(layerStores)); | |
| 81 | const layerEventSinkProvided = layerEventSink; | |
| 82 | const layerProjectionMaintenanceProvided = ProjectionMaintenance.layer.pipe( | |
| 83 | Layer.provide(layerStores), | |
| 82 | 84 | ); |
| 83 | const eventSinkProvided = OrchestrationV2EventSinkLayerLive; | |
| 84 | const projectionMaintenanceProvided = ProjectionMaintenance.layer.pipe(Layer.provide(storesLayer)); | |
| 85 | const legacyV1ThreadImporterProvided = LegacyV1ThreadImporter.layer.pipe( | |
| 86 | Layer.provide(eventSinkProvided), | |
| 85 | const layerLegacyV1ThreadImporterProvided = LegacyV1ThreadImporter.layer.pipe( | |
| 86 | Layer.provide(layerEventSinkProvided), | |
| 87 | 87 | ); |
| 88 | 88 | |
| 89 | export const ProjectServiceLayerLive = ProjectService.layer.pipe( | |
| 89 | export const layerProjectService = ProjectService.layer.pipe( | |
| 90 | 90 | Layer.provide( |
| 91 | 91 | Layer.mergeAll( |
| 92 | 92 | ProjectStore.layer, |
| 93 | 93 | ProjectionStore.layer, |
| 94 | eventSinkProvided, | |
| 94 | layerEventSinkProvided, | |
| 95 | 95 | IdAllocator.layer, |
| 96 | legacyV1ThreadImporterProvided, | |
| 96 | layerLegacyV1ThreadImporterProvided, | |
| 97 | 97 | ), |
| 98 | 98 | ), |
| 99 | 99 | ); |
| 100 | 100 | |
| 101 | const providerEventIngestorProvided = ProviderEventIngestor.layer.pipe( | |
| 101 | const layerProviderEventIngestorProvided = ProviderEventIngestor.layer.pipe( | |
| 102 | 102 | Layer.provide( |
| 103 | 103 | Layer.mergeAll( |
| 104 | eventSinkProvided, | |
| 104 | layerEventSinkProvided, | |
| 105 | 105 | IdAllocator.layer, |
| 106 | 106 | ProjectionStore.layer, |
| 107 | 107 | ThreadCommandExecutor.layer, |
| ... | ... | @@ -109,222 +109,234 @@ const providerEventIngestorProvided = ProviderEventIngestor.layer.pipe( |
| 109 | 109 | ), |
| 110 | 110 | ); |
| 111 | 111 | |
| 112 | const checkpointServiceProvided = CheckpointService.layer.pipe(Layer.provide(IdAllocator.layer)); | |
| 113 | const contextHandoffServiceProvided = ContextHandoffService.layer.pipe( | |
| 112 | const layerCheckpointServiceProvided = CheckpointService.layer.pipe( | |
| 113 | Layer.provide(IdAllocator.layer), | |
| 114 | ); | |
| 115 | const layerContextHandoffServiceProvided = ContextHandoffService.layer.pipe( | |
| 114 | 116 | Layer.provide(IdAllocator.layer), |
| 115 | 117 | ); |
| 116 | 118 | |
| 117 | const providerAdapterRegistryProvided = ProviderAdapterRegistry.layerFromProviderInstanceRegistry; | |
| 118 | const providerSwitchServiceProvided = ProviderSwitchService.layer.pipe( | |
| 119 | Layer.provide(providerAdapterRegistryProvided), | |
| 119 | const layerProviderAdapterRegistryProvided = | |
| 120 | ProviderAdapterRegistry.layerFromProviderInstanceRegistry; | |
| 121 | const layerProviderSwitchServiceProvided = ProviderSwitchService.layer.pipe( | |
| 122 | Layer.provide(layerProviderAdapterRegistryProvided), | |
| 120 | 123 | ); |
| 121 | 124 | |
| 122 | const providerSessionManagerProvided = ProviderSessionManager.layer.pipe( | |
| 125 | const layerProviderSessionManagerProvided = ProviderSessionManager.layer.pipe( | |
| 123 | 126 | Layer.provide( |
| 124 | 127 | Layer.mergeAll( |
| 125 | providerAdapterRegistryProvided, | |
| 126 | eventSinkProvided, | |
| 128 | layerProviderAdapterRegistryProvided, | |
| 129 | layerEventSinkProvided, | |
| 127 | 130 | IdAllocator.layer, |
| 128 | providerEventIngestorProvided, | |
| 131 | layerProviderEventIngestorProvided, | |
| 129 | 132 | ProjectionStore.layer, |
| 130 | 133 | ), |
| 131 | 134 | ), |
| 132 | 135 | ); |
| 133 | 136 | |
| 134 | const providerAuthServiceProvided = ProviderAuthServiceLive.pipe( | |
| 135 | Layer.provide(Layer.merge(ProjectionStore.layer, providerSessionManagerProvided)), | |
| 137 | const layerProviderAuthServiceProvided = ProviderAuthService.layer.pipe( | |
| 138 | Layer.provide(Layer.merge(ProjectionStore.layer, layerProviderSessionManagerProvided)), | |
| 136 | 139 | ); |
| 137 | 140 | |
| 138 | const runExecutionServiceProvided = RunExecutionService.layer.pipe( | |
| 141 | const layerRunExecutionServiceProvided = RunExecutionService.layer.pipe( | |
| 139 | 142 | Layer.provide( |
| 140 | 143 | Layer.mergeAll( |
| 141 | checkpointServiceProvided, | |
| 142 | eventSinkProvided, | |
| 144 | layerCheckpointServiceProvided, | |
| 145 | layerEventSinkProvided, | |
| 143 | 146 | IdAllocator.layer, |
| 144 | providerEventIngestorProvided, | |
| 147 | layerProviderEventIngestorProvided, | |
| 145 | 148 | ), |
| 146 | 149 | ), |
| 147 | 150 | ); |
| 148 | 151 | |
| 149 | const providerTurnStartServiceProvided = ProviderTurnStartService.layer.pipe( | |
| 152 | const layerProviderTurnStartServiceProvided = ProviderTurnStartService.layer.pipe( | |
| 150 | 153 | Layer.provide( |
| 151 | 154 | Layer.mergeAll( |
| 152 | contextHandoffServiceProvided, | |
| 153 | eventSinkProvided, | |
| 155 | layerContextHandoffServiceProvided, | |
| 156 | layerEventSinkProvided, | |
| 154 | 157 | IdAllocator.layer, |
| 155 | 158 | ProjectionStore.layer, |
| 156 | providerSessionManagerProvided, | |
| 157 | providerAuthServiceProvided, | |
| 158 | runExecutionServiceProvided, | |
| 159 | runtimePolicyProvided, | |
| 159 | layerProviderSessionManagerProvided, | |
| 160 | layerProviderAuthServiceProvided, | |
| 161 | layerRunExecutionServiceProvided, | |
| 162 | layerRuntimePolicyProvided, | |
| 160 | 163 | ), |
| 161 | 164 | ), |
| 162 | 165 | ); |
| 163 | 166 | |
| 164 | const providerTurnControlServiceProvided = ProviderTurnControlService.layer.pipe( | |
| 165 | Layer.provide(Layer.merge(ProjectionStore.layer, providerSessionManagerProvided)), | |
| 167 | const layerProviderTurnControlServiceProvided = ProviderTurnControlService.layer.pipe( | |
| 168 | Layer.provide(Layer.merge(ProjectionStore.layer, layerProviderSessionManagerProvided)), | |
| 166 | 169 | ); |
| 167 | const runtimeRequestServiceProvided = RuntimeRequestService.layer.pipe( | |
| 168 | Layer.provide(Layer.merge(ProjectionStore.layer, providerSessionManagerProvided)), | |
| 170 | const layerRuntimeRequestServiceProvided = RuntimeRequestService.layer.pipe( | |
| 171 | Layer.provide(Layer.merge(ProjectionStore.layer, layerProviderSessionManagerProvided)), | |
| 169 | 172 | ); |
| 170 | const checkpointRollbackServiceProvided = CheckpointRollbackService.layer.pipe( | |
| 173 | const layerCheckpointRollbackServiceProvided = CheckpointRollbackService.layer.pipe( | |
| 171 | 174 | Layer.provide( |
| 172 | 175 | Layer.mergeAll( |
| 173 | 176 | ProjectStore.layer, |
| 174 | checkpointServiceProvided, | |
| 175 | eventSinkProvided, | |
| 177 | layerCheckpointServiceProvided, | |
| 178 | layerEventSinkProvided, | |
| 176 | 179 | IdAllocator.layer, |
| 177 | 180 | ProjectionStore.layer, |
| 178 | providerSessionManagerProvided, | |
| 179 | runtimePolicyProvided, | |
| 181 | layerProviderSessionManagerProvided, | |
| 182 | layerRuntimePolicyProvided, | |
| 180 | 183 | ), |
| 181 | 184 | ), |
| 182 | 185 | ); |
| 183 | const checkpointCaptureServiceProvided = CheckpointCaptureService.layer.pipe( | |
| 186 | const layerCheckpointCaptureServiceProvided = CheckpointCaptureService.layer.pipe( | |
| 184 | 187 | Layer.provide( |
| 185 | 188 | Layer.mergeAll( |
| 186 | checkpointServiceProvided, | |
| 187 | eventSinkProvided, | |
| 189 | layerCheckpointServiceProvided, | |
| 190 | layerEventSinkProvided, | |
| 188 | 191 | IdAllocator.layer, |
| 189 | 192 | ProjectionStore.layer, |
| 190 | 193 | ), |
| 191 | 194 | ), |
| 192 | 195 | ); |
| 193 | const runFinalizationServiceProvided = RunFinalizationService.layer.pipe( | |
| 194 | Layer.provide(Layer.merge(checkpointCaptureServiceProvided, ProjectionStore.layer)), | |
| 196 | const layerRunFinalizationServiceProvided = RunFinalizationService.layer.pipe( | |
| 197 | Layer.provide(Layer.merge(layerCheckpointCaptureServiceProvided, ProjectionStore.layer)), | |
| 195 | 198 | ); |
| 196 | 199 | |
| 197 | const orchestratorProvided = Orchestrator.layer.pipe( | |
| 200 | const layerOrchestratorProvided = Orchestrator.layer.pipe( | |
| 198 | 201 | Layer.provide( |
| 199 | 202 | Layer.mergeAll( |
| 200 | checkpointServiceProvided, | |
| 203 | layerCheckpointServiceProvided, | |
| 201 | 204 | CommandPolicy.layer, |
| 202 | storesLayer, | |
| 203 | eventSinkProvided, | |
| 204 | commandReceiptStoreProvided, | |
| 205 | contextHandoffServiceProvided, | |
| 205 | layerStores, | |
| 206 | layerEventSinkProvided, | |
| 207 | layerCommandReceiptStoreProvided, | |
| 208 | layerContextHandoffServiceProvided, | |
| 206 | 209 | IdAllocator.layer, |
| 207 | 210 | ProjectStore.layer, |
| 208 | providerAdapterRegistryProvided, | |
| 211 | layerProviderAdapterRegistryProvided, | |
| 209 | 212 | // Same layer reference as the continuation worker and the adapter |
| 210 | 213 | // infrastructure so layer memoization yields one shared request queue. |
| 211 | 214 | ProviderContinuationRequests.layer, |
| 212 | providerEventIngestorProvided, | |
| 213 | runtimePolicyProvided, | |
| 214 | providerSessionManagerProvided, | |
| 215 | providerSwitchServiceProvided, | |
| 216 | runExecutionServiceProvided, | |
| 215 | layerProviderEventIngestorProvided, | |
| 216 | layerRuntimePolicyProvided, | |
| 217 | layerProviderSessionManagerProvided, | |
| 218 | layerProviderSwitchServiceProvided, | |
| 219 | layerRunExecutionServiceProvided, | |
| 217 | 220 | ThreadForkService.layer, |
| 218 | 221 | ), |
| 219 | 222 | ), |
| 220 | 223 | ); |
| 221 | 224 | |
| 222 | const agentSessionImporterProvided = AgentSessionImporter.layer.pipe( | |
| 225 | const layerAgentSessionImporterProvided = AgentSessionImporter.layer.pipe( | |
| 223 | 226 | Layer.provide( |
| 224 | 227 | Layer.mergeAll( |
| 225 | 228 | AgentSessionScanner.layer, |
| 226 | ProjectServiceLayerLive, | |
| 227 | orchestratorProvided, | |
| 228 | eventSinkProvided, | |
| 229 | layerProjectService, | |
| 230 | layerOrchestratorProvided, | |
| 231 | layerEventSinkProvided, | |
| 229 | 232 | IdAllocator.layer, |
| 230 | 233 | ProviderSessionRuntime.layer, |
| 231 | 234 | ), |
| 232 | 235 | ), |
| 233 | 236 | ); |
| 234 | 237 | |
| 235 | const threadManagementProvided = ThreadManagementService.layerWithLegacyImporter.pipe( | |
| 236 | Layer.provide(Layer.merge(orchestratorProvided, legacyV1ThreadImporterProvided)), | |
| 238 | const layerThreadManagementProvided = ThreadManagementService.layerWithLegacyImporter.pipe( | |
| 239 | Layer.provide(Layer.merge(layerOrchestratorProvided, layerLegacyV1ThreadImporterProvided)), | |
| 237 | 240 | ); |
| 238 | export const ProjectSetupScriptRunnerLayerLive = ProjectSetupScriptRunner.layer.pipe( | |
| 239 | Layer.provide(ProjectServiceLayerLive), | |
| 241 | export const layerProjectSetupScriptRunner = ProjectSetupScriptRunner.layer.pipe( | |
| 242 | Layer.provide(layerProjectService), | |
| 240 | 243 | ); |
| 241 | const managedProjectFoldersProvided = ManagedProjectFolders.layer.pipe( | |
| 242 | Layer.provide(ProjectServiceLayerLive), | |
| 244 | const layerManagedProjectFoldersProvided = ManagedProjectFolders.layer.pipe( | |
| 245 | Layer.provide(layerProjectService), | |
| 243 | 246 | ); |
| 244 | const threadLaunchProvided = ThreadLaunchService.layer.pipe( | |
| 247 | const layerThreadLaunchProvided = ThreadLaunchService.layer.pipe( | |
| 245 | 248 | Layer.provide( |
| 246 | 249 | Layer.mergeAll( |
| 247 | ProjectServiceLayerLive, | |
| 248 | ProjectSetupScriptRunnerLayerLive, | |
| 249 | managedProjectFoldersProvided, | |
| 250 | threadManagementProvided, | |
| 251 | commandReceiptStoreProvided, | |
| 250 | layerProjectService, | |
| 251 | layerProjectSetupScriptRunner, | |
| 252 | layerManagedProjectFoldersProvided, | |
| 253 | layerThreadManagementProvided, | |
| 254 | layerCommandReceiptStoreProvided, | |
| 252 | 255 | IdAllocator.layer, |
| 253 | 256 | ), |
| 254 | 257 | ), |
| 255 | 258 | ); |
| 256 | const threadLifecycleProvided = ThreadLifecycleService.layer.pipe( | |
| 257 | Layer.provide(threadManagementProvided), | |
| 259 | const layerThreadLifecycleProvided = ThreadLifecycleService.layer.pipe( | |
| 260 | Layer.provide(layerThreadManagementProvided), | |
| 261 | ); | |
| 262 | const layerSecretRequestsProvided = SecretRequests.layer.pipe( | |
| 263 | Layer.provide(layerThreadManagementProvided), | |
| 258 | 264 | ); |
| 259 | const secretRequestsProvided = SecretRequests.layer.pipe(Layer.provide(threadManagementProvided)); | |
| 260 | const scheduledTaskProvided = ScheduledTaskService.layer.pipe( | |
| 265 | const layerScheduledTaskProvided = ScheduledTaskService.layer.pipe( | |
| 261 | 266 | Layer.provide( |
| 262 | Layer.mergeAll(threadLaunchProvided, threadManagementProvided, secretRequestsProvided), | |
| 267 | Layer.mergeAll( | |
| 268 | layerThreadLaunchProvided, | |
| 269 | layerThreadManagementProvided, | |
| 270 | layerSecretRequestsProvided, | |
| 271 | ), | |
| 263 | 272 | ), |
| 264 | 273 | ); |
| 265 | const providerContinuationWorkerProvided = ProviderContinuationService.workerLive.pipe( | |
| 274 | const layerProviderContinuationWorkerProvided = ProviderContinuationService.layer.pipe( | |
| 266 | 275 | Layer.provide( |
| 267 | Layer.mergeAll(ProviderContinuationRequests.layer, threadManagementProvided, IdAllocator.layer), | |
| 276 | Layer.mergeAll( | |
| 277 | ProviderContinuationRequests.layer, | |
| 278 | layerThreadManagementProvided, | |
| 279 | IdAllocator.layer, | |
| 280 | ), | |
| 268 | 281 | ), |
| 269 | 282 | ); |
| 270 | const threadTitleRegenerationProvided = ThreadTitleRegenerationService.layer.pipe( | |
| 271 | Layer.provide(Layer.mergeAll(threadManagementProvided, ProjectStore.layer, TextGeneration.layer)), | |
| 283 | const layerThreadTitleRegenerationProvided = ThreadTitleRegenerationService.layer.pipe( | |
| 284 | Layer.provide( | |
| 285 | Layer.mergeAll(layerThreadManagementProvided, ProjectStore.layer, TextGeneration.layer), | |
| 286 | ), | |
| 272 | 287 | ); |
| 273 | const effectExecutorProvided = EffectWorker.executorLayer.pipe( | |
| 288 | const layerEffectExecutorProvided = EffectWorker.layerExecutor.pipe( | |
| 274 | 289 | Layer.provide( |
| 275 | 290 | Layer.mergeAll( |
| 276 | runFinalizationServiceProvided, | |
| 277 | checkpointRollbackServiceProvided, | |
| 278 | providerSessionManagerProvided, | |
| 279 | providerTurnControlServiceProvided, | |
| 280 | providerTurnStartServiceProvided, | |
| 281 | runtimeRequestServiceProvided, | |
| 282 | threadTitleRegenerationProvided, | |
| 283 | threadManagementProvided, | |
| 291 | layerRunFinalizationServiceProvided, | |
| 292 | layerCheckpointRollbackServiceProvided, | |
| 293 | layerProviderSessionManagerProvided, | |
| 294 | layerProviderTurnControlServiceProvided, | |
| 295 | layerProviderTurnStartServiceProvided, | |
| 296 | layerRuntimeRequestServiceProvided, | |
| 297 | layerThreadTitleRegenerationProvided, | |
| 298 | layerThreadManagementProvided, | |
| 284 | 299 | ), |
| 285 | 300 | ), |
| 286 | 301 | ); |
| 287 | const effectWorkerProvided = EffectWorker.layer.pipe( | |
| 288 | Layer.provide(Layer.merge(storesLayer, effectExecutorProvided)), | |
| 302 | const layerEffectWorkerProvided = EffectWorker.layer.pipe( | |
| 303 | Layer.provide(Layer.merge(layerStores, layerEffectExecutorProvided)), | |
| 289 | 304 | ); |
| 290 | const providerRuntimeRecoveryProvided = ProviderRuntimeRecoveryService.layer.pipe( | |
| 305 | const layerProviderRuntimeRecoveryProvided = ProviderRuntimeRecoveryService.layer.pipe( | |
| 291 | 306 | Layer.provide( |
| 292 | 307 | Layer.mergeAll( |
| 293 | effectWorkerProvided, | |
| 294 | storesLayer, | |
| 295 | eventSinkProvided, | |
| 308 | layerEffectWorkerProvided, | |
| 309 | layerStores, | |
| 310 | layerEventSinkProvided, | |
| 296 | 311 | IdAllocator.layer, |
| 297 | 312 | ProjectionStore.layer, |
| 298 | 313 | ), |
| 299 | 314 | ), |
| 300 | 315 | ); |
| 301 | 316 | |
| 302 | export const OrchestrationV2LayerLive = Layer.mergeAll( | |
| 303 | orchestratorProvided, | |
| 304 | threadManagementProvided, | |
| 305 | effectWorkerProvided, | |
| 306 | providerSessionManagerProvided, | |
| 307 | providerAuthServiceProvided, | |
| 308 | providerRuntimeRecoveryProvided, | |
| 309 | projectionMaintenanceProvided, | |
| 310 | legacyV1ThreadImporterProvided, | |
| 317 | export const layer = Layer.mergeAll( | |
| 318 | layerOrchestratorProvided, | |
| 319 | layerThreadManagementProvided, | |
| 320 | layerEffectWorkerProvided, | |
| 321 | layerProviderSessionManagerProvided, | |
| 322 | layerProviderAuthServiceProvided, | |
| 323 | layerProviderRuntimeRecoveryProvided, | |
| 324 | layerProjectionMaintenanceProvided, | |
| 325 | layerLegacyV1ThreadImporterProvided, | |
| 311 | 326 | ); |
| 312 | 327 | |
| 313 | export const OrchestrationV2ProductionLayerLive = Layer.mergeAll( | |
| 314 | OrchestrationV2LayerLive.pipe(Layer.provide(ProjectServiceLayerLive)), | |
| 315 | ProjectServiceLayerLive, | |
| 316 | managedProjectFoldersProvided, | |
| 317 | threadLaunchProvided, | |
| 318 | threadLifecycleProvided, | |
| 319 | scheduledTaskProvided, | |
| 320 | secretRequestsProvided, | |
| 321 | UsageLimitRecoveryWorker.workerLive.pipe( | |
| 322 | Layer.provide(Layer.mergeAll(ProjectionStore.layer, threadManagementProvided)), | |
| 328 | export const layerProduction = Layer.mergeAll( | |
| 329 | layer.pipe(Layer.provide(layerProjectService)), | |
| 330 | layerProjectService, | |
| 331 | layerManagedProjectFoldersProvided, | |
| 332 | layerThreadLaunchProvided, | |
| 333 | layerThreadLifecycleProvided, | |
| 334 | layerScheduledTaskProvided, | |
| 335 | layerSecretRequestsProvided, | |
| 336 | UsageLimitRecoveryWorker.layer.pipe( | |
| 337 | Layer.provide(Layer.mergeAll(ProjectionStore.layer, layerThreadManagementProvided)), | |
| 323 | 338 | ), |
| 324 | providerContinuationWorkerProvided, | |
| 325 | agentSessionImporterProvided, | |
| 326 | EffectOutbox.pruneWorkerLive.pipe(Layer.provide(EffectOutbox.layer)), | |
| 327 | ).pipe( | |
| 328 | Layer.provide(Scheduler.layer), | |
| 329 | Layer.provideMerge(OrchestrationEventInfrastructureLayerLive), | |
| 330 | ); | |
| 339 | layerProviderContinuationWorkerProvided, | |
| 340 | layerAgentSessionImporterProvided, | |
| 341 | EffectOutbox.layerPruneWorker.pipe(Layer.provide(EffectOutbox.layer)), | |
| 342 | ).pipe(Layer.provide(Scheduler.layer), Layer.provideMerge(layerEventInfrastructure)); |
apps/server/src/orchestration-v2/testkit/ClaudeReplayFixtures.integration.test.ts+2-2| ... | ... | @@ -18,7 +18,7 @@ import { provideDeterministicTestRuntime } from "./DeterministicRuntime.ts"; |
| 18 | 18 | import { ORCHESTRATOR_REPLAY_FIXTURES } from "./fixtures/index.ts"; |
| 19 | 19 | import { subagentInput } from "./fixtures/subagent/input.ts"; |
| 20 | 20 | import { runOrchestratorV2Scenario } from "./OrchestratorScenario.ts"; |
| 21 | import { makeOrchestratorV2ProviderReplayLayer } from "./ProviderReplayHarness.ts"; | |
| 21 | import * as ProviderReplayHarness from "./ProviderReplayHarness.ts"; | |
| 22 | 22 | import { materializeReplayTranscriptRuntimeInstructions } from "./ReplayTranscriptNdjson.ts"; |
| 23 | 23 | import { CLAUDE_MODEL_SELECTION, materializeFixtureInput } from "./fixtures/shared.ts"; |
| 24 | 24 | import { |
| ... | ... | @@ -188,7 +188,7 @@ describe("Claude Agent SDK replay fixtures", () => { |
| 188 | 188 | assert.deepEqual(after.messages, child.messages); |
| 189 | 189 | }).pipe( |
| 190 | 190 | Effect.provide( |
| 191 | makeOrchestratorV2ProviderReplayLayer(scenario, ClaudeOrchestratorReplayHarness), | |
| 191 | ProviderReplayHarness.layerProviderReplay(scenario, ClaudeOrchestratorReplayHarness), | |
| 192 | 192 | ), |
| 193 | 193 | provideDeterministicTestRuntime, |
| 194 | 194 | Effect.scoped, |
apps/server/src/orchestration-v2/testkit/OrchestratorReplayRecovery.integration.test.ts+9-11| ... | ... | @@ -12,18 +12,16 @@ import { |
| 12 | 12 | ClaudeOrchestratorReplayHarness, |
| 13 | 13 | makeClaudeRestartReplayHarness, |
| 14 | 14 | } from "../Adapters/ClaudeAdapterV2.testkit.ts"; |
| 15 | import { | |
| 16 | CodexOrchestratorReplayHarness, | |
| 17 | makeCodexProviderAdapterRegistryReplayLayer, | |
| 18 | } from "../Adapters/CodexAdapterV2.testkit.ts"; | |
| 15 | import { CodexOrchestratorReplayHarness } from "../Adapters/CodexAdapterV2.testkit.ts"; | |
| 16 | import * as CodexAdapterV2Testkit from "../Adapters/CodexAdapterV2.testkit.ts"; | |
| 19 | 17 | import { |
| 20 | 18 | type CursorAgentSdkReplayTranscript, |
| 21 | 19 | CursorOrchestratorReplayHarness, |
| 22 | 20 | makeCursorAgentSdkReplayRunner, |
| 23 | makeCursorProviderAdapterRegistryReplayLayer, | |
| 24 | 21 | } from "../Adapters/CursorAdapterV2.testkit.ts"; |
| 22 | import * as CursorAdapterV2Testkit from "../Adapters/CursorAdapterV2.testkit.ts"; | |
| 25 | 23 | import * as IdAllocator from "../IdAllocator.ts"; |
| 26 | import { makeSqlitePersistenceLive } from "../../persistence/Layers/Sqlite.ts"; | |
| 24 | import * as SqlitePersistence from "../../persistence/Layers/Sqlite.ts"; | |
| 27 | 25 | import { provideDeterministicTestRuntime } from "./DeterministicRuntime.ts"; |
| 28 | 26 | import { |
| 29 | 27 | CLAUDE_MODEL_SELECTION, |
| ... | ... | @@ -136,12 +134,12 @@ const runCursorRecovery = Effect.fn("runCursorRecovery")(function* (input: { |
| 136 | 134 | const { phase1Commands, phase1Steps, phase2Commands, phase2Steps } = |
| 137 | 135 | splitAfterFirstIdle(materialized); |
| 138 | 136 | const options = { |
| 139 | databaseLayer: makeSqlitePersistenceLive(dbPath).pipe(Layer.provide(NodeServices.layer)), | |
| 137 | databaseLayer: SqlitePersistence.layerFromPath(dbPath).pipe(Layer.provide(NodeServices.layer)), | |
| 140 | 138 | }; |
| 141 | 139 | const harness = { |
| 142 | 140 | ...CursorOrchestratorReplayHarness, |
| 143 | 141 | makeProviderAdapterRegistryLayer: () => |
| 144 | makeCursorProviderAdapterRegistryReplayLayer(input.transcript, { | |
| 142 | CursorAdapterV2Testkit.layer(input.transcript, { | |
| 145 | 143 | runner: input.runner, |
| 146 | 144 | assertCompleteOnFinalize: false, |
| 147 | 145 | }), |
| ... | ... | @@ -237,10 +235,10 @@ describe("orchestrator replay recovery", () => { |
| 237 | 235 | const harness = { |
| 238 | 236 | ...CodexOrchestratorReplayHarness, |
| 239 | 237 | makeProviderAdapterRegistryLayer: () => |
| 240 | makeCodexProviderAdapterRegistryReplayLayer({ transcript, driver }), | |
| 238 | CodexAdapterV2Testkit.layer({ transcript, driver }), | |
| 241 | 239 | }; |
| 242 | 240 | const options = { |
| 243 | databaseLayer: makeSqlitePersistenceLive(dbPath).pipe( | |
| 241 | databaseLayer: SqlitePersistence.layerFromPath(dbPath).pipe( | |
| 244 | 242 | Layer.provide(NodeServices.layer), |
| 245 | 243 | ), |
| 246 | 244 | }; |
| ... | ... | @@ -340,7 +338,7 @@ describe("orchestrator replay recovery", () => { |
| 340 | 338 | splitAfterFirstIdle(materialized); |
| 341 | 339 | const { harness, assertComplete } = makeClaudeRestartReplayHarness(transcript); |
| 342 | 340 | const options = { |
| 343 | databaseLayer: makeSqlitePersistenceLive(path.join(tempDir, "state.sqlite")).pipe( | |
| 341 | databaseLayer: SqlitePersistence.layerFromPath(path.join(tempDir, "state.sqlite")).pipe( | |
| 344 | 342 | Layer.provide(NodeServices.layer), |
| 345 | 343 | ), |
| 346 | 344 | }; |
apps/server/src/orchestration-v2/testkit/OrchestratorReplayRestartBackgroundNote.integration.test.ts+8-10| ... | ... | @@ -12,7 +12,7 @@ import { |
| 12 | 12 | } from "../Adapters/ClaudeAdapterV2.testkit.ts"; |
| 13 | 13 | import * as IdAllocator from "../IdAllocator.ts"; |
| 14 | 14 | import * as EffectWorker from "../EffectWorker.ts"; |
| 15 | import { makeSqlitePersistenceLive } from "../../persistence/Layers/Sqlite.ts"; | |
| 15 | import * as SqlitePersistence from "../../persistence/Layers/Sqlite.ts"; | |
| 16 | 16 | import { provideDeterministicTestRuntime } from "./DeterministicRuntime.ts"; |
| 17 | 17 | import { CLAUDE_BACKGROUND_SUBAGENT_AFTER_ROOT_PROMPT } from "./fixtures/claude_background_subagent_after_root/input.ts"; |
| 18 | 18 | import { |
| ... | ... | @@ -20,10 +20,8 @@ import { |
| 20 | 20 | materializeFixtureInput, |
| 21 | 21 | projectionFor, |
| 22 | 22 | } from "./fixtures/shared.ts"; |
| 23 | import { | |
| 24 | makeOrchestratorV2ProviderReplayLayer, | |
| 25 | runOrchestratorV2ProviderReplayScenario, | |
| 26 | } from "./ProviderReplayHarness.ts"; | |
| 23 | import { runOrchestratorV2ProviderReplayScenario } from "./ProviderReplayHarness.ts"; | |
| 24 | import * as ProviderReplayHarness from "./ProviderReplayHarness.ts"; | |
| 27 | 25 | import { runOrchestratorV2Scenario } from "./OrchestratorScenario.ts"; |
| 28 | 26 | import { checkpointWorkspace } from "./ReplayFixtureWorkspace.ts"; |
| 29 | 27 | import { readProviderReplayTranscript } from "./ReplayTranscriptNdjson.ts"; |
| ... | ... | @@ -162,7 +160,7 @@ const runRestart = Effect.fn("runRestart")(function* (input: { |
| 162 | 160 | const phase1Steps = materialized.steps.slice(0, firstIdle + 1); |
| 163 | 161 | const phase2Steps = materialized.steps.slice(firstIdle + 1); |
| 164 | 162 | const { harness, assertComplete } = makeClaudeRestartReplayHarness(transcript); |
| 165 | const databaseLayer = makeSqlitePersistenceLive(path.join(tempDir, "state.sqlite")).pipe( | |
| 163 | const layerDatabase = SqlitePersistence.layerFromPath(path.join(tempDir, "state.sqlite")).pipe( | |
| 166 | 164 | Layer.provide(NodeServices.layer), |
| 167 | 165 | ); |
| 168 | 166 | const scenario = (name: string, steps: typeof materialized.steps) => ({ |
| ... | ... | @@ -176,7 +174,7 @@ const runRestart = Effect.fn("runRestart")(function* (input: { |
| 176 | 174 | |
| 177 | 175 | const before = yield* Effect.scoped( |
| 178 | 176 | runOrchestratorV2ProviderReplayScenario(scenario("before-restart", phase1Steps), harness, { |
| 179 | databaseLayer, | |
| 177 | databaseLayer: layerDatabase, | |
| 180 | 178 | }), |
| 181 | 179 | ); |
| 182 | 180 | const settled = projectionFor(before, SCENARIO); |
| ... | ... | @@ -193,8 +191,8 @@ const runRestart = Effect.fn("runRestart")(function* (input: { |
| 193 | 191 | return yield* runOrchestratorV2Scenario(restartScenario); |
| 194 | 192 | }).pipe( |
| 195 | 193 | Effect.provide( |
| 196 | makeOrchestratorV2ProviderReplayLayer(restartScenario, harness, { | |
| 197 | databaseLayer, | |
| 194 | ProviderReplayHarness.layerProviderReplay(restartScenario, harness, { | |
| 195 | databaseLayer: layerDatabase, | |
| 198 | 196 | recoverOnStartup: true, |
| 199 | 197 | continueThreadsAfterServerUpdate: input.continueThreadsAfterServerUpdate, |
| 200 | 198 | }), |
| ... | ... | @@ -211,7 +209,7 @@ const runRestart = Effect.fn("runRestart")(function* (input: { |
| 211 | 209 | |
| 212 | 210 | const after = yield* Effect.scoped( |
| 213 | 211 | runOrchestratorV2ProviderReplayScenario(scenario("after-restart", phase2Steps), harness, { |
| 214 | databaseLayer, | |
| 212 | databaseLayer: layerDatabase, | |
| 215 | 213 | continueThreadsAfterServerUpdate: input.continueThreadsAfterServerUpdate, |
| 216 | 214 | }), |
| 217 | 215 | ); |
apps/server/src/orchestration-v2/testkit/ProviderReplayHarness.ts+120-107| ... | ... | @@ -13,7 +13,7 @@ import type { SqlError } from "effect/sql/SqlError"; |
| 13 | 13 | |
| 14 | 14 | import * as CheckpointStore from "../../checkpointing/CheckpointStore.ts"; |
| 15 | 15 | import * as ServerConfig from "../../config.ts"; |
| 16 | import { SqlitePersistenceMemory } from "../../persistence/Layers/Sqlite.ts"; | |
| 16 | import * as SqlitePersistence from "../../persistence/Layers/Sqlite.ts"; | |
| 17 | 17 | import * as ServerSettings from "../../serverSettings.ts"; |
| 18 | 18 | import * as ThreadManagementService from "../ThreadManagementService.ts"; |
| 19 | 19 | import * as McpSessionRegistryTestkit from "../../mcp/McpSessionRegistry.testkit.ts"; |
| ... | ... | @@ -43,7 +43,7 @@ import * as ProviderSessionManager from "../ProviderSessionManager.ts"; |
| 43 | 43 | import * as ProviderSwitchService from "../ProviderSwitchService.ts"; |
| 44 | 44 | import * as ProviderTurnControlService from "../ProviderTurnControlService.ts"; |
| 45 | 45 | import * as ProviderTurnStartService from "../ProviderTurnStartService.ts"; |
| 46 | import { worktreeRepairDependenciesTestLayer } from "../ProviderTurnStartService.testkit.ts"; | |
| 46 | import * as ProviderTurnStartServiceTestkit from "../ProviderTurnStartService.testkit.ts"; | |
| 47 | 47 | import * as RunExecutionService from "../RunExecutionService.ts"; |
| 48 | 48 | import * as RunFinalizationService from "../RunFinalizationService.ts"; |
| 49 | 49 | import * as ThreadTitleRegenerationService from "../ThreadTitleRegenerationService.ts"; |
| ... | ... | @@ -206,7 +206,7 @@ export function runOrchestratorV2ProviderReplayScenario< |
| 206 | 206 | : [], |
| 207 | 207 | ) ?? [], |
| 208 | 208 | ); |
| 209 | const layer = makeOrchestratorV2ProviderReplayLayer(scenario, harness, { | |
| 209 | const layer = layerProviderReplay(scenario, harness, { | |
| 210 | 210 | ...options, |
| 211 | 211 | replayGate, |
| 212 | 212 | }); |
| ... | ... | @@ -214,10 +214,7 @@ export function runOrchestratorV2ProviderReplayScenario< |
| 214 | 214 | return runOrchestratorV2Scenario(scenario, { replayGate }).pipe(Effect.provide(layer)); |
| 215 | 215 | } |
| 216 | 216 | |
| 217 | export function makeOrchestratorV2ProviderReplayLayer< | |
| 218 | Transcript extends ProviderReplayTranscript, | |
| 219 | Error, | |
| 220 | >( | |
| 217 | export function layerProviderReplay<Transcript extends ProviderReplayTranscript, Error>( | |
| 221 | 218 | scenario: OrchestratorV2ProviderReplayScenario<Transcript>, |
| 222 | 219 | harness: OrchestratorV2ProviderReplayHarness<Transcript, Error>, |
| 223 | 220 | options: { |
| ... | ... | @@ -239,14 +236,14 @@ export function makeOrchestratorV2ProviderReplayLayer< |
| 239 | 236 | Orchestrator.OrchestratorV2 | EffectWorker.OrchestrationEffectWorkerV2 | EventSink.EventSinkV2, |
| 240 | 237 | Error | MigrationError | PlatformError.PlatformError | SqlError |
| 241 | 238 | > { |
| 242 | const registryLayer = harness.makeProviderAdapterRegistryLayer( | |
| 239 | const layerRegistry = harness.makeProviderAdapterRegistryLayer( | |
| 243 | 240 | scenario.transcript, |
| 244 | 241 | options.replayGate === undefined ? {} : { replayGate: options.replayGate }, |
| 245 | 242 | ); |
| 246 | return makeOrchestratorV2ReplayLayerWithRegistry(scenario, registryLayer, options); | |
| 243 | return layerWithRegistry(scenario, layerRegistry, options); | |
| 247 | 244 | } |
| 248 | 245 | |
| 249 | export function makeOrchestratorV2ReplayLayerWithRegistry<Error>( | |
| 246 | export function layerWithRegistry<Error>( | |
| 250 | 247 | scenario: Pick<OrchestratorV2ProviderReplayScenario, "name" | "runtimePolicyOverride">, |
| 251 | 248 | registryLayer: Layer.Layer<ProviderAdapterRegistry.ProviderAdapterRegistryV2, Error>, |
| 252 | 249 | options: { |
| ... | ... | @@ -267,166 +264,173 @@ export function makeOrchestratorV2ReplayLayerWithRegistry<Error>( |
| 267 | 264 | Orchestrator.OrchestratorV2 | EffectWorker.OrchestrationEffectWorkerV2 | EventSink.EventSinkV2, |
| 268 | 265 | Error | MigrationError | PlatformError.PlatformError | SqlError |
| 269 | 266 | > { |
| 270 | const serverConfigLayer = Layer.effect( | |
| 267 | const layerServerConfig = Layer.effect( | |
| 271 | 268 | ServerConfig.ServerConfig, |
| 272 | 269 | makeReplayServerConfig(scenario.name).pipe(Effect.orDie), |
| 273 | 270 | ).pipe(Layer.provide(NodeServices.layer)); |
| 274 | const runtimeLayer = | |
| 271 | const layerRuntime = | |
| 275 | 272 | scenario.runtimePolicyOverride === undefined |
| 276 | 273 | ? RuntimePolicy.layer |
| 277 | 274 | : RuntimePolicy.layerWithOverride(scenario.runtimePolicyOverride).pipe( |
| 278 | 275 | Layer.provide(RuntimePolicy.layer), |
| 279 | 276 | ); |
| 280 | const databaseLayer = options.databaseLayer ?? SqlitePersistenceMemory; | |
| 277 | const layerDatabase = options.databaseLayer ?? SqlitePersistence.layerMemory; | |
| 281 | 278 | // One queue shared by the adapters, the orchestrator, and the worker, like |
| 282 | 279 | // runtimeLayer.ts; layer memoization keeps it a single instance. |
| 283 | const continuationRequestsLayer = | |
| 280 | const layerContinuationRequests = | |
| 284 | 281 | options.runContinuationWorker === true ? ProviderContinuationRequests.layer : Layer.empty; |
| 285 | const providedRegistryLayer = registryLayer.pipe(Layer.provide(continuationRequestsLayer)); | |
| 286 | const serverSettingsLayer = ServerSettings.layerTest({ | |
| 282 | const layerProvidedRegistry = registryLayer.pipe(Layer.provide(layerContinuationRequests)); | |
| 283 | const layerServerSettings = ServerSettings.layerTest({ | |
| 287 | 284 | responseStreamingMode: "turn", |
| 288 | 285 | ...(options.continueThreadsAfterServerUpdate === undefined |
| 289 | 286 | ? {} |
| 290 | 287 | : { continueThreadsAfterServerUpdate: options.continueThreadsAfterServerUpdate }), |
| 291 | 288 | }).pipe(Layer.orDie); |
| 292 | const storesLayer = Layer.mergeAll( | |
| 289 | const layerStores = Layer.mergeAll( | |
| 293 | 290 | EventStore.layer, |
| 294 | 291 | ProjectionStore.layer, |
| 295 | 292 | ProjectStore.layer, |
| 296 | 293 | CommandReceiptStore.layer, |
| 297 | 294 | EffectOutbox.layer, |
| 298 | 295 | TurnItemPositionStore.layer, |
| 299 | ).pipe(Layer.provide(databaseLayer)); | |
| 300 | const eventSinkProvided = EventSink.layerFromStores.pipe( | |
| 301 | Layer.provide(Layer.mergeAll(storesLayer, databaseLayer)), | |
| 296 | ).pipe(Layer.provide(layerDatabase)); | |
| 297 | const layerEventSinkProvided = EventSink.layerFromStores.pipe( | |
| 298 | Layer.provide(Layer.mergeAll(layerStores, layerDatabase)), | |
| 299 | ); | |
| 300 | const layerCommandReceiptStoreProvided = CommandReceiptStore.layer.pipe( | |
| 301 | Layer.provide(layerDatabase), | |
| 302 | 302 | ); |
| 303 | const commandReceiptStoreProvided = CommandReceiptStore.layer.pipe(Layer.provide(databaseLayer)); | |
| 304 | const providerEventIngestorProvided = ProviderEventIngestor.layer.pipe( | |
| 303 | const layerProviderEventIngestorProvided = ProviderEventIngestor.layer.pipe( | |
| 305 | 304 | Layer.provide( |
| 306 | 305 | Layer.mergeAll( |
| 307 | storesLayer, | |
| 308 | eventSinkProvided, | |
| 306 | layerStores, | |
| 307 | layerEventSinkProvided, | |
| 309 | 308 | IdAllocator.layer, |
| 310 | 309 | ThreadCommandExecutor.layer, |
| 311 | 310 | ), |
| 312 | 311 | ), |
| 313 | 312 | ); |
| 314 | const vcsDriverRegistryLayer = VcsDriverRegistry.layer.pipe( | |
| 313 | const layerVcsDriverRegistry = VcsDriverRegistry.layer.pipe( | |
| 315 | 314 | Layer.provide(VcsProcess.layer), |
| 316 | Layer.provide(serverConfigLayer), | |
| 315 | Layer.provide(layerServerConfig), | |
| 317 | 316 | Layer.provide(NodeServices.layer), |
| 318 | 317 | ); |
| 319 | const checkpointStoreLayer = CheckpointStore.layer.pipe( | |
| 320 | Layer.provide(vcsDriverRegistryLayer), | |
| 318 | const layerCheckpointStore = CheckpointStore.layer.pipe( | |
| 319 | Layer.provide(layerVcsDriverRegistry), | |
| 321 | 320 | Layer.provide(NodeServices.layer), |
| 322 | 321 | ); |
| 323 | const checkpointServiceProvided = CheckpointService.layer.pipe( | |
| 324 | Layer.provide(Layer.mergeAll(checkpointStoreLayer, IdAllocator.layer)), | |
| 322 | const layerCheckpointServiceProvided = CheckpointService.layer.pipe( | |
| 323 | Layer.provide(Layer.mergeAll(layerCheckpointStore, IdAllocator.layer)), | |
| 325 | 324 | ); |
| 326 | const contextHandoffServiceProvided = ContextHandoffService.layer.pipe( | |
| 325 | const layerContextHandoffServiceProvided = ContextHandoffService.layer.pipe( | |
| 327 | 326 | Layer.provide(IdAllocator.layer), |
| 328 | 327 | ); |
| 329 | const persistenceLayer = Layer.mergeAll( | |
| 330 | storesLayer, | |
| 331 | eventSinkProvided, | |
| 332 | commandReceiptStoreProvided, | |
| 328 | const layerPersistence = Layer.mergeAll( | |
| 329 | layerStores, | |
| 330 | layerEventSinkProvided, | |
| 331 | layerCommandReceiptStoreProvided, | |
| 333 | 332 | IdAllocator.layer, |
| 334 | providerEventIngestorProvided, | |
| 333 | layerProviderEventIngestorProvided, | |
| 335 | 334 | ); |
| 336 | const providerSessionManagerProvided = ProviderSessionManager.layerWithOptions({ | |
| 335 | const layerProviderSessionManagerProvided = ProviderSessionManager.layerWithOptions({ | |
| 337 | 336 | configureMcp: false, |
| 338 | 337 | }).pipe( |
| 339 | 338 | Layer.provide( |
| 340 | 339 | Layer.mergeAll( |
| 341 | providedRegistryLayer, | |
| 342 | eventSinkProvided, | |
| 340 | layerProvidedRegistry, | |
| 341 | layerEventSinkProvided, | |
| 343 | 342 | IdAllocator.layer, |
| 344 | 343 | McpSessionRegistryTestkit.layer, |
| 345 | providerEventIngestorProvided, | |
| 346 | storesLayer, | |
| 344 | layerProviderEventIngestorProvided, | |
| 345 | layerStores, | |
| 347 | 346 | ), |
| 348 | 347 | ), |
| 349 | 348 | ); |
| 350 | const providerSwitchServiceProvided = ProviderSwitchService.layer.pipe( | |
| 351 | Layer.provide(providedRegistryLayer), | |
| 349 | const layerProviderSwitchServiceProvided = ProviderSwitchService.layer.pipe( | |
| 350 | Layer.provide(layerProvidedRegistry), | |
| 352 | 351 | ); |
| 353 | const runExecutionServiceProvided = RunExecutionService.layer.pipe( | |
| 352 | const layerRunExecutionServiceProvided = RunExecutionService.layer.pipe( | |
| 354 | 353 | Layer.provide( |
| 355 | 354 | Layer.mergeAll( |
| 356 | checkpointServiceProvided, | |
| 357 | eventSinkProvided, | |
| 355 | layerCheckpointServiceProvided, | |
| 356 | layerEventSinkProvided, | |
| 358 | 357 | IdAllocator.layer, |
| 359 | providerEventIngestorProvided, | |
| 360 | serverSettingsLayer, | |
| 358 | layerProviderEventIngestorProvided, | |
| 359 | layerServerSettings, | |
| 361 | 360 | ), |
| 362 | 361 | ), |
| 363 | 362 | ); |
| 364 | const providerTurnStartServiceProvided = ProviderTurnStartService.layer.pipe( | |
| 363 | const layerProviderTurnStartServiceProvided = ProviderTurnStartService.layer.pipe( | |
| 365 | 364 | Layer.provide( |
| 366 | 365 | Layer.mergeAll( |
| 367 | contextHandoffServiceProvided, | |
| 368 | eventSinkProvided, | |
| 366 | layerContextHandoffServiceProvided, | |
| 367 | layerEventSinkProvided, | |
| 369 | 368 | IdAllocator.layer, |
| 370 | storesLayer, | |
| 371 | providerSessionManagerProvided, | |
| 369 | layerStores, | |
| 370 | layerProviderSessionManagerProvided, | |
| 372 | 371 | Layer.mock(ProviderAuthService.ProviderAuthService)({ |
| 373 | 372 | tryHandlePromptCommand: () => Effect.succeed(false), |
| 374 | 373 | }), |
| 375 | runExecutionServiceProvided, | |
| 376 | runtimeLayer, | |
| 374 | layerRunExecutionServiceProvided, | |
| 375 | layerRuntime, | |
| 377 | 376 | ), |
| 378 | 377 | ), |
| 379 | 378 | ); |
| 380 | const providerTurnControlServiceProvided = ProviderTurnControlService.layer.pipe( | |
| 381 | Layer.provide(Layer.merge(storesLayer, providerSessionManagerProvided)), | |
| 379 | const layerProviderTurnControlServiceProvided = ProviderTurnControlService.layer.pipe( | |
| 380 | Layer.provide(Layer.merge(layerStores, layerProviderSessionManagerProvided)), | |
| 382 | 381 | ); |
| 383 | const runtimeRequestServiceProvided = RuntimeRequestService.layer.pipe( | |
| 384 | Layer.provide(Layer.merge(storesLayer, providerSessionManagerProvided)), | |
| 382 | const layerRuntimeRequestServiceProvided = RuntimeRequestService.layer.pipe( | |
| 383 | Layer.provide(Layer.merge(layerStores, layerProviderSessionManagerProvided)), | |
| 385 | 384 | ); |
| 386 | const checkpointRollbackServiceProvided = CheckpointRollbackService.layer.pipe( | |
| 385 | const layerCheckpointRollbackServiceProvided = CheckpointRollbackService.layer.pipe( | |
| 387 | 386 | Layer.provide( |
| 388 | 387 | Layer.mergeAll( |
| 389 | checkpointServiceProvided, | |
| 390 | eventSinkProvided, | |
| 388 | layerCheckpointServiceProvided, | |
| 389 | layerEventSinkProvided, | |
| 391 | 390 | IdAllocator.layer, |
| 392 | storesLayer, | |
| 393 | providerSessionManagerProvided, | |
| 394 | runtimeLayer, | |
| 391 | layerStores, | |
| 392 | layerProviderSessionManagerProvided, | |
| 393 | layerRuntime, | |
| 395 | 394 | ), |
| 396 | 395 | ), |
| 397 | 396 | ); |
| 398 | const checkpointCaptureServiceProvided = CheckpointCaptureService.layer.pipe( | |
| 397 | const layerCheckpointCaptureServiceProvided = CheckpointCaptureService.layer.pipe( | |
| 399 | 398 | Layer.provide( |
| 400 | Layer.mergeAll(checkpointServiceProvided, eventSinkProvided, IdAllocator.layer, storesLayer), | |
| 399 | Layer.mergeAll( | |
| 400 | layerCheckpointServiceProvided, | |
| 401 | layerEventSinkProvided, | |
| 402 | IdAllocator.layer, | |
| 403 | layerStores, | |
| 404 | ), | |
| 401 | 405 | ), |
| 402 | 406 | ); |
| 403 | const runFinalizationServiceProvided = RunFinalizationService.layer.pipe( | |
| 404 | Layer.provide(Layer.merge(checkpointCaptureServiceProvided, storesLayer)), | |
| 407 | const layerRunFinalizationServiceProvided = RunFinalizationService.layer.pipe( | |
| 408 | Layer.provide(Layer.merge(layerCheckpointCaptureServiceProvided, layerStores)), | |
| 405 | 409 | ); |
| 406 | const threadTitleRegenerationTestLayer = Layer.succeed( | |
| 410 | const layerThreadTitleRegenerationTest = Layer.succeed( | |
| 407 | 411 | ThreadTitleRegenerationService.ThreadTitleRegenerationService, |
| 408 | 412 | ThreadTitleRegenerationService.ThreadTitleRegenerationService.of({ |
| 409 | 413 | execute: () => Effect.void, |
| 410 | 414 | }), |
| 411 | 415 | ); |
| 412 | const orchestratorProvided = Orchestrator.layer.pipe( | |
| 416 | const layerOrchestratorProvided = Orchestrator.layer.pipe( | |
| 413 | 417 | Layer.provide( |
| 414 | 418 | Layer.mergeAll( |
| 415 | checkpointServiceProvided, | |
| 419 | layerCheckpointServiceProvided, | |
| 416 | 420 | CommandPolicy.layer, |
| 417 | contextHandoffServiceProvided, | |
| 418 | persistenceLayer, | |
| 419 | providedRegistryLayer, | |
| 420 | continuationRequestsLayer, | |
| 421 | runtimeLayer, | |
| 422 | providerSessionManagerProvided, | |
| 423 | providerSwitchServiceProvided, | |
| 424 | runExecutionServiceProvided, | |
| 421 | layerContextHandoffServiceProvided, | |
| 422 | layerPersistence, | |
| 423 | layerProvidedRegistry, | |
| 424 | layerContinuationRequests, | |
| 425 | layerRuntime, | |
| 426 | layerProviderSessionManagerProvided, | |
| 427 | layerProviderSwitchServiceProvided, | |
| 428 | layerRunExecutionServiceProvided, | |
| 425 | 429 | ThreadForkService.layer, |
| 426 | 430 | ), |
| 427 | 431 | ), |
| 428 | 432 | ); |
| 429 | const threadManagementProvided = Layer.unwrap( | |
| 433 | const layerThreadManagementProvided = Layer.unwrap( | |
| 430 | 434 | Effect.gen(function* () { |
| 431 | 435 | const orchestrator = yield* Orchestrator.OrchestratorV2; |
| 432 | 436 | return Layer.mock(ThreadManagementService.ThreadManagementService)({ |
| ... | ... | @@ -437,49 +441,53 @@ export function makeOrchestratorV2ReplayLayerWithRegistry<Error>( |
| 437 | 441 | delegatedTaskResultPending: orchestrator.delegatedTaskResultPending, |
| 438 | 442 | }); |
| 439 | 443 | }), |
| 440 | ).pipe(Layer.provide(orchestratorProvided)); | |
| 441 | const continuationWorkerProvided = | |
| 444 | ).pipe(Layer.provide(layerOrchestratorProvided)); | |
| 445 | const layerContinuationWorkerProvided = | |
| 442 | 446 | options.runContinuationWorker === true |
| 443 | ? ProviderContinuationService.workerLive.pipe( | |
| 447 | ? ProviderContinuationService.layer.pipe( | |
| 444 | 448 | Layer.provide( |
| 445 | Layer.mergeAll(continuationRequestsLayer, threadManagementProvided, IdAllocator.layer), | |
| 449 | Layer.mergeAll( | |
| 450 | layerContinuationRequests, | |
| 451 | layerThreadManagementProvided, | |
| 452 | IdAllocator.layer, | |
| 453 | ), | |
| 446 | 454 | ), |
| 447 | 455 | ) |
| 448 | 456 | : Layer.empty; |
| 449 | const effectExecutorProvided = EffectWorker.executorLayer.pipe( | |
| 457 | const layerEffectExecutorProvided = EffectWorker.layerExecutor.pipe( | |
| 450 | 458 | Layer.provide( |
| 451 | 459 | Layer.mergeAll( |
| 452 | runFinalizationServiceProvided, | |
| 453 | checkpointRollbackServiceProvided, | |
| 454 | providerSessionManagerProvided, | |
| 455 | providerTurnControlServiceProvided, | |
| 456 | providerTurnStartServiceProvided, | |
| 457 | runtimeRequestServiceProvided, | |
| 458 | threadTitleRegenerationTestLayer, | |
| 459 | serverSettingsLayer, | |
| 460 | threadManagementProvided, | |
| 460 | layerRunFinalizationServiceProvided, | |
| 461 | layerCheckpointRollbackServiceProvided, | |
| 462 | layerProviderSessionManagerProvided, | |
| 463 | layerProviderTurnControlServiceProvided, | |
| 464 | layerProviderTurnStartServiceProvided, | |
| 465 | layerRuntimeRequestServiceProvided, | |
| 466 | layerThreadTitleRegenerationTest, | |
| 467 | layerServerSettings, | |
| 468 | layerThreadManagementProvided, | |
| 461 | 469 | ), |
| 462 | 470 | ), |
| 463 | 471 | ); |
| 464 | const effectWorkerProvided = EffectWorker.layer.pipe( | |
| 465 | Layer.provide(Layer.merge(storesLayer, effectExecutorProvided)), | |
| 472 | const layerEffectWorkerProvided = EffectWorker.layer.pipe( | |
| 473 | Layer.provide(Layer.merge(layerStores, layerEffectExecutorProvided)), | |
| 466 | 474 | ); |
| 467 | const replayRuntime = Layer.mergeAll( | |
| 468 | orchestratorProvided, | |
| 469 | effectWorkerProvided, | |
| 470 | eventSinkProvided, | |
| 471 | continuationWorkerProvided, | |
| 472 | ).pipe(Layer.provide(worktreeRepairDependenciesTestLayer), Layer.provide(NodeServices.layer)); | |
| 475 | const layerReplayRuntime = Layer.mergeAll( | |
| 476 | layerOrchestratorProvided, | |
| 477 | layerEffectWorkerProvided, | |
| 478 | layerEventSinkProvided, | |
| 479 | layerContinuationWorkerProvided, | |
| 480 | ).pipe(Layer.provide(ProviderTurnStartServiceTestkit.layer), Layer.provide(NodeServices.layer)); | |
| 473 | 481 | |
| 474 | 482 | // Build the daemon from the exact worker instance exposed alongside the |
| 475 | 483 | // orchestrator. Keeping this acquisition in the replay layer makes the |
| 476 | 484 | // outbox lifecycle explicit and prevents test-only command-side draining. |
| 477 | 485 | if (options.runEffectWorker === false) { |
| 478 | return replayRuntime; | |
| 486 | return layerReplayRuntime; | |
| 479 | 487 | } |
| 480 | 488 | // Built before the runtime it shares stores with, so recovery commits before |
| 481 | 489 | // the effect worker claims anything, as in serverRuntimeStartup. |
| 482 | const startupRecovery: Layer.Layer< | |
| 490 | const layerStartupRecovery: Layer.Layer< | |
| 483 | 491 | never, |
| 484 | 492 | MigrationError | PlatformError.PlatformError | SqlError |
| 485 | 493 | > = |
| ... | ... | @@ -491,7 +499,12 @@ export function makeOrchestratorV2ReplayLayerWithRegistry<Error>( |
| 491 | 499 | ).pipe( |
| 492 | 500 | Layer.provide(ProviderRuntimeRecoveryService.layer), |
| 493 | 501 | Layer.provide( |
| 494 | Layer.mergeAll(storesLayer, eventSinkProvided, IdAllocator.layer, serverSettingsLayer), | |
| 502 | Layer.mergeAll( | |
| 503 | layerStores, | |
| 504 | layerEventSinkProvided, | |
| 505 | IdAllocator.layer, | |
| 506 | layerServerSettings, | |
| 507 | ), | |
| 495 | 508 | ), |
| 496 | 509 | ) |
| 497 | 510 | : Layer.empty; |
| ... | ... | @@ -504,5 +517,5 @@ export function makeOrchestratorV2ReplayLayerWithRegistry<Error>( |
| 504 | 517 | yield* EffectWorker.runDaemon.pipe(Effect.forkScoped); |
| 505 | 518 | return orchestrator; |
| 506 | 519 | }), |
| 507 | ).pipe(Layer.provideMerge(replayRuntime), Layer.provide(startupRecovery)); | |
| 520 | ).pipe(Layer.provideMerge(layerReplayRuntime), Layer.provide(layerStartupRecovery)); | |
| 508 | 521 | } |
apps/server/src/orchestration-v2/testkit/ProviderSwitch.integration.test.ts+72-70| ... | ... | @@ -32,7 +32,7 @@ import * as Stream from "effect/Stream"; |
| 32 | 32 | import * as Schema from "effect/Schema"; |
| 33 | 33 | import * as SqlClient from "effect/sql/SqlClient"; |
| 34 | 34 | |
| 35 | import { SqlitePersistenceMemory } from "../../persistence/Layers/Sqlite.ts"; | |
| 35 | import * as SqlitePersistence from "../../persistence/Layers/Sqlite.ts"; | |
| 36 | 36 | import { CommandPolicyCapabilityUnsupportedError } from "../CommandPolicy.ts"; |
| 37 | 37 | import { ClaudeProviderCapabilitiesV2 } from "../Adapters/ClaudeAdapterV2.ts"; |
| 38 | 38 | import { |
| ... | ... | @@ -64,7 +64,7 @@ import { |
| 64 | 64 | CURSOR_MODEL_SELECTION, |
| 65 | 65 | GROK_MODEL_SELECTION, |
| 66 | 66 | } from "./fixtures/shared.ts"; |
| 67 | import { makeOrchestratorV2ReplayLayerWithRegistry } from "./ProviderReplayHarness.ts"; | |
| 67 | import * as ProviderReplayHarness from "./ProviderReplayHarness.ts"; | |
| 68 | 68 | import { checkpointWorkspace } from "./ReplayFixtureWorkspace.ts"; |
| 69 | 69 | |
| 70 | 70 | const encodeJson = Schema.encodeEffect(Schema.fromJsonString(Schema.Unknown)); |
| ... | ... | @@ -444,7 +444,7 @@ describe("orchestration v2 provider switching", () => { |
| 444 | 444 | ? { options: [{ id: "contextWindow", value: "1m" }] } |
| 445 | 445 | : { model: `${CLAUDE_MODEL_SELECTION.model}-large` }), |
| 446 | 446 | }; |
| 447 | const registry = ProviderAdapterRegistry.makeLayer([ | |
| 447 | const layerRegistry = ProviderAdapterRegistry.layerFromAdapters([ | |
| 448 | 448 | makeTestAdapter({ |
| 449 | 449 | instanceId: CODEX_MODEL_SELECTION.instanceId, |
| 450 | 450 | driver: CODEX_DRIVER, |
| ... | ... | @@ -880,7 +880,7 @@ describe("orchestration v2 provider switching", () => { |
| 880 | 880 | } |
| 881 | 881 | }).pipe( |
| 882 | 882 | Effect.provide( |
| 883 | makeOrchestratorV2ReplayLayerWithRegistry( | |
| 883 | ProviderReplayHarness.layerWithRegistry( | |
| 884 | 884 | { |
| 885 | 885 | name: `handoff-${scenario}`, |
| 886 | 886 | runtimePolicyOverride: { |
| ... | ... | @@ -889,7 +889,7 @@ describe("orchestration v2 provider switching", () => { |
| 889 | 889 | sandboxPolicy: { type: "readOnly" }, |
| 890 | 890 | }, |
| 891 | 891 | }, |
| 892 | registry, | |
| 892 | layerRegistry, | |
| 893 | 893 | ), |
| 894 | 894 | ), |
| 895 | 895 | ); |
| ... | ... | @@ -906,7 +906,7 @@ describe("orchestration v2 provider switching", () => { |
| 906 | 906 | const injectedHistory = yield* Ref.make<ReadonlyArray<unknown>>([]); |
| 907 | 907 | const failOnce = yield* Ref.make(true); |
| 908 | 908 | const generation = yield* Ref.make(0); |
| 909 | const registry = ProviderAdapterRegistry.makeLayer([ | |
| 909 | const layerRegistry = ProviderAdapterRegistry.layerFromAdapters([ | |
| 910 | 910 | makeTestAdapter({ |
| 911 | 911 | instanceId: CODEX_MODEL_SELECTION.instanceId, |
| 912 | 912 | driver: CODEX_DRIVER, |
| ... | ... | @@ -1042,7 +1042,7 @@ describe("orchestration v2 provider switching", () => { |
| 1042 | 1042 | ); |
| 1043 | 1043 | }).pipe( |
| 1044 | 1044 | Effect.provide( |
| 1045 | makeOrchestratorV2ReplayLayerWithRegistry( | |
| 1045 | ProviderReplayHarness.layerWithRegistry( | |
| 1046 | 1046 | { |
| 1047 | 1047 | name: `handoff-retry-${failure}`, |
| 1048 | 1048 | runtimePolicyOverride: { |
| ... | ... | @@ -1051,7 +1051,7 @@ describe("orchestration v2 provider switching", () => { |
| 1051 | 1051 | sandboxPolicy: { type: "readOnly" }, |
| 1052 | 1052 | }, |
| 1053 | 1053 | }, |
| 1054 | registry, | |
| 1054 | layerRegistry, | |
| 1055 | 1055 | ), |
| 1056 | 1056 | ), |
| 1057 | 1057 | ); |
| ... | ... | @@ -1068,7 +1068,7 @@ describe("orchestration v2 provider switching", () => { |
| 1068 | 1068 | const capturedTurns = yield* Ref.make<ReadonlyArray<CapturedTurn>>([]); |
| 1069 | 1069 | const failResumeOnce = yield* Ref.make(false); |
| 1070 | 1070 | const generation = yield* Ref.make(0); |
| 1071 | const registry = ProviderAdapterRegistry.makeLayer([ | |
| 1071 | const layerRegistry = ProviderAdapterRegistry.layerFromAdapters([ | |
| 1072 | 1072 | makeTestAdapter({ |
| 1073 | 1073 | instanceId: CLAUDE_MODEL_SELECTION.instanceId, |
| 1074 | 1074 | driver: CLAUDE_DRIVER, |
| ... | ... | @@ -1171,7 +1171,7 @@ describe("orchestration v2 provider switching", () => { |
| 1171 | 1171 | assert.isTrue(turns[2]?.nativeThreadHasTurns); |
| 1172 | 1172 | }).pipe( |
| 1173 | 1173 | Effect.provide( |
| 1174 | makeOrchestratorV2ReplayLayerWithRegistry( | |
| 1174 | ProviderReplayHarness.layerWithRegistry( | |
| 1175 | 1175 | { |
| 1176 | 1176 | name: "resume-fallback-native-turns", |
| 1177 | 1177 | runtimePolicyOverride: { |
| ... | ... | @@ -1180,7 +1180,7 @@ describe("orchestration v2 provider switching", () => { |
| 1180 | 1180 | sandboxPolicy: { type: "readOnly" }, |
| 1181 | 1181 | }, |
| 1182 | 1182 | }, |
| 1183 | registry, | |
| 1183 | layerRegistry, | |
| 1184 | 1184 | ), |
| 1185 | 1185 | ), |
| 1186 | 1186 | ); |
| ... | ... | @@ -1215,7 +1215,7 @@ describe("orchestration v2 provider switching", () => { |
| 1215 | 1215 | const targetSelection = returning ? CODEX_MODEL_SELECTION : CLAUDE_MODEL_SELECTION; |
| 1216 | 1216 | const originalPrompt = "Keep the release marker violet and preserve the existing API."; |
| 1217 | 1217 | const partialResponse = "I checked the API and found the release configuration."; |
| 1218 | const registryLayer = ProviderAdapterRegistry.makeLayer( | |
| 1218 | const layerRegistry = ProviderAdapterRegistry.layerFromAdapters( | |
| 1219 | 1219 | ( |
| 1220 | 1220 | [ |
| 1221 | 1221 | [CODEX_MODEL_SELECTION, CODEX_DRIVER, CodexProviderCapabilitiesV2], |
| ... | ... | @@ -1354,7 +1354,7 @@ describe("orchestration v2 provider switching", () => { |
| 1354 | 1354 | assert.notInclude(back.text, partialResponse); |
| 1355 | 1355 | }).pipe( |
| 1356 | 1356 | Effect.provide( |
| 1357 | makeOrchestratorV2ReplayLayerWithRegistry( | |
| 1357 | ProviderReplayHarness.layerWithRegistry( | |
| 1358 | 1358 | { |
| 1359 | 1359 | name: `handoff-${status}-${queued}-${returning}`, |
| 1360 | 1360 | runtimePolicyOverride: { |
| ... | ... | @@ -1367,7 +1367,7 @@ describe("orchestration v2 provider switching", () => { |
| 1367 | 1367 | }, |
| 1368 | 1368 | }, |
| 1369 | 1369 | }, |
| 1370 | registryLayer, | |
| 1370 | layerRegistry, | |
| 1371 | 1371 | ), |
| 1372 | 1372 | ), |
| 1373 | 1373 | ); |
| ... | ... | @@ -1387,7 +1387,7 @@ describe("orchestration v2 provider switching", () => { |
| 1387 | 1387 | const capturedTurns = yield* Ref.make<ReadonlyArray<CapturedTurn>>([]); |
| 1388 | 1388 | const started = yield* Deferred.make<void>(); |
| 1389 | 1389 | const scenarioThreadId = ThreadId.make(`thread:queued-capability:${key}`); |
| 1390 | const registryLayer = ProviderAdapterRegistry.makeLayer([ | |
| 1390 | const layerRegistry = ProviderAdapterRegistry.layerFromAdapters([ | |
| 1391 | 1391 | makeTestAdapter({ |
| 1392 | 1392 | instanceId: CODEX_MODEL_SELECTION.instanceId, |
| 1393 | 1393 | driver: CODEX_DRIVER, |
| ... | ... | @@ -1477,7 +1477,7 @@ describe("orchestration v2 provider switching", () => { |
| 1477 | 1477 | assert.deepEqual(projection.thread.modelSelection, CODEX_MODEL_SELECTION); |
| 1478 | 1478 | }).pipe( |
| 1479 | 1479 | Effect.provide( |
| 1480 | makeOrchestratorV2ReplayLayerWithRegistry( | |
| 1480 | ProviderReplayHarness.layerWithRegistry( | |
| 1481 | 1481 | { |
| 1482 | 1482 | name: `queued-capability-${key}`, |
| 1483 | 1483 | runtimePolicyOverride: { |
| ... | ... | @@ -1490,7 +1490,7 @@ describe("orchestration v2 provider switching", () => { |
| 1490 | 1490 | }, |
| 1491 | 1491 | }, |
| 1492 | 1492 | }, |
| 1493 | registryLayer, | |
| 1493 | layerRegistry, | |
| 1494 | 1494 | ), |
| 1495 | 1495 | ), |
| 1496 | 1496 | ); |
| ... | ... | @@ -1505,7 +1505,7 @@ describe("orchestration v2 provider switching", () => { |
| 1505 | 1505 | const cwd = yield* checkpointWorkspace("queued-steer-provider-switch"); |
| 1506 | 1506 | const capturedTurns = yield* Ref.make<ReadonlyArray<CapturedTurn>>([]); |
| 1507 | 1507 | const started = yield* Deferred.make<void>(); |
| 1508 | const registryLayer = ProviderAdapterRegistry.makeLayer([ | |
| 1508 | const layerRegistry = ProviderAdapterRegistry.layerFromAdapters([ | |
| 1509 | 1509 | makeTestAdapter({ |
| 1510 | 1510 | instanceId: CODEX_MODEL_SELECTION.instanceId, |
| 1511 | 1511 | driver: CODEX_DRIVER, |
| ... | ... | @@ -1670,7 +1670,7 @@ describe("orchestration v2 provider switching", () => { |
| 1670 | 1670 | return yield* orchestrator.getThreadProjection(queuedThreadId); |
| 1671 | 1671 | }).pipe( |
| 1672 | 1672 | Effect.provide( |
| 1673 | makeOrchestratorV2ReplayLayerWithRegistry( | |
| 1673 | ProviderReplayHarness.layerWithRegistry( | |
| 1674 | 1674 | { |
| 1675 | 1675 | name: "queued-steer-provider-switch", |
| 1676 | 1676 | runtimePolicyOverride: { |
| ... | ... | @@ -1683,7 +1683,7 @@ describe("orchestration v2 provider switching", () => { |
| 1683 | 1683 | }, |
| 1684 | 1684 | }, |
| 1685 | 1685 | }, |
| 1686 | registryLayer, | |
| 1686 | layerRegistry, | |
| 1687 | 1687 | ), |
| 1688 | 1688 | ), |
| 1689 | 1689 | ); |
| ... | ... | @@ -1744,7 +1744,7 @@ describe("orchestration v2 provider switching", () => { |
| 1744 | 1744 | capturedTurns, |
| 1745 | 1745 | }), |
| 1746 | 1746 | ]; |
| 1747 | const registryLayer = Layer.succeed( | |
| 1747 | const layerRegistry = Layer.succeed( | |
| 1748 | 1748 | ProviderAdapterRegistry.ProviderAdapterRegistryV2, |
| 1749 | 1749 | ProviderAdapterRegistry.ProviderAdapterRegistryV2.of({ |
| 1750 | 1750 | get: (instanceId) => { |
| ... | ... | @@ -1861,7 +1861,7 @@ describe("orchestration v2 provider switching", () => { |
| 1861 | 1861 | return delivered; |
| 1862 | 1862 | }).pipe( |
| 1863 | 1863 | Effect.provide( |
| 1864 | makeOrchestratorV2ReplayLayerWithRegistry( | |
| 1864 | ProviderReplayHarness.layerWithRegistry( | |
| 1865 | 1865 | { |
| 1866 | 1866 | name: "queued-account-switch", |
| 1867 | 1867 | runtimePolicyOverride: { |
| ... | ... | @@ -1874,7 +1874,7 @@ describe("orchestration v2 provider switching", () => { |
| 1874 | 1874 | }, |
| 1875 | 1875 | }, |
| 1876 | 1876 | }, |
| 1877 | registryLayer, | |
| 1877 | layerRegistry, | |
| 1878 | 1878 | ), |
| 1879 | 1879 | ), |
| 1880 | 1880 | ); |
| ... | ... | @@ -1900,7 +1900,7 @@ describe("orchestration v2 provider switching", () => { |
| 1900 | 1900 | const cwd = yield* checkpointWorkspace("queued-provider-switch"); |
| 1901 | 1901 | const capturedTurns = yield* Ref.make<ReadonlyArray<CapturedTurn>>([]); |
| 1902 | 1902 | const started = yield* Deferred.make<void>(); |
| 1903 | const registryLayer = ProviderAdapterRegistry.makeLayer([ | |
| 1903 | const layerRegistry = ProviderAdapterRegistry.layerFromAdapters([ | |
| 1904 | 1904 | makeTestAdapter({ |
| 1905 | 1905 | instanceId: ProviderInstanceId.make("codex"), |
| 1906 | 1906 | driver: CODEX_DRIVER, |
| ... | ... | @@ -1924,8 +1924,8 @@ describe("orchestration v2 provider switching", () => { |
| 1924 | 1924 | }), |
| 1925 | 1925 | ]); |
| 1926 | 1926 | const queuedThreadId = ThreadId.make("thread:queued-provider-switch"); |
| 1927 | const databaseLayer = SqlitePersistenceMemory; | |
| 1928 | const outboxProvided = EffectOutbox.layer.pipe(Layer.provide(databaseLayer)); | |
| 1927 | const layerDatabase = SqlitePersistence.layerMemory; | |
| 1928 | const layerOutboxProvided = EffectOutbox.layer.pipe(Layer.provide(layerDatabase)); | |
| 1929 | 1929 | const projection = yield* Effect.gen(function* () { |
| 1930 | 1930 | const orchestrator = yield* Orchestrator.OrchestratorV2; |
| 1931 | 1931 | const worker = yield* EffectWorker.OrchestrationEffectWorkerV2; |
| ... | ... | @@ -2072,7 +2072,7 @@ describe("orchestration v2 provider switching", () => { |
| 2072 | 2072 | }).pipe( |
| 2073 | 2073 | Effect.provide( |
| 2074 | 2074 | Layer.merge( |
| 2075 | makeOrchestratorV2ReplayLayerWithRegistry( | |
| 2075 | ProviderReplayHarness.layerWithRegistry( | |
| 2076 | 2076 | { |
| 2077 | 2077 | name: "queued-provider-switch", |
| 2078 | 2078 | runtimePolicyOverride: { |
| ... | ... | @@ -2085,10 +2085,10 @@ describe("orchestration v2 provider switching", () => { |
| 2085 | 2085 | }, |
| 2086 | 2086 | }, |
| 2087 | 2087 | }, |
| 2088 | registryLayer, | |
| 2089 | { databaseLayer }, | |
| 2088 | layerRegistry, | |
| 2089 | { databaseLayer: layerDatabase }, | |
| 2090 | 2090 | ), |
| 2091 | outboxProvided, | |
| 2091 | layerOutboxProvided, | |
| 2092 | 2092 | ), |
| 2093 | 2093 | ), |
| 2094 | 2094 | ); |
| ... | ... | @@ -2150,7 +2150,7 @@ describe("orchestration v2 provider switching", () => { |
| 2150 | 2150 | canConsumeHandoffSummaries: false, |
| 2151 | 2151 | }, |
| 2152 | 2152 | }; |
| 2153 | const registryLayer = ProviderAdapterRegistry.makeLayer([ | |
| 2153 | const layerRegistry = ProviderAdapterRegistry.layerFromAdapters([ | |
| 2154 | 2154 | makeTestAdapter({ |
| 2155 | 2155 | instanceId: CODEX_MODEL_SELECTION.instanceId, |
| 2156 | 2156 | driver: CODEX_DRIVER, |
| ... | ... | @@ -2299,7 +2299,7 @@ describe("orchestration v2 provider switching", () => { |
| 2299 | 2299 | return yield* orchestrator.getThreadProjection(rejectedThreadId); |
| 2300 | 2300 | }).pipe( |
| 2301 | 2301 | Effect.provide( |
| 2302 | makeOrchestratorV2ReplayLayerWithRegistry( | |
| 2302 | ProviderReplayHarness.layerWithRegistry( | |
| 2303 | 2303 | { |
| 2304 | 2304 | name: "queued-handoff-rejection", |
| 2305 | 2305 | runtimePolicyOverride: { |
| ... | ... | @@ -2312,7 +2312,7 @@ describe("orchestration v2 provider switching", () => { |
| 2312 | 2312 | }, |
| 2313 | 2313 | }, |
| 2314 | 2314 | }, |
| 2315 | registryLayer, | |
| 2315 | layerRegistry, | |
| 2316 | 2316 | ), |
| 2317 | 2317 | ), |
| 2318 | 2318 | ); |
| ... | ... | @@ -2358,7 +2358,7 @@ describe("orchestration v2 provider switching", () => { |
| 2358 | 2358 | const capturedTurns = yield* Ref.make<ReadonlyArray<CapturedTurn>>([]); |
| 2359 | 2359 | const firstTurnStarted = yield* Deferred.make<void>(); |
| 2360 | 2360 | const releaseFirstTurn = yield* Deferred.make<void>(); |
| 2361 | const registryLayer = ProviderAdapterRegistry.makeLayer([ | |
| 2361 | const layerRegistry = ProviderAdapterRegistry.layerFromAdapters([ | |
| 2362 | 2362 | makeTestAdapter({ |
| 2363 | 2363 | instanceId: ProviderInstanceId.make("codex"), |
| 2364 | 2364 | driver: CODEX_DRIVER, |
| ... | ... | @@ -2378,22 +2378,24 @@ describe("orchestration v2 provider switching", () => { |
| 2378 | 2378 | capturedTurns, |
| 2379 | 2379 | }), |
| 2380 | 2380 | ]); |
| 2381 | const databaseLayer = SqlitePersistenceMemory; | |
| 2382 | const eventStoreProvided = EventStore.layer.pipe(Layer.provideMerge(databaseLayer)); | |
| 2383 | const projectionStoreProvided = ProjectionStore.layer.pipe( | |
| 2384 | Layer.provideMerge(databaseLayer), | |
| 2381 | const layerDatabase = SqlitePersistence.layerMemory; | |
| 2382 | const layerEventStoreProvided = EventStore.layer.pipe(Layer.provideMerge(layerDatabase)); | |
| 2383 | const layerProjectionStoreProvided = ProjectionStore.layer.pipe( | |
| 2384 | Layer.provideMerge(layerDatabase), | |
| 2385 | 2385 | ); |
| 2386 | const storesProvided = Layer.mergeAll( | |
| 2387 | databaseLayer, | |
| 2388 | eventStoreProvided, | |
| 2389 | projectionStoreProvided, | |
| 2386 | const layerStoresProvided = Layer.mergeAll( | |
| 2387 | layerDatabase, | |
| 2388 | layerEventStoreProvided, | |
| 2389 | layerProjectionStoreProvided, | |
| 2390 | 2390 | ); |
| 2391 | const eventSinkProvided = EventSink.layer.pipe(Layer.provide(storesProvided)); | |
| 2392 | const importerProvided = LegacyV1ThreadImporter.layer.pipe( | |
| 2393 | Layer.provide(Layer.mergeAll(storesProvided, eventSinkProvided)), | |
| 2391 | const layerEventSinkProvided = EventSink.layer.pipe(Layer.provide(layerStoresProvided)); | |
| 2392 | const layerImporterProvided = LegacyV1ThreadImporter.layer.pipe( | |
| 2393 | Layer.provide(Layer.mergeAll(layerStoresProvided, layerEventSinkProvided)), | |
| 2394 | 2394 | ); |
| 2395 | const maintenanceProvided = ProjectionMaintenance.layer.pipe(Layer.provide(storesProvided)); | |
| 2396 | const orchestratorProvided = makeOrchestratorV2ReplayLayerWithRegistry( | |
| 2395 | const layerMaintenanceProvided = ProjectionMaintenance.layer.pipe( | |
| 2396 | Layer.provide(layerStoresProvided), | |
| 2397 | ); | |
| 2398 | const layerOrchestratorProvided = ProviderReplayHarness.layerWithRegistry( | |
| 2397 | 2399 | { |
| 2398 | 2400 | name: "provider-switch-legacy-import", |
| 2399 | 2401 | runtimePolicyOverride: { |
| ... | ... | @@ -2406,14 +2408,14 @@ describe("orchestration v2 provider switching", () => { |
| 2406 | 2408 | }, |
| 2407 | 2409 | }, |
| 2408 | 2410 | }, |
| 2409 | registryLayer, | |
| 2410 | { databaseLayer }, | |
| 2411 | layerRegistry, | |
| 2412 | { databaseLayer: layerDatabase }, | |
| 2411 | 2413 | ); |
| 2412 | const testLayer = Layer.mergeAll( | |
| 2413 | storesProvided, | |
| 2414 | importerProvided, | |
| 2415 | maintenanceProvided, | |
| 2416 | orchestratorProvided, | |
| 2414 | const layerTest = Layer.mergeAll( | |
| 2415 | layerStoresProvided, | |
| 2416 | layerImporterProvided, | |
| 2417 | layerMaintenanceProvided, | |
| 2418 | layerOrchestratorProvided, | |
| 2417 | 2419 | ); |
| 2418 | 2420 | |
| 2419 | 2421 | const projection = yield* Effect.gen(function* () { |
| ... | ... | @@ -2586,7 +2588,7 @@ describe("orchestration v2 provider switching", () => { |
| 2586 | 2588 | const rebuilt = yield* orchestrator.getThreadProjection(importedThreadId); |
| 2587 | 2589 | assert.deepEqual(rebuilt.attempts, beforeRebuild.attempts); |
| 2588 | 2590 | return rebuilt; |
| 2589 | }).pipe(Effect.provide(testLayer)); | |
| 2591 | }).pipe(Effect.provide(layerTest)); | |
| 2590 | 2592 | |
| 2591 | 2593 | const turns = yield* Ref.get(capturedTurns); |
| 2592 | 2594 | assert.deepEqual( |
| ... | ... | @@ -2644,7 +2646,7 @@ describe("orchestration v2 provider switching", () => { |
| 2644 | 2646 | const cwd = yield* checkpointWorkspace("provider-switch"); |
| 2645 | 2647 | const capturedTurns = yield* Ref.make<ReadonlyArray<CapturedTurn>>([]); |
| 2646 | 2648 | const codexNativeThreadGeneration = yield* Ref.make(0); |
| 2647 | const registryLayer = ProviderAdapterRegistry.makeLayer([ | |
| 2649 | const layerRegistry = ProviderAdapterRegistry.layerFromAdapters([ | |
| 2648 | 2650 | makeTestAdapter({ |
| 2649 | 2651 | instanceId: ProviderInstanceId.make("codex"), |
| 2650 | 2652 | driver: CODEX_DRIVER, |
| ... | ... | @@ -2753,7 +2755,7 @@ describe("orchestration v2 provider switching", () => { |
| 2753 | 2755 | return yield* waitForIdle(threadId); |
| 2754 | 2756 | }).pipe( |
| 2755 | 2757 | Effect.provide( |
| 2756 | makeOrchestratorV2ReplayLayerWithRegistry( | |
| 2758 | ProviderReplayHarness.layerWithRegistry( | |
| 2757 | 2759 | { |
| 2758 | 2760 | name: "provider-switch", |
| 2759 | 2761 | runtimePolicyOverride: { |
| ... | ... | @@ -2766,7 +2768,7 @@ describe("orchestration v2 provider switching", () => { |
| 2766 | 2768 | }, |
| 2767 | 2769 | }, |
| 2768 | 2770 | }, |
| 2769 | registryLayer, | |
| 2771 | layerRegistry, | |
| 2770 | 2772 | ), |
| 2771 | 2773 | ), |
| 2772 | 2774 | ); |
| ... | ... | @@ -2856,7 +2858,7 @@ describe("orchestration v2 provider switching", () => { |
| 2856 | 2858 | const targetPrompt = "What release color did we choose?"; |
| 2857 | 2859 | const cwd = yield* checkpointWorkspace("cross-provider-fork"); |
| 2858 | 2860 | const capturedTurns = yield* Ref.make<ReadonlyArray<CapturedTurn>>([]); |
| 2859 | const registryLayer = ProviderAdapterRegistry.makeLayer([ | |
| 2861 | const layerRegistry = ProviderAdapterRegistry.layerFromAdapters([ | |
| 2860 | 2862 | makeTestAdapter({ |
| 2861 | 2863 | instanceId: ProviderInstanceId.make("codex"), |
| 2862 | 2864 | driver: CODEX_DRIVER, |
| ... | ... | @@ -2935,7 +2937,7 @@ describe("orchestration v2 provider switching", () => { |
| 2935 | 2937 | return yield* waitForIdle(targetThreadId); |
| 2936 | 2938 | }).pipe( |
| 2937 | 2939 | Effect.provide( |
| 2938 | makeOrchestratorV2ReplayLayerWithRegistry( | |
| 2940 | ProviderReplayHarness.layerWithRegistry( | |
| 2939 | 2941 | { |
| 2940 | 2942 | name: "cross-provider-fork", |
| 2941 | 2943 | runtimePolicyOverride: { |
| ... | ... | @@ -2948,7 +2950,7 @@ describe("orchestration v2 provider switching", () => { |
| 2948 | 2950 | }, |
| 2949 | 2951 | }, |
| 2950 | 2952 | }, |
| 2951 | registryLayer, | |
| 2953 | layerRegistry, | |
| 2952 | 2954 | ), |
| 2953 | 2955 | ), |
| 2954 | 2956 | ); |
| ... | ... | @@ -2996,7 +2998,7 @@ describe("orchestration v2 provider switching", () => { |
| 2996 | 2998 | const targetPrompt = "What deployment marker did we choose?"; |
| 2997 | 2999 | const cwd = yield* checkpointWorkspace("cursor-portable-fork"); |
| 2998 | 3000 | const capturedTurns = yield* Ref.make<ReadonlyArray<CapturedTurn>>([]); |
| 2999 | const registryLayer = ProviderAdapterRegistry.makeLayer([ | |
| 3001 | const layerRegistry = ProviderAdapterRegistry.layerFromAdapters([ | |
| 3000 | 3002 | makeTestAdapter({ |
| 3001 | 3003 | instanceId: ProviderInstanceId.make("cursor"), |
| 3002 | 3004 | driver: CURSOR_DRIVER, |
| ... | ... | @@ -3071,7 +3073,7 @@ describe("orchestration v2 provider switching", () => { |
| 3071 | 3073 | return yield* waitForIdle(targetThreadId); |
| 3072 | 3074 | }).pipe( |
| 3073 | 3075 | Effect.provide( |
| 3074 | makeOrchestratorV2ReplayLayerWithRegistry( | |
| 3076 | ProviderReplayHarness.layerWithRegistry( | |
| 3075 | 3077 | { |
| 3076 | 3078 | name: "cursor-portable-fork", |
| 3077 | 3079 | runtimePolicyOverride: { |
| ... | ... | @@ -3084,7 +3086,7 @@ describe("orchestration v2 provider switching", () => { |
| 3084 | 3086 | }, |
| 3085 | 3087 | }, |
| 3086 | 3088 | }, |
| 3087 | registryLayer, | |
| 3089 | layerRegistry, | |
| 3088 | 3090 | ), |
| 3089 | 3091 | ), |
| 3090 | 3092 | ); |
| ... | ... | @@ -3131,7 +3133,7 @@ describe("orchestration v2 provider switching", () => { |
| 3131 | 3133 | const mergePrompt = "Report all three remembered markers."; |
| 3132 | 3134 | const cwd = yield* checkpointWorkspace("cross-provider-merge"); |
| 3133 | 3135 | const capturedTurns = yield* Ref.make<ReadonlyArray<CapturedTurn>>([]); |
| 3134 | const registryLayer = ProviderAdapterRegistry.makeLayer([ | |
| 3136 | const layerRegistry = ProviderAdapterRegistry.layerFromAdapters([ | |
| 3135 | 3137 | makeTestAdapter({ |
| 3136 | 3138 | instanceId: ProviderInstanceId.make("codex"), |
| 3137 | 3139 | driver: CODEX_DRIVER, |
| ... | ... | @@ -3283,7 +3285,7 @@ describe("orchestration v2 provider switching", () => { |
| 3283 | 3285 | return yield* waitForIdle(sourceThreadId); |
| 3284 | 3286 | }).pipe( |
| 3285 | 3287 | Effect.provide( |
| 3286 | makeOrchestratorV2ReplayLayerWithRegistry( | |
| 3288 | ProviderReplayHarness.layerWithRegistry( | |
| 3287 | 3289 | { |
| 3288 | 3290 | name: "cross-provider-merge", |
| 3289 | 3291 | runtimePolicyOverride: { |
| ... | ... | @@ -3296,7 +3298,7 @@ describe("orchestration v2 provider switching", () => { |
| 3296 | 3298 | }, |
| 3297 | 3299 | }, |
| 3298 | 3300 | }, |
| 3299 | registryLayer, | |
| 3301 | layerRegistry, | |
| 3300 | 3302 | ), |
| 3301 | 3303 | ), |
| 3302 | 3304 | ); |
| ... | ... | @@ -3354,7 +3356,7 @@ describe("orchestration v2 provider switching", () => { |
| 3354 | 3356 | } satisfies ModelSelection; |
| 3355 | 3357 | const cwd = yield* checkpointWorkspace("custom-codex-instances"); |
| 3356 | 3358 | const capturedTurns = yield* Ref.make<ReadonlyArray<CapturedTurn>>([]); |
| 3357 | const registryLayer = ProviderAdapterRegistry.makeLayer([ | |
| 3359 | const layerRegistry = ProviderAdapterRegistry.layerFromAdapters([ | |
| 3358 | 3360 | makeTestAdapter({ |
| 3359 | 3361 | instanceId: personalSelection.instanceId, |
| 3360 | 3362 | driver: CODEX_DRIVER, |
| ... | ... | @@ -3413,7 +3415,7 @@ describe("orchestration v2 provider switching", () => { |
| 3413 | 3415 | ]); |
| 3414 | 3416 | }).pipe( |
| 3415 | 3417 | Effect.provide( |
| 3416 | makeOrchestratorV2ReplayLayerWithRegistry( | |
| 3418 | ProviderReplayHarness.layerWithRegistry( | |
| 3417 | 3419 | { |
| 3418 | 3420 | name: "custom-codex-instances", |
| 3419 | 3421 | runtimePolicyOverride: { |
| ... | ... | @@ -3426,7 +3428,7 @@ describe("orchestration v2 provider switching", () => { |
| 3426 | 3428 | }, |
| 3427 | 3429 | }, |
| 3428 | 3430 | }, |
| 3429 | registryLayer, | |
| 3431 | layerRegistry, | |
| 3430 | 3432 | ), |
| 3431 | 3433 | ), |
| 3432 | 3434 | ); |
apps/server/src/persistence/Layers/OrchestrationCommandReceipts.ts+1-1| ... | ... | @@ -120,7 +120,7 @@ const makeOrchestrationCommandReceiptRepository = Effect.gen(function* () { |
| 120 | 120 | } satisfies OrchestrationCommandReceipts.OrchestrationCommandReceiptRepositoryShape; |
| 121 | 121 | }); |
| 122 | 122 | |
| 123 | export const OrchestrationCommandReceiptRepositoryLive = Layer.effect( | |
| 123 | export const layer = Layer.effect( | |
| 124 | 124 | OrchestrationCommandReceipts.OrchestrationCommandReceiptRepository, |
| 125 | 125 | makeOrchestrationCommandReceiptRepository, |
| 126 | 126 | ); |
apps/server/src/persistence/Layers/OrchestrationEventStore.sequence.test.ts+7-7| ... | ... | @@ -11,11 +11,11 @@ import * as SqlClient from "effect/sql/SqlClient"; |
| 11 | 11 | import { runMigrations } from "../Migrations.ts"; |
| 12 | 12 | import * as NodeSqliteClient from "@t3tools/shared/nodeSqliteClient"; |
| 13 | 13 | import * as OrchestrationEventStore from "../Services/OrchestrationEventStore.ts"; |
| 14 | import { OrchestrationEventStoreLive } from "./OrchestrationEventStore.ts"; | |
| 15 | import { SqlitePersistenceMemory } from "./Sqlite.ts"; | |
| 14 | import * as OrchestrationEventStoreLayer from "./OrchestrationEventStore.ts"; | |
| 15 | import * as SqlitePersistence from "./Sqlite.ts"; | |
| 16 | 16 | |
| 17 | const eventStoreLayer = OrchestrationEventStoreLive.pipe( | |
| 18 | Layer.provideMerge(SqlitePersistenceMemory), | |
| 17 | const layerEventStore = OrchestrationEventStoreLayer.layer.pipe( | |
| 18 | Layer.provideMerge(SqlitePersistence.layerMemory), | |
| 19 | 19 | ); |
| 20 | 20 | const occurredAt = "2026-09-03T00:00:00.000Z"; |
| 21 | 21 | const encodeJson = Schema.encodeSync(Schema.fromJsonString(Schema.Unknown)); |
| ... | ... | @@ -112,7 +112,7 @@ it.effect("keeps application and scoped agent high-water marks separate from leg |
| 112 | 112 | assert.equal(yield* store.latestApplicationSequence, legacyProject.sequence); |
| 113 | 113 | assert.equal(yield* store.latestAgentSequence(), 0); |
| 114 | 114 | assert.equal(yield* store.latestAgentSequence(target), 0); |
| 115 | }).pipe(Effect.provide(Layer.fresh(eventStoreLayer))), | |
| 115 | }).pipe(Effect.provide(Layer.fresh(layerEventStore))), | |
| 116 | 116 | ); |
| 117 | 117 | |
| 118 | 118 | it.effect( |
| ... | ... | @@ -206,7 +206,7 @@ it.effect( |
| 206 | 206 | [...catchUp.map((row) => row.sequence), liveSequence], |
| 207 | 207 | ); |
| 208 | 208 | assert.isAbove(liveSequence, highWater); |
| 209 | }).pipe(Effect.provide(Layer.fresh(eventStoreLayer))), | |
| 209 | }).pipe(Effect.provide(Layer.fresh(layerEventStore))), | |
| 210 | 210 | ); |
| 211 | 211 | |
| 212 | 212 | it.effect("uses indexed high-water lookups for populated history without OR scans", () => |
| ... | ... | @@ -267,7 +267,7 @@ it.effect("uses indexed high-water lookups for populated history without OR scan |
| 267 | 267 | } |
| 268 | 268 | }).pipe( |
| 269 | 269 | Effect.provide( |
| 270 | OrchestrationEventStoreLive.pipe( | |
| 270 | OrchestrationEventStoreLayer.layer.pipe( | |
| 271 | 271 | Layer.provideMerge(NodeSqliteClient.layer({ filename: ":memory:" })), |
| 272 | 272 | ), |
| 273 | 273 | ), |
apps/server/src/persistence/Layers/OrchestrationEventStore.test.ts+8-6| ... | ... | @@ -27,13 +27,15 @@ import { |
| 27 | 27 | import { PersistenceDecodeError } from "../Errors.ts"; |
| 28 | 28 | import { toShellApplicationEvent } from "../../orchestration-v2/ShellStream.ts"; |
| 29 | 29 | import * as OrchestrationEventStore from "../Services/OrchestrationEventStore.ts"; |
| 30 | import { OrchestrationEventStoreLive } from "./OrchestrationEventStore.ts"; | |
| 31 | import { SqlitePersistenceMemory } from "./Sqlite.ts"; | |
| 30 | import * as OrchestrationEventStoreLayer from "./OrchestrationEventStore.ts"; | |
| 31 | import * as SqlitePersistence from "./Sqlite.ts"; | |
| 32 | 32 | const isPersistenceDecodeError = Schema.is(PersistenceDecodeError); |
| 33 | 33 | const isLiveStreamBufferError = Schema.is(LiveStreamBufferError); |
| 34 | 34 | |
| 35 | const TestLayer = OrchestrationEventStoreLive.pipe(Layer.provideMerge(SqlitePersistenceMemory)); | |
| 36 | const layer = it.layer(TestLayer); | |
| 35 | const layerTest = OrchestrationEventStoreLayer.layer.pipe( | |
| 36 | Layer.provideMerge(SqlitePersistence.layerMemory), | |
| 37 | ); | |
| 38 | const layer = it.layer(layerTest); | |
| 37 | 39 | |
| 38 | 40 | layer("OrchestrationEventStore", (it) => { |
| 39 | 41 | it.effect("retains only shell metadata from oversized replay and live application events", () => |
| ... | ... | @@ -464,7 +466,7 @@ it.effect.each(["high-water", "replay"] as const)( |
| 464 | 466 | assert.isTrue(isLiveStreamBufferError(result.failure.cause)); |
| 465 | 467 | } |
| 466 | 468 | }), |
| 467 | ).pipe(Effect.provide(Layer.fresh(TestLayer))), | |
| 469 | ).pipe(Effect.provide(Layer.fresh(layerTest))), | |
| 468 | 470 | ); |
| 469 | 471 | |
| 470 | 472 | it.effect("releases consumed application replay pages", () => |
| ... | ... | @@ -516,5 +518,5 @@ it.effect("releases consumed application replay pages", () => |
| 516 | 518 | ), |
| 517 | 519 | ); |
| 518 | 520 | assert.equal(count, 1501); |
| 519 | }).pipe(Effect.provide(TestLayer)), | |
| 521 | }).pipe(Effect.provide(layerTest)), | |
| 520 | 522 | ); |
apps/server/src/persistence/Layers/OrchestrationEventStore.ts+1-4| ... | ... | @@ -606,7 +606,4 @@ const makeEventStore = Effect.gen(function* () { |
| 606 | 606 | } satisfies OrchestrationEventStore.OrchestrationEventStoreShape; |
| 607 | 607 | }); |
| 608 | 608 | |
| 609 | export const OrchestrationEventStoreLive = Layer.effect( | |
| 610 | OrchestrationEventStore.OrchestrationEventStore, | |
| 611 | makeEventStore, | |
| 612 | ); | |
| 609 | export const layer = Layer.effect(OrchestrationEventStore.OrchestrationEventStore, makeEventStore); |
apps/server/src/persistence/Layers/Sqlite.test.ts+5-8| ... | ... | @@ -10,11 +10,8 @@ import * as Effect from "effect/Effect"; |
| 10 | 10 | import * as Layer from "effect/Layer"; |
| 11 | 11 | import * as SqlClient from "effect/sql/SqlClient"; |
| 12 | 12 | |
| 13 | import { | |
| 14 | SqlitePersistenceMemory, | |
| 15 | WAL_SIZE_LIMIT_BYTES, | |
| 16 | makeSqlitePersistenceLive, | |
| 17 | } from "./Sqlite.ts"; | |
| 13 | import { WAL_SIZE_LIMIT_BYTES } from "./Sqlite.ts"; | |
| 14 | import * as SqlitePersistence from "./Sqlite.ts"; | |
| 18 | 15 | |
| 19 | 16 | const lockHolderSource = ` |
| 20 | 17 | const { DatabaseSync } = require("node:sqlite"); |
| ... | ... | @@ -56,7 +53,7 @@ it.effect("waits out a concurrent writer instead of failing with SQLITE_BUSY", ( |
| 56 | 53 | const rows = yield* sql<{ readonly id: number }>`SELECT id FROM busy_probe`; |
| 57 | 54 | assert.deepEqual([...rows], [{ id: 1 }]); |
| 58 | 55 | }).pipe( |
| 59 | Effect.provide(makeSqlitePersistenceLive(dbPath).pipe(Layer.provide(NodeServices.layer))), | |
| 56 | Effect.provide(SqlitePersistence.layerFromPath(dbPath).pipe(Layer.provide(NodeServices.layer))), | |
| 60 | 57 | Effect.ensuring(Effect.sync(() => NodeFS.rmSync(tempDir, { recursive: true, force: true }))), |
| 61 | 58 | ); |
| 62 | 59 | }); |
| ... | ... | @@ -82,7 +79,7 @@ it.effect("shrinks the WAL file back to the size limit after a large write", () |
| 82 | 79 | yield* sql`INSERT INTO wal_probe(payload) VALUES (x'00')`; |
| 83 | 80 | assert.isAtMost(walFileSize(), WAL_SIZE_LIMIT_BYTES); |
| 84 | 81 | }).pipe( |
| 85 | Effect.provide(makeSqlitePersistenceLive(dbPath).pipe(Layer.provide(NodeServices.layer))), | |
| 82 | Effect.provide(SqlitePersistence.layerFromPath(dbPath).pipe(Layer.provide(NodeServices.layer))), | |
| 86 | 83 | Effect.ensuring(Effect.sync(() => NodeFS.rmSync(tempDir, { recursive: true, force: true }))), |
| 87 | 84 | ); |
| 88 | 85 | }); |
| ... | ... | @@ -92,5 +89,5 @@ it.effect("applies busy_timeout in the shared persistence setup", () => |
| 92 | 89 | const sql = yield* SqlClient.SqlClient; |
| 93 | 90 | const rows = yield* sql<{ readonly timeout: number }>`PRAGMA busy_timeout`; |
| 94 | 91 | assert.equal(rows[0]?.timeout, 5000); |
| 95 | }).pipe(Effect.provide(SqlitePersistenceMemory)), | |
| 92 | }).pipe(Effect.provide(SqlitePersistence.layerMemory)), | |
| 96 | 93 | ); |
apps/server/src/persistence/Layers/Sqlite.ts+6-8| ... | ... | @@ -12,7 +12,7 @@ import * as ServerConfig from "../../config.ts"; |
| 12 | 12 | // Size the -wal file is cut back to on the first commit after a WAL reset. |
| 13 | 13 | export const WAL_SIZE_LIMIT_BYTES = 32 * 1024 * 1024; |
| 14 | 14 | |
| 15 | const setup = Layer.effectDiscard( | |
| 15 | const layerSetup = Layer.effectDiscard( | |
| 16 | 16 | Effect.gen(function* () { |
| 17 | 17 | const sql = yield* SqlClient.SqlClient; |
| 18 | 18 | // CLI and server write from separate processes; wait rather than fail with SQLITE_BUSY. |
| ... | ... | @@ -26,15 +26,13 @@ const setup = Layer.effectDiscard( |
| 26 | 26 | }), |
| 27 | 27 | ); |
| 28 | 28 | |
| 29 | export const makeSqlitePersistenceLive = Effect.fn("makeSqlitePersistenceLive")(function* ( | |
| 30 | dbPath: string, | |
| 31 | ) { | |
| 29 | export const layerFromPath = Effect.fn("makeSqlitePersistenceLive")(function* (dbPath: string) { | |
| 32 | 30 | const fs = yield* FileSystem.FileSystem; |
| 33 | 31 | const path = yield* Path.Path; |
| 34 | 32 | yield* fs.makeDirectory(path.dirname(dbPath), { recursive: true }); |
| 35 | 33 | |
| 36 | 34 | return Layer.provideMerge( |
| 37 | setup, | |
| 35 | layerSetup, | |
| 38 | 36 | NodeSqliteClient.layer({ |
| 39 | 37 | filename: dbPath, |
| 40 | 38 | spanAttributes: { |
| ... | ... | @@ -45,8 +43,8 @@ export const makeSqlitePersistenceLive = Effect.fn("makeSqlitePersistenceLive")( |
| 45 | 43 | ); |
| 46 | 44 | }, Layer.unwrap); |
| 47 | 45 | |
| 48 | export const SqlitePersistenceMemory = Layer.provideMerge( | |
| 49 | setup, | |
| 46 | export const layerMemory = Layer.provideMerge( | |
| 47 | layerSetup, | |
| 50 | 48 | NodeSqliteClient.layer({ filename: ":memory:" }), |
| 51 | 49 | ); |
| 52 | 50 | |
| ... | ... | @@ -54,6 +52,6 @@ export const layerConfig = Layer.unwrap( |
| 54 | 52 | Effect.gen(function* () { |
| 55 | 53 | const { dbPath } = yield* ServerConfig.ServerConfig; |
| 56 | 54 | yield* initializeV2Database(dbPath); |
| 57 | return makeSqlitePersistenceLive(dbPath); | |
| 55 | return layerFromPath(dbPath); | |
| 58 | 56 | }), |
| 59 | 57 | ); |
apps/server/src/persistence/RepositoryErrorCorrelation.test.ts+9-9| ... | ... | @@ -8,7 +8,7 @@ import * as SqlClient from "effect/sql/SqlClient"; |
| 8 | 8 | import * as AuthPairingLinks from "./AuthPairingLinks.ts"; |
| 9 | 9 | import * as AuthSessions from "./AuthSessions.ts"; |
| 10 | 10 | import * as PersistenceErrors from "./Errors.ts"; |
| 11 | import { SqlitePersistenceMemory } from "./Layers/Sqlite.ts"; | |
| 11 | import * as SqlitePersistence from "./Layers/Sqlite.ts"; | |
| 12 | 12 | import * as ProviderSessionRuntime from "./ProviderSessionRuntime.ts"; |
| 13 | 13 | |
| 14 | 14 | const issuedAt = DateTime.makeUnsafe("2026-06-20T00:00:00.000Z"); |
| ... | ... | @@ -16,12 +16,12 @@ const expiresAt = DateTime.makeUnsafe("2027-06-20T00:00:00.000Z"); |
| 16 | 16 | const now = DateTime.makeUnsafe("2026-06-21T00:00:00.000Z"); |
| 17 | 17 | const scopes: ReadonlyArray<AuthEnvironmentScope> = ["access:read"]; |
| 18 | 18 | |
| 19 | const authSessionLayer = AuthSessions.layer.pipe(Layer.provideMerge(SqlitePersistenceMemory)); | |
| 20 | const authPairingLinkLayer = AuthPairingLinks.layer.pipe( | |
| 21 | Layer.provideMerge(SqlitePersistenceMemory), | |
| 19 | const layerAuthSession = AuthSessions.layer.pipe(Layer.provideMerge(SqlitePersistence.layerMemory)); | |
| 20 | const layerAuthPairingLink = AuthPairingLinks.layer.pipe( | |
| 21 | Layer.provideMerge(SqlitePersistence.layerMemory), | |
| 22 | 22 | ); |
| 23 | const providerSessionRuntimeLayer = ProviderSessionRuntime.layer.pipe( | |
| 24 | Layer.provideMerge(SqlitePersistenceMemory), | |
| 23 | const layerProviderSessionRuntime = ProviderSessionRuntime.layer.pipe( | |
| 24 | Layer.provideMerge(SqlitePersistence.layerMemory), | |
| 25 | 25 | ); |
| 26 | 26 | |
| 27 | 27 | describe("persistence error correlation", () => { |
| ... | ... | @@ -101,7 +101,7 @@ describe("persistence error correlation", () => { |
| 101 | 101 | "SQL error in AuthSessionRepository.revokeAllExcept:query", |
| 102 | 102 | ); |
| 103 | 103 | assert.notInclude(revokeOtherError.message, DateTime.formatIso(now)); |
| 104 | }).pipe(Effect.provide(authSessionLayer)), | |
| 104 | }).pipe(Effect.provide(layerAuthSession)), | |
| 105 | 105 | ); |
| 106 | 106 | |
| 107 | 107 | it.effect("correlates pairing-link create and revoke failures by id only", () => |
| ... | ... | @@ -179,7 +179,7 @@ describe("persistence error correlation", () => { |
| 179 | 179 | assert.deepStrictEqual(revokeError.correlation, { pairingLinkId: id }); |
| 180 | 180 | assert.notInclude(revokeError.message, credential); |
| 181 | 181 | assert.notInclude(revokeError.message, DateTime.formatIso(now)); |
| 182 | }).pipe(Effect.provide(authPairingLinkLayer)), | |
| 182 | }).pipe(Effect.provide(layerAuthPairingLink)), | |
| 183 | 183 | ); |
| 184 | 184 | |
| 185 | 185 | it.effect("skips undecodable provider runtime rows and correlates SQL failures by thread", () => |
| ... | ... | @@ -256,6 +256,6 @@ describe("persistence error correlation", () => { |
| 256 | 256 | ); |
| 257 | 257 | assert.notInclude(sqlFailure.message, runtimePayload); |
| 258 | 258 | assert.notInclude(sqlFailure.message, lastSeenAt); |
| 259 | }).pipe(Effect.provide(providerSessionRuntimeLayer)), | |
| 259 | }).pipe(Effect.provide(layerProviderSessionRuntime)), | |
| 260 | 260 | ); |
| 261 | 261 | }); |
apps/server/src/persistence/initializeV2Database.test.ts+11-11| ... | ... | @@ -45,15 +45,15 @@ it.effect( |
| 45 | 45 | yield* seed; |
| 46 | 46 | const original = NodeFS.readFileSync(sourcePath); |
| 47 | 47 | const config = yield* ServerConfig.ServerConfig; |
| 48 | const databaseLayer = SqlitePersistence.layerConfig.pipe( | |
| 48 | const layerDatabase = SqlitePersistence.layerConfig.pipe( | |
| 49 | 49 | Layer.provide(ServerConfig.layer({ ...config, dbPath: destinationPath })), |
| 50 | 50 | ); |
| 51 | const stores = Layer.mergeAll(EventStore.layer, ProjectionStore.layer).pipe( | |
| 52 | Layer.provideMerge(databaseLayer), | |
| 51 | const layerStores = Layer.mergeAll(EventStore.layer, ProjectionStore.layer).pipe( | |
| 52 | Layer.provideMerge(layerDatabase), | |
| 53 | 53 | ); |
| 54 | const sink = EventSink.layer.pipe(Layer.provide(stores)); | |
| 55 | const importer = LegacyV1ThreadImporter.layer.pipe( | |
| 56 | Layer.provideMerge(Layer.mergeAll(stores, sink)), | |
| 54 | const layerSink = EventSink.layer.pipe(Layer.provide(layerStores)); | |
| 55 | const layerImporter = LegacyV1ThreadImporter.layer.pipe( | |
| 56 | Layer.provideMerge(Layer.mergeAll(layerStores, layerSink)), | |
| 57 | 57 | ); |
| 58 | 58 | yield* Effect.gen(function* () { |
| 59 | 59 | const sql = yield* SqlClient.SqlClient; |
| ... | ... | @@ -81,7 +81,7 @@ it.effect( |
| 81 | 81 | assert.isNotNull(imported[0]?.transcript_imported_at); |
| 82 | 82 | yield* sql`CREATE TABLE v2_work (text TEXT)`; |
| 83 | 83 | yield* sql`INSERT INTO v2_work VALUES ('Keep V2 work')`; |
| 84 | }).pipe(Effect.provide(importer)); | |
| 84 | }).pipe(Effect.provide(layerImporter)); | |
| 85 | 85 | assert.deepEqual(NodeFS.readFileSync(sourcePath), original); |
| 86 | 86 | const v1 = new NodeSqlite.DatabaseSync(sourcePath); |
| 87 | 87 | try { |
| ... | ... | @@ -105,7 +105,7 @@ it.effect( |
| 105 | 105 | const sql = yield* SqlClient.SqlClient; |
| 106 | 106 | assert.equal((yield* sql`SELECT text FROM v2_work`)[0]?.text, "Keep V2 work"); |
| 107 | 107 | assert.equal((yield* sql`SELECT title FROM projection_threads`)[0]?.title, "V1 thread"); |
| 108 | }).pipe(Effect.provide(databaseLayer)); | |
| 108 | }).pipe(Effect.provide(layerDatabase)); | |
| 109 | 109 | }).pipe( |
| 110 | 110 | Effect.provide( |
| 111 | 111 | ServerConfig.layerTest(directory, directory).pipe(Layer.provideMerge(NodeServices.layer)), |
| ... | ... | @@ -173,14 +173,14 @@ it.effect("starts fresh without V1 and never imports over existing V2 state", () |
| 173 | 173 | const destinationPath = NodePath.join(directory, "userdata", "statev2.sqlite"); |
| 174 | 174 | return Effect.gen(function* () { |
| 175 | 175 | const config = yield* ServerConfig.ServerConfig; |
| 176 | const database = SqlitePersistence.layerConfig.pipe( | |
| 176 | const layerDatabase = SqlitePersistence.layerConfig.pipe( | |
| 177 | 177 | Layer.provide(ServerConfig.layer({ ...config, dbPath: destinationPath })), |
| 178 | 178 | ); |
| 179 | 179 | yield* Effect.gen(function* () { |
| 180 | 180 | const sql = yield* SqlClient.SqlClient; |
| 181 | 181 | yield* sql`CREATE TABLE v2_work (text TEXT)`; |
| 182 | 182 | yield* sql`INSERT INTO v2_work VALUES ('fresh V2 work')`; |
| 183 | }).pipe(Effect.provide(database)); | |
| 183 | }).pipe(Effect.provide(layerDatabase)); | |
| 184 | 184 | const sourcePath = NodePath.join(NodePath.dirname(destinationPath), "state.sqlite"); |
| 185 | 185 | assert.isFalse(NodeFS.existsSync(sourcePath)); |
| 186 | 186 | NodeFS.writeFileSync(sourcePath, "This source must never be opened once V2 exists"); |
| ... | ... | @@ -188,7 +188,7 @@ it.effect("starts fresh without V1 and never imports over existing V2 state", () |
| 188 | 188 | yield* Effect.gen(function* () { |
| 189 | 189 | const sql = yield* SqlClient.SqlClient; |
| 190 | 190 | assert.equal((yield* sql`SELECT text FROM v2_work`)[0]?.text, "fresh V2 work"); |
| 191 | }).pipe(Effect.provide(database)); | |
| 191 | }).pipe(Effect.provide(layerDatabase)); | |
| 192 | 192 | }).pipe( |
| 193 | 193 | Effect.provide( |
| 194 | 194 | ServerConfig.layerTest(directory, directory).pipe(Layer.provideMerge(NodeServices.layer)), |
apps/server/src/preview/PortScanner.test.ts+25-25| ... | ... | @@ -39,13 +39,13 @@ const processProbeFailure: ProcessRunner.ProcessRunner["Service"]["run"] = (inpu |
| 39 | 39 | }), |
| 40 | 40 | ); |
| 41 | 41 | |
| 42 | const TestProcessRunner = Layer.succeed(ProcessRunner.ProcessRunner, { | |
| 42 | const layerTestProcessRunner = Layer.succeed(ProcessRunner.ProcessRunner, { | |
| 43 | 43 | run: processProbeFailure, |
| 44 | 44 | }); |
| 45 | 45 | |
| 46 | 46 | let integrationListeningPort: number | null = null; |
| 47 | 47 | |
| 48 | const TestIntegrationNet = Layer.succeed(Net.NetService, { | |
| 48 | const layerTestIntegrationNet = Layer.succeed(Net.NetService, { | |
| 49 | 49 | canListenOnHost: () => Effect.succeed(true), |
| 50 | 50 | isPortAvailableOnLoopback: (port) => Effect.sync(() => port !== integrationListeningPort), |
| 51 | 51 | hasListenerOnHost: (port) => Effect.sync(() => port === integrationListeningPort), |
| ... | ... | @@ -53,7 +53,7 @@ const TestIntegrationNet = Layer.succeed(Net.NetService, { |
| 53 | 53 | findAvailablePort: (preferred) => Effect.succeed(preferred), |
| 54 | 54 | }); |
| 55 | 55 | |
| 56 | const makeProbeFailureLayer = ( | |
| 56 | const layerProbeFailure = ( | |
| 57 | 57 | run: ProcessRunner.ProcessRunner["Service"]["run"], |
| 58 | 58 | fetch: typeof globalThis.fetch = globalThis.fetch, |
| 59 | 59 | ) => |
| ... | ... | @@ -74,11 +74,11 @@ const makeProbeFailureLayer = ( |
| 74 | 74 | ), |
| 75 | 75 | ); |
| 76 | 76 | |
| 77 | const TestPortDiscoveryLive = PortScanner.layer.pipe( | |
| 77 | const layerTestPortDiscovery = PortScanner.layer.pipe( | |
| 78 | 78 | Layer.provide( |
| 79 | 79 | Layer.mergeAll( |
| 80 | TestProcessRunner, | |
| 81 | TestIntegrationNet, | |
| 80 | layerTestProcessRunner, | |
| 81 | layerTestIntegrationNet, | |
| 82 | 82 | Layer.succeed(HostProcessPlatform, "win32"), |
| 83 | 83 | FetchHttpClient.layer, |
| 84 | 84 | ), |
| ... | ... | @@ -87,7 +87,7 @@ const TestPortDiscoveryLive = PortScanner.layer.pipe( |
| 87 | 87 | |
| 88 | 88 | const LSOF_TEST_PORT = 43_123; |
| 89 | 89 | |
| 90 | const makeLsofScannerLayer = (input: { | |
| 90 | const layerLsofScanner = (input: { | |
| 91 | 91 | readonly pid: () => number; |
| 92 | 92 | readonly fetch: typeof globalThis.fetch; |
| 93 | 93 | }) => |
| ... | ... | @@ -205,7 +205,7 @@ const commonNonHttpServer = Effect.acquireRelease( |
| 205 | 205 | * platform so the tests exercise the TCP-probe fallback without depending on |
| 206 | 206 | * `lsof` being installed. |
| 207 | 207 | */ |
| 208 | effectIt.layer(TestPortDiscoveryLive)("PortDiscovery integration (TCP probe fallback)", (it) => { | |
| 208 | effectIt.layer(layerTestPortDiscovery)("PortDiscovery integration (TCP probe fallback)", (it) => { | |
| 209 | 209 | it.effect( |
| 210 | 210 | "scan() returns an HTTP server we just opened on a curated dev port", |
| 211 | 211 | Effect.fn("PortScannerTest.scanFindsCommonDevServer")(function* () { |
| ... | ... | @@ -254,7 +254,7 @@ effectIt.effect("revalidates a successful HTML probe after its cache entry expir |
| 254 | 254 | ? Promise.resolve(new Response("hello", { headers: { "content-type": "text/html" } })) |
| 255 | 255 | : Promise.reject(new TypeError("not HTTP")); |
| 256 | 256 | }) as typeof globalThis.fetch; |
| 257 | const layer = makeLsofScannerLayer({ pid: () => 1234, fetch: fetchFn }); | |
| 257 | const layer = layerLsofScanner({ pid: () => 1234, fetch: fetchFn }); | |
| 258 | 258 | |
| 259 | 259 | return Effect.gen(function* () { |
| 260 | 260 | const scanner = yield* PortScanner.PortDiscovery; |
| ... | ... | @@ -288,7 +288,7 @@ effectIt.effect("keeps a full configured URL when the discovered server root fai |
| 288 | 288 | }), |
| 289 | 289 | ); |
| 290 | 290 | }) as typeof globalThis.fetch; |
| 291 | const layer = makeLsofScannerLayer({ pid: () => 1234, fetch: fetchFn }); | |
| 291 | const layer = layerLsofScanner({ pid: () => 1234, fetch: fetchFn }); | |
| 292 | 292 | |
| 293 | 293 | return Effect.gen(function* () { |
| 294 | 294 | const scanner = yield* PortScanner.PortDiscovery; |
| ... | ... | @@ -308,7 +308,7 @@ effectIt.effect("probes configured custom ports through a canonical loopback hos |
| 308 | 308 | requests.push(String(input)); |
| 309 | 309 | return Promise.resolve(new Response("docs", { headers: { "content-type": "text/html" } })); |
| 310 | 310 | }) as typeof globalThis.fetch; |
| 311 | const layer = makeProbeFailureLayer(processProbeFailure, fetchFn); | |
| 311 | const layer = layerProbeFailure(processProbeFailure, fetchFn); | |
| 312 | 312 | |
| 313 | 313 | return Effect.gen(function* () { |
| 314 | 314 | const scanner = yield* PortScanner.PortDiscovery; |
| ... | ... | @@ -333,7 +333,7 @@ effectIt.effect("preserves explicit loopback hosts and bounds wildcard rewrites" |
| 333 | 333 | requests.push(String(input)); |
| 334 | 334 | return Promise.resolve(new Response("docs", { headers: { "content-type": "text/html" } })); |
| 335 | 335 | }) as typeof globalThis.fetch; |
| 336 | const layer = makeProbeFailureLayer(processProbeFailure, fetchFn); | |
| 336 | const layer = layerProbeFailure(processProbeFailure, fetchFn); | |
| 337 | 337 | |
| 338 | 338 | return Effect.gen(function* () { |
| 339 | 339 | const scanner = yield* PortScanner.PortDiscovery; |
| ... | ... | @@ -354,7 +354,7 @@ effectIt.effect("projects configured paths independently for simultaneous subscr |
| 354 | 354 | : new Response("not found", { status: 404 }), |
| 355 | 355 | ); |
| 356 | 356 | }) as typeof globalThis.fetch; |
| 357 | const layer = makeLsofScannerLayer({ pid: () => 1234, fetch: fetchFn }); | |
| 357 | const layer = layerLsofScanner({ pid: () => 1234, fetch: fetchFn }); | |
| 358 | 358 | |
| 359 | 359 | return Effect.gen(function* () { |
| 360 | 360 | const scanner = yield* PortScanner.PortDiscovery; |
| ... | ... | @@ -387,7 +387,7 @@ effectIt.effect( |
| 387 | 387 | ? new Response("app", { headers: { "content-type": "text/html" } }) |
| 388 | 388 | : new Response("not found", { status: 404 }), |
| 389 | 389 | )) as typeof globalThis.fetch; |
| 390 | const layer = makeLsofScannerLayer({ pid: () => 1234, fetch: fetchFn }); | |
| 390 | const layer = layerLsofScanner({ pid: () => 1234, fetch: fetchFn }); | |
| 391 | 391 | |
| 392 | 392 | return Effect.gen(function* () { |
| 393 | 393 | const scanner = yield* PortScanner.PortDiscovery; |
| ... | ... | @@ -420,7 +420,7 @@ effectIt.effect("stops probing a subscriber's configured paths after its scope c |
| 420 | 420 | : new Response("not found", { status: 404 }), |
| 421 | 421 | ); |
| 422 | 422 | }) as typeof globalThis.fetch; |
| 423 | const layer = makeLsofScannerLayer({ pid: () => 1234, fetch: fetchFn }); | |
| 423 | const layer = layerLsofScanner({ pid: () => 1234, fetch: fetchFn }); | |
| 424 | 424 | |
| 425 | 425 | return Effect.gen(function* () { |
| 426 | 426 | const scanner = yield* PortScanner.PortDiscovery; |
| ... | ... | @@ -450,7 +450,7 @@ effectIt.effect("writes no poll span while no client retains the scanner", () => |
| 450 | 450 | return new Tracer.NativeSpan(options); |
| 451 | 451 | }, |
| 452 | 452 | }); |
| 453 | const layer = makeProbeFailureLayer(processProbeFailure); | |
| 453 | const layer = layerProbeFailure(processProbeFailure); | |
| 454 | 454 | |
| 455 | 455 | return Effect.gen(function* () { |
| 456 | 456 | const scanner = yield* PortScanner.PortDiscovery; |
| ... | ... | @@ -468,7 +468,7 @@ effectIt.effect("uses the current configured fragment when readiness comes from |
| 468 | 468 | requests.push(String(input)); |
| 469 | 469 | return Promise.resolve(new Response("docs", { headers: { "content-type": "text/html" } })); |
| 470 | 470 | }) as typeof globalThis.fetch; |
| 471 | const layer = makeLsofScannerLayer({ pid: () => 1234, fetch: fetchFn }); | |
| 471 | const layer = layerLsofScanner({ pid: () => 1234, fetch: fetchFn }); | |
| 472 | 472 | const oldUrl = `http://localhost:${LSOF_TEST_PORT}/docs#old`; |
| 473 | 473 | const newUrl = `http://localhost:${LSOF_TEST_PORT}/docs#new`; |
| 474 | 474 | |
| ... | ... | @@ -488,7 +488,7 @@ effectIt.effect("shares a configured root probe with discovered-root classificat |
| 488 | 488 | requests.push(String(input)); |
| 489 | 489 | return Promise.resolve(new Response("app", { headers: { "content-type": "text/html" } })); |
| 490 | 490 | }) as typeof globalThis.fetch; |
| 491 | const layer = makeLsofScannerLayer({ pid: () => 1234, fetch: fetchFn }); | |
| 491 | const layer = layerLsofScanner({ pid: () => 1234, fetch: fetchFn }); | |
| 492 | 492 | |
| 493 | 493 | return Effect.gen(function* () { |
| 494 | 494 | const scanner = yield* PortScanner.PortDiscovery; |
| ... | ... | @@ -517,7 +517,7 @@ effectIt.effect("starts fresh cache entries after the probing batch completes", |
| 517 | 517 | requests.push(String(input)); |
| 518 | 518 | return Promise.resolve(new Response("app", { headers: { "content-type": "text/html" } })); |
| 519 | 519 | }) as typeof globalThis.fetch; |
| 520 | const layer = makeLsofScannerLayer({ pid: () => 1234, fetch: fetchFn }); | |
| 520 | const layer = layerLsofScanner({ pid: () => 1234, fetch: fetchFn }); | |
| 521 | 521 | |
| 522 | 522 | yield* Effect.gen(function* () { |
| 523 | 523 | const scanner = yield* PortScanner.PortDiscovery; |
| ... | ... | @@ -537,7 +537,7 @@ effectIt.effect("caches a failed web probe until its bounded cache entry expires |
| 537 | 537 | ? Promise.resolve(new Response("hello", { headers: { "content-type": "text/html" } })) |
| 538 | 538 | : Promise.reject(new TypeError("not HTTP")); |
| 539 | 539 | }) as typeof globalThis.fetch; |
| 540 | const layer = makeLsofScannerLayer({ pid: () => 1234, fetch: fetchFn }); | |
| 540 | const layer = layerLsofScanner({ pid: () => 1234, fetch: fetchFn }); | |
| 541 | 541 | |
| 542 | 542 | return Effect.gen(function* () { |
| 543 | 543 | const scanner = yield* PortScanner.PortDiscovery; |
| ... | ... | @@ -562,7 +562,7 @@ effectIt.effect("falls back to HTTPS and does not follow redirects while probing |
| 562 | 562 | if (String(input).startsWith("http:")) throw new TypeError("TLS listener"); |
| 563 | 563 | return new Response(null, { status: 302, headers: { location: "https://example.com" } }); |
| 564 | 564 | }) as typeof globalThis.fetch; |
| 565 | const layer = makeLsofScannerLayer({ pid: () => 1234, fetch: fetchFn }); | |
| 565 | const layer = layerLsofScanner({ pid: () => 1234, fetch: fetchFn }); | |
| 566 | 566 | |
| 567 | 567 | return Effect.gen(function* () { |
| 568 | 568 | const scanner = yield* PortScanner.PortDiscovery; |
| ... | ... | @@ -581,7 +581,7 @@ effectIt.effect( |
| 581 | 581 | new Response("not found", { status: 404, headers: { "content-type": "text/html" } }); |
| 582 | 582 | const fetchFn = ((_input: Parameters<typeof globalThis.fetch>[0]) => |
| 583 | 583 | Promise.resolve(makeResponse())) as typeof globalThis.fetch; |
| 584 | const layer = makeLsofScannerLayer({ pid: () => pid, fetch: fetchFn }); | |
| 584 | const layer = layerLsofScanner({ pid: () => pid, fetch: fetchFn }); | |
| 585 | 585 | |
| 586 | 586 | return Effect.gen(function* () { |
| 587 | 587 | const scanner = yield* PortScanner.PortDiscovery; |
| ... | ... | @@ -639,7 +639,7 @@ effectIt.effect("aborts HTTP and HTTPS probes when they time out", () => { |
| 639 | 639 | signal?.addEventListener("abort", onAbort, { once: true }); |
| 640 | 640 | } |
| 641 | 641 | })) as typeof globalThis.fetch; |
| 642 | const layer = makeLsofScannerLayer({ pid: () => 1234, fetch: fetchFn }); | |
| 642 | const layer = layerLsofScanner({ pid: () => 1234, fetch: fetchFn }); | |
| 643 | 643 | |
| 644 | 644 | return Effect.gen(function* () { |
| 645 | 645 | const scanner = yield* PortScanner.PortDiscovery; |
| ... | ... | @@ -656,7 +656,7 @@ effectIt.effect("aborts HTTP and HTTPS probes when they time out", () => { |
| 656 | 656 | effectIt.effect("does not swallow process probe defects", () => |
| 657 | 657 | Effect.gen(function* () { |
| 658 | 658 | const defect = new Error("unexpected process probe defect"); |
| 659 | const layer = makeProbeFailureLayer(() => Effect.die(defect)); | |
| 659 | const layer = layerProbeFailure(() => Effect.die(defect)); | |
| 660 | 660 | |
| 661 | 661 | const exit = yield* Effect.flatMap(PortScanner.PortDiscovery, (scanner) => scanner.scan()).pipe( |
| 662 | 662 | Effect.provide(layer), |
| ... | ... | @@ -673,7 +673,7 @@ effectIt.effect("does not swallow process probe defects", () => |
| 673 | 673 | |
| 674 | 674 | effectIt.effect("does not swallow process probe interruption", () => |
| 675 | 675 | Effect.gen(function* () { |
| 676 | const layer = makeProbeFailureLayer(() => Effect.interrupt); | |
| 676 | const layer = layerProbeFailure(() => Effect.interrupt); | |
| 677 | 677 | |
| 678 | 678 | const exit = yield* Effect.flatMap(PortScanner.PortDiscovery, (scanner) => scanner.scan()).pipe( |
| 679 | 679 | Effect.provide(layer), |
apps/server/src/process/externalLauncher.test.ts+34-34| ... | ... | @@ -58,7 +58,7 @@ function makeMockDetachedHandle(input: MockSpawnResult & { readonly onUnref?: () |
| 58 | 58 | }); |
| 59 | 59 | } |
| 60 | 60 | |
| 61 | const testLayer = (input: { | |
| 61 | const layerTest = (input: { | |
| 62 | 62 | readonly platform: NodeJS.Platform; |
| 63 | 63 | readonly env?: Record<string, string>; |
| 64 | 64 | readonly resolveExecutable?: (command: string) => string | undefined; |
| ... | ... | @@ -66,7 +66,7 @@ const testLayer = (input: { |
| 66 | 66 | readonly onUnref?: () => void; |
| 67 | 67 | readonly spawnResult?: (command: ChildProcess.StandardCommand) => MockSpawnResult | undefined; |
| 68 | 68 | }) => { |
| 69 | const spawnerLayer = Layer.succeed( | |
| 69 | const layerSpawner = Layer.succeed( | |
| 70 | 70 | ChildProcessSpawner.ChildProcessSpawner, |
| 71 | 71 | ChildProcessSpawner.make((command) => |
| 72 | 72 | Effect.sync(() => { |
| ... | ... | @@ -84,7 +84,7 @@ const testLayer = (input: { |
| 84 | 84 | ); |
| 85 | 85 | |
| 86 | 86 | return Layer.mergeAll( |
| 87 | ExternalLauncher.layer.pipe(Layer.provide(Layer.merge(NodeServices.layer, spawnerLayer))), | |
| 87 | ExternalLauncher.layer.pipe(Layer.provide(Layer.merge(NodeServices.layer, layerSpawner))), | |
| 88 | 88 | Layer.succeed(HostProcessPlatform, input.platform), |
| 89 | 89 | Layer.succeed( |
| 90 | 90 | SpawnExecutableResolution, |
| ... | ... | @@ -109,7 +109,7 @@ it.effect("launches the default browser through the platform command", () => { |
| 109 | 109 | assert.equal(didUnref, true); |
| 110 | 110 | }).pipe( |
| 111 | 111 | Effect.provide( |
| 112 | testLayer({ | |
| 112 | layerTest({ | |
| 113 | 113 | platform: "linux", |
| 114 | 114 | onSpawn: (command) => { |
| 115 | 115 | spawned = command; |
| ... | ... | @@ -138,7 +138,7 @@ it.effect("launches an installed editor with platform-safe arguments", () => |
| 138 | 138 | }); |
| 139 | 139 | }).pipe( |
| 140 | 140 | Effect.provide( |
| 141 | testLayer({ | |
| 141 | layerTest({ | |
| 142 | 142 | platform: "win32", |
| 143 | 143 | env: { PATH: binDir, PATHEXT: ".COM;.EXE;.BAT;.CMD" }, |
| 144 | 144 | resolveExecutable: (command) => |
| ... | ... | @@ -183,7 +183,7 @@ for (const platform of ["darwin", "linux"] as const) { |
| 183 | 183 | } |
| 184 | 184 | }).pipe( |
| 185 | 185 | Effect.provide( |
| 186 | testLayer({ | |
| 186 | layerTest({ | |
| 187 | 187 | platform, |
| 188 | 188 | env: { PATH: binDir }, |
| 189 | 189 | onSpawn: (command) => spawned.push(command), |
| ... | ... | @@ -226,7 +226,7 @@ it.effect("launches Cursor in classic IDE mode through the Windows command shim" |
| 226 | 226 | }); |
| 227 | 227 | }).pipe( |
| 228 | 228 | Effect.provide( |
| 229 | testLayer({ | |
| 229 | layerTest({ | |
| 230 | 230 | platform: "win32", |
| 231 | 231 | env: { PATH: binDir, PATHEXT: ".COM;.EXE;.BAT;.CMD" }, |
| 232 | 232 | resolveExecutable: (command) => |
| ... | ... | @@ -268,7 +268,7 @@ it.effect.skipIf(windowsHost)("reveals a file in Finder with open -R on macOS", |
| 268 | 268 | }); |
| 269 | 269 | }).pipe( |
| 270 | 270 | Effect.provide( |
| 271 | testLayer({ | |
| 271 | layerTest({ | |
| 272 | 272 | platform: "darwin", |
| 273 | 273 | env: { PATH: binDir }, |
| 274 | 274 | onSpawn: (command) => { |
| ... | ... | @@ -310,7 +310,7 @@ it.effect("reveals a file in File Explorer through PowerShell on Windows", () => |
| 310 | 310 | return yield* launcher.resolveFileManagerRevealKind(); |
| 311 | 311 | }).pipe( |
| 312 | 312 | Effect.provide( |
| 313 | testLayer({ | |
| 313 | layerTest({ | |
| 314 | 314 | platform: "win32", |
| 315 | 315 | env: { PATH: binDir, PATHEXT: ".COM;.EXE;.BAT;.CMD", SYSTEMROOT: systemRoot }, |
| 316 | 316 | onSpawn: (command) => { |
| ... | ... | @@ -414,7 +414,7 @@ it.effect("does not advertise reveal on Windows when PowerShell is missing", () |
| 414 | 414 | }; |
| 415 | 415 | }).pipe( |
| 416 | 416 | Effect.provide( |
| 417 | testLayer({ | |
| 417 | layerTest({ | |
| 418 | 418 | platform: "win32", |
| 419 | 419 | env: { |
| 420 | 420 | PATH: binDir, |
| ... | ... | @@ -458,7 +458,7 @@ it.effect.skipIf(windowsHost)( |
| 458 | 458 | return { kind, editors }; |
| 459 | 459 | }).pipe( |
| 460 | 460 | Effect.provide( |
| 461 | testLayer({ | |
| 461 | layerTest({ | |
| 462 | 462 | platform: "linux", |
| 463 | 463 | env: { |
| 464 | 464 | PATH: binDir, |
| ... | ... | @@ -506,7 +506,7 @@ it.effect.skipIf(windowsHost)( |
| 506 | 506 | }; |
| 507 | 507 | }).pipe( |
| 508 | 508 | Effect.provide( |
| 509 | testLayer({ | |
| 509 | layerTest({ | |
| 510 | 510 | platform: "linux", |
| 511 | 511 | env: { |
| 512 | 512 | PATH: binDir, |
| ... | ... | @@ -550,7 +550,7 @@ it.effect.skipIf(windowsHost)( |
| 550 | 550 | return revealKind; |
| 551 | 551 | }).pipe( |
| 552 | 552 | Effect.provide( |
| 553 | testLayer({ | |
| 553 | layerTest({ | |
| 554 | 554 | platform: "linux", |
| 555 | 555 | env: { |
| 556 | 556 | PATH: binDir, |
| ... | ... | @@ -606,7 +606,7 @@ it.effect.skipIf(windowsHost)( |
| 606 | 606 | return { editors, kind }; |
| 607 | 607 | }).pipe( |
| 608 | 608 | Effect.provide( |
| 609 | testLayer({ | |
| 609 | layerTest({ | |
| 610 | 610 | platform: "linux", |
| 611 | 611 | env: { |
| 612 | 612 | PATH: binDir, |
| ... | ... | @@ -656,7 +656,7 @@ it.effect.skipIf(windowsHost)( |
| 656 | 656 | }); |
| 657 | 657 | }).pipe( |
| 658 | 658 | Effect.provide( |
| 659 | testLayer({ | |
| 659 | layerTest({ | |
| 660 | 660 | platform: "linux", |
| 661 | 661 | env: { |
| 662 | 662 | PATH: binDir, |
| ... | ... | @@ -699,7 +699,7 @@ it.effect.skipIf(windowsHost)("reveals by opening the containing directory on Li |
| 699 | 699 | }); |
| 700 | 700 | }).pipe( |
| 701 | 701 | Effect.provide( |
| 702 | testLayer({ | |
| 702 | layerTest({ | |
| 703 | 703 | platform: "linux", |
| 704 | 704 | env: { PATH: binDir, DISPLAY: ":0" }, |
| 705 | 705 | onSpawn: (command) => { |
| ... | ... | @@ -731,7 +731,7 @@ it.effect.skipIf(windowsHost)( |
| 731 | 731 | const editors = yield* Effect.gen(function* () { |
| 732 | 732 | const launcher = yield* ExternalLauncher.ExternalLauncher; |
| 733 | 733 | return yield* launcher.resolveAvailableEditors(); |
| 734 | }).pipe(Effect.provide(testLayer({ platform: "linux", env: { PATH: binDir } }))); | |
| 734 | }).pipe(Effect.provide(layerTest({ platform: "linux", env: { PATH: binDir } }))); | |
| 735 | 735 | |
| 736 | 736 | assert.equal(editors.includes("file-manager"), false); |
| 737 | 737 | }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)), |
| ... | ... | @@ -756,7 +756,7 @@ it.effect.skipIf(windowsHost)( |
| 756 | 756 | return yield* launcher.resolveAvailableEditors(); |
| 757 | 757 | }).pipe( |
| 758 | 758 | Effect.provide( |
| 759 | testLayer({ | |
| 759 | layerTest({ | |
| 760 | 760 | platform: "linux", |
| 761 | 761 | env: { PATH: binDir, DISPLAY: ":0" }, |
| 762 | 762 | onSpawn: (command) => { |
| ... | ... | @@ -798,7 +798,7 @@ it.effect.skipIf(windowsHost)( |
| 798 | 798 | return yield* launcher.resolveAvailableEditors(); |
| 799 | 799 | }).pipe( |
| 800 | 800 | Effect.provide( |
| 801 | testLayer({ | |
| 801 | layerTest({ | |
| 802 | 802 | platform: "linux", |
| 803 | 803 | env: { PATH: binDir, DISPLAY: ":0" }, |
| 804 | 804 | spawnResult: (command) => (command.command === "xdg-mime" ? { stdout: "" } : undefined), |
| ... | ... | @@ -828,7 +828,7 @@ it.effect.skipIf(windowsHost)( |
| 828 | 828 | return yield* launcher.resolveAvailableEditors(); |
| 829 | 829 | }).pipe( |
| 830 | 830 | Effect.provide( |
| 831 | testLayer({ | |
| 831 | layerTest({ | |
| 832 | 832 | platform: "linux", |
| 833 | 833 | env: { PATH: binDir, DISPLAY: ":0" }, |
| 834 | 834 | spawnResult: (command) => |
| ... | ... | @@ -863,7 +863,7 @@ it.live.skipIf(windowsHost)("a stalled handler probe drops only the file manager |
| 863 | 863 | return yield* launcher.resolveAvailableEditors(); |
| 864 | 864 | }).pipe( |
| 865 | 865 | Effect.provide( |
| 866 | testLayer({ | |
| 866 | layerTest({ | |
| 867 | 867 | platform: "linux", |
| 868 | 868 | env: { PATH: binDir, DISPLAY: ":0" }, |
| 869 | 869 | spawnResult: (command) => (command.command === "xdg-mime" ? { stall: true } : undefined), |
| ... | ... | @@ -891,7 +891,7 @@ it.effect.skipIf(windowsHost)( |
| 891 | 891 | const launcher = yield* ExternalLauncher.ExternalLauncher; |
| 892 | 892 | return yield* launcher.resolveAvailableEditors(); |
| 893 | 893 | }).pipe( |
| 894 | Effect.provide(testLayer({ platform: "linux", env: { PATH: binDir, DISPLAY: ":0" } })), | |
| 894 | Effect.provide(layerTest({ platform: "linux", env: { PATH: binDir, DISPLAY: ":0" } })), | |
| 895 | 895 | ); |
| 896 | 896 | |
| 897 | 897 | assert.equal(editors.includes("file-manager"), false); |
| ... | ... | @@ -911,7 +911,7 @@ it.effect("discovers editors through the service API", () => |
| 911 | 911 | return yield* launcher.resolveAvailableEditors(); |
| 912 | 912 | }).pipe( |
| 913 | 913 | Effect.provide( |
| 914 | testLayer({ | |
| 914 | layerTest({ | |
| 915 | 915 | platform: "win32", |
| 916 | 916 | env: { PATH: binDir, PATHEXT: ".COM;.EXE;.BAT;.CMD" }, |
| 917 | 917 | }), |
| ... | ... | @@ -1021,7 +1021,7 @@ for (const { platform, installPath, editor, args } of [ |
| 1021 | 1021 | yield* launcher.launchEditor({ editor, cwd: "/workspace with spaces/file.ts:12:4" }); |
| 1022 | 1022 | }).pipe( |
| 1023 | 1023 | Effect.provide( |
| 1024 | testLayer({ | |
| 1024 | layerTest({ | |
| 1025 | 1025 | platform, |
| 1026 | 1026 | env: { HOME: home, LOCALAPPDATA: home, PATH: path.join(home, "empty") }, |
| 1027 | 1027 | onSpawn: (command) => { |
| ... | ... | @@ -1064,7 +1064,7 @@ for (const { platform, installPath, onPath } of [ |
| 1064 | 1064 | return yield* launcher.resolveAvailableEditors(); |
| 1065 | 1065 | }).pipe( |
| 1066 | 1066 | Effect.provide( |
| 1067 | testLayer({ | |
| 1067 | layerTest({ | |
| 1068 | 1068 | platform, |
| 1069 | 1069 | env: { |
| 1070 | 1070 | HOME: home, |
| ... | ... | @@ -1091,17 +1091,17 @@ it.effect.skipIf(windowsHost)("ignores unusable app bundles and keeps PATH launc |
| 1091 | 1091 | const launcher = yield* ExternalLauncher.ExternalLauncher; |
| 1092 | 1092 | return yield* launcher.resolveAvailableEditors(); |
| 1093 | 1093 | }); |
| 1094 | const before = yield* discover.pipe(Effect.provide(testLayer({ platform: "darwin", env }))); | |
| 1094 | const before = yield* discover.pipe(Effect.provide(layerTest({ platform: "darwin", env }))); | |
| 1095 | 1095 | yield* fs.makeDirectory(executable, { recursive: true }); |
| 1096 | 1096 | assert.deepEqual( |
| 1097 | yield* discover.pipe(Effect.provide(testLayer({ platform: "darwin", env }))), | |
| 1097 | yield* discover.pipe(Effect.provide(layerTest({ platform: "darwin", env }))), | |
| 1098 | 1098 | before, |
| 1099 | 1099 | ); |
| 1100 | 1100 | yield* fs.remove(executable, { recursive: true }); |
| 1101 | 1101 | yield* fs.writeFileString(executable, "#!/bin/sh\n"); |
| 1102 | 1102 | yield* fs.chmod(executable, 0o644); |
| 1103 | 1103 | assert.deepEqual( |
| 1104 | yield* discover.pipe(Effect.provide(testLayer({ platform: "darwin", env }))), | |
| 1104 | yield* discover.pipe(Effect.provide(layerTest({ platform: "darwin", env }))), | |
| 1105 | 1105 | before, |
| 1106 | 1106 | ); |
| 1107 | 1107 | yield* fs.chmod(executable, 0o755); |
| ... | ... | @@ -1114,7 +1114,7 @@ it.effect.skipIf(windowsHost)("ignores unusable app bundles and keeps PATH launc |
| 1114 | 1114 | yield* launcher.launchEditor({ editor: "cursor", cwd: "/workspace" }); |
| 1115 | 1115 | }).pipe( |
| 1116 | 1116 | Effect.provide( |
| 1117 | testLayer({ | |
| 1117 | layerTest({ | |
| 1118 | 1118 | platform: "darwin", |
| 1119 | 1119 | env: { ...env, PATH: `${env.PATH}:/usr/bin` }, |
| 1120 | 1120 | onSpawn: (command) => { |
| ... | ... | @@ -1130,7 +1130,7 @@ it.effect.skipIf(windowsHost)("ignores unusable app bundles and keeps PATH launc |
| 1130 | 1130 | it.effect("memoizes editor discovery and refreshes after the cache window", () => { |
| 1131 | 1131 | let statCalls = 0; |
| 1132 | 1132 | const fileInfo = { type: "File" } as FileSystem.File.Info; |
| 1133 | const launcherLayer = ExternalLauncher.layer.pipe( | |
| 1133 | const layerLauncher = ExternalLauncher.layer.pipe( | |
| 1134 | 1134 | Layer.provide( |
| 1135 | 1135 | Layer.mergeAll( |
| 1136 | 1136 | FileSystem.layerNoop({ |
| ... | ... | @@ -1171,7 +1171,7 @@ it.effect("memoizes editor discovery and refreshes after the cache window", () = |
| 1171 | 1171 | }).pipe( |
| 1172 | 1172 | Effect.provide( |
| 1173 | 1173 | Layer.mergeAll( |
| 1174 | launcherLayer, | |
| 1174 | layerLauncher, | |
| 1175 | 1175 | Layer.succeed(HostProcessPlatform, "win32"), |
| 1176 | 1176 | ConfigProvider.layer( |
| 1177 | 1177 | ConfigProvider.fromEnv({ |
| ... | ... | @@ -1194,7 +1194,7 @@ it.effect("keeps scanning after the caller is interrupted and shares that scan", |
| 1194 | 1194 | const fileInfo = { type: "File" } as FileSystem.File.Info; |
| 1195 | 1195 | const release = Deferred.makeUnsafe<void>(); |
| 1196 | 1196 | let parkedStats = 0; |
| 1197 | const launcherLayer = ExternalLauncher.layer.pipe( | |
| 1197 | const layerLauncher = ExternalLauncher.layer.pipe( | |
| 1198 | 1198 | Layer.provide( |
| 1199 | 1199 | Layer.mergeAll( |
| 1200 | 1200 | FileSystem.layerNoop({ |
| ... | ... | @@ -1236,7 +1236,7 @@ it.effect("keeps scanning after the caller is interrupted and shares that scan", |
| 1236 | 1236 | }).pipe( |
| 1237 | 1237 | Effect.provide( |
| 1238 | 1238 | Layer.mergeAll( |
| 1239 | launcherLayer, | |
| 1239 | layerLauncher, | |
| 1240 | 1240 | Layer.succeed(HostProcessPlatform, "win32"), |
| 1241 | 1241 | ConfigProvider.layer( |
| 1242 | 1242 | ConfigProvider.fromEnv({ |
| ... | ... | @@ -1260,5 +1260,5 @@ it.effect("rejects unknown editors through the service API", () => |
| 1260 | 1260 | assert.instanceOf(error, ExternalLauncher.ExternalLauncherUnknownEditorError); |
| 1261 | 1261 | assert.equal(error.editor, "missing-editor"); |
| 1262 | 1262 | assert.equal(error.message, "Unknown editor: missing-editor"); |
| 1263 | }).pipe(Effect.provide(testLayer({ platform: "linux", env: { PATH: "" } }))), | |
| 1263 | }).pipe(Effect.provide(layerTest({ platform: "linux", env: { PATH: "" } }))), | |
| 1264 | 1264 | ); |
apps/server/src/project/AgentSessionImporter.test.ts+2-2| ... | ... | @@ -59,7 +59,7 @@ it.effect("imports messages once and preserves the provider native resume bindin |
| 59 | 59 | }, |
| 60 | 60 | }), |
| 61 | 61 | }); |
| 62 | const testLayer = AgentSessionImporter.layer.pipe( | |
| 62 | const layerTest = AgentSessionImporter.layer.pipe( | |
| 63 | 63 | Layer.provide( |
| 64 | 64 | Layer.mergeAll( |
| 65 | 65 | Layer.succeed(AgentSessionScanner.AgentSessionScanner, scanner), |
| ... | ... | @@ -143,5 +143,5 @@ it.effect("imports messages once and preserves the provider native resume bindin |
| 143 | 143 | }), |
| 144 | 144 | ]); |
| 145 | 145 | expect(recorded).toHaveLength(2); |
| 146 | }).pipe(Effect.provide(testLayer)); | |
| 146 | }).pipe(Effect.provide(layerTest)); | |
| 147 | 147 | }); |
apps/server/src/project/AgentSessionScanner.test.ts+10-10| ... | ... | @@ -33,7 +33,7 @@ const makeProjectShell = (workspaceRoot: string): OrchestrationProjectShell => ( |
| 33 | 33 | updatedAt: "2026-01-01T00:00:00.000Z", |
| 34 | 34 | }); |
| 35 | 35 | |
| 36 | const makeProjectStoreLayer = (importedWorkspaceRoots: ReadonlyArray<string>) => | |
| 36 | const layerProjectStore = (importedWorkspaceRoots: ReadonlyArray<string>) => | |
| 37 | 37 | Layer.mock(ProjectStore.ProjectStoreV2)({ |
| 38 | 38 | listShells: () => |
| 39 | 39 | Effect.succeed( |
| ... | ... | @@ -54,7 +54,7 @@ interface ScannerTestInput { |
| 54 | 54 | readonly providerInstances?: ContractServerSettings["providerInstances"]; |
| 55 | 55 | } |
| 56 | 56 | |
| 57 | const makeScannerTestLayer = (input: ScannerTestInput) => | |
| 57 | const layerScannerTest = (input: ScannerTestInput) => | |
| 58 | 58 | AgentSessionScanner.layer.pipe( |
| 59 | 59 | Layer.provide( |
| 60 | 60 | Layer.mergeAll( |
| ... | ... | @@ -71,7 +71,7 @@ const makeScannerTestLayer = (input: ScannerTestInput) => |
| 71 | 71 | input.claudeHomePath, |
| 72 | 72 | input.configBaseDir ?? { prefix: "t3code-scanner-config-" }, |
| 73 | 73 | ), |
| 74 | makeProjectStoreLayer(input.importedWorkspaceRoots ?? []), | |
| 74 | layerProjectStore(input.importedWorkspaceRoots ?? []), | |
| 75 | 75 | ), |
| 76 | 76 | ), |
| 77 | 77 | ); |
| ... | ... | @@ -80,7 +80,7 @@ const runScan = (input: ScannerTestInput) => |
| 80 | 80 | Effect.gen(function* () { |
| 81 | 81 | const scanner = yield* AgentSessionScanner.AgentSessionScanner; |
| 82 | 82 | return yield* scanner.scan; |
| 83 | }).pipe(Effect.provide(makeScannerTestLayer(input))); | |
| 83 | }).pipe(Effect.provide(layerScannerTest(input))); | |
| 84 | 84 | |
| 85 | 85 | const runRecentThreadOutcomes = (input: ScannerTestInput & { readonly workspaceRoot: string }) => |
| 86 | 86 | Effect.gen(function* () { |
| ... | ... | @@ -89,7 +89,7 @@ const runRecentThreadOutcomes = (input: ScannerTestInput & { readonly workspaceR |
| 89 | 89 | Stream.runCollect, |
| 90 | 90 | Effect.map((outcomes) => Array.from(outcomes)), |
| 91 | 91 | ); |
| 92 | }).pipe(Effect.provide(makeScannerTestLayer(input))); | |
| 92 | }).pipe(Effect.provide(layerScannerTest(input))); | |
| 93 | 93 | |
| 94 | 94 | const runRecentThreads = (input: ScannerTestInput & { readonly workspaceRoot: string }) => |
| 95 | 95 | runRecentThreadOutcomes(input).pipe( |
| ... | ... | @@ -1705,7 +1705,7 @@ it.layer(NodeServices.layer)("AgentSessionScanner", (it) => { |
| 1705 | 1705 | expect(scan.candidates[0]?.threadCount).toBe(5); |
| 1706 | 1706 | return yield* scanner.recentThreads(workspace).pipe(Stream.runCollect); |
| 1707 | 1707 | }).pipe( |
| 1708 | Effect.provide(makeScannerTestLayer({ claudeHomePath, codexHomePath })), | |
| 1708 | Effect.provide(layerScannerTest({ claudeHomePath, codexHomePath })), | |
| 1709 | 1709 | Effect.provideService(FileSystem.FileSystem, trackedFileSystem), |
| 1710 | 1710 | ); |
| 1711 | 1711 | |
| ... | ... | @@ -1855,7 +1855,7 @@ it.layer(NodeServices.layer)("AgentSessionScanner", (it) => { |
| 1855 | 1855 | } else { |
| 1856 | 1856 | expect(outcomes).toEqual([{ _tag: "Skipped" }]); |
| 1857 | 1857 | } |
| 1858 | }).pipe(Effect.provide(makeScannerTestLayer({ claudeHomePath, codexHomePath }))); | |
| 1858 | }).pipe(Effect.provide(layerScannerTest({ claudeHomePath, codexHomePath }))); | |
| 1859 | 1859 | }), |
| 1860 | 1860 | ); |
| 1861 | 1861 | } |
| ... | ... | @@ -1926,7 +1926,7 @@ it.layer(NodeServices.layer)("AgentSessionScanner", (it) => { |
| 1926 | 1926 | thread: { providerSessionId: "replaced-session" }, |
| 1927 | 1927 | source: { size: imported.source.size, mtimeMs: imported.source.mtimeMs }, |
| 1928 | 1928 | }); |
| 1929 | }).pipe(Effect.provide(makeScannerTestLayer({ claudeHomePath, codexHomePath }))); | |
| 1929 | }).pipe(Effect.provide(layerScannerTest({ claudeHomePath, codexHomePath }))); | |
| 1930 | 1930 | }), |
| 1931 | 1931 | ); |
| 1932 | 1932 | |
| ... | ... | @@ -2350,7 +2350,7 @@ it.layer(NodeServices.layer)("AgentSessionScanner", (it) => { |
| 2350 | 2350 | Effect.map((items) => Array.from(items)), |
| 2351 | 2351 | ); |
| 2352 | 2352 | }).pipe( |
| 2353 | Effect.provide(makeScannerTestLayer({ claudeHomePath, codexHomePath })), | |
| 2353 | Effect.provide(layerScannerTest({ claudeHomePath, codexHomePath })), | |
| 2354 | 2354 | Effect.provideService(FileSystem.FileSystem, simulatedFileSystem), |
| 2355 | 2355 | ); |
| 2356 | 2356 | |
| ... | ... | @@ -2573,7 +2573,7 @@ it.layer(NodeServices.layer)("AgentSessionScanner", (it) => { |
| 2573 | 2573 | expect(scan.truncated).toBe(true); |
| 2574 | 2574 | return yield* scanner.recentThreads(recentWorkspace).pipe(Stream.runCollect); |
| 2575 | 2575 | }).pipe( |
| 2576 | Effect.provide(makeScannerTestLayer(input)), | |
| 2576 | Effect.provide(layerScannerTest(input)), | |
| 2577 | 2577 | Effect.provideService(FileSystem.FileSystem, simulatedFileSystem), |
| 2578 | 2578 | ); |
| 2579 | 2579 |
apps/server/src/project/ManagedProjectFolders.test.ts+13-13| ... | ... | @@ -12,8 +12,8 @@ import * as Scope from "effect/Scope"; |
| 12 | 12 | |
| 13 | 13 | import * as ServerConfig from "../config.ts"; |
| 14 | 14 | import * as GitWorkflow from "../git/GitWorkflowService.ts"; |
| 15 | import { ProjectServiceLayerLive } from "../orchestration-v2/runtimeLayer.ts"; | |
| 16 | import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts"; | |
| 15 | import * as RuntimeLayer from "../orchestration-v2/runtimeLayer.ts"; | |
| 16 | import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts"; | |
| 17 | 17 | import * as ProcessRunner from "../processRunner.ts"; |
| 18 | 18 | import * as GitVcsDriver from "../vcs/GitVcsDriver.ts"; |
| 19 | 19 | import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts"; |
| ... | ... | @@ -27,7 +27,7 @@ import * as ManagedProjectFolders from "./ManagedProjectFolders.ts"; |
| 27 | 27 | |
| 28 | 28 | // Real repository detection: the service only asks the Git workflow whether |
| 29 | 29 | // the data dir is inside a checkout. |
| 30 | const gitWorkflowLayer = Layer.unwrap( | |
| 30 | const layerGitWorkflow = Layer.unwrap( | |
| 31 | 31 | Effect.gen(function* () { |
| 32 | 32 | const registry = yield* VcsDriverRegistry.VcsDriverRegistry; |
| 33 | 33 | return Layer.mock(GitWorkflow.GitWorkflowService)({ |
| ... | ... | @@ -40,7 +40,7 @@ const gitWorkflowLayer = Layer.unwrap( |
| 40 | 40 | }), |
| 41 | 41 | ).pipe(Layer.provide(VcsDriverRegistry.layer.pipe(Layer.provide(VcsProcess.layer)))); |
| 42 | 42 | |
| 43 | const enrichmentLayer = ProjectEnrichmentService.layer.pipe( | |
| 43 | const layerEnrichment = ProjectEnrichmentService.layer.pipe( | |
| 44 | 44 | Layer.provide( |
| 45 | 45 | Layer.mergeAll( |
| 46 | 46 | Layer.succeed(RepositoryIdentityResolver.RepositoryIdentityResolver, { |
| ... | ... | @@ -53,7 +53,7 @@ const enrichmentLayer = ProjectEnrichmentService.layer.pipe( |
| 53 | 53 | ), |
| 54 | 54 | ); |
| 55 | 55 | |
| 56 | const realGitLayer = GitVcsDriver.layer.pipe(Layer.provide(VcsProcess.layer)); | |
| 56 | const layerRealGit = GitVcsDriver.layer.pipe(Layer.provide(VcsProcess.layer)); | |
| 57 | 57 | |
| 58 | 58 | interface HarnessOptions { |
| 59 | 59 | /** A git driver for failures real git cannot produce on demand. */ |
| ... | ... | @@ -68,7 +68,7 @@ interface HarnessOptions { |
| 68 | 68 | * The service over a real ProjectService and real git, with its data dir at |
| 69 | 69 | * `baseDir`. |
| 70 | 70 | */ |
| 71 | const makeLayer = (baseDir: string, options?: HarnessOptions) => | |
| 71 | const layer = (baseDir: string, options?: HarnessOptions) => | |
| 72 | 72 | ManagedProjectFolders.layer.pipe( |
| 73 | 73 | Layer.provide( |
| 74 | 74 | options?.projects === undefined |
| ... | ... | @@ -78,12 +78,12 @@ const makeLayer = (baseDir: string, options?: HarnessOptions) => |
| 78 | 78 | ProjectService.ProjectService.pipe(Effect.map(options.projects)), |
| 79 | 79 | ), |
| 80 | 80 | ), |
| 81 | Layer.provideMerge(ProjectServiceLayerLive), | |
| 82 | Layer.provideMerge(enrichmentLayer), | |
| 81 | Layer.provideMerge(RuntimeLayer.layerProjectService), | |
| 82 | Layer.provideMerge(layerEnrichment), | |
| 83 | 83 | Layer.provideMerge(WorkspacePaths.layer), |
| 84 | Layer.provideMerge(gitWorkflowLayer), | |
| 85 | Layer.provideMerge(options?.git ?? realGitLayer), | |
| 86 | Layer.provideMerge(SqlitePersistenceMemory), | |
| 84 | Layer.provideMerge(layerGitWorkflow), | |
| 85 | Layer.provideMerge(options?.git ?? layerRealGit), | |
| 86 | Layer.provideMerge(SqlitePersistence.layerMemory), | |
| 87 | 87 | Layer.provideMerge(ServerConfig.layerTest(baseDir, baseDir)), |
| 88 | 88 | Layer.provideMerge(NodeServices.layer), |
| 89 | 89 | ); |
| ... | ... | @@ -105,7 +105,7 @@ const withScratch = <A, E>( |
| 105 | 105 | Effect.gen(function* () { |
| 106 | 106 | const fileSystem = yield* FileSystem.FileSystem; |
| 107 | 107 | const baseDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-managed-folders-" }); |
| 108 | return yield* body({ baseDir }).pipe(Effect.provide(makeLayer(baseDir, options))); | |
| 108 | return yield* body({ baseDir }).pipe(Effect.provide(layer(baseDir, options))); | |
| 109 | 109 | }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)); |
| 110 | 110 | |
| 111 | 111 | const git = (cwd: string, args: ReadonlyArray<string>) => |
| ... | ... | @@ -141,7 +141,7 @@ it.effect("offers nothing when the data dir sits inside a Git checkout", () => |
| 141 | 141 | assert.isTrue(Option.isNone(yield* scratch.scratchRoot)); |
| 142 | 142 | const failure = yield* Effect.flip(scratch.ensureScratchProject); |
| 143 | 143 | assert.equal(failure._tag, "ScratchUnavailableError"); |
| 144 | }).pipe(Effect.provide(makeLayer(baseDir))); | |
| 144 | }).pipe(Effect.provide(layer(baseDir))); | |
| 145 | 145 | }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)), |
| 146 | 146 | ); |
| 147 | 147 |
apps/server/src/project/ProjectEnrichmentService.test.ts+13-13| ... | ... | @@ -33,7 +33,7 @@ const waitForAvailable = Effect.fn("ProjectEnrichmentServiceTest.waitForAvailabl |
| 33 | 33 | return yield* Effect.die(`Project metadata for ${workspaceRoot} was not resolved in time.`); |
| 34 | 34 | }); |
| 35 | 35 | |
| 36 | const makeLayer = ( | |
| 36 | const layer = ( | |
| 37 | 37 | metadataLayer: Layer.Layer< |
| 38 | 38 | | ProjectFaviconResolver.ProjectFaviconResolver |
| 39 | 39 | | RepositoryIdentityResolver.RepositoryIdentityResolver |
| ... | ... | @@ -46,7 +46,7 @@ const makeLayer = ( |
| 46 | 46 | |
| 47 | 47 | it.effect("preserves either enrichment field when the other resolver fails", () => |
| 48 | 48 | Effect.gen(function* () { |
| 49 | const metadataLayer = Layer.merge( | |
| 49 | const layerMetadata = Layer.merge( | |
| 50 | 50 | Layer.succeed(RepositoryIdentityResolver.RepositoryIdentityResolver, { |
| 51 | 51 | resolve: (workspaceRoot) => |
| 52 | 52 | workspaceRoot === "/repo-fails" |
| ... | ... | @@ -90,14 +90,14 @@ it.effect("preserves either enrichment field when the other resolver fails", () |
| 90 | 90 | ); |
| 91 | 91 | assert.isNull(repositoryFailure.repositoryIdentity); |
| 92 | 92 | assert.equal(repositoryFailure.faviconPath, "/repo-fails/favicon.svg"); |
| 93 | }).pipe(Effect.provide(makeLayer(metadataLayer))); | |
| 93 | }).pipe(Effect.provide(layer(layerMetadata))); | |
| 94 | 94 | }), |
| 95 | 95 | ); |
| 96 | 96 | |
| 97 | 97 | it.effect("publishes repository completion while favicon enrichment is still pending", () => |
| 98 | 98 | Effect.gen(function* () { |
| 99 | 99 | const releaseFavicon = yield* Deferred.make<void>(); |
| 100 | const metadataLayer = Layer.merge( | |
| 100 | const layerMetadata = Layer.merge( | |
| 101 | 101 | Layer.succeed(RepositoryIdentityResolver.RepositoryIdentityResolver, { |
| 102 | 102 | resolve: (workspaceRoot) => Effect.succeed(identity(workspaceRoot)), |
| 103 | 103 | }), |
| ... | ... | @@ -117,7 +117,7 @@ it.effect("publishes repository completion while favicon enrichment is still pen |
| 117 | 117 | assert.isTrue(change.repositoryIdentityResolved); |
| 118 | 118 | assert.equal(change.enrichment.repositoryIdentity?.canonicalKey, "example.test/v1/completed"); |
| 119 | 119 | assert.isNull(change.enrichment.faviconPath); |
| 120 | }).pipe(Effect.provide(makeLayer(metadataLayer))); | |
| 120 | }).pipe(Effect.provide(layer(layerMetadata))); | |
| 121 | 121 | }), |
| 122 | 122 | ); |
| 123 | 123 | |
| ... | ... | @@ -125,7 +125,7 @@ it.effect("keeps repository workers available when every favicon worker is hung" |
| 125 | 125 | Effect.gen(function* () { |
| 126 | 126 | const faviconWorkersStarted = yield* Deferred.make<void>(); |
| 127 | 127 | const faviconStarts = yield* Ref.make(0); |
| 128 | const metadataLayer = Layer.merge( | |
| 128 | const layerMetadata = Layer.merge( | |
| 129 | 129 | Layer.succeed(RepositoryIdentityResolver.RepositoryIdentityResolver, { |
| 130 | 130 | resolve: (workspaceRoot) => Effect.succeed(identity(workspaceRoot)), |
| 131 | 131 | }), |
| ... | ... | @@ -158,7 +158,7 @@ it.effect("keeps repository workers available when every favicon worker is hung" |
| 158 | 158 | assert.equal(yield* Ref.get(faviconStarts), 2); |
| 159 | 159 | }).pipe( |
| 160 | 160 | Effect.provide( |
| 161 | makeLayer(metadataLayer, { | |
| 161 | layer(layerMetadata, { | |
| 162 | 162 | cacheCapacity: 8, |
| 163 | 163 | maxPending: 4, |
| 164 | 164 | concurrency: 2, |
| ... | ... | @@ -172,7 +172,7 @@ it.effect("getAvailable returns immediately while repository identity is still u |
| 172 | 172 | Effect.gen(function* () { |
| 173 | 173 | const repositoryStarted = yield* Deferred.make<void>(); |
| 174 | 174 | const releaseRepository = yield* Deferred.make<void>(); |
| 175 | const metadataLayer = Layer.merge( | |
| 175 | const layerMetadata = Layer.merge( | |
| 176 | 176 | Layer.succeed(RepositoryIdentityResolver.RepositoryIdentityResolver, { |
| 177 | 177 | resolve: (workspaceRoot) => |
| 178 | 178 | Deferred.succeed(repositoryStarted, undefined).pipe( |
| ... | ... | @@ -205,7 +205,7 @@ it.effect("getAvailable returns immediately while repository identity is still u |
| 205 | 205 | ); |
| 206 | 206 | assert.equal(resolved.repositoryIdentity?.canonicalKey, "example.test/v1/pending-identity"); |
| 207 | 207 | assert.isTrue(resolved.repositoryIdentityResolved); |
| 208 | }).pipe(Effect.provide(makeLayer(metadataLayer))); | |
| 208 | }).pipe(Effect.provide(layer(layerMetadata))); | |
| 209 | 209 | }), |
| 210 | 210 | ); |
| 211 | 211 | |
| ... | ... | @@ -215,7 +215,7 @@ it.effect( |
| 215 | 215 | Effect.gen(function* () { |
| 216 | 216 | const repositoryStarted = yield* Deferred.make<void>(); |
| 217 | 217 | const releaseRepository = yield* Deferred.make<void>(); |
| 218 | const metadataLayer = Layer.merge( | |
| 218 | const layerMetadata = Layer.merge( | |
| 219 | 219 | Layer.succeed(RepositoryIdentityResolver.RepositoryIdentityResolver, { |
| 220 | 220 | resolve: (workspaceRoot) => { |
| 221 | 221 | if (workspaceRoot === "/no-remote") { |
| ... | ... | @@ -268,7 +268,7 @@ it.effect( |
| 268 | 268 | const failed = yield* service.peek("/fails"); |
| 269 | 269 | assert.isNull(failed.repositoryIdentity); |
| 270 | 270 | assert.isFalse(failed.repositoryIdentityResolved); |
| 271 | }).pipe(Effect.provide(makeLayer(metadataLayer))); | |
| 271 | }).pipe(Effect.provide(layer(layerMetadata))); | |
| 272 | 272 | }), |
| 273 | 273 | ); |
| 274 | 274 | |
| ... | ... | @@ -278,7 +278,7 @@ it.effect("deduplicates requests, bounds pending work, and reloads invalidated r |
| 278 | 278 | const releaseFirst = yield* Deferred.make<void>(); |
| 279 | 279 | const repositoryCalls = yield* Ref.make<ReadonlyArray<string>>([]); |
| 280 | 280 | const version = yield* Ref.make(1); |
| 281 | const metadataLayer = Layer.merge( | |
| 281 | const layerMetadata = Layer.merge( | |
| 282 | 282 | Layer.succeed(RepositoryIdentityResolver.RepositoryIdentityResolver, { |
| 283 | 283 | resolve: (workspaceRoot) => |
| 284 | 284 | Effect.gen(function* () { |
| ... | ... | @@ -332,7 +332,7 @@ it.effect("deduplicates requests, bounds pending work, and reloads invalidated r |
| 332 | 332 | ]); |
| 333 | 333 | }).pipe( |
| 334 | 334 | Effect.provide( |
| 335 | makeLayer(metadataLayer, { | |
| 335 | layer(layerMetadata, { | |
| 336 | 336 | cacheCapacity: 8, |
| 337 | 337 | maxPending: 2, |
| 338 | 338 | concurrency: 1, |
apps/server/src/project/ProjectFaviconResolver.test.ts+2-2| ... | ... | @@ -12,7 +12,7 @@ import * as WorkspacePaths from "../workspace/WorkspacePaths.ts"; |
| 12 | 12 | import * as ProjectFaviconResolver from "./ProjectFaviconResolver.ts"; |
| 13 | 13 | import * as T3ProjectFileLoader from "./T3ProjectFileLoader.ts"; |
| 14 | 14 | |
| 15 | const TestLayer = Layer.empty.pipe( | |
| 15 | const layerTest = Layer.empty.pipe( | |
| 16 | 16 | Layer.provideMerge( |
| 17 | 17 | ProjectFaviconResolver.layer.pipe( |
| 18 | 18 | Layer.provide(WorkspacePaths.layer), |
| ... | ... | @@ -49,7 +49,7 @@ const makeResolverWithFileSystem = (fileSystem: FileSystem.FileSystem) => |
| 49 | 49 | Effect.provideService(FileSystem.FileSystem, fileSystem), |
| 50 | 50 | ); |
| 51 | 51 | |
| 52 | it.layer(TestLayer)("ProjectFaviconResolverLive", (it) => { | |
| 52 | it.layer(layerTest)("ProjectFaviconResolverLive", (it) => { | |
| 53 | 53 | describe("resolvePath", () => { |
| 54 | 54 | it.effect("serves repeated resolves from cache instead of re-walking candidates", () => |
| 55 | 55 | Effect.gen(function* () { |
apps/server/src/project/ProjectService.deletion.test.ts+14-14| ... | ... | @@ -27,19 +27,19 @@ import * as ProjectionStore from "../orchestration-v2/ProjectionStore.ts"; |
| 27 | 27 | import * as ProjectStore from "../orchestration-v2/ProjectStore.ts"; |
| 28 | 28 | import * as ThreadCommandExecutor from "../orchestration-v2/ThreadCommandExecutor.ts"; |
| 29 | 29 | import { planThreadDeletion } from "../orchestration-v2/ThreadDeletion.ts"; |
| 30 | import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts"; | |
| 30 | import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts"; | |
| 31 | 31 | import * as WorkspacePaths from "../workspace/WorkspacePaths.ts"; |
| 32 | 32 | import * as ProjectEnrichmentService from "./ProjectEnrichmentService.ts"; |
| 33 | 33 | import * as ProjectFaviconResolver from "./ProjectFaviconResolver.ts"; |
| 34 | 34 | import * as ProjectService from "./ProjectService.ts"; |
| 35 | 35 | import * as RepositoryIdentityResolver from "./RepositoryIdentityResolver.ts"; |
| 36 | 36 | |
| 37 | const eventPersistenceLayer = EventSink.layer.pipe( | |
| 37 | const layerEventPersistence = EventSink.layer.pipe( | |
| 38 | 38 | Layer.provideMerge(Layer.merge(EventStore.layer, ProjectionStore.layer)), |
| 39 | 39 | ); |
| 40 | const servicesLayer = Layer.mergeAll( | |
| 41 | LegacyV1ThreadImporter.layer.pipe(Layer.provideMerge(eventPersistenceLayer)), | |
| 42 | ProjectionMaintenance.layer.pipe(Layer.provide(eventPersistenceLayer)), | |
| 40 | const layerServices = Layer.mergeAll( | |
| 41 | LegacyV1ThreadImporter.layer.pipe(Layer.provideMerge(layerEventPersistence)), | |
| 42 | ProjectionMaintenance.layer.pipe(Layer.provide(layerEventPersistence)), | |
| 43 | 43 | ProjectStore.layer, |
| 44 | 44 | IdAllocator.layer, |
| 45 | 45 | ThreadCommandExecutor.layer, |
| ... | ... | @@ -64,7 +64,7 @@ const servicesLayer = Layer.mergeAll( |
| 64 | 64 | ), |
| 65 | 65 | ), |
| 66 | 66 | ); |
| 67 | const databaseLayer = SqlitePersistenceMemory.pipe( | |
| 67 | const layerDatabase = SqlitePersistence.layerMemory.pipe( | |
| 68 | 68 | Layer.provideMerge(ServerConfig.layerTest(process.cwd(), { prefix: "project-deletion-test-" })), |
| 69 | 69 | Layer.provideMerge(NodeServices.layer), |
| 70 | 70 | ); |
| ... | ... | @@ -239,8 +239,8 @@ it.effect("retries a partial project deletion without repeating child events or |
| 239 | 239 | ], |
| 240 | 240 | ); |
| 241 | 241 | } |
| 242 | }).pipe(Effect.provide(servicesLayer)); | |
| 243 | }).pipe(Effect.provide(databaseLayer)), | |
| 242 | }).pipe(Effect.provide(layerServices)); | |
| 243 | }).pipe(Effect.provide(layerDatabase)), | |
| 244 | 244 | ); |
| 245 | 245 | |
| 246 | 246 | it.effect( |
| ... | ... | @@ -354,8 +354,8 @@ it.effect( |
| 354 | 354 | type: "attachment.cleanup", |
| 355 | 355 | attachmentIds: ["legacy_screenshot"], |
| 356 | 356 | }); |
| 357 | }).pipe(Effect.provide(servicesLayer)); | |
| 358 | }).pipe(Effect.provide(databaseLayer)), | |
| 357 | }).pipe(Effect.provide(layerServices)); | |
| 358 | }).pipe(Effect.provide(layerDatabase)), | |
| 359 | 359 | ); |
| 360 | 360 | |
| 361 | 361 | it.effect("rejects a child deletion command ID already accepted for an unrelated thread", () => |
| ... | ... | @@ -404,8 +404,8 @@ it.effect("rejects a child deletion command ID already accepted for an unrelated |
| 404 | 404 | WHERE thread_id = ${threadId} |
| 405 | 405 | `; |
| 406 | 406 | assert.deepEqual(cleanup, []); |
| 407 | }).pipe(Effect.provide(servicesLayer)); | |
| 408 | }).pipe(Effect.provide(databaseLayer)), | |
| 407 | }).pipe(Effect.provide(layerServices)); | |
| 408 | }).pipe(Effect.provide(layerDatabase)), | |
| 409 | 409 | ); |
| 410 | 410 | |
| 411 | 411 | it.effect("deletes a project without force once its imported threads were deleted in V2", () => |
| ... | ... | @@ -481,6 +481,6 @@ it.effect("deletes a project without force once its imported threads were delete |
| 481 | 481 | }); |
| 482 | 482 | assert.isNotNull(deleted.deletedAt); |
| 483 | 483 | assert.isTrue(Option.isNone(yield* service.getById(projectId))); |
| 484 | }).pipe(Effect.provide(servicesLayer)); | |
| 485 | }).pipe(Effect.provide(databaseLayer)), | |
| 484 | }).pipe(Effect.provide(layerServices)); | |
| 485 | }).pipe(Effect.provide(layerDatabase)), | |
| 486 | 486 | ); |
apps/server/src/project/ProjectService.test.ts+24-29| ... | ... | @@ -16,25 +16,22 @@ import * as IdAllocator from "../orchestration-v2/IdAllocator.ts"; |
| 16 | 16 | import * as LegacyV1ThreadImporter from "../orchestration-v2/legacy/LegacyV1ThreadImporter.ts"; |
| 17 | 17 | import * as ProjectionStore from "../orchestration-v2/ProjectionStore.ts"; |
| 18 | 18 | import * as ProjectStore from "../orchestration-v2/ProjectStore.ts"; |
| 19 | import { | |
| 20 | OrchestrationV2EventSinkLayerLive, | |
| 21 | ProjectServiceLayerLive, | |
| 22 | } from "../orchestration-v2/runtimeLayer.ts"; | |
| 19 | import * as RuntimeLayer from "../orchestration-v2/runtimeLayer.ts"; | |
| 23 | 20 | import * as ThreadCommandExecutor from "../orchestration-v2/ThreadCommandExecutor.ts"; |
| 24 | import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts"; | |
| 21 | import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts"; | |
| 25 | 22 | import * as WorkspacePaths from "../workspace/WorkspacePaths.ts"; |
| 26 | 23 | import * as ProjectEnrichmentService from "./ProjectEnrichmentService.ts"; |
| 27 | 24 | import * as ProjectFaviconResolver from "./ProjectFaviconResolver.ts"; |
| 28 | 25 | import * as ProjectService from "./ProjectService.ts"; |
| 29 | 26 | import * as RepositoryIdentityResolver from "./RepositoryIdentityResolver.ts"; |
| 30 | 27 | |
| 31 | const workspacePathsLayer = Layer.succeed(WorkspacePaths.WorkspacePaths, { | |
| 28 | const layerWorkspacePaths = Layer.succeed(WorkspacePaths.WorkspacePaths, { | |
| 32 | 29 | normalizeWorkspaceRoot: (workspaceRoot) => Effect.succeed(workspaceRoot.replace(/\/$/, "")), |
| 33 | 30 | resolveRelativePathWithinRoot: ({ workspaceRoot, relativePath }) => |
| 34 | 31 | Effect.succeed({ absolutePath: `${workspaceRoot}/${relativePath}`, relativePath }), |
| 35 | 32 | }); |
| 36 | 33 | |
| 37 | const metadataLayer = Layer.merge( | |
| 34 | const layerMetadata = Layer.merge( | |
| 38 | 35 | Layer.succeed(RepositoryIdentityResolver.RepositoryIdentityResolver, { |
| 39 | 36 | resolve: (workspaceRoot) => |
| 40 | 37 | Effect.succeed({ |
| ... | ... | @@ -52,38 +49,36 @@ const metadataLayer = Layer.merge( |
| 52 | 49 | }), |
| 53 | 50 | ); |
| 54 | 51 | |
| 55 | const makeTestLayer = ( | |
| 52 | const layerTestFor = ( | |
| 56 | 53 | projectMetadataLayer: Layer.Layer< |
| 57 | 54 | | ProjectFaviconResolver.ProjectFaviconResolver |
| 58 | 55 | | RepositoryIdentityResolver.RepositoryIdentityResolver |
| 59 | 56 | >, |
| 60 | 57 | ) => |
| 61 | ProjectServiceLayerLive.pipe( | |
| 58 | RuntimeLayer.layerProjectService.pipe( | |
| 62 | 59 | Layer.provideMerge(ProjectEnrichmentService.layer), |
| 63 | Layer.provideMerge(workspacePathsLayer), | |
| 60 | Layer.provideMerge(layerWorkspacePaths), | |
| 64 | 61 | Layer.provideMerge(projectMetadataLayer), |
| 65 | Layer.provideMerge(SqlitePersistenceMemory), | |
| 62 | Layer.provideMerge(SqlitePersistence.layerMemory), | |
| 66 | 63 | Layer.provide(ServerConfig.layerTest(process.cwd(), { prefix: "project-service-test-" })), |
| 67 | 64 | Layer.provide(NodeServices.layer), |
| 68 | 65 | ); |
| 69 | 66 | |
| 70 | const TestLayer = makeTestLayer(metadataLayer); | |
| 67 | const layerTest = layerTestFor(layerMetadata); | |
| 71 | 68 | |
| 72 | 69 | /** Every dependency of ProjectService.make, so a test can swap one of them. */ |
| 73 | const ProjectServiceDependenciesLayer = Layer.mergeAll( | |
| 74 | OrchestrationV2EventSinkLayerLive, | |
| 70 | const layerProjectServiceDependencies = Layer.mergeAll( | |
| 71 | RuntimeLayer.layerEventSink, | |
| 75 | 72 | ProjectStore.layer, |
| 76 | 73 | ProjectionStore.layer, |
| 77 | 74 | IdAllocator.layer, |
| 78 | 75 | ThreadCommandExecutor.layer, |
| 79 | 76 | ).pipe( |
| 80 | Layer.provideMerge( | |
| 81 | LegacyV1ThreadImporter.layer.pipe(Layer.provide(OrchestrationV2EventSinkLayerLive)), | |
| 82 | ), | |
| 77 | Layer.provideMerge(LegacyV1ThreadImporter.layer.pipe(Layer.provide(RuntimeLayer.layerEventSink))), | |
| 83 | 78 | Layer.provideMerge(ProjectEnrichmentService.layer), |
| 84 | Layer.provideMerge(workspacePathsLayer), | |
| 85 | Layer.provideMerge(metadataLayer), | |
| 86 | Layer.provideMerge(SqlitePersistenceMemory), | |
| 79 | Layer.provideMerge(layerWorkspacePaths), | |
| 80 | Layer.provideMerge(layerMetadata), | |
| 81 | Layer.provideMerge(SqlitePersistence.layerMemory), | |
| 87 | 82 | Layer.provide(ServerConfig.layerTest(process.cwd(), { prefix: "project-service-race-" })), |
| 88 | 83 | Layer.provide(NodeServices.layer), |
| 89 | 84 | ); |
| ... | ... | @@ -101,7 +96,7 @@ const waitForProject = Effect.fn("ProjectServiceTest.waitForProject")(function* |
| 101 | 96 | return yield* Effect.die(`Project ${projectId} was not enriched in time.`); |
| 102 | 97 | }); |
| 103 | 98 | |
| 104 | it.layer(TestLayer)("ProjectService", (it) => { | |
| 99 | it.layer(layerTest)("ProjectService", (it) => { | |
| 105 | 100 | it.effect("creates, updates, resolves, snapshots, and soft-deletes projects", () => |
| 106 | 101 | Effect.gen(function* () { |
| 107 | 102 | const service = yield* ProjectService.ProjectService; |
| ... | ... | @@ -500,7 +495,7 @@ it.effect( |
| 500 | 495 | const repositoryCalls = yield* Ref.make(0); |
| 501 | 496 | const faviconCalls = yield* Ref.make(0); |
| 502 | 497 | |
| 503 | const slowMetadataLayer = Layer.merge( | |
| 498 | const layerSlowMetadata = Layer.merge( | |
| 504 | 499 | Layer.succeed(RepositoryIdentityResolver.RepositoryIdentityResolver, { |
| 505 | 500 | resolve: (workspaceRoot) => |
| 506 | 501 | Effect.gen(function* () { |
| ... | ... | @@ -592,13 +587,13 @@ it.effect( |
| 592 | 587 | assert.equal(secondSnapshot.projects[0]?.faviconPath, "/work/slow-enrichment/favicon.svg"); |
| 593 | 588 | assert.equal(yield* Ref.get(repositoryCalls), 1); |
| 594 | 589 | assert.equal(yield* Ref.get(faviconCalls), 1); |
| 595 | }).pipe(Effect.provide(makeTestLayer(slowMetadataLayer))); | |
| 590 | }).pipe(Effect.provide(layerTestFor(layerSlowMetadata))); | |
| 596 | 591 | }), |
| 597 | 592 | ); |
| 598 | 593 | |
| 599 | 594 | it.effect("keeps project snapshots available when optional metadata enrichment fails", () => |
| 600 | 595 | Effect.gen(function* () { |
| 601 | const failingMetadataLayer = Layer.merge( | |
| 596 | const layerFailingMetadata = Layer.merge( | |
| 602 | 597 | Layer.succeed(RepositoryIdentityResolver.RepositoryIdentityResolver, { |
| 603 | 598 | resolve: () => Effect.succeed(null), |
| 604 | 599 | }), |
| ... | ... | @@ -630,14 +625,14 @@ it.effect("keeps project snapshots available when optional metadata enrichment f |
| 630 | 625 | assert.equal(snapshot.projects[0]?.title, "Still visible"); |
| 631 | 626 | assert.isNull(snapshot.projects[0]?.repositoryIdentity ?? null); |
| 632 | 627 | assert.isNull(snapshot.projects[0]?.faviconPath ?? null); |
| 633 | }).pipe(Effect.provide(makeTestLayer(failingMetadataLayer))); | |
| 628 | }).pipe(Effect.provide(layerTestFor(layerFailingMetadata))); | |
| 634 | 629 | }), |
| 635 | 630 | ); |
| 636 | 631 | |
| 637 | 632 | it.effect("invalidates workspace-derived metadata when a project moves", () => |
| 638 | 633 | Effect.gen(function* () { |
| 639 | 634 | const metadataVersion = yield* Ref.make(1); |
| 640 | const versionedMetadataLayer = Layer.merge( | |
| 635 | const layerVersionedMetadata = Layer.merge( | |
| 641 | 636 | Layer.succeed(RepositoryIdentityResolver.RepositoryIdentityResolver, { |
| 642 | 637 | resolve: (workspaceRoot) => |
| 643 | 638 | Ref.get(metadataVersion).pipe( |
| ... | ... | @@ -700,7 +695,7 @@ it.effect("invalidates workspace-derived metadata when a project moves", () => |
| 700 | 695 | )).faviconPath, |
| 701 | 696 | "/work/original/favicon-v3.svg", |
| 702 | 697 | ); |
| 703 | }).pipe(Effect.provide(makeTestLayer(versionedMetadataLayer))); | |
| 698 | }).pipe(Effect.provide(layerTestFor(layerVersionedMetadata))); | |
| 704 | 699 | }), |
| 705 | 700 | ); |
| 706 | 701 | |
| ... | ... | @@ -741,7 +736,7 @@ it.effect("serializes two projects claiming the same workspace root", () => |
| 741 | 736 | yield* Deferred.succeed(releaseFirst, undefined); |
| 742 | 737 | assert.equal((yield* Fiber.join(first)).id, "project:race:first"); |
| 743 | 738 | assert.equal((yield* Fiber.join(second))._tag, "ProjectConflictError"); |
| 744 | }).pipe(Effect.provide(ProjectServiceDependenciesLayer)), | |
| 739 | }).pipe(Effect.provide(layerProjectServiceDependencies)), | |
| 745 | 740 | ); |
| 746 | 741 | |
| 747 | 742 | it.effect("rejects an update that waited on the lock while its project was deleted", () => |
| ... | ... | @@ -801,5 +796,5 @@ it.effect("rejects an update that waited on the lock while its project was delet |
| 801 | 796 | Option.getOrThrow(yield* service.getById(projectId, { includeDeleted: true })).title, |
| 802 | 797 | "Update race", |
| 803 | 798 | ); |
| 804 | }).pipe(Effect.provide(ProjectServiceDependenciesLayer)), | |
| 799 | }).pipe(Effect.provide(layerProjectServiceDependencies)), | |
| 805 | 800 | ); |
apps/server/src/project/RepositoryIdentityResolver.test.ts+11-11| ... | ... | @@ -26,7 +26,7 @@ const git = (cwd: string, args: ReadonlyArray<string>) => |
| 26 | 26 | }); |
| 27 | 27 | }).pipe(Effect.provide(ProcessRunner.layer)); |
| 28 | 28 | |
| 29 | const makeRepositoryIdentityResolverTestLayer = (options: { | |
| 29 | const layerRepositoryIdentityResolverTest = (options: { | |
| 30 | 30 | readonly positiveCacheTtl?: Duration.Input; |
| 31 | 31 | readonly negativeCacheTtl?: Duration.Input; |
| 32 | 32 | }) => |
| ... | ... | @@ -45,7 +45,7 @@ it.layer(NodeServices.layer)("RepositoryIdentityResolverLive", (it) => { |
| 45 | 45 | let remoteUrl = "git@github.com:T3Tools/t3code.git"; |
| 46 | 46 | let refinements = 0; |
| 47 | 47 | let refinementFails = false; |
| 48 | const processRunner = Layer.succeed(ProcessRunner.ProcessRunner, { | |
| 48 | const layerProcessRunner = Layer.succeed(ProcessRunner.ProcessRunner, { | |
| 49 | 49 | run: (input) => |
| 50 | 50 | Effect.sync(() => { |
| 51 | 51 | calls.push(input.args); |
| ... | ... | @@ -63,7 +63,7 @@ it.layer(NodeServices.layer)("RepositoryIdentityResolverLive", (it) => { |
| 63 | 63 | }; |
| 64 | 64 | }), |
| 65 | 65 | }); |
| 66 | const resolverLayer = Layer.effect( | |
| 66 | const layerResolver = Layer.effect( | |
| 67 | 67 | RepositoryIdentityResolver.RepositoryIdentityResolver, |
| 68 | 68 | RepositoryIdentityResolver.make({ |
| 69 | 69 | refine: (identity) => { |
| ... | ... | @@ -88,7 +88,7 @@ it.layer(NodeServices.layer)("RepositoryIdentityResolverLive", (it) => { |
| 88 | 88 | ); |
| 89 | 89 | }, |
| 90 | 90 | }), |
| 91 | ).pipe(Layer.provide(processRunner)); | |
| 91 | ).pipe(Layer.provide(layerProcessRunner)); | |
| 92 | 92 | |
| 93 | 93 | return Effect.gen(function* () { |
| 94 | 94 | const resolver = yield* RepositoryIdentityResolver.RepositoryIdentityResolver; |
| ... | ... | @@ -125,13 +125,13 @@ it.layer(NodeServices.layer)("RepositoryIdentityResolverLive", (it) => { |
| 125 | 125 | const unavailable = yield* resolver.resolve(rootPath, { refresh: true }); |
| 126 | 126 | expect(unavailable?.webUrl).toBeUndefined(); |
| 127 | 127 | expect(unavailable?.canonicalKey).toBe("ssh.forge.test/team/repo"); |
| 128 | }).pipe(Effect.provide(Layer.merge(TestClock.layer(), resolverLayer))); | |
| 128 | }).pipe(Effect.provide(Layer.merge(TestClock.layer(), layerResolver))); | |
| 129 | 129 | }); |
| 130 | 130 | |
| 131 | 131 | it.effect("retries Git root discovery after the negative TTL", () => { |
| 132 | 132 | const calls: Array<ReadonlyArray<string>> = []; |
| 133 | 133 | let rootAttempts = 0; |
| 134 | const processRunner = Layer.succeed(ProcessRunner.ProcessRunner, { | |
| 134 | const layerProcessRunner = Layer.succeed(ProcessRunner.ProcessRunner, { | |
| 135 | 135 | run: (input) => |
| 136 | 136 | Effect.sync(() => { |
| 137 | 137 | calls.push(input.args); |
| ... | ... | @@ -153,10 +153,10 @@ it.layer(NodeServices.layer)("RepositoryIdentityResolverLive", (it) => { |
| 153 | 153 | }; |
| 154 | 154 | }), |
| 155 | 155 | }); |
| 156 | const resolverLayer = Layer.effect( | |
| 156 | const layerResolver = Layer.effect( | |
| 157 | 157 | RepositoryIdentityResolver.RepositoryIdentityResolver, |
| 158 | 158 | RepositoryIdentityResolver.make(), |
| 159 | ).pipe(Layer.provide(processRunner)); | |
| 159 | ).pipe(Layer.provide(layerProcessRunner)); | |
| 160 | 160 | |
| 161 | 161 | return Effect.gen(function* () { |
| 162 | 162 | const resolver = yield* RepositoryIdentityResolver.RepositoryIdentityResolver; |
| ... | ... | @@ -171,7 +171,7 @@ it.layer(NodeServices.layer)("RepositoryIdentityResolverLive", (it) => { |
| 171 | 171 | ["-C", "/repo/packages/web", "rev-parse", "--show-toplevel"], |
| 172 | 172 | ["-C", "/repo", "remote", "-v"], |
| 173 | 173 | ]); |
| 174 | }).pipe(Effect.provide(Layer.merge(TestClock.layer(), resolverLayer))); | |
| 174 | }).pipe(Effect.provide(Layer.merge(TestClock.layer(), layerResolver))); | |
| 175 | 175 | }); |
| 176 | 176 | |
| 177 | 177 | it.effect("normalizes equivalent GitHub remotes into a stable repository identity", () => |
| ... | ... | @@ -341,7 +341,7 @@ it.layer(NodeServices.layer)("RepositoryIdentityResolverLive", (it) => { |
| 341 | 341 | Effect.provide( |
| 342 | 342 | Layer.merge( |
| 343 | 343 | TestClock.layer(), |
| 344 | makeRepositoryIdentityResolverTestLayer({ | |
| 344 | layerRepositoryIdentityResolverTest({ | |
| 345 | 345 | negativeCacheTtl: Duration.millis(50), |
| 346 | 346 | positiveCacheTtl: Duration.seconds(1), |
| 347 | 347 | }), |
| ... | ... | @@ -382,7 +382,7 @@ it.layer(NodeServices.layer)("RepositoryIdentityResolverLive", (it) => { |
| 382 | 382 | Effect.provide( |
| 383 | 383 | Layer.merge( |
| 384 | 384 | TestClock.layer(), |
| 385 | makeRepositoryIdentityResolverTestLayer({ | |
| 385 | layerRepositoryIdentityResolverTest({ | |
| 386 | 386 | negativeCacheTtl: Duration.millis(50), |
| 387 | 387 | positiveCacheTtl: Duration.millis(100), |
| 388 | 388 | }), |
apps/server/src/project/T3ProjectFileLoader.test.ts+2-2| ... | ... | @@ -8,7 +8,7 @@ import * as Path from "effect/Path"; |
| 8 | 8 | |
| 9 | 9 | import * as T3ProjectFileLoader from "./T3ProjectFileLoader.ts"; |
| 10 | 10 | |
| 11 | const TestLayer = Layer.empty.pipe( | |
| 11 | const layerTest = Layer.empty.pipe( | |
| 12 | 12 | Layer.provideMerge(T3ProjectFileLoader.layer), |
| 13 | 13 | Layer.provideMerge(NodeServices.layer), |
| 14 | 14 | ); |
| ... | ... | @@ -26,7 +26,7 @@ const writeProjectFile = Effect.fn("writeProjectFile")(function* (cwd: string, c |
| 26 | 26 | yield* fileSystem.writeFileString(path.join(cwd, "t3.json"), contents).pipe(Effect.orDie); |
| 27 | 27 | }); |
| 28 | 28 | |
| 29 | it.layer(TestLayer)("T3ProjectFileLoader", (it) => { | |
| 29 | it.layer(layerTest)("T3ProjectFileLoader", (it) => { | |
| 30 | 30 | describe("load", () => { |
| 31 | 31 | it.effect("loads and decodes a valid t3.json", () => |
| 32 | 32 | Effect.gen(function* () { |
apps/server/src/project/http.ts+1-1| ... | ... | @@ -29,7 +29,7 @@ export const failProjectMutation = Effect.fn("environment.projects.failMutation" |
| 29 | 29 | return yield* failEnvironmentInternal("project_mutation_failed", cause); |
| 30 | 30 | }); |
| 31 | 31 | |
| 32 | export const projectHttpApiLayer = HttpApiBuilder.group( | |
| 32 | export const layer = HttpApiBuilder.group( | |
| 33 | 33 | EnvironmentHttpApi, |
| 34 | 34 | "projects", |
| 35 | 35 | Effect.fnUntraced(function* (handlers) { |
apps/server/src/provider/CodexManagedRuntime.test.ts+2-2| ... | ... | @@ -86,7 +86,7 @@ it.effect.each( |
| 86 | 86 | source, |
| 87 | 87 | version: "0.156.1", |
| 88 | 88 | }; |
| 89 | const installerLayer = Layer.mock(CodexInstallation.CodexInstallation)({ | |
| 89 | const layerInstaller = Layer.mock(CodexInstallation.CodexInstallation)({ | |
| 90 | 90 | managedDirectory: "/isolated/tools/codex", |
| 91 | 91 | resolve: () => Effect.succeed(executable), |
| 92 | 92 | acquire: () => |
| ... | ... | @@ -183,7 +183,7 @@ it.effect.each( |
| 183 | 183 | EnvironmentId.make("00000000-0000-4000-8000-000000000001"), |
| 184 | 184 | ), |
| 185 | 185 | }), |
| 186 | Effect.provide(installerLayer), | |
| 186 | Effect.provide(layerInstaller), | |
| 187 | 187 | Effect.provideService( |
| 188 | 188 | HttpClient.HttpClient, |
| 189 | 189 | HttpClient.make((request) => |
apps/server/src/provider/Drivers/AntigravityDriver.test.ts+4-4| ... | ... | @@ -127,7 +127,7 @@ const makeHarness = Effect.fn("makeAntigravityDriverHarness")(function* ( |
| 127 | 127 | handle: ChildProcessSpawner.ChildProcessHandle; |
| 128 | 128 | }> = []; |
| 129 | 129 | |
| 130 | const installation = Layer.mock(AntigravityInstallation.AntigravityInstallation)({ | |
| 130 | const layerInstallation = Layer.mock(AntigravityInstallation.AntigravityInstallation)({ | |
| 131 | 131 | managedDirectory: root, |
| 132 | 132 | resolve: () => |
| 133 | 133 | Effect.gen(function* () { |
| ... | ... | @@ -203,7 +203,7 @@ const makeHarness = Effect.fn("makeAntigravityDriverHarness")(function* ( |
| 203 | 203 | { name: "BROWSER", value: "must-not-run" }, |
| 204 | 204 | ].map((variable) => ({ ...variable, sensitive: false })), |
| 205 | 205 | }).pipe( |
| 206 | Effect.provide(installation), | |
| 206 | Effect.provide(layerInstallation), | |
| 207 | 207 | Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, observedSpawner), |
| 208 | 208 | ); |
| 209 | 209 | const refresh = instance.refreshModels; |
| ... | ... | @@ -244,7 +244,7 @@ const makeHarness = Effect.fn("makeAntigravityDriverHarness")(function* ( |
| 244 | 244 | }; |
| 245 | 245 | }); |
| 246 | 246 | |
| 247 | const testLayer = ServerConfig.layerTest(process.cwd(), { | |
| 247 | const layerTest = ServerConfig.layerTest(process.cwd(), { | |
| 248 | 248 | prefix: "t3-antigravity-driver-config-", |
| 249 | 249 | }).pipe( |
| 250 | 250 | Layer.provideMerge(NodeServices.layer), |
| ... | ... | @@ -264,7 +264,7 @@ const testLayer = ServerConfig.layerTest(process.cwd(), { |
| 264 | 264 | Layer.provideMerge(IdAllocator.layer), |
| 265 | 265 | ); |
| 266 | 266 | |
| 267 | it.layer(testLayer)("AntigravityDriver", (it) => { | |
| 267 | it.layer(layerTest)("AntigravityDriver", (it) => { | |
| 268 | 268 | it.effect.skipIf(windowsHost)( |
| 269 | 269 | "preserves the Node install message when starting a standalone provider", |
| 270 | 270 | () => |
apps/server/src/provider/Drivers/CodexDriver.test.ts+2-2| ... | ... | @@ -39,7 +39,7 @@ import * as IdAllocator from "../../orchestration-v2/IdAllocator.ts"; |
| 39 | 39 | import { ProviderAdapterV2RuntimePolicy } from "../../orchestration-v2/ProviderAdapter.ts"; |
| 40 | 40 | import * as ProviderCredentialStore from "../ProviderCredentialStore.ts"; |
| 41 | 41 | |
| 42 | const testLayer = ServerConfig.layerTest(process.cwd(), { | |
| 42 | const layerTest = ServerConfig.layerTest(process.cwd(), { | |
| 43 | 43 | prefix: "t3-codex-driver-maintenance-", |
| 44 | 44 | }).pipe( |
| 45 | 45 | Layer.provideMerge(NodeServices.layer), |
| ... | ... | @@ -90,7 +90,7 @@ const noSpawn = ChildProcessSpawner.make(() => |
| 90 | 90 | ); |
| 91 | 91 | const encodeCredentials = Schema.encodeEffect(Schema.fromJsonString(Schema.Unknown)); |
| 92 | 92 | |
| 93 | it.layer(testLayer)("CodexDriver", (it) => { | |
| 93 | it.layer(layerTest)("CodexDriver", (it) => { | |
| 94 | 94 | it.effect("disconnect refreshes a restored managed account while its auth flow is idle", () => |
| 95 | 95 | Effect.gen(function* () { |
| 96 | 96 | const instanceId = ProviderInstanceId.make("restored-managed-account"); |
apps/server/src/provider/Drivers/CursorDriver.test.ts+2-2| ... | ... | @@ -21,7 +21,7 @@ import * as IdAllocator from "../../orchestration-v2/IdAllocator.ts"; |
| 21 | 21 | import { ProviderAdapterV2RuntimePolicy } from "../../orchestration-v2/ProviderAdapter.ts"; |
| 22 | 22 | import { Cursor } from "../cursorSdk.ts"; |
| 23 | 23 | |
| 24 | const testLayer = ServerSecretStore.layer.pipe( | |
| 24 | const layerTest = ServerSecretStore.layer.pipe( | |
| 25 | 25 | Layer.provideMerge( |
| 26 | 26 | ServerConfig.layerTest(process.cwd(), { |
| 27 | 27 | prefix: "t3-cursor-driver-copy-command-", |
| ... | ... | @@ -54,7 +54,7 @@ const testLayer = ServerSecretStore.layer.pipe( |
| 54 | 54 | ), |
| 55 | 55 | ); |
| 56 | 56 | |
| 57 | it.layer(testLayer)("CursorDriver", (it) => { | |
| 57 | it.layer(layerTest)("CursorDriver", (it) => { | |
| 58 | 58 | it.effect( |
| 59 | 59 | "persists browser credentials, uses them for chat, and closes the SDK session on logout", |
| 60 | 60 | () => |
apps/server/src/provider/Drivers/CursorDriver.ts+1-1| ... | ... | @@ -231,7 +231,7 @@ export const CursorDriver: ProviderDriver<CursorSettings, CursorDriverEnv> = { |
| 231 | 231 | Effect.provideService(FileSystem.FileSystem, fileSystem), |
| 232 | 232 | Effect.provideService(Path.Path, path), |
| 233 | 233 | Effect.map(stampSnapshot), |
| 234 | Effect.provide(CursorSdkCatalog.CursorSdkCatalogLive), | |
| 234 | Effect.provide(CursorSdkCatalog.layer), | |
| 235 | 235 | ); |
| 236 | 236 | |
| 237 | 237 | const snapshotSettings = makeProviderSnapshotSettingsSource(effectiveConfig, serverSettings); |
apps/server/src/provider/Drivers/GrokDriver.test.ts+2-2| ... | ... | @@ -17,7 +17,7 @@ import { GrokDriver } from "./GrokDriver.ts"; |
| 17 | 17 | |
| 18 | 18 | import * as IdAllocator from "../../orchestration-v2/IdAllocator.ts"; |
| 19 | 19 | |
| 20 | const testLayer = ServerConfig.layerTest(process.cwd(), { | |
| 20 | const layerTest = ServerConfig.layerTest(process.cwd(), { | |
| 21 | 21 | prefix: "t3-grok-driver-update-", |
| 22 | 22 | }).pipe( |
| 23 | 23 | Layer.provideMerge(NodeServices.layer), |
| ... | ... | @@ -49,7 +49,7 @@ const noSpawner = ChildProcessSpawner.make(() => |
| 49 | 49 | // The `#!/bin/sh` stub below cannot be resolved as an executable on Windows. |
| 50 | 50 | const windowsHost = HostProcessPlatform.defaultValue() === "win32"; |
| 51 | 51 | |
| 52 | it.layer(testLayer)("GrokDriver", (it) => { | |
| 52 | it.layer(layerTest)("GrokDriver", (it) => { | |
| 53 | 53 | it.effect.skipIf(windowsHost)("updates through the configured executable's own updater", () => |
| 54 | 54 | Effect.gen(function* () { |
| 55 | 55 | const fs = yield* FileSystem.FileSystem; |
apps/server/src/provider/Drivers/OpenCodeDriver.test.ts+2-2| ... | ... | @@ -211,7 +211,7 @@ const changingRuntime = { |
| 211 | 211 | startOpenCodeServerProcess: () => reachedServer("start"), |
| 212 | 212 | connectToOpenCodeServer: () => reachedServer("connect"), |
| 213 | 213 | } as unknown as OpenCodeRuntime.OpenCodeRuntimeShape; |
| 214 | const updateLayer = Layer.mergeAll( | |
| 214 | const layerUpdate = Layer.mergeAll( | |
| 215 | 215 | ServerConfig.layerTest(process.cwd(), { prefix: "t3-opencode-driver-update-" }), |
| 216 | 216 | IdAllocator.layer, |
| 217 | 217 | ServerSettings.layerTest(), |
| ... | ... | @@ -223,7 +223,7 @@ const updateLayer = Layer.mergeAll( |
| 223 | 223 | Layer.succeed(OpenCodeRuntime.OpenCodeRuntime, changingRuntime), |
| 224 | 224 | ).pipe(Layer.provideMerge(NodeServices.layer)); |
| 225 | 225 | |
| 226 | it.layer(updateLayer)("OpenCodeDriver updates", (it) => { | |
| 226 | it.layer(layerUpdate)("OpenCodeDriver updates", (it) => { | |
| 227 | 227 | it.effect("never runs the binary for a disabled instance's update check", () => |
| 228 | 228 | Effect.gen(function* () { |
| 229 | 229 | versionProbes.length = 0; |
apps/server/src/provider/Layers/AcpRegistryCatalog.ts+1-1| ... | ... | @@ -7,7 +7,7 @@ import * as AcpRegistrySupport from "../acp/AcpRegistrySupport.ts"; |
| 7 | 7 | import * as AcpRegistryRuntimeCoordinator from "../acp/AcpRegistryRuntimeCoordinator.ts"; |
| 8 | 8 | |
| 9 | 9 | /** Server-lifetime ACP Registry catalog shared by setup, snapshots, and turn launch. */ |
| 10 | export const AcpRegistryCatalogLive = Layer.merge( | |
| 10 | export const layer = Layer.merge( | |
| 11 | 11 | Layer.unwrap( |
| 12 | 12 | Effect.gen(function* () { |
| 13 | 13 | const config = yield* ServerConfig.ServerConfig; |
apps/server/src/provider/Layers/AntigravityProvider.test.ts+2-2| ... | ... | @@ -106,7 +106,7 @@ const expectedCommands = [ |
| 106 | 106 | { name: "logout", description: "Sign out of Google" }, |
| 107 | 107 | ]; |
| 108 | 108 | |
| 109 | const testLayer = Layer.merge( | |
| 109 | const layerTest = Layer.merge( | |
| 110 | 110 | Layer.mock(BackgroundPolicy.BackgroundPolicy)({ |
| 111 | 111 | shouldRunScopeWork: () => Effect.succeed(false), |
| 112 | 112 | }), |
| ... | ... | @@ -216,7 +216,7 @@ describe("Antigravity model catalog", () => { |
| 216 | 216 | }); |
| 217 | 217 | }); |
| 218 | 218 | |
| 219 | it.layer(testLayer)("Antigravity provider snapshots", (it) => { | |
| 219 | it.layer(layerTest)("Antigravity provider snapshots", (it) => { | |
| 220 | 220 | it.effect("does not probe or run helper safety checks while disabled", () => |
| 221 | 221 | Effect.scoped( |
| 222 | 222 | Effect.gen(function* () { |
apps/server/src/provider/Layers/CursorProvider.test.ts+3-3| ... | ... | @@ -176,7 +176,7 @@ describe("checkCursorProviderStatus", () => { |
| 176 | 176 | { CURSOR_API_KEY: "test-cursor-key" }, |
| 177 | 177 | ).pipe( |
| 178 | 178 | Effect.provide( |
| 179 | CursorSdkCatalog.makeCursorSdkCatalogTestLayer((apiKey) => { | |
| 179 | CursorSdkCatalog.layerTest((apiKey) => { | |
| 180 | 180 | expect(apiKey).toBe("test-cursor-key"); |
| 181 | 181 | return Effect.succeed({ |
| 182 | 182 | user: { |
| ... | ... | @@ -212,7 +212,7 @@ describe("checkCursorProviderStatus", () => { |
| 212 | 212 | CURSOR_API_KEY: "invalid-test-key", |
| 213 | 213 | }).pipe( |
| 214 | 214 | Effect.provide( |
| 215 | CursorSdkCatalog.makeCursorSdkCatalogTestLayer(() => | |
| 215 | CursorSdkCatalog.layerTest(() => | |
| 216 | 216 | Effect.fail( |
| 217 | 217 | new CursorSdkCatalog.CursorSdkCatalogError({ |
| 218 | 218 | authenticationFailure: true, |
| ... | ... | @@ -235,7 +235,7 @@ describe("checkCursorProviderStatus", () => { |
| 235 | 235 | Effect.gen(function* () { |
| 236 | 236 | const provider = yield* checkCursorProviderStatus(baseCursorSettings).pipe( |
| 237 | 237 | Effect.provide( |
| 238 | CursorSdkCatalog.makeCursorSdkCatalogTestLayer(() => | |
| 238 | CursorSdkCatalog.layerTest(() => | |
| 239 | 239 | Effect.die("SDK catalog must not be used without CURSOR_API_KEY"), |
| 240 | 240 | ), |
| 241 | 241 | ), |
apps/server/src/provider/Layers/CursorSdkCatalog.ts+2-4| ... | ... | @@ -85,10 +85,8 @@ export const makeCursorSdkCatalog = Effect.fn("CursorSdkCatalog.make")(function* |
| 85 | 85 | }); |
| 86 | 86 | }); |
| 87 | 87 | |
| 88 | export const CursorSdkCatalogLive = Layer.effect(CursorSdkCatalog, makeCursorSdkCatalog()); | |
| 88 | export const layer = Layer.effect(CursorSdkCatalog, makeCursorSdkCatalog()); | |
| 89 | 89 | |
| 90 | export function makeCursorSdkCatalogTestLayer( | |
| 91 | read: CursorSdkCatalogShape["read"], | |
| 92 | ): Layer.Layer<CursorSdkCatalog> { | |
| 90 | export function layerTest(read: CursorSdkCatalogShape["read"]): Layer.Layer<CursorSdkCatalog> { | |
| 93 | 91 | return Layer.succeed(CursorSdkCatalog, CursorSdkCatalog.of({ read })); |
| 94 | 92 | } |
apps/server/src/provider/Layers/OpenCodeProvider.test.ts+3-3| ... | ... | @@ -314,7 +314,7 @@ it("keeps native and MCP commands while preserving compaction and separate skill |
| 314 | 314 | ); |
| 315 | 315 | }); |
| 316 | 316 | |
| 317 | const testLayer = Layer.succeed(OpenCodeRuntime.OpenCodeRuntime, OpenCodeRuntimeTestDouble).pipe( | |
| 317 | const layerTest = Layer.succeed(OpenCodeRuntime.OpenCodeRuntime, OpenCodeRuntimeTestDouble).pipe( | |
| 318 | 318 | Layer.provideMerge(ServerConfig.layerTest(process.cwd(), process.cwd())), |
| 319 | 319 | Layer.provideMerge(NodeServices.layer), |
| 320 | 320 | ); |
| ... | ... | @@ -358,7 +358,7 @@ const checkProvider = Effect.fn("checkProvider")(function* ( |
| 358 | 358 | ); |
| 359 | 359 | }); |
| 360 | 360 | |
| 361 | it.layer(testLayer)("checkOpenCodeProviderStatus", (it) => { | |
| 361 | it.layer(layerTest)("checkOpenCodeProviderStatus", (it) => { | |
| 362 | 362 | it.effect("shows a codex-style missing binary message", () => |
| 363 | 363 | Effect.gen(function* () { |
| 364 | 364 | runtimeMock.state.runVersionError = new Error("spawn opencode ENOENT"); |
| ... | ... | @@ -657,7 +657,7 @@ it.layer(testLayer)("checkOpenCodeProviderStatus", (it) => { |
| 657 | 657 | ); |
| 658 | 658 | }); |
| 659 | 659 | |
| 660 | it.layer(testLayer)("checkOpenCodeProviderStatus with configured server URL", (it) => { | |
| 660 | it.layer(layerTest)("checkOpenCodeProviderStatus with configured server URL", (it) => { | |
| 661 | 661 | it.effect("does not send a local environment password to a configured server", () => |
| 662 | 662 | Effect.gen(function* () { |
| 663 | 663 | const snapshot = yield* checkProvider( |
apps/server/src/provider/Layers/ProviderAuthService.ts+1-4| ... | ... | @@ -250,7 +250,4 @@ export const makeProviderAuthService = Effect.gen(function* () { |
| 250 | 250 | }); |
| 251 | 251 | }); |
| 252 | 252 | |
| 253 | export const ProviderAuthServiceLive = Layer.effect( | |
| 254 | ProviderAuthService.ProviderAuthService, | |
| 255 | makeProviderAuthService, | |
| 256 | ); | |
| 253 | export const layer = Layer.effect(ProviderAuthService.ProviderAuthService, makeProviderAuthService); |
apps/server/src/provider/Layers/ProviderInstanceRegistryHydration.ts+12-14| ... | ... | @@ -29,7 +29,7 @@ |
| 29 | 29 | * ---------- |
| 30 | 30 | * On layer build we: |
| 31 | 31 | * 1. Read the current `ServerSettings` once and use it to seed the |
| 32 | * registry's initial state via `ProviderInstanceRegistryMutableLayer`. | |
| 32 | * registry's initial state via `ProviderInstanceRegistryLive.layer`. | |
| 33 | 33 | * 2. Fork a daemon fiber (lifetime tied to the layer's scope) that |
| 34 | 34 | * acquires `ServerSettingsService.subscribeChanges` and calls |
| 35 | 35 | * `ProviderInstanceRegistryMutator.reconcile` on every emission. |
| ... | ... | @@ -55,13 +55,11 @@ import * as Settings from "../../serverSettings.ts"; |
| 55 | 55 | import { BUILT_IN_DRIVERS, type BuiltInDriversEnv } from "../builtInDrivers.ts"; |
| 56 | 56 | import * as ProviderInstanceRegistry from "../Services/ProviderInstanceRegistry.ts"; |
| 57 | 57 | import * as ProviderInstanceRegistryMutator from "../Services/ProviderInstanceRegistryMutator.ts"; |
| 58 | import { ProviderInstanceRegistryMutableLayer } from "./ProviderInstanceRegistryLive.ts"; | |
| 59 | import { | |
| 60 | type ProviderOrchestrationAdapterInfrastructure, | |
| 61 | ProviderOrchestrationAdapterInfrastructureLive, | |
| 62 | } from "./ProviderOrchestrationAdapterInfrastructure.ts"; | |
| 58 | import * as ProviderInstanceRegistryLive from "./ProviderInstanceRegistryLive.ts"; | |
| 59 | import { type ProviderOrchestrationAdapterInfrastructure } from "./ProviderOrchestrationAdapterInfrastructure.ts"; | |
| 60 | import * as ProviderOrchestrationAdapterInfrastructureLayer from "./ProviderOrchestrationAdapterInfrastructure.ts"; | |
| 63 | 61 | import * as AcpRegistrySupport from "../acp/AcpRegistrySupport.ts"; |
| 64 | import { AcpRegistryCatalogLive } from "./AcpRegistryCatalog.ts"; | |
| 62 | import * as AcpRegistryCatalogLayer from "./AcpRegistryCatalog.ts"; | |
| 65 | 63 | |
| 66 | 64 | type ProviderInstanceRegistryHydrationEnv = |
| 67 | 65 | | Exclude< |
| ... | ... | @@ -127,7 +125,7 @@ export const deriveProviderInstanceConfigMap = ( |
| 127 | 125 | * configs, so the only way the watcher could fail is a settings stream |
| 128 | 126 | * tear-down, which logs and exits cleanly. |
| 129 | 127 | */ |
| 130 | const SettingsWatcherLive = Layer.effectDiscard( | |
| 128 | const layerSettingsWatcher = Layer.effectDiscard( | |
| 131 | 129 | Effect.gen(function* () { |
| 132 | 130 | const mutator = yield* ProviderInstanceRegistryMutator.ProviderInstanceRegistryMutator; |
| 133 | 131 | const serverSettings = yield* Settings.ServerSettingsService; |
| ... | ... | @@ -152,7 +150,7 @@ const SettingsWatcherLive = Layer.effectDiscard( |
| 152 | 150 | * sync with subsequent `streamChanges` emissions. |
| 153 | 151 | * |
| 154 | 152 | * The Layer's two halves: |
| 155 | * - `ProviderInstanceRegistryMutableLayer` produces the registry + | |
| 153 | * - `ProviderInstanceRegistryLive.layer` produces the registry + | |
| 156 | 154 | * mutator from the initial config map. Its scope owns every |
| 157 | 155 | * per-instance child scope created during reconcile. |
| 158 | 156 | * - `SettingsWatcherLive` consumes the mutator, acquires its settings |
| ... | ... | @@ -163,7 +161,7 @@ const SettingsWatcherLive = Layer.effectDiscard( |
| 163 | 161 | * The mutator tag is technically also exposed; only this module imports |
| 164 | 162 | * it, so the visibility leak is harmless in practice. |
| 165 | 163 | */ |
| 166 | export const ProviderInstanceRegistryHydrationLive: Layer.Layer< | |
| 164 | export const layer: Layer.Layer< | |
| 167 | 165 | ProviderInstanceRegistry.ProviderInstanceRegistry, |
| 168 | 166 | never, |
| 169 | 167 | ProviderInstanceRegistryHydrationEnv |
| ... | ... | @@ -178,15 +176,15 @@ export const ProviderInstanceRegistryHydrationLive: Layer.Layer< |
| 178 | 176 | ? ({} as ProviderInstanceConfigMap) |
| 179 | 177 | : deriveProviderInstanceConfigMap(initialSettings); |
| 180 | 178 | |
| 181 | const mutableLayer = ProviderInstanceRegistryMutableLayer({ | |
| 179 | const layerMutable = ProviderInstanceRegistryLive.layer({ | |
| 182 | 180 | drivers: BUILT_IN_DRIVERS, |
| 183 | 181 | configMap: initialConfigMap, |
| 184 | 182 | }).pipe( |
| 185 | Layer.provide(ProviderOrchestrationAdapterInfrastructureLive), | |
| 186 | Layer.provide(AcpRegistryCatalogLive), | |
| 183 | Layer.provide(ProviderOrchestrationAdapterInfrastructureLayer.layer), | |
| 184 | Layer.provide(AcpRegistryCatalogLayer.layer), | |
| 187 | 185 | ); |
| 188 | 186 | |
| 189 | return SettingsWatcherLive.pipe(Layer.provideMerge(mutableLayer)); | |
| 187 | return layerSettingsWatcher.pipe(Layer.provideMerge(layerMutable)); | |
| 190 | 188 | }), |
| 191 | 189 | ) as Layer.Layer< |
| 192 | 190 | ProviderInstanceRegistry.ProviderInstanceRegistry, |
apps/server/src/provider/Layers/ProviderInstanceRegistryLive.test.ts+22-22| ... | ... | @@ -63,9 +63,9 @@ import * as OpenCodeServerLedger from "../OpenCodeServerLedger.ts"; |
| 63 | 63 | import * as ResetCreditCoordinator from "./resetCreditCoordinator.ts"; |
| 64 | 64 | import * as ProviderEventLoggers from "./ProviderEventLoggers.ts"; |
| 65 | 65 | import { makeProviderInstanceRegistry } from "./ProviderInstanceRegistryLive.ts"; |
| 66 | import { ProviderOrchestrationAdapterInfrastructureLive } from "./ProviderOrchestrationAdapterInfrastructure.ts"; | |
| 66 | import * as ProviderOrchestrationAdapterInfrastructure from "./ProviderOrchestrationAdapterInfrastructure.ts"; | |
| 67 | 67 | |
| 68 | const TestHttpClientLive = Layer.succeed( | |
| 68 | const layerTestHttpClient = Layer.succeed( | |
| 69 | 69 | HttpClient.HttpClient, |
| 70 | 70 | HttpClient.make((request) => |
| 71 | 71 | Effect.succeed(HttpClientResponse.fromWeb(request, Response.json({ version: "0.0.0" }))), |
| ... | ... | @@ -74,7 +74,7 @@ const TestHttpClientLive = Layer.succeed( |
| 74 | 74 | |
| 75 | 75 | const TEST_EPOCH = DateTime.makeUnsafe("1970-01-01T00:00:00.000Z"); |
| 76 | 76 | |
| 77 | const BackgroundPolicyAlwaysRunLayer = Layer.mock(BackgroundPolicy.BackgroundPolicy)({ | |
| 77 | const layerBackgroundPolicyAlwaysRun = Layer.mock(BackgroundPolicy.BackgroundPolicy)({ | |
| 78 | 78 | reportClientActivity: () => Effect.void, |
| 79 | 79 | removeRpcClient: () => Effect.void, |
| 80 | 80 | reportHostPowerState: () => Effect.void, |
| ... | ... | @@ -201,7 +201,7 @@ describe("ProviderInstanceRegistryLive — multi-instance codex slice", () => { |
| 201 | 201 | // `NodeServices.layer` through `Layer.provideMerge` to satisfy that |
| 202 | 202 | // dependency while still surfacing NodeServices to the test body (the |
| 203 | 203 | // codex driver's `create` yields `ChildProcessSpawner` directly). |
| 204 | const baseLayer = ServerConfig.layerTest(process.cwd(), { | |
| 204 | const layerBase = ServerConfig.layerTest(process.cwd(), { | |
| 205 | 205 | prefix: "provider-instance-registry-test", |
| 206 | 206 | }).pipe( |
| 207 | 207 | Layer.provideMerge(NodeServices.layer), |
| ... | ... | @@ -218,9 +218,9 @@ describe("ProviderInstanceRegistryLive — multi-instance codex slice", () => { |
| 218 | 218 | ), |
| 219 | 219 | }), |
| 220 | 220 | ), |
| 221 | Layer.provideMerge(BackgroundPolicyAlwaysRunLayer), | |
| 221 | Layer.provideMerge(layerBackgroundPolicyAlwaysRun), | |
| 222 | 222 | Layer.provideMerge(ServerSettings.layerTest()), |
| 223 | Layer.provideMerge(TestHttpClientLive), | |
| 223 | Layer.provideMerge(layerTestHttpClient), | |
| 224 | 224 | Layer.provideMerge(ServerSettings.layerTest()), |
| 225 | 225 | Layer.provideMerge( |
| 226 | 226 | Layer.succeed( |
| ... | ... | @@ -231,8 +231,8 @@ describe("ProviderInstanceRegistryLive — multi-instance codex slice", () => { |
| 231 | 231 | Layer.provideMerge(ModelManifest.layerTest), |
| 232 | 232 | Layer.provideMerge(ResetCreditCoordinator.layerTest), |
| 233 | 233 | ); |
| 234 | const testLayer = ProviderOrchestrationAdapterInfrastructureLive.pipe( | |
| 235 | Layer.provideMerge(baseLayer), | |
| 234 | const layerTest = ProviderOrchestrationAdapterInfrastructure.layer.pipe( | |
| 235 | Layer.provideMerge(layerBase), | |
| 236 | 236 | ); |
| 237 | 237 | |
| 238 | 238 | it.live("boots two independent codex instances from a ProviderInstanceConfigMap", () => |
| ... | ... | @@ -310,7 +310,7 @@ describe("ProviderInstanceRegistryLive — multi-instance codex slice", () => { |
| 310 | 310 | // Nothing goes to the unavailable bucket — both drivers are registered. |
| 311 | 311 | const unavailable = yield* registry.listUnavailable; |
| 312 | 312 | expect(unavailable).toEqual([]); |
| 313 | }).pipe(Effect.provide(testLayer)), | |
| 313 | }).pipe(Effect.provide(layerTest)), | |
| 314 | 314 | ); |
| 315 | 315 | |
| 316 | 316 | it.live("treats an explicit in-config enabled:false as disabling despite the envelope", () => |
| ... | ... | @@ -336,7 +336,7 @@ describe("ProviderInstanceRegistryLive — multi-instance codex slice", () => { |
| 336 | 336 | expect(instance!.enabled).toBe(false); |
| 337 | 337 | const snapshot = yield* instance!.snapshot.getSnapshot; |
| 338 | 338 | expect(snapshot.enabled).toBe(false); |
| 339 | }).pipe(Effect.provide(testLayer)), | |
| 339 | }).pipe(Effect.provide(layerTest)), | |
| 340 | 340 | ); |
| 341 | 341 | |
| 342 | 342 | it.live("reports Codex's answer when a redemption changed nothing", () => |
| ... | ... | @@ -374,7 +374,7 @@ describe("ProviderInstanceRegistryLive — multi-instance codex slice", () => { |
| 374 | 374 | // The usage read fails, so the re-probe cannot confirm new limits. |
| 375 | 375 | yield* codex!.snapshot.refresh; |
| 376 | 376 | expect(yield* codex!.consumeResetCredit!()).toBe("alreadyRedeemed"); |
| 377 | }).pipe(Effect.provide(testLayer)), | |
| 377 | }).pipe(Effect.provide(layerTest)), | |
| 378 | 378 | ); |
| 379 | 379 | |
| 380 | 380 | it.live("runs Codex and Claude readiness probes from configured tilde paths", () => |
| ... | ... | @@ -428,7 +428,7 @@ describe("ProviderInstanceRegistryLive — multi-instance codex slice", () => { |
| 428 | 428 | installed: true, |
| 429 | 429 | version: "2.1.219", |
| 430 | 430 | }); |
| 431 | }).pipe(Effect.provide(testLayer)), | |
| 431 | }).pipe(Effect.provide(layerTest)), | |
| 432 | 432 | ); |
| 433 | 433 | |
| 434 | 434 | const redeemClaudeReset = (claim: { result: string; usageFailsAfterClaim: boolean }) => |
| ... | ... | @@ -497,7 +497,7 @@ describe("ProviderInstanceRegistryLive — multi-instance codex slice", () => { |
| 497 | 497 | }).pipe( |
| 498 | 498 | // macOS logins live in the Keychain, where resets are never read. |
| 499 | 499 | Effect.provideService(HostProcessPlatform, "linux"), |
| 500 | Effect.provide(testLayer), | |
| 500 | Effect.provide(layerTest), | |
| 501 | 501 | ); |
| 502 | 502 | |
| 503 | 503 | it.live("refreshes Claude usage after redeeming a reset", () => |
| ... | ... | @@ -558,7 +558,7 @@ describe("ProviderInstanceRegistryLive — multi-instance codex slice", () => { |
| 558 | 558 | expect(ghost.driver).toBe("ghostDriver"); |
| 559 | 559 | expect(ghost.availability).toBe("unavailable"); |
| 560 | 560 | expect(ghost.unavailableReason).toMatch(/ghostDriver/); |
| 561 | }).pipe(Effect.provide(testLayer)), | |
| 561 | }).pipe(Effect.provide(layerTest)), | |
| 562 | 562 | ); |
| 563 | 563 | }); |
| 564 | 564 | |
| ... | ... | @@ -575,7 +575,7 @@ describe("ProviderInstanceRegistryLive — all drivers slice", () => { |
| 575 | 575 | // provides `OpenCodeRuntimeLive`'s deps while keeping its own outputs |
| 576 | 576 | // surfaced; that merged layer then provides `ServerConfig.layerTest`'s |
| 577 | 577 | // `FileSystem` dep while keeping everything else surfaced to the test. |
| 578 | const infraLayer = OpenCodeRuntime.OpenCodeRuntimeLive.pipe( | |
| 578 | const layerInfra = OpenCodeRuntime.layer.pipe( | |
| 579 | 579 | Layer.provide(OpenCodeServerLedger.layerTest), |
| 580 | 580 | Layer.provideMerge(NodeServices.layer), |
| 581 | 581 | Layer.provideMerge( |
| ... | ... | @@ -591,17 +591,17 @@ describe("ProviderInstanceRegistryLive — all drivers slice", () => { |
| 591 | 591 | }), |
| 592 | 592 | ), |
| 593 | 593 | ); |
| 594 | const baseLayer = AntigravityInstallation.AntigravityInstallation.layer.pipe( | |
| 594 | const layerBase = AntigravityInstallation.AntigravityInstallation.layer.pipe( | |
| 595 | 595 | Layer.provideMerge(ServerSecretStore.layer), |
| 596 | 596 | Layer.provideMerge( |
| 597 | 597 | ServerConfig.layerTest(process.cwd(), { |
| 598 | 598 | prefix: "provider-instance-registry-all-drivers-test", |
| 599 | 599 | }), |
| 600 | 600 | ), |
| 601 | Layer.provideMerge(infraLayer), | |
| 602 | Layer.provideMerge(BackgroundPolicyAlwaysRunLayer), | |
| 601 | Layer.provideMerge(layerInfra), | |
| 602 | Layer.provideMerge(layerBackgroundPolicyAlwaysRun), | |
| 603 | 603 | Layer.provideMerge(ServerSettings.layerTest()), |
| 604 | Layer.provideMerge(TestHttpClientLive), | |
| 604 | Layer.provideMerge(layerTestHttpClient), | |
| 605 | 605 | Layer.provideMerge(ServerSettings.layerTest()), |
| 606 | 606 | Layer.provideMerge( |
| 607 | 607 | Layer.succeed( |
| ... | ... | @@ -612,8 +612,8 @@ describe("ProviderInstanceRegistryLive — all drivers slice", () => { |
| 612 | 612 | Layer.provideMerge(ModelManifest.layerTest), |
| 613 | 613 | Layer.provideMerge(ResetCreditCoordinator.layerTest), |
| 614 | 614 | ); |
| 615 | const testLayer = ProviderOrchestrationAdapterInfrastructureLive.pipe( | |
| 616 | Layer.provideMerge(baseLayer), | |
| 615 | const layerTest = ProviderOrchestrationAdapterInfrastructure.layer.pipe( | |
| 616 | Layer.provideMerge(layerBase), | |
| 617 | 617 | ); |
| 618 | 618 | |
| 619 | 619 | it.live("boots one instance of every shipped driver from a single config map", () => |
| ... | ... | @@ -776,6 +776,6 @@ describe("ProviderInstanceRegistryLive — all drivers slice", () => { |
| 776 | 776 | expect(openCodeSnapshot.continuation?.groupKey).toBe( |
| 777 | 777 | `${openCodeDriverKind}:instance:${openCodeId}`, |
| 778 | 778 | ); |
| 779 | }).pipe(Effect.provide(testLayer)), | |
| 779 | }).pipe(Effect.provide(layerTest)), | |
| 780 | 780 | ); |
| 781 | 781 | }); |
apps/server/src/provider/Layers/ProviderInstanceRegistryLive.ts+1-1| ... | ... | @@ -412,7 +412,7 @@ export const makeProviderInstanceRegistry = <R>(input: { |
| 412 | 412 | * changes. Tests that exercise the mutator directly can pair this Layer |
| 413 | 413 | * with a test-local `ServerSettingsService`. |
| 414 | 414 | */ |
| 415 | export const ProviderInstanceRegistryMutableLayer = <R>(input: { | |
| 415 | export const layer = <R>(input: { | |
| 416 | 416 | readonly drivers: ReadonlyArray<AnyProviderDriver<R>>; |
| 417 | 417 | readonly configMap: ProviderInstanceConfigMap; |
| 418 | 418 | }): Layer.Layer< |
apps/server/src/provider/Layers/ProviderOrchestrationAdapterInfrastructure.ts+4-4| ... | ... | @@ -18,10 +18,10 @@ export type ProviderOrchestrationAdapterInfrastructure = |
| 18 | 18 | * reference the orchestration runtime provides to its continuation worker so |
| 19 | 19 | * Effect layer memoization yields one shared queue. |
| 20 | 20 | */ |
| 21 | export const ProviderOrchestrationAdapterInfrastructureLive = Layer.mergeAll( | |
| 22 | ClaudeAdapterV2.claudeAgentSdkQueryRunnerLiveLayer, | |
| 23 | CodexAdapterV2.codexAppServerClientFactoryFromSettingsLayer, | |
| 24 | CursorAgentSdk.cursorAgentSdkRunnerLiveLayer, | |
| 21 | export const layer = Layer.mergeAll( | |
| 22 | ClaudeAdapterV2.layerQueryRunner, | |
| 23 | CodexAdapterV2.layerAppServerClientFactory, | |
| 24 | CursorAgentSdk.layer, | |
| 25 | 25 | IdAllocator.layer, |
| 26 | 26 | ProviderContinuationRequests.layer, |
| 27 | 27 | ); |
apps/server/src/provider/Layers/ProviderRegistry.test.ts+2704-2744| ... | ... | @@ -47,14 +47,14 @@ import * as ResetCreditCoordinator from "./resetCreditCoordinator.ts"; |
| 47 | 47 | import * as OpenCodeRuntime from "../opencodeRuntime.ts"; |
| 48 | 48 | import * as OpenCodeServerLedger from "../OpenCodeServerLedger.ts"; |
| 49 | 49 | import * as ProviderEventLoggers from "./ProviderEventLoggers.ts"; |
| 50 | import { ProviderInstanceRegistryHydrationLive } from "./ProviderInstanceRegistryHydration.ts"; | |
| 50 | import * as ProviderInstanceRegistryHydration from "./ProviderInstanceRegistryHydration.ts"; | |
| 51 | 51 | import { |
| 52 | 52 | mergeProviderSnapshot, |
| 53 | 53 | mergeProviderSnapshots, |
| 54 | 54 | selectProvidersByKind, |
| 55 | 55 | upsertProviderWorkspaceSnapshot, |
| 56 | ProviderRegistryLive, | |
| 57 | 56 | } from "./ProviderRegistry.ts"; |
| 57 | import * as ProviderRegistryLayer from "./ProviderRegistry.ts"; | |
| 58 | 58 | import * as ServerConfig from "../../config.ts"; |
| 59 | 59 | import * as ServerSettingsModule from "../../serverSettings.ts"; |
| 60 | 60 | import { |
| ... | ... | @@ -92,14 +92,14 @@ const withBundledCompatibility = (snapshot: ServerProvider) => |
| 92 | 92 | ); |
| 93 | 93 | |
| 94 | 94 | // Provider metadata checks use a bundled manifest and stubbed HTTP. |
| 95 | const TestHttpClientLive = Layer.succeed( | |
| 95 | const layerTestHttpClient = Layer.succeed( | |
| 96 | 96 | HttpClient.HttpClient, |
| 97 | 97 | HttpClient.make((request) => |
| 98 | 98 | Effect.succeed(HttpClientResponse.fromWeb(request, Response.json({ version: "0.0.0" }))), |
| 99 | 99 | ), |
| 100 | 100 | ).pipe(Layer.provideMerge(ModelManifest.layerTest)); |
| 101 | 101 | |
| 102 | const BackgroundPolicyAlwaysRunLayer = Layer.mock(BackgroundPolicy.BackgroundPolicy)({ | |
| 102 | const layerBackgroundPolicyAlwaysRun = Layer.mock(BackgroundPolicy.BackgroundPolicy)({ | |
| 103 | 103 | reportClientActivity: () => Effect.void, |
| 104 | 104 | removeRpcClient: () => Effect.void, |
| 105 | 105 | reportHostPowerState: () => Effect.void, |
| ... | ... | @@ -191,7 +191,7 @@ function mockHandle(result: { stdout: string; stderr: string; code: number }) { |
| 191 | 191 | }); |
| 192 | 192 | } |
| 193 | 193 | |
| 194 | function mockSpawnerLayer( | |
| 194 | function layerMockSpawner( | |
| 195 | 195 | handler: (args: ReadonlyArray<string>) => { |
| 196 | 196 | stdout: string; |
| 197 | 197 | stderr: string; |
| ... | ... | @@ -234,7 +234,7 @@ function recordingMockSpawnerLayer( |
| 234 | 234 | return { layer, commands }; |
| 235 | 235 | } |
| 236 | 236 | |
| 237 | function mockCommandSpawnerLayer( | |
| 237 | function layerMockCommandSpawner( | |
| 238 | 238 | handler: ( |
| 239 | 239 | command: string, |
| 240 | 240 | args: ReadonlyArray<string>, |
| ... | ... | @@ -252,7 +252,7 @@ function mockCommandSpawnerLayer( |
| 252 | 252 | ); |
| 253 | 253 | } |
| 254 | 254 | |
| 255 | function failingSpawnerLayer(description: string) { | |
| 255 | function layerFailingSpawner(description: string) { | |
| 256 | 256 | return Layer.succeed( |
| 257 | 257 | ChildProcessSpawner.ChildProcessSpawner, |
| 258 | 258 | ChildProcessSpawner.make(() => |
| ... | ... | @@ -268,7 +268,7 @@ function failingSpawnerLayer(description: string) { |
| 268 | 268 | ); |
| 269 | 269 | } |
| 270 | 270 | |
| 271 | function hangingScopedSpawnerLayer(killCalls: Ref.Ref<number>) { | |
| 271 | function layerHangingScopedSpawner(killCalls: Ref.Ref<number>) { | |
| 272 | 272 | return Layer.succeed( |
| 273 | 273 | ChildProcessSpawner.ChildProcessSpawner, |
| 274 | 274 | ChildProcessSpawner.make(() => |
| ... | ... | @@ -389,7 +389,7 @@ const awaitPersistedProvider = ( |
| 389 | 389 | Effect.forkScoped, |
| 390 | 390 | ); |
| 391 | 391 | |
| 392 | const TestNodeServices = Layer.mergeAll( | |
| 392 | const layerTestNodeServices = Layer.mergeAll( | |
| 393 | 393 | NodeServices.layer, |
| 394 | 394 | Layer.mock(CodexInstallation.CodexInstallation)({ |
| 395 | 395 | managedDirectory: "unused-managed-installation", |
| ... | ... | @@ -400,1714 +400,737 @@ const TestNodeServices = Layer.mergeAll( |
| 400 | 400 | }), |
| 401 | 401 | ); |
| 402 | 402 | |
| 403 | it.layer(Layer.mergeAll(TestNodeServices, ServerSettingsModule.layerTest(), TestHttpClientLive))( | |
| 404 | "ProviderRegistry", | |
| 405 | (it) => { | |
| 406 | describe("checkCodexProviderStatus", () => { | |
| 407 | it.effect("uses the app-server account and model list for provider status", () => | |
| 408 | Effect.gen(function* () { | |
| 409 | const status = yield* checkCodexProviderStatus(defaultCodexSettings, () => | |
| 410 | Effect.succeed( | |
| 411 | makeCodexProbeSnapshot({ | |
| 412 | skills: [ | |
| 413 | { | |
| 414 | name: "github:gh-fix-ci", | |
| 415 | path: "/Users/test/.codex/skills/gh-fix-ci/SKILL.md", | |
| 416 | enabled: true, | |
| 417 | displayName: "CI Debug", | |
| 418 | shortDescription: "Debug failing GitHub Actions checks", | |
| 419 | }, | |
| 420 | ], | |
| 421 | }), | |
| 422 | ), | |
| 423 | ); | |
| 424 | assert.strictEqual(status.status, "ready"); | |
| 425 | assert.strictEqual(status.installed, true); | |
| 426 | assert.strictEqual(status.version, "1.0.0"); | |
| 427 | assert.strictEqual(status.auth.status, "authenticated"); | |
| 428 | assert.strictEqual(status.auth.type, "chatgpt"); | |
| 429 | assert.strictEqual(status.auth.label, "ChatGPT Pro 20x Subscription"); | |
| 430 | assert.strictEqual(status.auth.email, "test@example.com"); | |
| 431 | assert.deepStrictEqual(status.models, [ | |
| 432 | { | |
| 433 | slug: "gpt-live-codex", | |
| 434 | name: "GPT Live Codex", | |
| 435 | isCustom: false, | |
| 436 | capabilities: codexModelCapabilities, | |
| 437 | }, | |
| 438 | ]); | |
| 439 | assert.deepStrictEqual(status.skills, [ | |
| 440 | { | |
| 441 | name: "github:gh-fix-ci", | |
| 442 | path: "/Users/test/.codex/skills/gh-fix-ci/SKILL.md", | |
| 443 | enabled: true, | |
| 444 | displayName: "CI Debug", | |
| 445 | shortDescription: "Debug failing GitHub Actions checks", | |
| 446 | }, | |
| 447 | ]); | |
| 448 | assert.deepStrictEqual(status.slashCommands.slice(1), [ | |
| 449 | { | |
| 450 | name: "goal", | |
| 451 | description: "Set a goal Codex keeps working toward until it is done", | |
| 452 | input: { hint: "Objective, or pause, resume, clear" }, | |
| 453 | }, | |
| 454 | { | |
| 455 | name: "feedback", | |
| 456 | description: "Send this thread and Codex logs to OpenAI", | |
| 457 | input: { hint: "Describe the issue (optional)" }, | |
| 458 | }, | |
| 459 | ]); | |
| 460 | }), | |
| 461 | ); | |
| 403 | it.layer( | |
| 404 | Layer.mergeAll(layerTestNodeServices, ServerSettingsModule.layerTest(), layerTestHttpClient), | |
| 405 | )("ProviderRegistry", (it) => { | |
| 406 | describe("checkCodexProviderStatus", () => { | |
| 407 | it.effect("uses the app-server account and model list for provider status", () => | |
| 408 | Effect.gen(function* () { | |
| 409 | const status = yield* checkCodexProviderStatus(defaultCodexSettings, () => | |
| 410 | Effect.succeed( | |
| 411 | makeCodexProbeSnapshot({ | |
| 412 | skills: [ | |
| 413 | { | |
| 414 | name: "github:gh-fix-ci", | |
| 415 | path: "/Users/test/.codex/skills/gh-fix-ci/SKILL.md", | |
| 416 | enabled: true, | |
| 417 | displayName: "CI Debug", | |
| 418 | shortDescription: "Debug failing GitHub Actions checks", | |
| 419 | }, | |
| 420 | ], | |
| 421 | }), | |
| 422 | ), | |
| 423 | ); | |
| 424 | assert.strictEqual(status.status, "ready"); | |
| 425 | assert.strictEqual(status.installed, true); | |
| 426 | assert.strictEqual(status.version, "1.0.0"); | |
| 427 | assert.strictEqual(status.auth.status, "authenticated"); | |
| 428 | assert.strictEqual(status.auth.type, "chatgpt"); | |
| 429 | assert.strictEqual(status.auth.label, "ChatGPT Pro 20x Subscription"); | |
| 430 | assert.strictEqual(status.auth.email, "test@example.com"); | |
| 431 | assert.deepStrictEqual(status.models, [ | |
| 432 | { | |
| 433 | slug: "gpt-live-codex", | |
| 434 | name: "GPT Live Codex", | |
| 435 | isCustom: false, | |
| 436 | capabilities: codexModelCapabilities, | |
| 437 | }, | |
| 438 | ]); | |
| 439 | assert.deepStrictEqual(status.skills, [ | |
| 440 | { | |
| 441 | name: "github:gh-fix-ci", | |
| 442 | path: "/Users/test/.codex/skills/gh-fix-ci/SKILL.md", | |
| 443 | enabled: true, | |
| 444 | displayName: "CI Debug", | |
| 445 | shortDescription: "Debug failing GitHub Actions checks", | |
| 446 | }, | |
| 447 | ]); | |
| 448 | assert.deepStrictEqual(status.slashCommands.slice(1), [ | |
| 449 | { | |
| 450 | name: "goal", | |
| 451 | description: "Set a goal Codex keeps working toward until it is done", | |
| 452 | input: { hint: "Objective, or pause, resume, clear" }, | |
| 453 | }, | |
| 454 | { | |
| 455 | name: "feedback", | |
| 456 | description: "Send this thread and Codex logs to OpenAI", | |
| 457 | input: { hint: "Describe the issue (optional)" }, | |
| 458 | }, | |
| 459 | ]); | |
| 460 | }), | |
| 461 | ); | |
| 462 | 462 | |
| 463 | it.effect("passes configured launch args to the Codex provider probe", () => | |
| 464 | Effect.gen(function* () { | |
| 465 | let observedLaunchArgs: string | undefined; | |
| 466 | const settings = decodeCodexSettings({ launchArgs: "--strict-config --enable foo" }); | |
| 463 | it.effect("passes configured launch args to the Codex provider probe", () => | |
| 464 | Effect.gen(function* () { | |
| 465 | let observedLaunchArgs: string | undefined; | |
| 466 | const settings = decodeCodexSettings({ launchArgs: "--strict-config --enable foo" }); | |
| 467 | 467 | |
| 468 | const status = yield* checkCodexProviderStatus(settings, (input) => { | |
| 469 | observedLaunchArgs = input.launchArgs; | |
| 470 | return Effect.succeed(makeCodexProbeSnapshot()); | |
| 471 | }); | |
| 468 | const status = yield* checkCodexProviderStatus(settings, (input) => { | |
| 469 | observedLaunchArgs = input.launchArgs; | |
| 470 | return Effect.succeed(makeCodexProbeSnapshot()); | |
| 471 | }); | |
| 472 | 472 | |
| 473 | assert.strictEqual(status.status, "ready"); | |
| 474 | assert.strictEqual(observedLaunchArgs, "--strict-config --enable foo"); | |
| 475 | }), | |
| 476 | ); | |
| 473 | assert.strictEqual(status.status, "ready"); | |
| 474 | assert.strictEqual(observedLaunchArgs, "--strict-config --enable foo"); | |
| 475 | }), | |
| 476 | ); | |
| 477 | 477 | |
| 478 | it.effect("returns unauthenticated when app-server requires OpenAI auth", () => | |
| 479 | Effect.gen(function* () { | |
| 480 | const status = yield* checkCodexProviderStatus(defaultCodexSettings, () => | |
| 481 | Effect.succeed( | |
| 482 | makeCodexProbeSnapshot({ | |
| 483 | account: { | |
| 484 | account: null, | |
| 485 | requiresOpenaiAuth: true, | |
| 486 | }, | |
| 487 | }), | |
| 488 | ), | |
| 489 | ); | |
| 478 | it.effect("returns unauthenticated when app-server requires OpenAI auth", () => | |
| 479 | Effect.gen(function* () { | |
| 480 | const status = yield* checkCodexProviderStatus(defaultCodexSettings, () => | |
| 481 | Effect.succeed( | |
| 482 | makeCodexProbeSnapshot({ | |
| 483 | account: { | |
| 484 | account: null, | |
| 485 | requiresOpenaiAuth: true, | |
| 486 | }, | |
| 487 | }), | |
| 488 | ), | |
| 489 | ); | |
| 490 | 490 | |
| 491 | assert.strictEqual(status.status, "error"); | |
| 492 | assert.strictEqual(status.auth.status, "unauthenticated"); | |
| 493 | assert.strictEqual( | |
| 494 | status.message, | |
| 495 | "Codex CLI is not authenticated. Run `codex login` and try again.", | |
| 496 | ); | |
| 497 | }), | |
| 498 | ); | |
| 491 | assert.strictEqual(status.status, "error"); | |
| 492 | assert.strictEqual(status.auth.status, "unauthenticated"); | |
| 493 | assert.strictEqual( | |
| 494 | status.message, | |
| 495 | "Codex CLI is not authenticated. Run `codex login` and try again.", | |
| 496 | ); | |
| 497 | }), | |
| 498 | ); | |
| 499 | 499 | |
| 500 | it.effect( | |
| 501 | "returns ready with unknown auth when app-server does not require OpenAI auth", | |
| 502 | () => | |
| 503 | Effect.gen(function* () { | |
| 504 | const status = yield* checkCodexProviderStatus(defaultCodexSettings, () => | |
| 505 | Effect.succeed( | |
| 506 | makeCodexProbeSnapshot({ | |
| 507 | account: { | |
| 508 | account: null, | |
| 509 | requiresOpenaiAuth: false, | |
| 510 | }, | |
| 511 | }), | |
| 512 | ), | |
| 513 | ); | |
| 500 | it.effect("returns ready with unknown auth when app-server does not require OpenAI auth", () => | |
| 501 | Effect.gen(function* () { | |
| 502 | const status = yield* checkCodexProviderStatus(defaultCodexSettings, () => | |
| 503 | Effect.succeed( | |
| 504 | makeCodexProbeSnapshot({ | |
| 505 | account: { | |
| 506 | account: null, | |
| 507 | requiresOpenaiAuth: false, | |
| 508 | }, | |
| 509 | }), | |
| 510 | ), | |
| 511 | ); | |
| 514 | 512 | |
| 515 | assert.strictEqual(status.status, "ready"); | |
| 516 | assert.strictEqual(status.auth.status, "unknown"); | |
| 517 | }), | |
| 518 | ); | |
| 513 | assert.strictEqual(status.status, "ready"); | |
| 514 | assert.strictEqual(status.auth.status, "unknown"); | |
| 515 | }), | |
| 516 | ); | |
| 519 | 517 | |
| 520 | it.effect("returns an api key label for codex api key auth", () => | |
| 521 | Effect.gen(function* () { | |
| 522 | const status = yield* checkCodexProviderStatus(defaultCodexSettings, () => | |
| 523 | Effect.succeed( | |
| 524 | makeCodexProbeSnapshot({ | |
| 525 | account: { | |
| 526 | account: { type: "apiKey" }, | |
| 527 | requiresOpenaiAuth: false, | |
| 528 | }, | |
| 529 | }), | |
| 530 | ), | |
| 531 | ); | |
| 518 | it.effect("returns an api key label for codex api key auth", () => | |
| 519 | Effect.gen(function* () { | |
| 520 | const status = yield* checkCodexProviderStatus(defaultCodexSettings, () => | |
| 521 | Effect.succeed( | |
| 522 | makeCodexProbeSnapshot({ | |
| 523 | account: { | |
| 524 | account: { type: "apiKey" }, | |
| 525 | requiresOpenaiAuth: false, | |
| 526 | }, | |
| 527 | }), | |
| 528 | ), | |
| 529 | ); | |
| 532 | 530 | |
| 533 | assert.strictEqual(status.status, "ready"); | |
| 534 | assert.strictEqual(status.auth.status, "authenticated"); | |
| 535 | assert.strictEqual(status.auth.type, "apiKey"); | |
| 536 | assert.strictEqual(status.auth.label, "OpenAI API Key"); | |
| 537 | }), | |
| 538 | ); | |
| 531 | assert.strictEqual(status.status, "ready"); | |
| 532 | assert.strictEqual(status.auth.status, "authenticated"); | |
| 533 | assert.strictEqual(status.auth.type, "apiKey"); | |
| 534 | assert.strictEqual(status.auth.label, "OpenAI API Key"); | |
| 535 | }), | |
| 536 | ); | |
| 539 | 537 | |
| 540 | it.effect("returns an Amazon Bedrock label for codex Bedrock auth", () => | |
| 541 | Effect.gen(function* () { | |
| 542 | const status = yield* checkCodexProviderStatus(defaultCodexSettings, () => | |
| 543 | Effect.succeed( | |
| 544 | makeCodexProbeSnapshot({ | |
| 545 | account: { | |
| 546 | account: { type: "amazonBedrock" }, | |
| 547 | requiresOpenaiAuth: false, | |
| 548 | }, | |
| 549 | }), | |
| 550 | ), | |
| 538 | it.effect("returns an Amazon Bedrock label for codex Bedrock auth", () => | |
| 539 | Effect.gen(function* () { | |
| 540 | const status = yield* checkCodexProviderStatus(defaultCodexSettings, () => | |
| 541 | Effect.succeed( | |
| 542 | makeCodexProbeSnapshot({ | |
| 543 | account: { | |
| 544 | account: { type: "amazonBedrock" }, | |
| 545 | requiresOpenaiAuth: false, | |
| 546 | }, | |
| 547 | }), | |
| 548 | ), | |
| 549 | ); | |
| 550 | ||
| 551 | assert.strictEqual(status.status, "ready"); | |
| 552 | assert.strictEqual(status.auth.status, "authenticated"); | |
| 553 | assert.strictEqual(status.auth.type, "amazonBedrock"); | |
| 554 | assert.strictEqual(status.auth.label, "Amazon Bedrock"); | |
| 555 | }), | |
| 556 | ); | |
| 557 | ||
| 558 | it.effect.each([ | |
| 559 | "codex", | |
| 560 | "/Applications/Custom App.app/Contents/Resources/codex", | |
| 561 | "C:\\Tools\\codex.exe", | |
| 562 | ])("explains how to configure a Codex executable that cannot start: %s", (binaryPath) => | |
| 563 | Effect.gen(function* () { | |
| 564 | const settings = { ...defaultCodexSettings, binaryPath }; | |
| 565 | const status = yield* checkCodexProviderStatus(settings, (input) => { | |
| 566 | assert.strictEqual(input.binaryPath, binaryPath); | |
| 567 | return Effect.fail( | |
| 568 | new CodexErrors.CodexAppServerSpawnError({ | |
| 569 | command: `${binaryPath} app-server`, | |
| 570 | cause: new Error("spawn ENOENT"), | |
| 571 | }), | |
| 551 | 572 | ); |
| 573 | }); | |
| 574 | assert.strictEqual(status.status, "error"); | |
| 575 | assert.strictEqual(status.installed, false); | |
| 576 | assert.strictEqual(status.auth.status, "unknown"); | |
| 577 | assert.include(status.message, binaryPath); | |
| 578 | assert.include(status.message, "Settings → Providers → Codex → Binary path on the server"); | |
| 579 | assert.strictEqual( | |
| 580 | status.message?.includes("Installing ChatGPT or Codex desktop"), | |
| 581 | binaryPath === "codex", | |
| 582 | ); | |
| 583 | assert.strictEqual(settings.binaryPath, binaryPath); | |
| 584 | }), | |
| 585 | ); | |
| 552 | 586 | |
| 553 | assert.strictEqual(status.status, "ready"); | |
| 554 | assert.strictEqual(status.auth.status, "authenticated"); | |
| 555 | assert.strictEqual(status.auth.type, "amazonBedrock"); | |
| 556 | assert.strictEqual(status.auth.label, "Amazon Bedrock"); | |
| 557 | }), | |
| 587 | it.effect("closes the app-server probe scope when provider status times out", () => | |
| 588 | Effect.gen(function* () { | |
| 589 | const killCalls = yield* Ref.make(0); | |
| 590 | const statusFiber = yield* checkCodexProviderStatus(defaultCodexSettings).pipe( | |
| 591 | Effect.provide(layerHangingScopedSpawner(killCalls)), | |
| 592 | Effect.forkChild, | |
| 593 | ); | |
| 594 | ||
| 595 | yield* Effect.yieldNow; | |
| 596 | yield* TestClock.adjust("11 seconds"); | |
| 597 | yield* Effect.yieldNow; | |
| 598 | ||
| 599 | const status = yield* Fiber.join(statusFiber); | |
| 600 | assert.strictEqual(status.status, "error"); | |
| 601 | assert.strictEqual( | |
| 602 | status.message, | |
| 603 | "Timed out while checking Codex app-server provider status.", | |
| 604 | ); | |
| 605 | assert.strictEqual(yield* Ref.get(killCalls), 1); | |
| 606 | }), | |
| 607 | ); | |
| 608 | }); | |
| 609 | ||
| 610 | describe("ProviderRegistryLive", () => { | |
| 611 | it("stores workspace skills and commands without changing machine metadata", () => { | |
| 612 | const provider = { | |
| 613 | instanceId: ProviderInstanceId.make("codex"), | |
| 614 | driver: ProviderDriverKind.make("codex"), | |
| 615 | status: "ready", | |
| 616 | enabled: true, | |
| 617 | installed: true, | |
| 618 | auth: { status: "authenticated" }, | |
| 619 | checkedAt: "2026-03-25T00:00:00.000Z", | |
| 620 | version: "1.0.0", | |
| 621 | models: [], | |
| 622 | slashCommands: [{ name: "global" }], | |
| 623 | skills: [{ name: "global", path: "/global/SKILL.md", enabled: true }], | |
| 624 | } satisfies ServerProvider; | |
| 625 | const scopedSnapshot = { | |
| 626 | ...provider, | |
| 627 | checkedAt: "2026-03-25T00:01:00.000Z", | |
| 628 | slashCommands: [{ name: "project" }], | |
| 629 | skills: [{ name: "project", path: "/project/SKILL.md", enabled: true }], | |
| 630 | } satisfies ServerProvider; | |
| 631 | ||
| 632 | const result = upsertProviderWorkspaceSnapshot(provider, "/project", scopedSnapshot); | |
| 633 | ||
| 634 | assert.deepStrictEqual(result.slashCommands, provider.slashCommands); | |
| 635 | assert.deepStrictEqual(result.skills, provider.skills); | |
| 636 | assert.deepStrictEqual(result.workspaceSnapshots, [ | |
| 637 | { | |
| 638 | cwd: "/project", | |
| 639 | checkedAt: scopedSnapshot.checkedAt, | |
| 640 | slashCommands: scopedSnapshot.slashCommands, | |
| 641 | skills: scopedSnapshot.skills, | |
| 642 | }, | |
| 643 | ]); | |
| 644 | ||
| 645 | const pendingSnapshot = { | |
| 646 | ...scopedSnapshot, | |
| 647 | slashCommands: [COMPACT_SLASH_COMMAND], | |
| 648 | slashCommandsPending: true, | |
| 649 | } satisfies ProviderWorkspaceSnapshot; | |
| 650 | const partial = upsertProviderWorkspaceSnapshot(result, "/project", pendingSnapshot); | |
| 651 | assert.deepStrictEqual(partial.workspaceSnapshots?.[0]?.slashCommands, [{ name: "project" }]); | |
| 652 | assert.strictEqual(partial.workspaceSnapshots?.[0]?.slashCommandsPending, true); | |
| 653 | const otherProject = upsertProviderWorkspaceSnapshot( | |
| 654 | partial, | |
| 655 | "/other-project", | |
| 656 | pendingSnapshot, | |
| 558 | 657 | ); |
| 658 | assert.deepStrictEqual(otherProject.workspaceSnapshots?.[1]?.slashCommands, [ | |
| 659 | COMPACT_SLASH_COMMAND, | |
| 660 | ]); | |
| 661 | const recovered = upsertProviderWorkspaceSnapshot(partial, "/project", { | |
| 662 | ...scopedSnapshot, | |
| 663 | slashCommands: [COMPACT_SLASH_COMMAND, { name: "replacement" }], | |
| 664 | }); | |
| 665 | assert.deepStrictEqual(recovered.workspaceSnapshots?.[0]?.slashCommands, [ | |
| 666 | COMPACT_SLASH_COMMAND, | |
| 667 | { name: "replacement" }, | |
| 668 | ]); | |
| 669 | assert.strictEqual(recovered.workspaceSnapshots?.[0]?.slashCommandsPending, undefined); | |
| 670 | }); | |
| 559 | 671 | |
| 560 | it.effect.each([ | |
| 561 | "codex", | |
| 562 | "/Applications/Custom App.app/Contents/Resources/codex", | |
| 563 | "C:\\Tools\\codex.exe", | |
| 564 | ])("explains how to configure a Codex executable that cannot start: %s", (binaryPath) => | |
| 565 | Effect.gen(function* () { | |
| 566 | const settings = { ...defaultCodexSettings, binaryPath }; | |
| 567 | const status = yield* checkCodexProviderStatus(settings, (input) => { | |
| 568 | assert.strictEqual(input.binaryPath, binaryPath); | |
| 569 | return Effect.fail( | |
| 570 | new CodexErrors.CodexAppServerSpawnError({ | |
| 571 | command: `${binaryPath} app-server`, | |
| 572 | cause: new Error("spawn ENOENT"), | |
| 573 | }), | |
| 574 | ); | |
| 575 | }); | |
| 576 | assert.strictEqual(status.status, "error"); | |
| 577 | assert.strictEqual(status.installed, false); | |
| 578 | assert.strictEqual(status.auth.status, "unknown"); | |
| 579 | assert.include(status.message, binaryPath); | |
| 580 | assert.include( | |
| 581 | status.message, | |
| 582 | "Settings → Providers → Codex → Binary path on the server", | |
| 583 | ); | |
| 584 | assert.strictEqual( | |
| 585 | status.message?.includes("Installing ChatGPT or Codex desktop"), | |
| 586 | binaryPath === "codex", | |
| 587 | ); | |
| 588 | assert.strictEqual(settings.binaryPath, binaryPath); | |
| 589 | }), | |
| 672 | it("preserves previously discovered provider models when a refresh returns none", () => { | |
| 673 | const previousProvider = { | |
| 674 | instanceId: ProviderInstanceId.make("cursor"), | |
| 675 | driver: ProviderDriverKind.make("cursor"), | |
| 676 | status: "ready", | |
| 677 | enabled: true, | |
| 678 | installed: true, | |
| 679 | auth: { status: "authenticated" }, | |
| 680 | checkedAt: "2026-04-14T00:00:00.000Z", | |
| 681 | version: "2026.04.09-f2b0fcd", | |
| 682 | models: [ | |
| 683 | { | |
| 684 | slug: "claude-opus-4-6", | |
| 685 | name: "Opus 4.6", | |
| 686 | isCustom: false, | |
| 687 | capabilities: createModelCapabilities({ | |
| 688 | optionDescriptors: [ | |
| 689 | selectDescriptor("reasoning", "Reasoning", [ | |
| 690 | { id: "high", label: "High", isDefault: true }, | |
| 691 | ]), | |
| 692 | booleanDescriptor("fastMode", "Fast Mode"), | |
| 693 | booleanDescriptor("thinking", "Thinking"), | |
| 694 | ], | |
| 695 | }), | |
| 696 | }, | |
| 697 | ], | |
| 698 | slashCommands: [{ name: "review", description: "Review changes" }], | |
| 699 | skills: [ | |
| 700 | { | |
| 701 | name: "typescript", | |
| 702 | description: "TypeScript help", | |
| 703 | path: "/skills/typescript/SKILL.md", | |
| 704 | enabled: true, | |
| 705 | }, | |
| 706 | ], | |
| 707 | } as const satisfies ServerProvider; | |
| 708 | const refreshedProvider = { | |
| 709 | ...previousProvider, | |
| 710 | checkedAt: "2026-04-14T00:01:00.000Z", | |
| 711 | models: [], | |
| 712 | slashCommands: [], | |
| 713 | skills: [], | |
| 714 | } satisfies ServerProvider; | |
| 715 | ||
| 716 | assert.deepStrictEqual(mergeProviderSnapshot(previousProvider, refreshedProvider).models, [ | |
| 717 | ...previousProvider.models, | |
| 718 | ]); | |
| 719 | assert.deepStrictEqual( | |
| 720 | mergeProviderSnapshot(previousProvider, refreshedProvider).slashCommands, | |
| 721 | [], | |
| 590 | 722 | ); |
| 723 | assert.deepStrictEqual(mergeProviderSnapshot(previousProvider, refreshedProvider).skills, []); | |
| 724 | }); | |
| 591 | 725 | |
| 592 | it.effect("closes the app-server probe scope when provider status times out", () => | |
| 593 | Effect.gen(function* () { | |
| 594 | const killCalls = yield* Ref.make(0); | |
| 595 | const statusFiber = yield* checkCodexProviderStatus(defaultCodexSettings).pipe( | |
| 596 | Effect.provide(hangingScopedSpawnerLayer(killCalls)), | |
| 597 | Effect.forkChild, | |
| 598 | ); | |
| 726 | it("drops custom models the refreshed snapshot no longer carries", () => { | |
| 727 | const previousProvider = { | |
| 728 | instanceId: ProviderInstanceId.make("claudeAgent"), | |
| 729 | driver: ProviderDriverKind.make("claudeAgent"), | |
| 730 | status: "ready", | |
| 731 | enabled: true, | |
| 732 | installed: true, | |
| 733 | auth: { status: "authenticated" }, | |
| 734 | checkedAt: "2026-04-14T00:00:00.000Z", | |
| 735 | version: "2.1.0", | |
| 736 | models: [ | |
| 737 | { | |
| 738 | slug: "claude-sonnet-4-6", | |
| 739 | name: "Sonnet 4.6", | |
| 740 | isCustom: false, | |
| 741 | capabilities: null, | |
| 742 | }, | |
| 743 | { | |
| 744 | slug: "removed-custom", | |
| 745 | name: "removed-custom", | |
| 746 | isCustom: true, | |
| 747 | capabilities: null, | |
| 748 | }, | |
| 749 | ], | |
| 750 | slashCommands: [], | |
| 751 | skills: [], | |
| 752 | } as const satisfies ServerProvider; | |
| 753 | const refreshedProvider = { | |
| 754 | ...previousProvider, | |
| 755 | checkedAt: "2026-04-14T00:01:00.000Z", | |
| 756 | models: [previousProvider.models[0]], | |
| 757 | } satisfies ServerProvider; | |
| 758 | ||
| 759 | assert.deepStrictEqual(mergeProviderSnapshot(previousProvider, refreshedProvider).models, [ | |
| 760 | ...refreshedProvider.models, | |
| 761 | ]); | |
| 762 | }); | |
| 599 | 763 | |
| 600 | yield* Effect.yieldNow; | |
| 601 | yield* TestClock.adjust("11 seconds"); | |
| 602 | yield* Effect.yieldNow; | |
| 764 | it("drops stale ACP Registry models missing from a completed discovery probe", () => { | |
| 765 | const previousProvider = { | |
| 766 | instanceId: ProviderInstanceId.make("acpRegistry_codex"), | |
| 767 | driver: ProviderDriverKind.make("acpRegistry"), | |
| 768 | status: "ready", | |
| 769 | enabled: true, | |
| 770 | installed: true, | |
| 771 | auth: { status: "authenticated" }, | |
| 772 | checkedAt: "2026-08-13T00:00:00.000Z", | |
| 773 | version: "1.2.0", | |
| 774 | models: [ | |
| 775 | { slug: "gpt-5.6-sol", name: "GPT-5.6-Sol", isCustom: false, capabilities: null }, | |
| 776 | { | |
| 777 | slug: "gpt-5.6-sol[low]", | |
| 778 | name: "GPT-5.6-Sol (low)", | |
| 779 | isCustom: false, | |
| 780 | capabilities: null, | |
| 781 | }, | |
| 782 | { slug: "default", name: "Default", isCustom: false, capabilities: null }, | |
| 783 | ], | |
| 784 | slashCommands: [], | |
| 785 | skills: [], | |
| 786 | } as const satisfies ServerProvider; | |
| 787 | const refreshedProvider = { | |
| 788 | ...previousProvider, | |
| 789 | checkedAt: "2026-08-13T00:01:00.000Z", | |
| 790 | models: [{ slug: "gpt-5.6-sol", name: "GPT-5.6-Sol", isCustom: false, capabilities: null }], | |
| 791 | } satisfies ServerProvider; | |
| 792 | ||
| 793 | assert.deepStrictEqual(mergeProviderSnapshot(previousProvider, refreshedProvider).models, [ | |
| 794 | ...refreshedProvider.models, | |
| 795 | ]); | |
| 796 | }); | |
| 603 | 797 | |
| 604 | const status = yield* Fiber.join(statusFiber); | |
| 605 | assert.strictEqual(status.status, "error"); | |
| 606 | assert.strictEqual( | |
| 607 | status.message, | |
| 608 | "Timed out while checking Codex app-server provider status.", | |
| 609 | ); | |
| 610 | assert.strictEqual(yield* Ref.get(killCalls), 1); | |
| 611 | }), | |
| 612 | ); | |
| 798 | it("retains ACP Registry models while discovery has not completed", () => { | |
| 799 | const previousProvider = { | |
| 800 | instanceId: ProviderInstanceId.make("acpRegistry_codex"), | |
| 801 | driver: ProviderDriverKind.make("acpRegistry"), | |
| 802 | status: "ready", | |
| 803 | enabled: true, | |
| 804 | installed: true, | |
| 805 | auth: { status: "authenticated" }, | |
| 806 | checkedAt: "2026-08-13T00:00:00.000Z", | |
| 807 | version: "1.2.0", | |
| 808 | models: [{ slug: "gpt-5.6-sol", name: "GPT-5.6-Sol", isCustom: false, capabilities: null }], | |
| 809 | slashCommands: [], | |
| 810 | skills: [], | |
| 811 | } as const satisfies ServerProvider; | |
| 812 | const checkingProvider = { | |
| 813 | ...previousProvider, | |
| 814 | checkedAt: "2026-08-13T00:01:00.000Z", | |
| 815 | auth: { status: "unknown" }, | |
| 816 | models: [{ slug: "default", name: "Default", isCustom: false, capabilities: null }], | |
| 817 | } satisfies ServerProvider; | |
| 818 | const failedProbeProvider = { | |
| 819 | ...checkingProvider, | |
| 820 | status: "warning", | |
| 821 | } satisfies ServerProvider; | |
| 822 | ||
| 823 | assert.deepStrictEqual(mergeProviderSnapshot(previousProvider, checkingProvider).models, [ | |
| 824 | { slug: "default", name: "Default", isCustom: false, capabilities: null }, | |
| 825 | { slug: "gpt-5.6-sol", name: "GPT-5.6-Sol", isCustom: false, capabilities: null }, | |
| 826 | ]); | |
| 827 | assert.deepStrictEqual(mergeProviderSnapshot(previousProvider, failedProbeProvider).models, [ | |
| 828 | { slug: "default", name: "Default", isCustom: false, capabilities: null }, | |
| 829 | { slug: "gpt-5.6-sol", name: "GPT-5.6-Sol", isCustom: false, capabilities: null }, | |
| 830 | ]); | |
| 613 | 831 | }); |
| 614 | 832 | |
| 615 | describe("ProviderRegistryLive", () => { | |
| 616 | it("stores workspace skills and commands without changing machine metadata", () => { | |
| 617 | const provider = { | |
| 618 | instanceId: ProviderInstanceId.make("codex"), | |
| 619 | driver: ProviderDriverKind.make("codex"), | |
| 620 | status: "ready", | |
| 621 | enabled: true, | |
| 622 | installed: true, | |
| 623 | auth: { status: "authenticated" }, | |
| 624 | checkedAt: "2026-03-25T00:00:00.000Z", | |
| 625 | version: "1.0.0", | |
| 626 | models: [], | |
| 627 | slashCommands: [{ name: "global" }], | |
| 628 | skills: [{ name: "global", path: "/global/SKILL.md", enabled: true }], | |
| 629 | } satisfies ServerProvider; | |
| 630 | const scopedSnapshot = { | |
| 631 | ...provider, | |
| 632 | checkedAt: "2026-03-25T00:01:00.000Z", | |
| 633 | slashCommands: [{ name: "project" }], | |
| 634 | skills: [{ name: "project", path: "/project/SKILL.md", enabled: true }], | |
| 635 | } satisfies ServerProvider; | |
| 833 | it("drops stale OpenCode models missing from a successful refresh", () => { | |
| 834 | const previousProvider = { | |
| 835 | instanceId: ProviderInstanceId.make("opencode"), | |
| 836 | driver: ProviderDriverKind.make("opencode"), | |
| 837 | status: "ready", | |
| 838 | enabled: true, | |
| 839 | installed: true, | |
| 840 | auth: { status: "authenticated" }, | |
| 841 | checkedAt: "2026-07-17T00:00:00.000Z", | |
| 842 | version: "1.0.0", | |
| 843 | models: [ | |
| 844 | { | |
| 845 | slug: "github/gpt-5", | |
| 846 | name: "GPT-5", | |
| 847 | subProvider: "GitHub", | |
| 848 | isCustom: false, | |
| 849 | capabilities: null, | |
| 850 | }, | |
| 851 | { | |
| 852 | slug: "removed-plugin/model", | |
| 853 | name: "Removed Plugin Model", | |
| 854 | subProvider: "Removed Plugin", | |
| 855 | isCustom: false, | |
| 856 | capabilities: null, | |
| 857 | }, | |
| 858 | ], | |
| 859 | slashCommands: [], | |
| 860 | skills: [], | |
| 861 | } as const satisfies ServerProvider; | |
| 862 | const refreshedProvider = { | |
| 863 | ...previousProvider, | |
| 864 | checkedAt: "2026-07-17T00:01:00.000Z", | |
| 865 | models: [ | |
| 866 | { | |
| 867 | slug: "github/gpt-5", | |
| 868 | name: "GPT-5", | |
| 869 | subProvider: "GitHub", | |
| 870 | isCustom: false, | |
| 871 | capabilities: null, | |
| 872 | }, | |
| 873 | ], | |
| 874 | } satisfies ServerProvider; | |
| 636 | 875 | |
| 637 | const result = upsertProviderWorkspaceSnapshot(provider, "/project", scopedSnapshot); | |
| 876 | assert.deepStrictEqual(mergeProviderSnapshot(previousProvider, refreshedProvider).models, [ | |
| 877 | ...refreshedProvider.models, | |
| 878 | ]); | |
| 879 | }); | |
| 638 | 880 | |
| 639 | assert.deepStrictEqual(result.slashCommands, provider.slashCommands); | |
| 640 | assert.deepStrictEqual(result.skills, provider.skills); | |
| 641 | assert.deepStrictEqual(result.workspaceSnapshots, [ | |
| 881 | it("retains stale OpenCode models when a refresh fails", () => { | |
| 882 | const previousProvider = { | |
| 883 | instanceId: ProviderInstanceId.make("opencode"), | |
| 884 | driver: ProviderDriverKind.make("opencode"), | |
| 885 | status: "ready", | |
| 886 | enabled: true, | |
| 887 | installed: true, | |
| 888 | auth: { status: "authenticated" }, | |
| 889 | checkedAt: "2026-07-17T00:00:00.000Z", | |
| 890 | version: "1.0.0", | |
| 891 | models: [ | |
| 642 | 892 | { |
| 643 | cwd: "/project", | |
| 644 | checkedAt: scopedSnapshot.checkedAt, | |
| 645 | slashCommands: scopedSnapshot.slashCommands, | |
| 646 | skills: scopedSnapshot.skills, | |
| 893 | slug: "github/gpt-5", | |
| 894 | name: "GPT-5", | |
| 895 | subProvider: "GitHub", | |
| 896 | isCustom: false, | |
| 897 | capabilities: null, | |
| 647 | 898 | }, |
| 648 | ]); | |
| 899 | ], | |
| 900 | slashCommands: [{ name: "review", description: "Review changes" }], | |
| 901 | skills: [ | |
| 902 | { | |
| 903 | name: "typescript", | |
| 904 | description: "TypeScript help", | |
| 905 | path: "/skills/typescript/SKILL.md", | |
| 906 | enabled: true, | |
| 907 | }, | |
| 908 | ], | |
| 909 | } as const satisfies ServerProvider; | |
| 910 | const refreshedProvider = { | |
| 911 | ...previousProvider, | |
| 912 | status: "error", | |
| 913 | auth: { status: "unknown" }, | |
| 914 | checkedAt: "2026-07-17T00:01:00.000Z", | |
| 915 | models: [], | |
| 916 | message: "Failed to refresh OpenCode models.", | |
| 917 | } satisfies ServerProvider; | |
| 918 | ||
| 919 | assert.deepStrictEqual(mergeProviderSnapshot(previousProvider, refreshedProvider).models, [ | |
| 920 | ...previousProvider.models, | |
| 921 | ]); | |
| 922 | assert.deepStrictEqual( | |
| 923 | mergeProviderSnapshot(previousProvider, refreshedProvider).slashCommands, | |
| 924 | previousProvider.slashCommands, | |
| 925 | ); | |
| 926 | assert.deepStrictEqual( | |
| 927 | mergeProviderSnapshot(previousProvider, refreshedProvider).skills, | |
| 928 | previousProvider.skills, | |
| 929 | ); | |
| 930 | }); | |
| 649 | 931 | |
| 650 | const pendingSnapshot = { | |
| 651 | ...scopedSnapshot, | |
| 652 | slashCommands: [COMPACT_SLASH_COMMAND], | |
| 653 | slashCommandsPending: true, | |
| 654 | } satisfies ProviderWorkspaceSnapshot; | |
| 655 | const partial = upsertProviderWorkspaceSnapshot(result, "/project", pendingSnapshot); | |
| 656 | assert.deepStrictEqual(partial.workspaceSnapshots?.[0]?.slashCommands, [ | |
| 657 | { name: "project" }, | |
| 658 | ]); | |
| 659 | assert.strictEqual(partial.workspaceSnapshots?.[0]?.slashCommandsPending, true); | |
| 660 | const otherProject = upsertProviderWorkspaceSnapshot( | |
| 661 | partial, | |
| 662 | "/other-project", | |
| 663 | pendingSnapshot, | |
| 664 | ); | |
| 665 | assert.deepStrictEqual(otherProject.workspaceSnapshots?.[1]?.slashCommands, [ | |
| 666 | COMPACT_SLASH_COMMAND, | |
| 667 | ]); | |
| 668 | const recovered = upsertProviderWorkspaceSnapshot(partial, "/project", { | |
| 669 | ...scopedSnapshot, | |
| 670 | slashCommands: [COMPACT_SLASH_COMMAND, { name: "replacement" }], | |
| 671 | }); | |
| 672 | assert.deepStrictEqual(recovered.workspaceSnapshots?.[0]?.slashCommands, [ | |
| 673 | COMPACT_SLASH_COMMAND, | |
| 674 | { name: "replacement" }, | |
| 675 | ]); | |
| 676 | assert.strictEqual(recovered.workspaceSnapshots?.[0]?.slashCommandsPending, undefined); | |
| 677 | }); | |
| 932 | it("classifies pending, logout, uninstall, and reconnect OpenCode inventories", () => { | |
| 933 | const previousProvider = { | |
| 934 | instanceId: ProviderInstanceId.make("opencode"), | |
| 935 | driver: ProviderDriverKind.make("opencode"), | |
| 936 | status: "ready", | |
| 937 | enabled: true, | |
| 938 | installed: true, | |
| 939 | auth: { status: "authenticated" }, | |
| 940 | checkedAt: "2026-07-17T00:00:00.000Z", | |
| 941 | version: "1.0.0", | |
| 942 | models: [ | |
| 943 | { | |
| 944 | slug: "github/gpt-5", | |
| 945 | name: "GPT-5", | |
| 946 | subProvider: "GitHub", | |
| 947 | isCustom: false, | |
| 948 | capabilities: null, | |
| 949 | }, | |
| 950 | { | |
| 951 | slug: "removed-plugin/model", | |
| 952 | name: "Removed Plugin Model", | |
| 953 | subProvider: "Removed Plugin", | |
| 954 | isCustom: false, | |
| 955 | capabilities: null, | |
| 956 | }, | |
| 957 | ], | |
| 958 | slashCommands: [{ name: "review", description: "Review changes" }], | |
| 959 | skills: [ | |
| 960 | { | |
| 961 | name: "typescript", | |
| 962 | description: "TypeScript help", | |
| 963 | path: "/skills/typescript/SKILL.md", | |
| 964 | enabled: true, | |
| 965 | }, | |
| 966 | ], | |
| 967 | } as const satisfies ServerProvider; | |
| 968 | const pendingProvider = { | |
| 969 | ...previousProvider, | |
| 970 | status: "warning", | |
| 971 | installed: false, | |
| 972 | auth: { status: "unknown" }, | |
| 973 | checkedAt: "2026-07-17T00:01:00.000Z", | |
| 974 | version: null, | |
| 975 | models: [], | |
| 976 | message: "OpenCode provider status has not been checked in this session yet.", | |
| 977 | } satisfies ServerProvider; | |
| 978 | const loggedOutProvider = { | |
| 979 | ...previousProvider, | |
| 980 | status: "warning", | |
| 981 | auth: { status: "unknown" }, | |
| 982 | checkedAt: "2026-07-17T00:02:00.000Z", | |
| 983 | models: [], | |
| 984 | slashCommands: [], | |
| 985 | skills: [], | |
| 986 | message: "OpenCode is available, but it did not report any connected upstream providers.", | |
| 987 | } satisfies ServerProvider; | |
| 988 | const missingProvider = { | |
| 989 | ...previousProvider, | |
| 990 | status: "error", | |
| 991 | installed: false, | |
| 992 | auth: { status: "unknown" }, | |
| 993 | checkedAt: "2026-07-17T00:03:00.000Z", | |
| 994 | version: null, | |
| 995 | models: [], | |
| 996 | message: "OpenCode CLI (`opencode`) is not installed or not on PATH.", | |
| 997 | } satisfies ServerProvider; | |
| 998 | const authoritativeProvider = { | |
| 999 | ...previousProvider, | |
| 1000 | checkedAt: "2026-07-17T00:04:00.000Z", | |
| 1001 | models: [previousProvider.models[0]!], | |
| 1002 | } satisfies ServerProvider; | |
| 1003 | const failedProvider = { | |
| 1004 | ...authoritativeProvider, | |
| 1005 | status: "error", | |
| 1006 | auth: { status: "unknown" }, | |
| 1007 | checkedAt: "2026-07-17T00:05:00.000Z", | |
| 1008 | models: [], | |
| 1009 | message: "Failed to refresh OpenCode models.", | |
| 1010 | } satisfies ServerProvider; | |
| 1011 | ||
| 1012 | assert.deepStrictEqual(mergeProviderSnapshot(previousProvider, pendingProvider).models, [ | |
| 1013 | ...previousProvider.models, | |
| 1014 | ]); | |
| 1015 | assert.deepStrictEqual(mergeProviderSnapshot(previousProvider, loggedOutProvider).models, []); | |
| 1016 | assert.deepStrictEqual( | |
| 1017 | mergeProviderSnapshot(previousProvider, loggedOutProvider).slashCommands, | |
| 1018 | [], | |
| 1019 | ); | |
| 1020 | assert.deepStrictEqual(mergeProviderSnapshot(previousProvider, loggedOutProvider).skills, []); | |
| 1021 | assert.deepStrictEqual(mergeProviderSnapshot(previousProvider, missingProvider).models, []); | |
| 678 | 1022 | |
| 679 | it("preserves previously discovered provider models when a refresh returns none", () => { | |
| 680 | const previousProvider = { | |
| 681 | instanceId: ProviderInstanceId.make("cursor"), | |
| 682 | driver: ProviderDriverKind.make("cursor"), | |
| 683 | status: "ready", | |
| 684 | enabled: true, | |
| 685 | installed: true, | |
| 686 | auth: { status: "authenticated" }, | |
| 687 | checkedAt: "2026-04-14T00:00:00.000Z", | |
| 688 | version: "2026.04.09-f2b0fcd", | |
| 689 | models: [ | |
| 690 | { | |
| 691 | slug: "claude-opus-4-6", | |
| 692 | name: "Opus 4.6", | |
| 693 | isCustom: false, | |
| 694 | capabilities: createModelCapabilities({ | |
| 695 | optionDescriptors: [ | |
| 696 | selectDescriptor("reasoning", "Reasoning", [ | |
| 697 | { id: "high", label: "High", isDefault: true }, | |
| 698 | ]), | |
| 699 | booleanDescriptor("fastMode", "Fast Mode"), | |
| 700 | booleanDescriptor("thinking", "Thinking"), | |
| 701 | ], | |
| 702 | }), | |
| 703 | }, | |
| 704 | ], | |
| 705 | slashCommands: [{ name: "review", description: "Review changes" }], | |
| 706 | skills: [ | |
| 707 | { | |
| 708 | name: "typescript", | |
| 709 | description: "TypeScript help", | |
| 710 | path: "/skills/typescript/SKILL.md", | |
| 711 | enabled: true, | |
| 712 | }, | |
| 713 | ], | |
| 714 | } as const satisfies ServerProvider; | |
| 715 | const refreshedProvider = { | |
| 716 | ...previousProvider, | |
| 717 | checkedAt: "2026-04-14T00:01:00.000Z", | |
| 718 | models: [], | |
| 719 | slashCommands: [], | |
| 720 | skills: [], | |
| 721 | } satisfies ServerProvider; | |
| 1023 | const afterRemoval = mergeProviderSnapshot(previousProvider, authoritativeProvider); | |
| 1024 | const afterFailure = mergeProviderSnapshot(afterRemoval, failedProvider); | |
| 722 | 1025 | |
| 723 | assert.deepStrictEqual(mergeProviderSnapshot(previousProvider, refreshedProvider).models, [ | |
| 724 | ...previousProvider.models, | |
| 725 | ]); | |
| 726 | assert.deepStrictEqual( | |
| 727 | mergeProviderSnapshot(previousProvider, refreshedProvider).slashCommands, | |
| 728 | [], | |
| 729 | ); | |
| 730 | assert.deepStrictEqual( | |
| 731 | mergeProviderSnapshot(previousProvider, refreshedProvider).skills, | |
| 732 | [], | |
| 733 | ); | |
| 1026 | assert.deepStrictEqual(afterFailure.models, [authoritativeProvider.models[0]!]); | |
| 1027 | }); | |
| 1028 | ||
| 1029 | describe("Codex model inventories", () => { | |
| 1030 | const cachedProvider = { | |
| 1031 | instanceId: ProviderInstanceId.make("codex-personal"), | |
| 1032 | driver: ProviderDriverKind.make("codex"), | |
| 1033 | status: "ready", | |
| 1034 | enabled: true, | |
| 1035 | installed: true, | |
| 1036 | auth: { status: "authenticated" }, | |
| 1037 | checkedAt: "2026-09-04T19:00:00.000Z", | |
| 1038 | version: "0.153.3", | |
| 1039 | models: ["vega-alpha", "joule-alpha", "kindle-alpha", "ultima-alpha", "solstice-alpha"].map( | |
| 1040 | (slug) => ({ slug, name: slug, isCustom: false, capabilities: null }), | |
| 1041 | ), | |
| 1042 | slashCommands: [], | |
| 1043 | skills: [], | |
| 1044 | } satisfies ServerProvider; | |
| 1045 | const customModel = { | |
| 1046 | slug: "custom-model", | |
| 1047 | name: "Custom model", | |
| 1048 | isCustom: true, | |
| 1049 | capabilities: null, | |
| 1050 | } as const; | |
| 1051 | const refreshedProvider = { | |
| 1052 | ...cachedProvider, | |
| 1053 | checkedAt: "2026-09-04T19:01:00.000Z", | |
| 1054 | models: [ | |
| 1055 | { slug: "gpt-6-astra", name: "GPT 6 Astra", isCustom: false, capabilities: null }, | |
| 1056 | cachedProvider.models[0]!, | |
| 1057 | customModel, | |
| 1058 | ], | |
| 1059 | } satisfies ServerProvider; | |
| 1060 | const pendingProvider = { | |
| 1061 | ...cachedProvider, | |
| 1062 | status: "warning", | |
| 1063 | installed: false, | |
| 1064 | auth: { status: "unknown" }, | |
| 1065 | models: [customModel], | |
| 1066 | } satisfies ServerProvider; | |
| 1067 | const failedProvider = { | |
| 1068 | ...pendingProvider, | |
| 1069 | checkedAt: "2026-09-04T19:02:00.000Z", | |
| 1070 | status: "error", | |
| 1071 | installed: true, | |
| 1072 | } satisfies ServerProvider; | |
| 1073 | ||
| 1074 | it("drops retired alpha models after discovery, including without OpenAI authentication", () => { | |
| 1075 | for (const authStatus of ["authenticated", "unknown"] as const) { | |
| 1076 | assert.deepStrictEqual( | |
| 1077 | mergeProviderSnapshot(cachedProvider, { | |
| 1078 | ...refreshedProvider, | |
| 1079 | auth: { status: authStatus }, | |
| 1080 | }).models, | |
| 1081 | refreshedProvider.models, | |
| 1082 | ); | |
| 1083 | } | |
| 734 | 1084 | }); |
| 735 | 1085 | |
| 736 | it("drops custom models the refreshed snapshot no longer carries", () => { | |
| 737 | const previousProvider = { | |
| 738 | instanceId: ProviderInstanceId.make("claudeAgent"), | |
| 739 | driver: ProviderDriverKind.make("claudeAgent"), | |
| 740 | status: "ready", | |
| 741 | enabled: true, | |
| 742 | installed: true, | |
| 743 | auth: { status: "authenticated" }, | |
| 744 | checkedAt: "2026-04-14T00:00:00.000Z", | |
| 745 | version: "2.1.0", | |
| 746 | models: [ | |
| 747 | { | |
| 748 | slug: "claude-sonnet-4-6", | |
| 749 | name: "Sonnet 4.6", | |
| 750 | isCustom: false, | |
| 751 | capabilities: null, | |
| 752 | }, | |
| 753 | { | |
| 754 | slug: "removed-custom", | |
| 755 | name: "removed-custom", | |
| 756 | isCustom: true, | |
| 757 | capabilities: null, | |
| 758 | }, | |
| 759 | ], | |
| 760 | slashCommands: [], | |
| 761 | skills: [], | |
| 762 | } as const satisfies ServerProvider; | |
| 763 | const refreshedProvider = { | |
| 764 | ...previousProvider, | |
| 765 | checkedAt: "2026-04-14T00:01:00.000Z", | |
| 766 | models: [previousProvider.models[0]], | |
| 767 | } satisfies ServerProvider; | |
| 768 | ||
| 769 | assert.deepStrictEqual(mergeProviderSnapshot(previousProvider, refreshedProvider).models, [ | |
| 770 | ...refreshedProvider.models, | |
| 771 | ]); | |
| 772 | }); | |
| 773 | ||
| 774 | it("drops stale ACP Registry models missing from a completed discovery probe", () => { | |
| 775 | const previousProvider = { | |
| 776 | instanceId: ProviderInstanceId.make("acpRegistry_codex"), | |
| 777 | driver: ProviderDriverKind.make("acpRegistry"), | |
| 778 | status: "ready", | |
| 779 | enabled: true, | |
| 780 | installed: true, | |
| 781 | auth: { status: "authenticated" }, | |
| 782 | checkedAt: "2026-08-13T00:00:00.000Z", | |
| 783 | version: "1.2.0", | |
| 784 | models: [ | |
| 785 | { slug: "gpt-5.6-sol", name: "GPT-5.6-Sol", isCustom: false, capabilities: null }, | |
| 786 | { | |
| 787 | slug: "gpt-5.6-sol[low]", | |
| 788 | name: "GPT-5.6-Sol (low)", | |
| 789 | isCustom: false, | |
| 790 | capabilities: null, | |
| 791 | }, | |
| 792 | { slug: "default", name: "Default", isCustom: false, capabilities: null }, | |
| 793 | ], | |
| 794 | slashCommands: [], | |
| 795 | skills: [], | |
| 796 | } as const satisfies ServerProvider; | |
| 797 | const refreshedProvider = { | |
| 798 | ...previousProvider, | |
| 799 | checkedAt: "2026-08-13T00:01:00.000Z", | |
| 800 | models: [ | |
| 801 | { slug: "gpt-5.6-sol", name: "GPT-5.6-Sol", isCustom: false, capabilities: null }, | |
| 802 | ], | |
| 803 | } satisfies ServerProvider; | |
| 804 | ||
| 805 | assert.deepStrictEqual(mergeProviderSnapshot(previousProvider, refreshedProvider).models, [ | |
| 806 | ...refreshedProvider.models, | |
| 807 | ]); | |
| 808 | }); | |
| 809 | ||
| 810 | it("retains ACP Registry models while discovery has not completed", () => { | |
| 811 | const previousProvider = { | |
| 812 | instanceId: ProviderInstanceId.make("acpRegistry_codex"), | |
| 813 | driver: ProviderDriverKind.make("acpRegistry"), | |
| 814 | status: "ready", | |
| 815 | enabled: true, | |
| 816 | installed: true, | |
| 817 | auth: { status: "authenticated" }, | |
| 818 | checkedAt: "2026-08-13T00:00:00.000Z", | |
| 819 | version: "1.2.0", | |
| 820 | models: [ | |
| 821 | { slug: "gpt-5.6-sol", name: "GPT-5.6-Sol", isCustom: false, capabilities: null }, | |
| 822 | ], | |
| 823 | slashCommands: [], | |
| 824 | skills: [], | |
| 825 | } as const satisfies ServerProvider; | |
| 826 | const checkingProvider = { | |
| 827 | ...previousProvider, | |
| 828 | checkedAt: "2026-08-13T00:01:00.000Z", | |
| 829 | auth: { status: "unknown" }, | |
| 830 | models: [{ slug: "default", name: "Default", isCustom: false, capabilities: null }], | |
| 831 | } satisfies ServerProvider; | |
| 832 | const failedProbeProvider = { | |
| 833 | ...checkingProvider, | |
| 834 | status: "warning", | |
| 835 | } satisfies ServerProvider; | |
| 836 | ||
| 837 | assert.deepStrictEqual(mergeProviderSnapshot(previousProvider, checkingProvider).models, [ | |
| 838 | { slug: "default", name: "Default", isCustom: false, capabilities: null }, | |
| 839 | { slug: "gpt-5.6-sol", name: "GPT-5.6-Sol", isCustom: false, capabilities: null }, | |
| 840 | ]); | |
| 841 | assert.deepStrictEqual( | |
| 842 | mergeProviderSnapshot(previousProvider, failedProbeProvider).models, | |
| 843 | [ | |
| 844 | { slug: "default", name: "Default", isCustom: false, capabilities: null }, | |
| 845 | { slug: "gpt-5.6-sol", name: "GPT-5.6-Sol", isCustom: false, capabilities: null }, | |
| 846 | ], | |
| 847 | ); | |
| 848 | }); | |
| 849 | ||
| 850 | it("drops stale OpenCode models missing from a successful refresh", () => { | |
| 851 | const previousProvider = { | |
| 852 | instanceId: ProviderInstanceId.make("opencode"), | |
| 853 | driver: ProviderDriverKind.make("opencode"), | |
| 854 | status: "ready", | |
| 855 | enabled: true, | |
| 856 | installed: true, | |
| 857 | auth: { status: "authenticated" }, | |
| 858 | checkedAt: "2026-07-17T00:00:00.000Z", | |
| 859 | version: "1.0.0", | |
| 860 | models: [ | |
| 861 | { | |
| 862 | slug: "github/gpt-5", | |
| 863 | name: "GPT-5", | |
| 864 | subProvider: "GitHub", | |
| 865 | isCustom: false, | |
| 866 | capabilities: null, | |
| 867 | }, | |
| 868 | { | |
| 869 | slug: "removed-plugin/model", | |
| 870 | name: "Removed Plugin Model", | |
| 871 | subProvider: "Removed Plugin", | |
| 872 | isCustom: false, | |
| 873 | capabilities: null, | |
| 874 | }, | |
| 875 | ], | |
| 876 | slashCommands: [], | |
| 877 | skills: [], | |
| 878 | } as const satisfies ServerProvider; | |
| 879 | const refreshedProvider = { | |
| 880 | ...previousProvider, | |
| 881 | checkedAt: "2026-07-17T00:01:00.000Z", | |
| 882 | models: [ | |
| 883 | { | |
| 884 | slug: "github/gpt-5", | |
| 885 | name: "GPT-5", | |
| 886 | subProvider: "GitHub", | |
| 887 | isCustom: false, | |
| 888 | capabilities: null, | |
| 889 | }, | |
| 890 | ], | |
| 891 | } satisfies ServerProvider; | |
| 892 | ||
| 893 | assert.deepStrictEqual(mergeProviderSnapshot(previousProvider, refreshedProvider).models, [ | |
| 894 | ...refreshedProvider.models, | |
| 895 | ]); | |
| 896 | }); | |
| 897 | ||
| 898 | it("retains stale OpenCode models when a refresh fails", () => { | |
| 899 | const previousProvider = { | |
| 900 | instanceId: ProviderInstanceId.make("opencode"), | |
| 901 | driver: ProviderDriverKind.make("opencode"), | |
| 902 | status: "ready", | |
| 903 | enabled: true, | |
| 904 | installed: true, | |
| 905 | auth: { status: "authenticated" }, | |
| 906 | checkedAt: "2026-07-17T00:00:00.000Z", | |
| 907 | version: "1.0.0", | |
| 908 | models: [ | |
| 909 | { | |
| 910 | slug: "github/gpt-5", | |
| 911 | name: "GPT-5", | |
| 912 | subProvider: "GitHub", | |
| 913 | isCustom: false, | |
| 914 | capabilities: null, | |
| 915 | }, | |
| 916 | ], | |
| 917 | slashCommands: [{ name: "review", description: "Review changes" }], | |
| 918 | skills: [ | |
| 919 | { | |
| 920 | name: "typescript", | |
| 921 | description: "TypeScript help", | |
| 922 | path: "/skills/typescript/SKILL.md", | |
| 923 | enabled: true, | |
| 924 | }, | |
| 925 | ], | |
| 926 | } as const satisfies ServerProvider; | |
| 927 | const refreshedProvider = { | |
| 928 | ...previousProvider, | |
| 929 | status: "error", | |
| 930 | auth: { status: "unknown" }, | |
| 931 | checkedAt: "2026-07-17T00:01:00.000Z", | |
| 932 | models: [], | |
| 933 | message: "Failed to refresh OpenCode models.", | |
| 934 | } satisfies ServerProvider; | |
| 935 | ||
| 936 | assert.deepStrictEqual(mergeProviderSnapshot(previousProvider, refreshedProvider).models, [ | |
| 937 | ...previousProvider.models, | |
| 938 | ]); | |
| 939 | assert.deepStrictEqual( | |
| 940 | mergeProviderSnapshot(previousProvider, refreshedProvider).slashCommands, | |
| 941 | previousProvider.slashCommands, | |
| 942 | ); | |
| 943 | assert.deepStrictEqual( | |
| 944 | mergeProviderSnapshot(previousProvider, refreshedProvider).skills, | |
| 945 | previousProvider.skills, | |
| 946 | ); | |
| 947 | }); | |
| 948 | ||
| 949 | it("classifies pending, logout, uninstall, and reconnect OpenCode inventories", () => { | |
| 950 | const previousProvider = { | |
| 951 | instanceId: ProviderInstanceId.make("opencode"), | |
| 952 | driver: ProviderDriverKind.make("opencode"), | |
| 953 | status: "ready", | |
| 954 | enabled: true, | |
| 955 | installed: true, | |
| 956 | auth: { status: "authenticated" }, | |
| 957 | checkedAt: "2026-07-17T00:00:00.000Z", | |
| 958 | version: "1.0.0", | |
| 959 | models: [ | |
| 960 | { | |
| 961 | slug: "github/gpt-5", | |
| 962 | name: "GPT-5", | |
| 963 | subProvider: "GitHub", | |
| 964 | isCustom: false, | |
| 965 | capabilities: null, | |
| 966 | }, | |
| 967 | { | |
| 968 | slug: "removed-plugin/model", | |
| 969 | name: "Removed Plugin Model", | |
| 970 | subProvider: "Removed Plugin", | |
| 971 | isCustom: false, | |
| 972 | capabilities: null, | |
| 973 | }, | |
| 974 | ], | |
| 975 | slashCommands: [{ name: "review", description: "Review changes" }], | |
| 976 | skills: [ | |
| 977 | { | |
| 978 | name: "typescript", | |
| 979 | description: "TypeScript help", | |
| 980 | path: "/skills/typescript/SKILL.md", | |
| 981 | enabled: true, | |
| 982 | }, | |
| 983 | ], | |
| 984 | } as const satisfies ServerProvider; | |
| 985 | const pendingProvider = { | |
| 986 | ...previousProvider, | |
| 987 | status: "warning", | |
| 988 | installed: false, | |
| 989 | auth: { status: "unknown" }, | |
| 990 | checkedAt: "2026-07-17T00:01:00.000Z", | |
| 991 | version: null, | |
| 992 | models: [], | |
| 993 | message: "OpenCode provider status has not been checked in this session yet.", | |
| 994 | } satisfies ServerProvider; | |
| 995 | const loggedOutProvider = { | |
| 996 | ...previousProvider, | |
| 997 | status: "warning", | |
| 998 | auth: { status: "unknown" }, | |
| 999 | checkedAt: "2026-07-17T00:02:00.000Z", | |
| 1000 | models: [], | |
| 1001 | slashCommands: [], | |
| 1002 | skills: [], | |
| 1003 | message: "OpenCode is available, but it did not report any connected upstream providers.", | |
| 1004 | } satisfies ServerProvider; | |
| 1005 | const missingProvider = { | |
| 1006 | ...previousProvider, | |
| 1007 | status: "error", | |
| 1008 | installed: false, | |
| 1009 | auth: { status: "unknown" }, | |
| 1010 | checkedAt: "2026-07-17T00:03:00.000Z", | |
| 1011 | version: null, | |
| 1012 | models: [], | |
| 1013 | message: "OpenCode CLI (`opencode`) is not installed or not on PATH.", | |
| 1014 | } satisfies ServerProvider; | |
| 1015 | const authoritativeProvider = { | |
| 1016 | ...previousProvider, | |
| 1017 | checkedAt: "2026-07-17T00:04:00.000Z", | |
| 1018 | models: [previousProvider.models[0]!], | |
| 1019 | } satisfies ServerProvider; | |
| 1020 | const failedProvider = { | |
| 1021 | ...authoritativeProvider, | |
| 1022 | status: "error", | |
| 1023 | auth: { status: "unknown" }, | |
| 1024 | checkedAt: "2026-07-17T00:05:00.000Z", | |
| 1025 | models: [], | |
| 1026 | message: "Failed to refresh OpenCode models.", | |
| 1027 | } satisfies ServerProvider; | |
| 1028 | ||
| 1029 | assert.deepStrictEqual(mergeProviderSnapshot(previousProvider, pendingProvider).models, [ | |
| 1030 | ...previousProvider.models, | |
| 1031 | ]); | |
| 1032 | assert.deepStrictEqual( | |
| 1033 | mergeProviderSnapshot(previousProvider, loggedOutProvider).models, | |
| 1034 | [], | |
| 1035 | ); | |
| 1036 | assert.deepStrictEqual( | |
| 1037 | mergeProviderSnapshot(previousProvider, loggedOutProvider).slashCommands, | |
| 1038 | [], | |
| 1039 | ); | |
| 1040 | assert.deepStrictEqual( | |
| 1041 | mergeProviderSnapshot(previousProvider, loggedOutProvider).skills, | |
| 1042 | [], | |
| 1043 | ); | |
| 1044 | assert.deepStrictEqual(mergeProviderSnapshot(previousProvider, missingProvider).models, []); | |
| 1045 | ||
| 1046 | const afterRemoval = mergeProviderSnapshot(previousProvider, authoritativeProvider); | |
| 1047 | const afterFailure = mergeProviderSnapshot(afterRemoval, failedProvider); | |
| 1048 | ||
| 1049 | assert.deepStrictEqual(afterFailure.models, [authoritativeProvider.models[0]!]); | |
| 1050 | }); | |
| 1051 | ||
| 1052 | describe("Codex model inventories", () => { | |
| 1053 | const cachedProvider = { | |
| 1054 | instanceId: ProviderInstanceId.make("codex-personal"), | |
| 1055 | driver: ProviderDriverKind.make("codex"), | |
| 1056 | status: "ready", | |
| 1057 | enabled: true, | |
| 1058 | installed: true, | |
| 1059 | auth: { status: "authenticated" }, | |
| 1060 | checkedAt: "2026-09-04T19:00:00.000Z", | |
| 1061 | version: "0.153.3", | |
| 1062 | models: [ | |
| 1063 | "vega-alpha", | |
| 1064 | "joule-alpha", | |
| 1065 | "kindle-alpha", | |
| 1066 | "ultima-alpha", | |
| 1067 | "solstice-alpha", | |
| 1068 | ].map((slug) => ({ slug, name: slug, isCustom: false, capabilities: null })), | |
| 1069 | slashCommands: [], | |
| 1070 | skills: [], | |
| 1071 | } satisfies ServerProvider; | |
| 1072 | const customModel = { | |
| 1073 | slug: "custom-model", | |
| 1074 | name: "Custom model", | |
| 1075 | isCustom: true, | |
| 1076 | capabilities: null, | |
| 1077 | } as const; | |
| 1078 | const refreshedProvider = { | |
| 1079 | ...cachedProvider, | |
| 1080 | checkedAt: "2026-09-04T19:01:00.000Z", | |
| 1081 | models: [ | |
| 1082 | { slug: "gpt-6-astra", name: "GPT 6 Astra", isCustom: false, capabilities: null }, | |
| 1083 | cachedProvider.models[0]!, | |
| 1084 | customModel, | |
| 1085 | ], | |
| 1086 | } satisfies ServerProvider; | |
| 1087 | const pendingProvider = { | |
| 1088 | ...cachedProvider, | |
| 1089 | status: "warning", | |
| 1090 | installed: false, | |
| 1091 | auth: { status: "unknown" }, | |
| 1092 | models: [customModel], | |
| 1093 | } satisfies ServerProvider; | |
| 1094 | const failedProvider = { | |
| 1095 | ...pendingProvider, | |
| 1096 | checkedAt: "2026-09-04T19:02:00.000Z", | |
| 1097 | status: "error", | |
| 1098 | installed: true, | |
| 1099 | } satisfies ServerProvider; | |
| 1100 | ||
| 1101 | it("drops retired alpha models after discovery, including without OpenAI authentication", () => { | |
| 1102 | for (const authStatus of ["authenticated", "unknown"] as const) { | |
| 1103 | assert.deepStrictEqual( | |
| 1104 | mergeProviderSnapshot(cachedProvider, { | |
| 1105 | ...refreshedProvider, | |
| 1106 | auth: { status: authStatus }, | |
| 1107 | }).models, | |
| 1108 | refreshedProvider.models, | |
| 1109 | ); | |
| 1110 | } | |
| 1111 | }); | |
| 1112 | ||
| 1113 | it("keeps discovered models during startup and failed probes without restoring removed custom models", () => { | |
| 1114 | for (const provider of [pendingProvider, failedProvider]) { | |
| 1115 | assert.deepStrictEqual( | |
| 1116 | mergeProviderSnapshot( | |
| 1117 | { | |
| 1118 | ...cachedProvider, | |
| 1119 | models: [...cachedProvider.models, { ...customModel, slug: "removed-custom" }], | |
| 1120 | }, | |
| 1121 | provider, | |
| 1122 | ).models, | |
| 1123 | [customModel, ...cachedProvider.models], | |
| 1124 | ); | |
| 1125 | } | |
| 1126 | }); | |
| 1127 | ||
| 1128 | it("clears discovered models after sign-out, disable, uninstall, or empty discovery", () => { | |
| 1129 | const emptyProvider = { ...refreshedProvider, models: [customModel] }; | |
| 1130 | const clearedProviders = [ | |
| 1131 | { ...emptyProvider, status: "error", auth: { status: "unauthenticated" } }, | |
| 1132 | { ...emptyProvider, status: "disabled", enabled: false }, | |
| 1133 | { ...emptyProvider, status: "error", installed: false, auth: { status: "unknown" } }, | |
| 1134 | emptyProvider, | |
| 1135 | { ...emptyProvider, models: [] }, | |
| 1136 | ] satisfies ReadonlyArray<ServerProvider>; | |
| 1137 | ||
| 1138 | for (const provider of clearedProviders) { | |
| 1139 | assert.deepStrictEqual( | |
| 1140 | mergeProviderSnapshot(cachedProvider, provider).models, | |
| 1141 | provider.models, | |
| 1142 | ); | |
| 1143 | } | |
| 1144 | }); | |
| 1145 | ||
| 1146 | it.effect("persists removals across failed refreshes and registry restarts", () => | |
| 1147 | Effect.gen(function* () { | |
| 1148 | const config = yield* ServerConfig.ServerConfig; | |
| 1149 | const filePath = yield* resolveProviderStatusCachePath({ | |
| 1150 | cacheDir: config.providerStatusCacheDir, | |
| 1151 | instanceId: cachedProvider.instanceId, | |
| 1152 | }); | |
| 1153 | yield* writeProviderStatusCache({ filePath, provider: cachedProvider }); | |
| 1154 | const nextProvider = yield* Ref.make<ServerProvider>(refreshedProvider); | |
| 1155 | const instance = { | |
| 1156 | instanceId: cachedProvider.instanceId, | |
| 1157 | driverKind: cachedProvider.driver, | |
| 1158 | continuationIdentity: { | |
| 1159 | driverKind: cachedProvider.driver, | |
| 1160 | continuationKey: "codex:instance:codex-personal", | |
| 1161 | }, | |
| 1162 | displayName: undefined, | |
| 1163 | enabled: true, | |
| 1164 | snapshot: { | |
| 1165 | resolveMaintenance: () => | |
| 1166 | Effect.succeed( | |
| 1167 | makeManualOnlyProviderMaintenanceCapabilities({ | |
| 1168 | provider: cachedProvider.driver, | |
| 1169 | packageName: null, | |
| 1170 | }), | |
| 1171 | ), | |
| 1172 | getSnapshot: Effect.succeed(pendingProvider), | |
| 1173 | refresh: Ref.get(nextProvider), | |
| 1174 | streamChanges: Stream.empty, | |
| 1175 | applyUsageLimits: () => Effect.void, | |
| 1176 | }, | |
| 1177 | orchestrationAdapter: {} as ProviderInstance["orchestrationAdapter"], | |
| 1178 | textGeneration: {} as ProviderInstance["textGeneration"], | |
| 1179 | } satisfies ProviderInstance; | |
| 1180 | const instanceRegistryLayer = Layer.succeed( | |
| 1181 | ProviderInstanceRegistry.ProviderInstanceRegistry, | |
| 1182 | { | |
| 1183 | getInstance: (id) => | |
| 1184 | Effect.succeed(id === instance.instanceId ? instance : undefined), | |
| 1185 | listInstances: Effect.succeed([instance]), | |
| 1186 | listUnavailable: Effect.succeed([]), | |
| 1187 | streamChanges: Stream.empty, | |
| 1188 | subscribeChanges: Effect.flatMap(PubSub.unbounded<void>(), PubSub.subscribe), | |
| 1189 | }, | |
| 1190 | ); | |
| 1191 | const retainedModels = [ | |
| 1192 | customModel, | |
| 1193 | ...refreshedProvider.models.filter((model) => !model.isCustom), | |
| 1194 | ]; | |
| 1195 | ||
| 1196 | for (const restarted of [false, true]) { | |
| 1197 | yield* Effect.gen(function* () { | |
| 1198 | const registry = yield* ProviderRegistry.ProviderRegistry; | |
| 1199 | const expectedModels = restarted | |
| 1200 | ? retainedModels | |
| 1201 | : [customModel, ...cachedProvider.models]; | |
| 1202 | assert.deepStrictEqual((yield* registry.getProviders)[0]?.models, expectedModels); | |
| 1203 | ||
| 1204 | yield* registry.refreshInstance(instance.instanceId); | |
| 1205 | assert.deepStrictEqual( | |
| 1206 | (yield* readProviderStatusCache(filePath))?.models, | |
| 1207 | restarted ? retainedModels : refreshedProvider.models, | |
| 1208 | ); | |
| 1209 | ||
| 1210 | yield* Ref.set(nextProvider, failedProvider); | |
| 1211 | const afterFailure = yield* registry.refreshInstance(instance.instanceId); | |
| 1212 | assert.deepStrictEqual(afterFailure[0]?.models, retainedModels); | |
| 1213 | assert.deepStrictEqual( | |
| 1214 | (yield* readProviderStatusCache(filePath))?.models, | |
| 1215 | retainedModels, | |
| 1216 | ); | |
| 1217 | }).pipe( | |
| 1218 | Effect.provide(ProviderRegistryLive.pipe(Layer.provide(instanceRegistryLayer))), | |
| 1219 | Effect.scoped, | |
| 1220 | ); | |
| 1221 | } | |
| 1222 | }).pipe( | |
| 1223 | Effect.provide( | |
| 1224 | ServerConfig.layerTest(process.cwd(), { | |
| 1225 | prefix: "t3-codex-retired-model-cache-", | |
| 1226 | }).pipe(Layer.provideMerge(NodeServices.layer)), | |
| 1227 | ), | |
| 1228 | ), | |
| 1229 | ); | |
| 1230 | }); | |
| 1231 | ||
| 1232 | describe("Antigravity model inventories", () => { | |
| 1233 | const previousProvider = { | |
| 1234 | instanceId: ProviderInstanceId.make("antigravity-personal"), | |
| 1235 | driver: ProviderDriverKind.make("antigravity"), | |
| 1236 | status: "ready", | |
| 1237 | enabled: true, | |
| 1238 | installed: true, | |
| 1239 | auth: { status: "authenticated" }, | |
| 1240 | checkedAt: "2026-09-02T00:00:00.000Z", | |
| 1241 | version: "0.1.3", | |
| 1242 | models: [ | |
| 1243 | { | |
| 1244 | slug: "gemini-3.1-pro-high", | |
| 1245 | name: "Gemini 3.1 Pro High", | |
| 1246 | isCustom: false, | |
| 1247 | capabilities: null, | |
| 1248 | }, | |
| 1249 | { | |
| 1250 | slug: "gemini-3-flash", | |
| 1251 | name: "Gemini 3 Flash", | |
| 1252 | isCustom: false, | |
| 1253 | capabilities: null, | |
| 1254 | }, | |
| 1255 | ], | |
| 1256 | slashCommands: [], | |
| 1257 | skills: [], | |
| 1258 | } as const satisfies ServerProvider; | |
| 1259 | ||
| 1260 | it("removes unavailable models after a successful refresh", () => { | |
| 1261 | for (const status of ["ready", "warning"] as const) { | |
| 1262 | const refreshedProvider = { | |
| 1263 | ...previousProvider, | |
| 1264 | status, | |
| 1265 | checkedAt: "2026-09-02T00:01:00.000Z", | |
| 1266 | models: [previousProvider.models[1]], | |
| 1267 | } satisfies ServerProvider; | |
| 1268 | const afterRefresh = mergeProviderSnapshot(previousProvider, refreshedProvider); | |
| 1269 | ||
| 1270 | assert.deepStrictEqual(afterRefresh.models, refreshedProvider.models); | |
| 1271 | ||
| 1272 | const afterFailure = mergeProviderSnapshot(afterRefresh, { | |
| 1273 | ...refreshedProvider, | |
| 1274 | status: "error", | |
| 1275 | auth: { status: "unknown" }, | |
| 1276 | models: [], | |
| 1277 | }); | |
| 1278 | assert.deepStrictEqual(afterFailure.models, refreshedProvider.models); | |
| 1279 | } | |
| 1280 | }); | |
| 1281 | ||
| 1282 | it("keeps cached models during health checks and temporary failures", () => { | |
| 1283 | for (const installed of [false, true]) { | |
| 1284 | const pendingProvider = { | |
| 1285 | ...previousProvider, | |
| 1286 | status: "warning", | |
| 1287 | installed, | |
| 1288 | auth: { status: "unknown" }, | |
| 1289 | checkedAt: "2026-09-02T00:01:00.000Z", | |
| 1290 | version: installed ? previousProvider.version : null, | |
| 1291 | models: [], | |
| 1292 | } satisfies ServerProvider; | |
| 1293 | ||
| 1294 | assert.deepStrictEqual( | |
| 1295 | mergeProviderSnapshot(previousProvider, pendingProvider).models, | |
| 1296 | previousProvider.models, | |
| 1297 | ); | |
| 1298 | } | |
| 1299 | ||
| 1300 | for (const authStatus of ["unknown", "authenticated"] as const) { | |
| 1301 | const failedProvider = { | |
| 1302 | ...previousProvider, | |
| 1303 | status: "error", | |
| 1304 | auth: { status: authStatus }, | |
| 1305 | checkedAt: "2026-09-02T00:02:00.000Z", | |
| 1306 | models: [], | |
| 1307 | } satisfies ServerProvider; | |
| 1308 | ||
| 1309 | assert.deepStrictEqual( | |
| 1310 | mergeProviderSnapshot(previousProvider, failedProvider).models, | |
| 1311 | previousProvider.models, | |
| 1312 | ); | |
| 1313 | } | |
| 1314 | }); | |
| 1315 | ||
| 1316 | it("clears models after sign-out, disable, uninstall, or an empty successful refresh", () => { | |
| 1317 | const emptyProvider = { | |
| 1318 | ...previousProvider, | |
| 1319 | checkedAt: "2026-09-02T00:01:00.000Z", | |
| 1320 | models: [], | |
| 1321 | } satisfies ServerProvider; | |
| 1322 | const clearedProviders = [ | |
| 1323 | { ...emptyProvider, status: "warning", auth: { status: "unauthenticated" } }, | |
| 1324 | { ...emptyProvider, status: "error", auth: { status: "unauthenticated" } }, | |
| 1325 | { ...emptyProvider, status: "disabled", enabled: false }, | |
| 1326 | { ...emptyProvider, status: "error", enabled: false }, | |
| 1327 | { ...emptyProvider, status: "error", installed: false, auth: { status: "unknown" } }, | |
| 1328 | emptyProvider, | |
| 1329 | ] satisfies ReadonlyArray<ServerProvider>; | |
| 1330 | ||
| 1331 | for (const provider of clearedProviders) { | |
| 1332 | const afterRemoval = mergeProviderSnapshot(previousProvider, provider); | |
| 1333 | assert.deepStrictEqual(afterRemoval.models, []); | |
| 1334 | ||
| 1335 | const afterFailure = mergeProviderSnapshot(afterRemoval, { | |
| 1336 | ...emptyProvider, | |
| 1337 | status: "error", | |
| 1338 | auth: { status: "unknown" }, | |
| 1339 | }); | |
| 1340 | assert.deepStrictEqual(afterFailure.models, []); | |
| 1341 | } | |
| 1342 | }); | |
| 1343 | }); | |
| 1344 | ||
| 1345 | describe("Antigravity saved account", () => { | |
| 1346 | const signedIn = { | |
| 1347 | instanceId: ProviderInstanceId.make("antigravity-personal"), | |
| 1348 | driver: ProviderDriverKind.make("antigravity"), | |
| 1349 | status: "ready", | |
| 1350 | enabled: true, | |
| 1351 | installed: true, | |
| 1352 | auth: { status: "authenticated", type: "oauth-personal", label: "Google account" }, | |
| 1353 | checkedAt: "2026-09-05T00:00:00.000Z", | |
| 1354 | version: "agy_acp_server_1.1.1", | |
| 1355 | models: [ | |
| 1356 | { | |
| 1357 | slug: "gemini-3.7-flash-high", | |
| 1358 | name: "Gemini 3.7 Flash", | |
| 1359 | isCustom: false, | |
| 1360 | capabilities: null, | |
| 1361 | }, | |
| 1362 | ], | |
| 1363 | slashCommands: [{ name: "plan" }], | |
| 1364 | skills: [], | |
| 1365 | } as const satisfies ServerProvider; | |
| 1366 | const uncheckedMessage = | |
| 1367 | "Antigravity is installed. Google account access is not checked yet."; | |
| 1368 | const restartProbe = { | |
| 1369 | ...signedIn, | |
| 1370 | status: "warning", | |
| 1371 | auth: { status: "unknown" }, | |
| 1372 | checkedAt: "2026-09-05T00:01:00.000Z", | |
| 1373 | message: uncheckedMessage, | |
| 1374 | models: [], | |
| 1375 | } as const satisfies ServerProvider; | |
| 1376 | ||
| 1377 | it("keeps the saved Google account through restart health checks", () => { | |
| 1378 | const merged = mergeProviderSnapshot(signedIn, restartProbe); | |
| 1379 | const { message: _uncheckedMessage, ...probeWithoutMessage } = restartProbe; | |
| 1380 | assert.deepStrictEqual(merged, { | |
| 1381 | ...probeWithoutMessage, | |
| 1382 | status: "ready", | |
| 1383 | auth: signedIn.auth, | |
| 1384 | models: signedIn.models, | |
| 1385 | }); | |
| 1386 | assert.equal("message" in merged, false); | |
| 1387 | // The next periodic probe reads the merged snapshot as its previous state. | |
| 1388 | assert.deepStrictEqual(mergeProviderSnapshot(merged, restartProbe), merged); | |
| 1389 | }); | |
| 1390 | ||
| 1391 | it("carries the account through the boot probe and a failed probe without hiding them", () => { | |
| 1392 | const booting = { | |
| 1393 | ...restartProbe, | |
| 1394 | installed: false, | |
| 1395 | version: null, | |
| 1396 | message: "Checking Antigravity availability.", | |
| 1397 | } satisfies ServerProvider; | |
| 1398 | assert.deepStrictEqual(mergeProviderSnapshot(signedIn, booting), { | |
| 1399 | ...booting, | |
| 1400 | auth: signedIn.auth, | |
| 1401 | models: signedIn.models, | |
| 1402 | }); | |
| 1403 | ||
| 1404 | const failed = { | |
| 1405 | ...restartProbe, | |
| 1406 | status: "error", | |
| 1407 | message: "Antigravity did not respond to its local health check within 90 seconds.", | |
| 1408 | } satisfies ServerProvider; | |
| 1409 | assert.deepStrictEqual(mergeProviderSnapshot(signedIn, failed), { | |
| 1410 | ...failed, | |
| 1411 | auth: signedIn.auth, | |
| 1412 | models: signedIn.models, | |
| 1413 | }); | |
| 1414 | }); | |
| 1415 | ||
| 1416 | it("does not invent an account after sign-out, disable, uninstall, or for other providers", () => { | |
| 1417 | const untouched = [ | |
| 1418 | { ...restartProbe, auth: { status: "unauthenticated" } }, | |
| 1419 | { ...restartProbe, status: "disabled", enabled: false }, | |
| 1420 | { ...restartProbe, status: "error", installed: false }, | |
| 1421 | { ...restartProbe, driver: ProviderDriverKind.make("codex") }, | |
| 1422 | // The instance was rebuilt with another sign-in method. | |
| 1423 | { ...restartProbe, auth: { status: "unknown", type: "gemini-api-key" } }, | |
| 1424 | ] satisfies ReadonlyArray<ServerProvider>; | |
| 1425 | for (const next of untouched) { | |
| 1426 | const merged = mergeProviderSnapshot(signedIn, next); | |
| 1427 | assert.deepStrictEqual(merged.auth, next.auth); | |
| 1428 | assert.equal(merged.status, next.status); | |
| 1429 | assert.equal(merged.message, next.message); | |
| 1430 | } | |
| 1431 | assert.deepStrictEqual( | |
| 1432 | mergeProviderSnapshot({ ...signedIn, auth: { status: "unknown" } }, restartProbe).auth, | |
| 1433 | { status: "unknown" }, | |
| 1434 | ); | |
| 1435 | assert.equal( | |
| 1436 | mergeProviderSnapshot( | |
| 1437 | { ...signedIn, driver: ProviderDriverKind.make("codex") }, | |
| 1438 | restartProbe, | |
| 1439 | ).auth.status, | |
| 1440 | "unknown", | |
| 1441 | ); | |
| 1442 | assert.deepStrictEqual( | |
| 1443 | mergeProviderSnapshot(signedIn, { | |
| 1444 | ...restartProbe, | |
| 1445 | auth: { status: "unknown", type: "oauth-personal" }, | |
| 1446 | }).auth, | |
| 1447 | signedIn.auth, | |
| 1448 | ); | |
| 1449 | }); | |
| 1450 | }); | |
| 1451 | ||
| 1452 | it("fills missing capabilities from the previous provider snapshot", () => { | |
| 1453 | const previousProvider = { | |
| 1454 | instanceId: ProviderInstanceId.make("cursor"), | |
| 1455 | driver: ProviderDriverKind.make("cursor"), | |
| 1456 | status: "ready", | |
| 1457 | enabled: true, | |
| 1458 | installed: true, | |
| 1459 | auth: { status: "authenticated" }, | |
| 1460 | checkedAt: "2026-04-14T00:00:00.000Z", | |
| 1461 | version: "2026.04.09-f2b0fcd", | |
| 1462 | models: [ | |
| 1463 | { | |
| 1464 | slug: "claude-opus-4-6", | |
| 1465 | name: "Opus 4.6", | |
| 1466 | isCustom: false, | |
| 1467 | capabilities: createModelCapabilities({ | |
| 1468 | optionDescriptors: [ | |
| 1469 | selectDescriptor("reasoning", "Reasoning", [ | |
| 1470 | { id: "high", label: "High", isDefault: true }, | |
| 1471 | ]), | |
| 1472 | booleanDescriptor("fastMode", "Fast Mode"), | |
| 1473 | booleanDescriptor("thinking", "Thinking"), | |
| 1474 | ], | |
| 1475 | }), | |
| 1476 | }, | |
| 1477 | ], | |
| 1478 | slashCommands: [], | |
| 1479 | skills: [], | |
| 1480 | } as const satisfies ServerProvider; | |
| 1481 | const refreshedProvider = { | |
| 1482 | ...previousProvider, | |
| 1483 | checkedAt: "2026-04-14T00:01:00.000Z", | |
| 1484 | models: [ | |
| 1485 | { | |
| 1486 | slug: "claude-opus-4-6", | |
| 1487 | name: "Opus 4.6", | |
| 1488 | isCustom: false, | |
| 1489 | capabilities: createModelCapabilities({ | |
| 1490 | optionDescriptors: [], | |
| 1491 | }), | |
| 1492 | }, | |
| 1493 | ], | |
| 1494 | } satisfies ServerProvider; | |
| 1495 | ||
| 1496 | assert.deepStrictEqual(mergeProviderSnapshot(previousProvider, refreshedProvider).models, [ | |
| 1497 | ...previousProvider.models, | |
| 1498 | ]); | |
| 1499 | }); | |
| 1500 | ||
| 1501 | it.effect("does not run provider probes during layer construction", () => | |
| 1502 | Effect.gen(function* () { | |
| 1503 | const codexDriver = ProviderDriverKind.make("codex"); | |
| 1504 | const codexInstanceId = ProviderInstanceId.make("codex"); | |
| 1505 | const initialProvider = { | |
| 1506 | instanceId: codexInstanceId, | |
| 1507 | driver: codexDriver, | |
| 1508 | status: "warning", | |
| 1509 | enabled: true, | |
| 1510 | installed: false, | |
| 1511 | auth: { status: "unknown" }, | |
| 1512 | checkedAt: "2026-06-10T00:00:00.000Z", | |
| 1513 | version: null, | |
| 1514 | message: "Checking Codex provider status.", | |
| 1515 | models: [], | |
| 1516 | slashCommands: [], | |
| 1517 | skills: [], | |
| 1518 | } as const satisfies ServerProvider; | |
| 1519 | const refreshCalls = yield* Ref.make(0); | |
| 1520 | const instance = { | |
| 1521 | instanceId: codexInstanceId, | |
| 1522 | driverKind: codexDriver, | |
| 1523 | continuationIdentity: { | |
| 1524 | driverKind: codexDriver, | |
| 1525 | continuationKey: "codex:instance:codex", | |
| 1526 | }, | |
| 1527 | displayName: undefined, | |
| 1528 | enabled: true, | |
| 1529 | snapshot: { | |
| 1530 | resolveMaintenance: () => | |
| 1531 | Effect.succeed( | |
| 1532 | makeManualOnlyProviderMaintenanceCapabilities({ | |
| 1533 | provider: codexDriver, | |
| 1534 | packageName: null, | |
| 1535 | }), | |
| 1536 | ), | |
| 1537 | getSnapshot: Effect.succeed(initialProvider), | |
| 1538 | refresh: Ref.update(refreshCalls, (count) => count + 1).pipe( | |
| 1539 | Effect.andThen(Effect.never), | |
| 1540 | ), | |
| 1541 | streamChanges: Stream.empty, | |
| 1542 | applyUsageLimits: () => Effect.void, | |
| 1543 | }, | |
| 1544 | orchestrationAdapter: {} as ProviderInstance["orchestrationAdapter"], | |
| 1545 | textGeneration: {} as ProviderInstance["textGeneration"], | |
| 1546 | } satisfies ProviderInstance; | |
| 1547 | const instanceRegistryLayer = Layer.succeed( | |
| 1548 | ProviderInstanceRegistry.ProviderInstanceRegistry, | |
| 1549 | { | |
| 1550 | getInstance: (instanceId) => | |
| 1551 | Effect.succeed(instanceId === codexInstanceId ? instance : undefined), | |
| 1552 | listInstances: Effect.succeed([instance]), | |
| 1553 | listUnavailable: Effect.succeed([]), | |
| 1554 | streamChanges: Stream.empty, | |
| 1555 | subscribeChanges: Effect.flatMap(PubSub.unbounded<void>(), PubSub.subscribe), | |
| 1556 | }, | |
| 1557 | ); | |
| 1558 | const scope = yield* Scope.make(); | |
| 1559 | yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void)); | |
| 1560 | const runtimeServices = yield* Layer.build( | |
| 1561 | ProviderRegistryLive.pipe( | |
| 1562 | Layer.provideMerge(instanceRegistryLayer), | |
| 1563 | Layer.provideMerge( | |
| 1564 | ServerConfig.layerTest(process.cwd(), { | |
| 1565 | prefix: "t3-provider-registry-background-refresh-", | |
| 1566 | }), | |
| 1567 | ), | |
| 1568 | Layer.provideMerge(NodeServices.layer), | |
| 1569 | ), | |
| 1570 | ).pipe(Scope.provide(scope)); | |
| 1571 | yield* Effect.gen(function* () { | |
| 1572 | const registry = yield* ProviderRegistry.ProviderRegistry; | |
| 1573 | assert.deepStrictEqual(yield* registry.getProviders, [initialProvider]); | |
| 1574 | assert.strictEqual(yield* Ref.get(refreshCalls), 0); | |
| 1575 | }).pipe(Effect.provide(runtimeServices)); | |
| 1576 | }), | |
| 1577 | ); | |
| 1578 | ||
| 1579 | it.effect("deduplicates cwd probes and clears snapshots when an instance rebuilds", () => | |
| 1580 | Effect.gen(function* () { | |
| 1581 | const driver = ProviderDriverKind.make("codex"); | |
| 1582 | const instanceId = ProviderInstanceId.make("codex"); | |
| 1583 | const machineProvider = { | |
| 1584 | instanceId, | |
| 1585 | driver, | |
| 1586 | status: "ready", | |
| 1587 | enabled: true, | |
| 1588 | installed: true, | |
| 1589 | auth: { status: "authenticated" }, | |
| 1590 | checkedAt: "2026-06-10T00:00:00.000Z", | |
| 1591 | version: "1.0.0", | |
| 1592 | models: [], | |
| 1593 | slashCommands: [{ name: "global" }], | |
| 1594 | skills: [{ name: "global", path: "/global/SKILL.md", enabled: true }], | |
| 1595 | } as const satisfies ServerProvider; | |
| 1596 | const scopedProvider = { | |
| 1597 | ...machineProvider, | |
| 1598 | checkedAt: "2026-06-10T00:01:00.000Z", | |
| 1599 | slashCommands: [{ name: "project" }], | |
| 1600 | skills: [{ name: "project", path: "/workspace/SKILL.md", enabled: true }], | |
| 1601 | } as const satisfies ServerProvider; | |
| 1602 | const pendingScopedProvider = { | |
| 1603 | ...scopedProvider, | |
| 1604 | status: "error", | |
| 1605 | installed: false, | |
| 1606 | slashCommands: [], | |
| 1607 | } as const satisfies ServerProvider; | |
| 1608 | const snapshotCalls = yield* Ref.make(0); | |
| 1609 | const scopedResult = yield* Ref.make<ProviderWorkspaceSnapshot>({ | |
| 1610 | ...scopedProvider, | |
| 1611 | status: "error", | |
| 1612 | slashCommands: [COMPACT_SLASH_COMMAND], | |
| 1613 | slashCommandsPending: true, | |
| 1614 | }); | |
| 1615 | const cacheInvalidations = yield* Ref.make(0); | |
| 1616 | const scanGate = yield* Ref.make<{ | |
| 1617 | readonly started: Deferred.Deferred<void>; | |
| 1618 | readonly release: Deferred.Deferred<void>; | |
| 1619 | } | null>(null); | |
| 1620 | const returnPendingSnapshot = yield* Ref.make(true); | |
| 1621 | const probeStarted = yield* Deferred.make<void>(); | |
| 1622 | const releaseProbe = yield* Deferred.make<void>(); | |
| 1623 | const makeInstance = ( | |
| 1624 | provider: ServerProvider, | |
| 1625 | snapshotForCwd: NonNullable<ProviderInstance["snapshotForCwd"]>, | |
| 1626 | ): ProviderInstance => ({ | |
| 1627 | instanceId, | |
| 1628 | driverKind: driver, | |
| 1629 | continuationIdentity: { | |
| 1630 | driverKind: driver, | |
| 1631 | continuationKey: "codex:instance:codex", | |
| 1632 | }, | |
| 1633 | displayName: undefined, | |
| 1634 | enabled: true, | |
| 1635 | snapshot: { | |
| 1636 | resolveMaintenance: () => | |
| 1637 | Effect.succeed( | |
| 1638 | makeManualOnlyProviderMaintenanceCapabilities({ | |
| 1639 | provider: driver, | |
| 1640 | packageName: null, | |
| 1641 | }), | |
| 1642 | ), | |
| 1643 | getSnapshot: Effect.succeed(provider), | |
| 1644 | refresh: Effect.succeed(provider), | |
| 1645 | streamChanges: Stream.empty, | |
| 1646 | applyUsageLimits: () => Effect.void, | |
| 1647 | }, | |
| 1648 | snapshotForCwd, | |
| 1649 | invalidateCaches: Ref.update(cacheInvalidations, (count) => count + 1), | |
| 1650 | orchestrationAdapter: {} as ProviderInstance["orchestrationAdapter"], | |
| 1651 | textGeneration: {} as ProviderInstance["textGeneration"], | |
| 1652 | }); | |
| 1653 | const firstInstance = makeInstance(machineProvider, () => | |
| 1654 | Effect.gen(function* () { | |
| 1655 | yield* Ref.update(snapshotCalls, (count) => count + 1); | |
| 1656 | if (yield* Ref.get(returnPendingSnapshot)) return pendingScopedProvider; | |
| 1657 | yield* Deferred.succeed(probeStarted, undefined); | |
| 1658 | yield* Deferred.await(releaseProbe); | |
| 1659 | const result = yield* Ref.get(scopedResult); | |
| 1660 | const gate = yield* Ref.getAndSet(scanGate, null); | |
| 1661 | if (gate) { | |
| 1662 | yield* Deferred.succeed(gate.started, undefined); | |
| 1663 | yield* Deferred.await(gate.release); | |
| 1664 | } | |
| 1665 | return result; | |
| 1666 | }), | |
| 1667 | ); | |
| 1668 | const rebuiltProvider = { | |
| 1669 | ...machineProvider, | |
| 1670 | checkedAt: "2026-06-10T00:02:00.000Z", | |
| 1671 | status: "warning", | |
| 1672 | installed: false, | |
| 1673 | auth: { status: "unknown" }, | |
| 1674 | } satisfies ServerProvider; | |
| 1675 | const rebuiltInstance = makeInstance(rebuiltProvider, () => | |
| 1676 | Ref.update(snapshotCalls, (count) => count + 1).pipe(Effect.as(scopedProvider)), | |
| 1677 | ); | |
| 1678 | const registryChanges = yield* PubSub.unbounded<void>(); | |
| 1679 | const instancesRef = yield* Ref.make<ReadonlyArray<ProviderInstance>>([firstInstance]); | |
| 1680 | const instanceRegistryLayer = Layer.succeed( | |
| 1681 | ProviderInstanceRegistry.ProviderInstanceRegistry, | |
| 1682 | { | |
| 1683 | getInstance: (requestedId) => | |
| 1684 | Ref.get(instancesRef).pipe( | |
| 1685 | Effect.map((instances) => | |
| 1686 | instances.find((instance) => instance.instanceId === requestedId), | |
| 1687 | ), | |
| 1688 | ), | |
| 1689 | listInstances: Ref.get(instancesRef), | |
| 1690 | listUnavailable: Effect.succeed([]), | |
| 1691 | streamChanges: Stream.fromPubSub(registryChanges), | |
| 1692 | subscribeChanges: PubSub.subscribe(registryChanges), | |
| 1693 | }, | |
| 1694 | ); | |
| 1695 | const scope = yield* Scope.make(); | |
| 1696 | yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void)); | |
| 1697 | const runtimeServices = yield* Layer.build( | |
| 1698 | ProviderRegistryLive.pipe( | |
| 1699 | Layer.provideMerge(instanceRegistryLayer), | |
| 1700 | Layer.provideMerge( | |
| 1701 | ServerConfig.layerTest(process.cwd(), { | |
| 1702 | prefix: "t3-provider-registry-workspace-snapshot-", | |
| 1703 | }), | |
| 1704 | ), | |
| 1705 | Layer.provideMerge(NodeServices.layer), | |
| 1706 | ), | |
| 1707 | ).pipe(Scope.provide(scope)); | |
| 1708 | ||
| 1709 | yield* Effect.gen(function* () { | |
| 1710 | const registry = yield* ProviderRegistry.ProviderRegistry; | |
| 1711 | yield* registry.refreshWorkspaceSnapshot({ instanceId, cwd: "/workspace" }); | |
| 1712 | assert.strictEqual((yield* registry.getProviders)[0]?.workspaceSnapshots, undefined); | |
| 1713 | yield* Ref.set(returnPendingSnapshot, false); | |
| 1714 | const workspaceUpdate = yield* registry.streamChanges.pipe( | |
| 1715 | Stream.runHead, | |
| 1716 | Effect.forkChild, | |
| 1717 | ); | |
| 1718 | yield* Effect.yieldNow; | |
| 1719 | const firstRefresh = yield* registry | |
| 1720 | .refreshWorkspaceSnapshot({ instanceId, cwd: "/workspace" }) | |
| 1721 | .pipe(Effect.forkChild); | |
| 1722 | yield* Deferred.await(probeStarted); | |
| 1723 | const duplicateRefresh = yield* registry | |
| 1724 | .refreshWorkspaceSnapshot({ instanceId, cwd: "/workspace" }) | |
| 1725 | .pipe(Effect.forkChild); | |
| 1726 | yield* Effect.yieldNow; | |
| 1727 | assert.strictEqual(yield* Ref.get(snapshotCalls), 2); | |
| 1728 | yield* Deferred.succeed(releaseProbe, undefined); | |
| 1729 | yield* Fiber.join(firstRefresh); | |
| 1730 | yield* Fiber.join(duplicateRefresh); | |
| 1731 | const published = yield* Fiber.join(workspaceUpdate); | |
| 1732 | assert.strictEqual(published._tag, "Some"); | |
| 1733 | const providers = yield* registry.getProviders; | |
| 1734 | assert.deepStrictEqual(providers[0]?.skills, machineProvider.skills); | |
| 1735 | assert.deepStrictEqual( | |
| 1736 | providers[0]?.workspaceSnapshots?.[0]?.skills, | |
| 1737 | scopedProvider.skills, | |
| 1738 | ); | |
| 1739 | assert.deepStrictEqual(providers[0]?.workspaceSnapshots?.[0]?.slashCommands, [ | |
| 1740 | COMPACT_SLASH_COMMAND, | |
| 1741 | ]); | |
| 1742 | assert.strictEqual(providers[0]?.workspaceSnapshots?.[0]?.slashCommandsPending, true); | |
| 1743 | yield* Ref.set(scopedResult, { | |
| 1744 | ...scopedProvider, | |
| 1745 | status: "error", | |
| 1746 | slashCommandsPending: false, | |
| 1747 | }); | |
| 1748 | yield* registry.refreshWorkspaceSnapshot({ instanceId, cwd: "/workspace" }); | |
| 1749 | assert.strictEqual(yield* Ref.get(snapshotCalls), 3); | |
| 1750 | assert.deepStrictEqual( | |
| 1751 | (yield* registry.getProviders)[0]?.workspaceSnapshots?.[0]?.slashCommands, | |
| 1752 | scopedProvider.slashCommands, | |
| 1753 | ); | |
| 1754 | assert.strictEqual( | |
| 1755 | (yield* registry.getProviders)[0]?.workspaceSnapshots?.[0]?.slashCommandsPending, | |
| 1756 | undefined, | |
| 1757 | ); | |
| 1758 | yield* registry.refreshWorkspaceSnapshot({ instanceId, cwd: "/workspace" }); | |
| 1759 | assert.strictEqual(yield* Ref.get(snapshotCalls), 3); | |
| 1760 | const newSkills = [ | |
| 1761 | ...scopedProvider.skills, | |
| 1762 | { name: "added", path: "/workspace/added/SKILL.md", enabled: true }, | |
| 1763 | ]; | |
| 1764 | yield* Ref.set(scopedResult, { ...scopedProvider, skills: newSkills }); | |
| 1765 | yield* registry.refreshWorkspaceSnapshot({ | |
| 1766 | instanceId, | |
| 1767 | cwd: "/workspace", | |
| 1768 | fresh: true, | |
| 1769 | }); | |
| 1770 | assert.strictEqual(yield* Ref.get(snapshotCalls), 4); | |
| 1771 | assert.strictEqual(yield* Ref.get(cacheInvalidations), 1); | |
| 1772 | assert.deepStrictEqual( | |
| 1773 | (yield* registry.getProviders)[0]?.workspaceSnapshots?.map((s) => s.skills), | |
| 1774 | [newSkills], | |
| 1775 | ); | |
| 1776 | ||
| 1777 | // A slow fresh scan that read older files must not overwrite a | |
| 1778 | // newer scan that finished first. | |
| 1779 | const slowStarted = yield* Deferred.make<void>(); | |
| 1780 | const releaseSlow = yield* Deferred.make<void>(); | |
| 1781 | yield* Ref.set(scanGate, { started: slowStarted, release: releaseSlow }); | |
| 1782 | yield* Ref.set(scopedResult, scopedProvider); | |
| 1783 | const slowScan = yield* registry | |
| 1784 | .refreshWorkspaceSnapshot({ instanceId, cwd: "/workspace", fresh: true }) | |
| 1785 | .pipe(Effect.forkChild); | |
| 1786 | yield* Deferred.await(slowStarted); | |
| 1787 | const latestSkills = [ | |
| 1788 | ...newSkills, | |
| 1789 | { name: "latest", path: "/workspace/latest/SKILL.md", enabled: true }, | |
| 1790 | ]; | |
| 1791 | yield* Ref.set(scopedResult, { ...scopedProvider, skills: latestSkills }); | |
| 1792 | yield* registry.refreshWorkspaceSnapshot({ | |
| 1793 | instanceId, | |
| 1794 | cwd: "/workspace", | |
| 1795 | fresh: true, | |
| 1796 | }); | |
| 1797 | yield* Deferred.succeed(releaseSlow, undefined); | |
| 1798 | yield* Fiber.join(slowScan); | |
| 1799 | assert.deepStrictEqual( | |
| 1800 | (yield* registry.getProviders)[0]?.workspaceSnapshots?.map((s) => s.skills), | |
| 1801 | [latestSkills], | |
| 1802 | ); | |
| 1803 | ||
| 1804 | yield* Ref.set(instancesRef, [rebuiltInstance]); | |
| 1805 | yield* PubSub.publish(registryChanges, undefined); | |
| 1806 | let rebuilt = yield* registry.getProviders; | |
| 1807 | for ( | |
| 1808 | let attempt = 0; | |
| 1809 | attempt < 50 && rebuilt[0]?.checkedAt !== rebuiltProvider.checkedAt; | |
| 1810 | attempt += 1 | |
| 1811 | ) { | |
| 1812 | yield* Effect.yieldNow; | |
| 1813 | rebuilt = yield* registry.getProviders; | |
| 1814 | } | |
| 1815 | assert.strictEqual(rebuilt[0]?.checkedAt, rebuiltProvider.checkedAt); | |
| 1816 | assert.strictEqual(rebuilt[0]?.workspaceSnapshots, undefined); | |
| 1817 | }).pipe(Effect.provide(runtimeServices)); | |
| 1818 | }), | |
| 1819 | ); | |
| 1820 | ||
| 1821 | it.effect("refreshes OpenCode catalogs and preserves other providers", () => | |
| 1822 | Effect.gen(function* () { | |
| 1823 | const codexDriver = ProviderDriverKind.make("codex"); | |
| 1824 | const openCodeDriver = ProviderDriverKind.make("opencode"); | |
| 1825 | const codexInstanceId = ProviderInstanceId.make("codex"); | |
| 1826 | const openCodeInstanceId = ProviderInstanceId.make("opencode"); | |
| 1827 | const codexRefreshCalls = yield* Ref.make(0); | |
| 1828 | const openCodeRefreshCalls = yield* Ref.make(0); | |
| 1829 | const codexProvider = { | |
| 1830 | instanceId: codexInstanceId, | |
| 1831 | driver: codexDriver, | |
| 1832 | status: "ready", | |
| 1833 | enabled: true, | |
| 1834 | installed: true, | |
| 1835 | auth: { status: "authenticated" }, | |
| 1836 | checkedAt: "2026-06-10T00:00:00.000Z", | |
| 1837 | version: "1.0.0", | |
| 1838 | models: [], | |
| 1839 | slashCommands: [], | |
| 1840 | skills: [], | |
| 1841 | } as const satisfies ServerProvider; | |
| 1842 | const failedOpenCodeProvider = { | |
| 1843 | instanceId: openCodeInstanceId, | |
| 1844 | driver: openCodeDriver, | |
| 1845 | status: "error", | |
| 1846 | enabled: true, | |
| 1847 | installed: true, | |
| 1848 | auth: { status: "unknown" }, | |
| 1849 | checkedAt: "2026-06-10T00:00:00.000Z", | |
| 1850 | version: "1.0.0", | |
| 1851 | message: "Failed to refresh OpenCode models.", | |
| 1852 | models: [], | |
| 1853 | slashCommands: [], | |
| 1854 | skills: [], | |
| 1855 | } as const satisfies ServerProvider; | |
| 1856 | const recoveredOpenCodeProvider = { | |
| 1857 | ...failedOpenCodeProvider, | |
| 1858 | status: "ready", | |
| 1859 | auth: { status: "authenticated" }, | |
| 1860 | checkedAt: "2026-06-10T00:01:00.000Z", | |
| 1861 | message: "One upstream provider connected through OpenCode.", | |
| 1862 | models: [ | |
| 1863 | { | |
| 1864 | slug: "github/gpt-5", | |
| 1865 | name: "GPT-5", | |
| 1866 | subProvider: "GitHub", | |
| 1867 | isCustom: false, | |
| 1868 | capabilities: null, | |
| 1869 | }, | |
| 1870 | ], | |
| 1871 | } as const satisfies ServerProvider; | |
| 1872 | const changedCatalogProvider = { | |
| 1873 | ...recoveredOpenCodeProvider, | |
| 1874 | checkedAt: "2026-06-10T00:02:00.000Z", | |
| 1875 | models: [ | |
| 1876 | { | |
| 1877 | slug: "anthropic/claude-sonnet-4", | |
| 1878 | name: "Claude Sonnet 4", | |
| 1879 | subProvider: "Anthropic", | |
| 1880 | isCustom: false, | |
| 1881 | capabilities: null, | |
| 1882 | }, | |
| 1883 | ], | |
| 1884 | } as const satisfies ServerProvider; | |
| 1885 | const catalogSnapshot = yield* Ref.make<ServerProvider>(recoveredOpenCodeProvider); | |
| 1886 | const instances = [ | |
| 1887 | { | |
| 1888 | instanceId: codexInstanceId, | |
| 1889 | driverKind: codexDriver, | |
| 1890 | continuationIdentity: { | |
| 1891 | driverKind: codexDriver, | |
| 1892 | continuationKey: "codex:instance:codex", | |
| 1893 | }, | |
| 1894 | displayName: undefined, | |
| 1895 | enabled: true, | |
| 1896 | snapshot: { | |
| 1897 | resolveMaintenance: () => | |
| 1898 | Effect.succeed( | |
| 1899 | makeManualOnlyProviderMaintenanceCapabilities({ | |
| 1900 | provider: codexDriver, | |
| 1901 | packageName: null, | |
| 1902 | }), | |
| 1903 | ), | |
| 1904 | getSnapshot: Effect.succeed(codexProvider), | |
| 1905 | refresh: Ref.update(codexRefreshCalls, (count) => count + 1).pipe( | |
| 1906 | Effect.as(codexProvider), | |
| 1907 | ), | |
| 1908 | streamChanges: Stream.empty, | |
| 1909 | applyUsageLimits: () => Effect.void, | |
| 1910 | }, | |
| 1911 | orchestrationAdapter: {} as ProviderInstance["orchestrationAdapter"], | |
| 1912 | textGeneration: {} as ProviderInstance["textGeneration"], | |
| 1913 | }, | |
| 1914 | { | |
| 1915 | instanceId: openCodeInstanceId, | |
| 1916 | driverKind: openCodeDriver, | |
| 1917 | continuationIdentity: { | |
| 1918 | driverKind: openCodeDriver, | |
| 1919 | continuationKey: "opencode:instance:opencode", | |
| 1920 | }, | |
| 1921 | displayName: undefined, | |
| 1922 | enabled: true, | |
| 1923 | snapshot: { | |
| 1924 | resolveMaintenance: () => | |
| 1925 | Effect.succeed( | |
| 1926 | makeManualOnlyProviderMaintenanceCapabilities({ | |
| 1927 | provider: openCodeDriver, | |
| 1928 | packageName: null, | |
| 1929 | }), | |
| 1930 | ), | |
| 1931 | getSnapshot: Effect.succeed(failedOpenCodeProvider), | |
| 1932 | refresh: Ref.update(openCodeRefreshCalls, (count) => count + 1).pipe( | |
| 1933 | Effect.andThen(Ref.get(catalogSnapshot)), | |
| 1934 | ), | |
| 1935 | streamChanges: Stream.empty, | |
| 1936 | applyUsageLimits: () => Effect.void, | |
| 1937 | }, | |
| 1938 | orchestrationAdapter: {} as ProviderInstance["orchestrationAdapter"], | |
| 1939 | textGeneration: {} as ProviderInstance["textGeneration"], | |
| 1940 | }, | |
| 1941 | ] satisfies ReadonlyArray<ProviderInstance>; | |
| 1942 | const instanceRegistryLayer = Layer.succeed( | |
| 1943 | ProviderInstanceRegistry.ProviderInstanceRegistry, | |
| 1944 | { | |
| 1945 | getInstance: (instanceId) => | |
| 1946 | Effect.succeed(instances.find((instance) => instance.instanceId === instanceId)), | |
| 1947 | listInstances: Effect.succeed(instances), | |
| 1948 | listUnavailable: Effect.succeed([]), | |
| 1949 | streamChanges: Stream.empty, | |
| 1950 | subscribeChanges: Effect.flatMap(PubSub.unbounded<void>(), PubSub.subscribe), | |
| 1951 | }, | |
| 1952 | ); | |
| 1953 | const scope = yield* Scope.make(); | |
| 1954 | yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void)); | |
| 1955 | const runtimeServices = yield* Layer.build( | |
| 1956 | ProviderRegistryLive.pipe( | |
| 1957 | Layer.provideMerge(instanceRegistryLayer), | |
| 1958 | Layer.provideMerge( | |
| 1959 | ServerConfig.layerTest(process.cwd(), { | |
| 1960 | prefix: "t3-provider-registry-reconnect-refresh-", | |
| 1961 | }), | |
| 1962 | ), | |
| 1963 | Layer.provideMerge(NodeServices.layer), | |
| 1964 | ), | |
| 1965 | ).pipe(Scope.provide(scope)); | |
| 1966 | ||
| 1967 | yield* Effect.gen(function* () { | |
| 1968 | const registry = yield* ProviderRegistry.ProviderRegistry; | |
| 1969 | const initialProviders = yield* registry.getProviders; | |
| 1970 | assert.strictEqual( | |
| 1971 | initialProviders.find((provider) => provider.instanceId === openCodeInstanceId) | |
| 1972 | ?.status, | |
| 1973 | "error", | |
| 1974 | ); | |
| 1975 | ||
| 1976 | const recoveredProviders = yield* registry.refresh(); | |
| 1977 | assert.deepStrictEqual( | |
| 1978 | recoveredProviders.find((provider) => provider.instanceId === openCodeInstanceId) | |
| 1979 | ?.models, | |
| 1980 | recoveredOpenCodeProvider.models, | |
| 1981 | ); | |
| 1982 | assert.deepStrictEqual( | |
| 1983 | recoveredProviders.find((provider) => provider.instanceId === codexInstanceId), | |
| 1984 | withBundledCompatibility(codexProvider), | |
| 1985 | ); | |
| 1986 | ||
| 1987 | yield* Ref.set(catalogSnapshot, changedCatalogProvider); | |
| 1988 | const changedProviders = yield* registry.refresh(); | |
| 1989 | assert.deepStrictEqual( | |
| 1990 | changedProviders.find((provider) => provider.instanceId === openCodeInstanceId) | |
| 1991 | ?.models, | |
| 1992 | changedCatalogProvider.models, | |
| 1993 | ); | |
| 1994 | assert.deepStrictEqual( | |
| 1995 | changedProviders.find((provider) => provider.instanceId === codexInstanceId), | |
| 1996 | withBundledCompatibility(codexProvider), | |
| 1997 | ); | |
| 1998 | }).pipe(Effect.provide(runtimeServices)); | |
| 1999 | ||
| 2000 | assert.strictEqual(yield* Ref.get(codexRefreshCalls), 2); | |
| 2001 | assert.strictEqual(yield* Ref.get(openCodeRefreshCalls), 2); | |
| 2002 | }), | |
| 2003 | ); | |
| 2004 | ||
| 2005 | it("persists merged provider snapshots for the providers that were refreshed", () => { | |
| 2006 | const previousProviders = [ | |
| 2007 | { | |
| 2008 | instanceId: ProviderInstanceId.make("cursor"), | |
| 2009 | driver: ProviderDriverKind.make("cursor"), | |
| 2010 | status: "ready", | |
| 2011 | enabled: true, | |
| 2012 | installed: true, | |
| 2013 | auth: { status: "authenticated" }, | |
| 2014 | checkedAt: "2026-04-14T00:00:00.000Z", | |
| 2015 | version: "2026.04.09-f2b0fcd", | |
| 2016 | models: [ | |
| 2017 | { | |
| 2018 | slug: "claude-opus-4-6", | |
| 2019 | name: "Opus 4.6", | |
| 2020 | isCustom: false, | |
| 2021 | capabilities: createModelCapabilities({ | |
| 2022 | optionDescriptors: [ | |
| 2023 | selectDescriptor("reasoning", "Reasoning", [ | |
| 2024 | { id: "high", label: "High", isDefault: true }, | |
| 2025 | ]), | |
| 2026 | booleanDescriptor("fastMode", "Fast Mode"), | |
| 2027 | booleanDescriptor("thinking", "Thinking"), | |
| 2028 | ], | |
| 2029 | }), | |
| 2030 | }, | |
| 2031 | ], | |
| 2032 | slashCommands: [], | |
| 2033 | skills: [], | |
| 2034 | }, | |
| 2035 | { | |
| 2036 | instanceId: ProviderInstanceId.make("codex"), | |
| 2037 | driver: ProviderDriverKind.make("codex"), | |
| 2038 | status: "ready", | |
| 2039 | enabled: true, | |
| 2040 | installed: true, | |
| 2041 | auth: { status: "authenticated" }, | |
| 2042 | checkedAt: "2026-04-14T00:00:00.000Z", | |
| 2043 | version: "1.0.0", | |
| 2044 | models: [], | |
| 2045 | slashCommands: [], | |
| 2046 | skills: [], | |
| 2047 | }, | |
| 2048 | ] as const satisfies ReadonlyArray<ServerProvider>; | |
| 2049 | const refreshedCursor = { | |
| 2050 | ...previousProviders[0], | |
| 2051 | checkedAt: "2026-04-14T00:01:00.000Z", | |
| 2052 | models: [], | |
| 2053 | } satisfies ServerProvider; | |
| 2054 | ||
| 2055 | const mergedProviders = mergeProviderSnapshots(previousProviders, [refreshedCursor]); | |
| 2056 | const persistedProviders = selectProvidersByKind( | |
| 2057 | mergedProviders, | |
| 2058 | new Set([ProviderDriverKind.make("cursor")]), | |
| 2059 | ); | |
| 2060 | ||
| 2061 | assert.deepStrictEqual(persistedProviders, [ | |
| 2062 | { | |
| 2063 | ...refreshedCursor, | |
| 2064 | models: [...previousProviders[0].models], | |
| 2065 | }, | |
| 2066 | ]); | |
| 2067 | }); | |
| 2068 | ||
| 2069 | it.effect("persists the merged snapshot when a live update has empty models", () => | |
| 2070 | Effect.gen(function* () { | |
| 2071 | const cursorDriver = ProviderDriverKind.make("cursor"); | |
| 2072 | const cursorInstanceId = ProviderInstanceId.make("cursor"); | |
| 2073 | const initialProvider = { | |
| 2074 | instanceId: cursorInstanceId, | |
| 2075 | driver: cursorDriver, | |
| 2076 | status: "ready", | |
| 2077 | enabled: true, | |
| 2078 | installed: true, | |
| 2079 | auth: { status: "authenticated" }, | |
| 2080 | checkedAt: "2026-04-14T00:00:00.000Z", | |
| 2081 | version: "2026.04.09-f2b0fcd", | |
| 2082 | models: [ | |
| 1086 | it("keeps discovered models during startup and failed probes without restoring removed custom models", () => { | |
| 1087 | for (const provider of [pendingProvider, failedProvider]) { | |
| 1088 | assert.deepStrictEqual( | |
| 1089 | mergeProviderSnapshot( | |
| 2083 | 1090 | { |
| 2084 | slug: "claude-opus-4-6", | |
| 2085 | name: "Opus 4.6", | |
| 2086 | isCustom: false, | |
| 2087 | capabilities: createModelCapabilities({ | |
| 2088 | optionDescriptors: [ | |
| 2089 | selectDescriptor("reasoning", "Reasoning", [ | |
| 2090 | { id: "high", label: "High", isDefault: true }, | |
| 2091 | ]), | |
| 2092 | ], | |
| 2093 | }), | |
| 1091 | ...cachedProvider, | |
| 1092 | models: [...cachedProvider.models, { ...customModel, slug: "removed-custom" }], | |
| 2094 | 1093 | }, |
| 2095 | ], | |
| 2096 | slashCommands: [], | |
| 2097 | skills: [], | |
| 2098 | } as const satisfies ServerProvider; | |
| 2099 | const refreshedProvider = { | |
| 2100 | ...initialProvider, | |
| 2101 | checkedAt: "2026-04-14T00:01:00.000Z", | |
| 2102 | models: [], | |
| 2103 | } satisfies ServerProvider; | |
| 2104 | const changes = yield* PubSub.unbounded<ServerProvider>(); | |
| 1094 | provider, | |
| 1095 | ).models, | |
| 1096 | [customModel, ...cachedProvider.models], | |
| 1097 | ); | |
| 1098 | } | |
| 1099 | }); | |
| 1100 | ||
| 1101 | it("clears discovered models after sign-out, disable, uninstall, or empty discovery", () => { | |
| 1102 | const emptyProvider = { ...refreshedProvider, models: [customModel] }; | |
| 1103 | const clearedProviders = [ | |
| 1104 | { ...emptyProvider, status: "error", auth: { status: "unauthenticated" } }, | |
| 1105 | { ...emptyProvider, status: "disabled", enabled: false }, | |
| 1106 | { ...emptyProvider, status: "error", installed: false, auth: { status: "unknown" } }, | |
| 1107 | emptyProvider, | |
| 1108 | { ...emptyProvider, models: [] }, | |
| 1109 | ] satisfies ReadonlyArray<ServerProvider>; | |
| 1110 | ||
| 1111 | for (const provider of clearedProviders) { | |
| 1112 | assert.deepStrictEqual( | |
| 1113 | mergeProviderSnapshot(cachedProvider, provider).models, | |
| 1114 | provider.models, | |
| 1115 | ); | |
| 1116 | } | |
| 1117 | }); | |
| 1118 | ||
| 1119 | it.effect("persists removals across failed refreshes and registry restarts", () => | |
| 1120 | Effect.gen(function* () { | |
| 1121 | const config = yield* ServerConfig.ServerConfig; | |
| 1122 | const filePath = yield* resolveProviderStatusCachePath({ | |
| 1123 | cacheDir: config.providerStatusCacheDir, | |
| 1124 | instanceId: cachedProvider.instanceId, | |
| 1125 | }); | |
| 1126 | yield* writeProviderStatusCache({ filePath, provider: cachedProvider }); | |
| 1127 | const nextProvider = yield* Ref.make<ServerProvider>(refreshedProvider); | |
| 2105 | 1128 | const instance = { |
| 2106 | instanceId: cursorInstanceId, | |
| 2107 | driverKind: cursorDriver, | |
| 1129 | instanceId: cachedProvider.instanceId, | |
| 1130 | driverKind: cachedProvider.driver, | |
| 2108 | 1131 | continuationIdentity: { |
| 2109 | driverKind: cursorDriver, | |
| 2110 | continuationKey: "cursor:instance:cursor", | |
| 1132 | driverKind: cachedProvider.driver, | |
| 1133 | continuationKey: "codex:instance:codex-personal", | |
| 2111 | 1134 | }, |
| 2112 | 1135 | displayName: undefined, |
| 2113 | 1136 | enabled: true, |
| ... | ... | @@ -2115,225 +1138,728 @@ it.layer(Layer.mergeAll(TestNodeServices, ServerSettingsModule.layerTest(), Test |
| 2115 | 1138 | resolveMaintenance: () => |
| 2116 | 1139 | Effect.succeed( |
| 2117 | 1140 | makeManualOnlyProviderMaintenanceCapabilities({ |
| 2118 | provider: cursorDriver, | |
| 1141 | provider: cachedProvider.driver, | |
| 2119 | 1142 | packageName: null, |
| 2120 | 1143 | }), |
| 2121 | 1144 | ), |
| 2122 | getSnapshot: Effect.succeed(initialProvider), | |
| 2123 | refresh: Effect.succeed(refreshedProvider), | |
| 2124 | streamChanges: Stream.fromPubSub(changes), | |
| 1145 | getSnapshot: Effect.succeed(pendingProvider), | |
| 1146 | refresh: Ref.get(nextProvider), | |
| 1147 | streamChanges: Stream.empty, | |
| 2125 | 1148 | applyUsageLimits: () => Effect.void, |
| 2126 | 1149 | }, |
| 2127 | 1150 | orchestrationAdapter: {} as ProviderInstance["orchestrationAdapter"], |
| 2128 | 1151 | textGeneration: {} as ProviderInstance["textGeneration"], |
| 2129 | 1152 | } satisfies ProviderInstance; |
| 2130 | const instanceRegistryLayer = Layer.succeed( | |
| 1153 | const layerInstanceRegistry = Layer.succeed( | |
| 2131 | 1154 | ProviderInstanceRegistry.ProviderInstanceRegistry, |
| 2132 | 1155 | { |
| 2133 | getInstance: (instanceId) => | |
| 2134 | Effect.succeed(instanceId === cursorInstanceId ? instance : undefined), | |
| 1156 | getInstance: (id) => | |
| 1157 | Effect.succeed(id === instance.instanceId ? instance : undefined), | |
| 2135 | 1158 | listInstances: Effect.succeed([instance]), |
| 2136 | 1159 | listUnavailable: Effect.succeed([]), |
| 2137 | 1160 | streamChanges: Stream.empty, |
| 2138 | subscribeChanges: Effect.flatMap(PubSub.unbounded<void>(), (pubsub) => | |
| 2139 | PubSub.subscribe(pubsub), | |
| 2140 | ), | |
| 1161 | subscribeChanges: Effect.flatMap(PubSub.unbounded<void>(), PubSub.subscribe), | |
| 2141 | 1162 | }, |
| 2142 | 1163 | ); |
| 2143 | const scope = yield* Scope.make(); | |
| 2144 | yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void)); | |
| 2145 | const runtimeServices = yield* Layer.build( | |
| 2146 | ProviderRegistryLive.pipe( | |
| 2147 | Layer.provideMerge(instanceRegistryLayer), | |
| 2148 | Layer.provideMerge( | |
| 2149 | ServerConfig.layerTest(process.cwd(), { | |
| 2150 | prefix: "t3-provider-registry-merged-persist-", | |
| 2151 | }), | |
| 2152 | ), | |
| 2153 | Layer.provideMerge(BackgroundPolicyAlwaysRunLayer), | |
| 2154 | Layer.provideMerge(NodeServices.layer), | |
| 2155 | ), | |
| 2156 | ).pipe(Scope.provide(scope)); | |
| 1164 | const retainedModels = [ | |
| 1165 | customModel, | |
| 1166 | ...refreshedProvider.models.filter((model) => !model.isCustom), | |
| 1167 | ]; | |
| 2157 | 1168 | |
| 2158 | yield* Effect.gen(function* () { | |
| 2159 | const registry = yield* ProviderRegistry.ProviderRegistry; | |
| 2160 | const config = yield* ServerConfig.ServerConfig; | |
| 2161 | const filePath = yield* resolveProviderStatusCachePath({ | |
| 2162 | cacheDir: config.providerStatusCacheDir, | |
| 2163 | instanceId: cursorInstanceId, | |
| 2164 | }); | |
| 1169 | for (const restarted of [false, true]) { | |
| 1170 | yield* Effect.gen(function* () { | |
| 1171 | const registry = yield* ProviderRegistry.ProviderRegistry; | |
| 1172 | const expectedModels = restarted | |
| 1173 | ? retainedModels | |
| 1174 | : [customModel, ...cachedProvider.models]; | |
| 1175 | assert.deepStrictEqual((yield* registry.getProviders)[0]?.models, expectedModels); | |
| 1176 | ||
| 1177 | yield* registry.refreshInstance(instance.instanceId); | |
| 1178 | assert.deepStrictEqual( | |
| 1179 | (yield* readProviderStatusCache(filePath))?.models, | |
| 1180 | restarted ? retainedModels : refreshedProvider.models, | |
| 1181 | ); | |
| 2165 | 1182 | |
| 2166 | assert.deepStrictEqual((yield* registry.getProviders)[0]?.models, [ | |
| 2167 | ...initialProvider.models, | |
| 2168 | ]); | |
| 2169 | const persisted = yield* awaitPersistedProvider(registry, refreshedProvider.checkedAt); | |
| 2170 | yield* PubSub.publish(changes, refreshedProvider); | |
| 2171 | yield* Fiber.join(persisted); | |
| 2172 | const cachedProvider = yield* readProviderStatusCache(filePath); | |
| 2173 | ||
| 2174 | assert.deepStrictEqual( | |
| 2175 | cachedProvider, | |
| 2176 | withBundledCompatibility({ | |
| 2177 | ...refreshedProvider, | |
| 2178 | models: [...initialProvider.models], | |
| 2179 | }), | |
| 1183 | yield* Ref.set(nextProvider, failedProvider); | |
| 1184 | const afterFailure = yield* registry.refreshInstance(instance.instanceId); | |
| 1185 | assert.deepStrictEqual(afterFailure[0]?.models, retainedModels); | |
| 1186 | assert.deepStrictEqual( | |
| 1187 | (yield* readProviderStatusCache(filePath))?.models, | |
| 1188 | retainedModels, | |
| 1189 | ); | |
| 1190 | }).pipe( | |
| 1191 | Effect.provide( | |
| 1192 | ProviderRegistryLayer.layer.pipe(Layer.provide(layerInstanceRegistry)), | |
| 1193 | ), | |
| 1194 | Effect.scoped, | |
| 2180 | 1195 | ); |
| 2181 | }).pipe(Effect.provide(runtimeServices)); | |
| 2182 | }), | |
| 1196 | } | |
| 1197 | }).pipe( | |
| 1198 | Effect.provide( | |
| 1199 | ServerConfig.layerTest(process.cwd(), { | |
| 1200 | prefix: "t3-codex-retired-model-cache-", | |
| 1201 | }).pipe(Layer.provideMerge(NodeServices.layer)), | |
| 1202 | ), | |
| 1203 | ), | |
| 2183 | 1204 | ); |
| 1205 | }); | |
| 2184 | 1206 | |
| 2185 | it.effect( | |
| 2186 | "persists authoritative OpenCode removals without resurrecting them on a failed live refresh", | |
| 2187 | () => | |
| 2188 | Effect.gen(function* () { | |
| 2189 | const openCodeDriver = ProviderDriverKind.make("opencode"); | |
| 2190 | const openCodeInstanceId = ProviderInstanceId.make("opencode"); | |
| 2191 | const initialProvider = { | |
| 2192 | instanceId: openCodeInstanceId, | |
| 2193 | driver: openCodeDriver, | |
| 2194 | status: "ready", | |
| 2195 | enabled: true, | |
| 2196 | installed: true, | |
| 2197 | auth: { status: "authenticated" }, | |
| 2198 | checkedAt: "2026-07-17T00:00:00.000Z", | |
| 2199 | version: "1.0.0", | |
| 2200 | models: [ | |
| 2201 | { | |
| 2202 | slug: "github/gpt-5", | |
| 2203 | name: "GPT-5", | |
| 2204 | subProvider: "GitHub", | |
| 2205 | isCustom: false, | |
| 2206 | capabilities: null, | |
| 2207 | }, | |
| 2208 | { | |
| 2209 | slug: "removed-plugin/model", | |
| 2210 | name: "Removed Plugin Model", | |
| 2211 | subProvider: "Removed Plugin", | |
| 2212 | isCustom: false, | |
| 2213 | capabilities: null, | |
| 2214 | }, | |
| 1207 | describe("Antigravity model inventories", () => { | |
| 1208 | const previousProvider = { | |
| 1209 | instanceId: ProviderInstanceId.make("antigravity-personal"), | |
| 1210 | driver: ProviderDriverKind.make("antigravity"), | |
| 1211 | status: "ready", | |
| 1212 | enabled: true, | |
| 1213 | installed: true, | |
| 1214 | auth: { status: "authenticated" }, | |
| 1215 | checkedAt: "2026-09-02T00:00:00.000Z", | |
| 1216 | version: "0.1.3", | |
| 1217 | models: [ | |
| 1218 | { | |
| 1219 | slug: "gemini-3.1-pro-high", | |
| 1220 | name: "Gemini 3.1 Pro High", | |
| 1221 | isCustom: false, | |
| 1222 | capabilities: null, | |
| 1223 | }, | |
| 1224 | { | |
| 1225 | slug: "gemini-3-flash", | |
| 1226 | name: "Gemini 3 Flash", | |
| 1227 | isCustom: false, | |
| 1228 | capabilities: null, | |
| 1229 | }, | |
| 1230 | ], | |
| 1231 | slashCommands: [], | |
| 1232 | skills: [], | |
| 1233 | } as const satisfies ServerProvider; | |
| 1234 | ||
| 1235 | it("removes unavailable models after a successful refresh", () => { | |
| 1236 | for (const status of ["ready", "warning"] as const) { | |
| 1237 | const refreshedProvider = { | |
| 1238 | ...previousProvider, | |
| 1239 | status, | |
| 1240 | checkedAt: "2026-09-02T00:01:00.000Z", | |
| 1241 | models: [previousProvider.models[1]], | |
| 1242 | } satisfies ServerProvider; | |
| 1243 | const afterRefresh = mergeProviderSnapshot(previousProvider, refreshedProvider); | |
| 1244 | ||
| 1245 | assert.deepStrictEqual(afterRefresh.models, refreshedProvider.models); | |
| 1246 | ||
| 1247 | const afterFailure = mergeProviderSnapshot(afterRefresh, { | |
| 1248 | ...refreshedProvider, | |
| 1249 | status: "error", | |
| 1250 | auth: { status: "unknown" }, | |
| 1251 | models: [], | |
| 1252 | }); | |
| 1253 | assert.deepStrictEqual(afterFailure.models, refreshedProvider.models); | |
| 1254 | } | |
| 1255 | }); | |
| 1256 | ||
| 1257 | it("keeps cached models during health checks and temporary failures", () => { | |
| 1258 | for (const installed of [false, true]) { | |
| 1259 | const pendingProvider = { | |
| 1260 | ...previousProvider, | |
| 1261 | status: "warning", | |
| 1262 | installed, | |
| 1263 | auth: { status: "unknown" }, | |
| 1264 | checkedAt: "2026-09-02T00:01:00.000Z", | |
| 1265 | version: installed ? previousProvider.version : null, | |
| 1266 | models: [], | |
| 1267 | } satisfies ServerProvider; | |
| 1268 | ||
| 1269 | assert.deepStrictEqual( | |
| 1270 | mergeProviderSnapshot(previousProvider, pendingProvider).models, | |
| 1271 | previousProvider.models, | |
| 1272 | ); | |
| 1273 | } | |
| 1274 | ||
| 1275 | for (const authStatus of ["unknown", "authenticated"] as const) { | |
| 1276 | const failedProvider = { | |
| 1277 | ...previousProvider, | |
| 1278 | status: "error", | |
| 1279 | auth: { status: authStatus }, | |
| 1280 | checkedAt: "2026-09-02T00:02:00.000Z", | |
| 1281 | models: [], | |
| 1282 | } satisfies ServerProvider; | |
| 1283 | ||
| 1284 | assert.deepStrictEqual( | |
| 1285 | mergeProviderSnapshot(previousProvider, failedProvider).models, | |
| 1286 | previousProvider.models, | |
| 1287 | ); | |
| 1288 | } | |
| 1289 | }); | |
| 1290 | ||
| 1291 | it("clears models after sign-out, disable, uninstall, or an empty successful refresh", () => { | |
| 1292 | const emptyProvider = { | |
| 1293 | ...previousProvider, | |
| 1294 | checkedAt: "2026-09-02T00:01:00.000Z", | |
| 1295 | models: [], | |
| 1296 | } satisfies ServerProvider; | |
| 1297 | const clearedProviders = [ | |
| 1298 | { ...emptyProvider, status: "warning", auth: { status: "unauthenticated" } }, | |
| 1299 | { ...emptyProvider, status: "error", auth: { status: "unauthenticated" } }, | |
| 1300 | { ...emptyProvider, status: "disabled", enabled: false }, | |
| 1301 | { ...emptyProvider, status: "error", enabled: false }, | |
| 1302 | { ...emptyProvider, status: "error", installed: false, auth: { status: "unknown" } }, | |
| 1303 | emptyProvider, | |
| 1304 | ] satisfies ReadonlyArray<ServerProvider>; | |
| 1305 | ||
| 1306 | for (const provider of clearedProviders) { | |
| 1307 | const afterRemoval = mergeProviderSnapshot(previousProvider, provider); | |
| 1308 | assert.deepStrictEqual(afterRemoval.models, []); | |
| 1309 | ||
| 1310 | const afterFailure = mergeProviderSnapshot(afterRemoval, { | |
| 1311 | ...emptyProvider, | |
| 1312 | status: "error", | |
| 1313 | auth: { status: "unknown" }, | |
| 1314 | }); | |
| 1315 | assert.deepStrictEqual(afterFailure.models, []); | |
| 1316 | } | |
| 1317 | }); | |
| 1318 | }); | |
| 1319 | ||
| 1320 | describe("Antigravity saved account", () => { | |
| 1321 | const signedIn = { | |
| 1322 | instanceId: ProviderInstanceId.make("antigravity-personal"), | |
| 1323 | driver: ProviderDriverKind.make("antigravity"), | |
| 1324 | status: "ready", | |
| 1325 | enabled: true, | |
| 1326 | installed: true, | |
| 1327 | auth: { status: "authenticated", type: "oauth-personal", label: "Google account" }, | |
| 1328 | checkedAt: "2026-09-05T00:00:00.000Z", | |
| 1329 | version: "agy_acp_server_1.1.1", | |
| 1330 | models: [ | |
| 1331 | { | |
| 1332 | slug: "gemini-3.7-flash-high", | |
| 1333 | name: "Gemini 3.7 Flash", | |
| 1334 | isCustom: false, | |
| 1335 | capabilities: null, | |
| 1336 | }, | |
| 1337 | ], | |
| 1338 | slashCommands: [{ name: "plan" }], | |
| 1339 | skills: [], | |
| 1340 | } as const satisfies ServerProvider; | |
| 1341 | const uncheckedMessage = | |
| 1342 | "Antigravity is installed. Google account access is not checked yet."; | |
| 1343 | const restartProbe = { | |
| 1344 | ...signedIn, | |
| 1345 | status: "warning", | |
| 1346 | auth: { status: "unknown" }, | |
| 1347 | checkedAt: "2026-09-05T00:01:00.000Z", | |
| 1348 | message: uncheckedMessage, | |
| 1349 | models: [], | |
| 1350 | } as const satisfies ServerProvider; | |
| 1351 | ||
| 1352 | it("keeps the saved Google account through restart health checks", () => { | |
| 1353 | const merged = mergeProviderSnapshot(signedIn, restartProbe); | |
| 1354 | const { message: _uncheckedMessage, ...probeWithoutMessage } = restartProbe; | |
| 1355 | assert.deepStrictEqual(merged, { | |
| 1356 | ...probeWithoutMessage, | |
| 1357 | status: "ready", | |
| 1358 | auth: signedIn.auth, | |
| 1359 | models: signedIn.models, | |
| 1360 | }); | |
| 1361 | assert.equal("message" in merged, false); | |
| 1362 | // The next periodic probe reads the merged snapshot as its previous state. | |
| 1363 | assert.deepStrictEqual(mergeProviderSnapshot(merged, restartProbe), merged); | |
| 1364 | }); | |
| 1365 | ||
| 1366 | it("carries the account through the boot probe and a failed probe without hiding them", () => { | |
| 1367 | const booting = { | |
| 1368 | ...restartProbe, | |
| 1369 | installed: false, | |
| 1370 | version: null, | |
| 1371 | message: "Checking Antigravity availability.", | |
| 1372 | } satisfies ServerProvider; | |
| 1373 | assert.deepStrictEqual(mergeProviderSnapshot(signedIn, booting), { | |
| 1374 | ...booting, | |
| 1375 | auth: signedIn.auth, | |
| 1376 | models: signedIn.models, | |
| 1377 | }); | |
| 1378 | ||
| 1379 | const failed = { | |
| 1380 | ...restartProbe, | |
| 1381 | status: "error", | |
| 1382 | message: "Antigravity did not respond to its local health check within 90 seconds.", | |
| 1383 | } satisfies ServerProvider; | |
| 1384 | assert.deepStrictEqual(mergeProviderSnapshot(signedIn, failed), { | |
| 1385 | ...failed, | |
| 1386 | auth: signedIn.auth, | |
| 1387 | models: signedIn.models, | |
| 1388 | }); | |
| 1389 | }); | |
| 1390 | ||
| 1391 | it("does not invent an account after sign-out, disable, uninstall, or for other providers", () => { | |
| 1392 | const untouched = [ | |
| 1393 | { ...restartProbe, auth: { status: "unauthenticated" } }, | |
| 1394 | { ...restartProbe, status: "disabled", enabled: false }, | |
| 1395 | { ...restartProbe, status: "error", installed: false }, | |
| 1396 | { ...restartProbe, driver: ProviderDriverKind.make("codex") }, | |
| 1397 | // The instance was rebuilt with another sign-in method. | |
| 1398 | { ...restartProbe, auth: { status: "unknown", type: "gemini-api-key" } }, | |
| 1399 | ] satisfies ReadonlyArray<ServerProvider>; | |
| 1400 | for (const next of untouched) { | |
| 1401 | const merged = mergeProviderSnapshot(signedIn, next); | |
| 1402 | assert.deepStrictEqual(merged.auth, next.auth); | |
| 1403 | assert.equal(merged.status, next.status); | |
| 1404 | assert.equal(merged.message, next.message); | |
| 1405 | } | |
| 1406 | assert.deepStrictEqual( | |
| 1407 | mergeProviderSnapshot({ ...signedIn, auth: { status: "unknown" } }, restartProbe).auth, | |
| 1408 | { status: "unknown" }, | |
| 1409 | ); | |
| 1410 | assert.equal( | |
| 1411 | mergeProviderSnapshot( | |
| 1412 | { ...signedIn, driver: ProviderDriverKind.make("codex") }, | |
| 1413 | restartProbe, | |
| 1414 | ).auth.status, | |
| 1415 | "unknown", | |
| 1416 | ); | |
| 1417 | assert.deepStrictEqual( | |
| 1418 | mergeProviderSnapshot(signedIn, { | |
| 1419 | ...restartProbe, | |
| 1420 | auth: { status: "unknown", type: "oauth-personal" }, | |
| 1421 | }).auth, | |
| 1422 | signedIn.auth, | |
| 1423 | ); | |
| 1424 | }); | |
| 1425 | }); | |
| 1426 | ||
| 1427 | it("fills missing capabilities from the previous provider snapshot", () => { | |
| 1428 | const previousProvider = { | |
| 1429 | instanceId: ProviderInstanceId.make("cursor"), | |
| 1430 | driver: ProviderDriverKind.make("cursor"), | |
| 1431 | status: "ready", | |
| 1432 | enabled: true, | |
| 1433 | installed: true, | |
| 1434 | auth: { status: "authenticated" }, | |
| 1435 | checkedAt: "2026-04-14T00:00:00.000Z", | |
| 1436 | version: "2026.04.09-f2b0fcd", | |
| 1437 | models: [ | |
| 1438 | { | |
| 1439 | slug: "claude-opus-4-6", | |
| 1440 | name: "Opus 4.6", | |
| 1441 | isCustom: false, | |
| 1442 | capabilities: createModelCapabilities({ | |
| 1443 | optionDescriptors: [ | |
| 1444 | selectDescriptor("reasoning", "Reasoning", [ | |
| 1445 | { id: "high", label: "High", isDefault: true }, | |
| 1446 | ]), | |
| 1447 | booleanDescriptor("fastMode", "Fast Mode"), | |
| 1448 | booleanDescriptor("thinking", "Thinking"), | |
| 2215 | 1449 | ], |
| 2216 | slashCommands: [], | |
| 2217 | skills: [], | |
| 2218 | } as const satisfies ServerProvider; | |
| 2219 | const authoritativeProvider = { | |
| 2220 | ...initialProvider, | |
| 2221 | checkedAt: "2026-07-17T00:01:00.000Z", | |
| 2222 | models: [initialProvider.models[0]!], | |
| 2223 | } satisfies ServerProvider; | |
| 2224 | const failedProvider = { | |
| 2225 | ...authoritativeProvider, | |
| 2226 | status: "error", | |
| 2227 | auth: { status: "unknown" }, | |
| 2228 | checkedAt: "2026-07-17T00:02:00.000Z", | |
| 2229 | models: [], | |
| 2230 | message: "Failed to refresh OpenCode models.", | |
| 2231 | } satisfies ServerProvider; | |
| 2232 | const changes = yield* PubSub.unbounded<ServerProvider>(); | |
| 2233 | const instance = { | |
| 2234 | instanceId: openCodeInstanceId, | |
| 2235 | driverKind: openCodeDriver, | |
| 2236 | continuationIdentity: { | |
| 2237 | driverKind: openCodeDriver, | |
| 2238 | continuationKey: "opencode:instance:opencode", | |
| 2239 | }, | |
| 2240 | displayName: undefined, | |
| 2241 | enabled: true, | |
| 2242 | snapshot: { | |
| 2243 | resolveMaintenance: () => | |
| 2244 | Effect.succeed( | |
| 2245 | makeManualOnlyProviderMaintenanceCapabilities({ | |
| 2246 | provider: openCodeDriver, | |
| 2247 | packageName: null, | |
| 2248 | }), | |
| 2249 | ), | |
| 2250 | getSnapshot: Effect.succeed(initialProvider), | |
| 2251 | refresh: Effect.succeed(authoritativeProvider), | |
| 2252 | streamChanges: Stream.fromPubSub(changes), | |
| 2253 | applyUsageLimits: () => Effect.void, | |
| 2254 | }, | |
| 2255 | orchestrationAdapter: {} as ProviderInstance["orchestrationAdapter"], | |
| 2256 | textGeneration: {} as ProviderInstance["textGeneration"], | |
| 2257 | } satisfies ProviderInstance; | |
| 2258 | const instanceRegistryLayer = Layer.succeed( | |
| 2259 | ProviderInstanceRegistry.ProviderInstanceRegistry, | |
| 2260 | { | |
| 2261 | getInstance: (instanceId) => | |
| 2262 | Effect.succeed(instanceId === openCodeInstanceId ? instance : undefined), | |
| 2263 | listInstances: Effect.succeed([instance]), | |
| 2264 | listUnavailable: Effect.succeed([]), | |
| 2265 | streamChanges: Stream.empty, | |
| 2266 | subscribeChanges: Effect.flatMap(PubSub.unbounded<void>(), (pubsub) => | |
| 2267 | PubSub.subscribe(pubsub), | |
| 2268 | ), | |
| 2269 | }, | |
| 2270 | ); | |
| 2271 | const scope = yield* Scope.make(); | |
| 2272 | yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void)); | |
| 2273 | const runtimeServices = yield* Layer.build( | |
| 2274 | ProviderRegistryLive.pipe( | |
| 2275 | Layer.provideMerge(instanceRegistryLayer), | |
| 2276 | Layer.provideMerge( | |
| 2277 | ServerConfig.layerTest(process.cwd(), { | |
| 2278 | prefix: "t3-provider-registry-opencode-authoritative-persist-", | |
| 2279 | }), | |
| 2280 | ), | |
| 2281 | Layer.provideMerge(NodeServices.layer), | |
| 1450 | }), | |
| 1451 | }, | |
| 1452 | ], | |
| 1453 | slashCommands: [], | |
| 1454 | skills: [], | |
| 1455 | } as const satisfies ServerProvider; | |
| 1456 | const refreshedProvider = { | |
| 1457 | ...previousProvider, | |
| 1458 | checkedAt: "2026-04-14T00:01:00.000Z", | |
| 1459 | models: [ | |
| 1460 | { | |
| 1461 | slug: "claude-opus-4-6", | |
| 1462 | name: "Opus 4.6", | |
| 1463 | isCustom: false, | |
| 1464 | capabilities: createModelCapabilities({ | |
| 1465 | optionDescriptors: [], | |
| 1466 | }), | |
| 1467 | }, | |
| 1468 | ], | |
| 1469 | } satisfies ServerProvider; | |
| 1470 | ||
| 1471 | assert.deepStrictEqual(mergeProviderSnapshot(previousProvider, refreshedProvider).models, [ | |
| 1472 | ...previousProvider.models, | |
| 1473 | ]); | |
| 1474 | }); | |
| 1475 | ||
| 1476 | it.effect("does not run provider probes during layer construction", () => | |
| 1477 | Effect.gen(function* () { | |
| 1478 | const codexDriver = ProviderDriverKind.make("codex"); | |
| 1479 | const codexInstanceId = ProviderInstanceId.make("codex"); | |
| 1480 | const initialProvider = { | |
| 1481 | instanceId: codexInstanceId, | |
| 1482 | driver: codexDriver, | |
| 1483 | status: "warning", | |
| 1484 | enabled: true, | |
| 1485 | installed: false, | |
| 1486 | auth: { status: "unknown" }, | |
| 1487 | checkedAt: "2026-06-10T00:00:00.000Z", | |
| 1488 | version: null, | |
| 1489 | message: "Checking Codex provider status.", | |
| 1490 | models: [], | |
| 1491 | slashCommands: [], | |
| 1492 | skills: [], | |
| 1493 | } as const satisfies ServerProvider; | |
| 1494 | const refreshCalls = yield* Ref.make(0); | |
| 1495 | const instance = { | |
| 1496 | instanceId: codexInstanceId, | |
| 1497 | driverKind: codexDriver, | |
| 1498 | continuationIdentity: { | |
| 1499 | driverKind: codexDriver, | |
| 1500 | continuationKey: "codex:instance:codex", | |
| 1501 | }, | |
| 1502 | displayName: undefined, | |
| 1503 | enabled: true, | |
| 1504 | snapshot: { | |
| 1505 | resolveMaintenance: () => | |
| 1506 | Effect.succeed( | |
| 1507 | makeManualOnlyProviderMaintenanceCapabilities({ | |
| 1508 | provider: codexDriver, | |
| 1509 | packageName: null, | |
| 1510 | }), | |
| 2282 | 1511 | ), |
| 2283 | ).pipe(Scope.provide(scope)); | |
| 1512 | getSnapshot: Effect.succeed(initialProvider), | |
| 1513 | refresh: Ref.update(refreshCalls, (count) => count + 1).pipe( | |
| 1514 | Effect.andThen(Effect.never), | |
| 1515 | ), | |
| 1516 | streamChanges: Stream.empty, | |
| 1517 | applyUsageLimits: () => Effect.void, | |
| 1518 | }, | |
| 1519 | orchestrationAdapter: {} as ProviderInstance["orchestrationAdapter"], | |
| 1520 | textGeneration: {} as ProviderInstance["textGeneration"], | |
| 1521 | } satisfies ProviderInstance; | |
| 1522 | const layerInstanceRegistry = Layer.succeed( | |
| 1523 | ProviderInstanceRegistry.ProviderInstanceRegistry, | |
| 1524 | { | |
| 1525 | getInstance: (instanceId) => | |
| 1526 | Effect.succeed(instanceId === codexInstanceId ? instance : undefined), | |
| 1527 | listInstances: Effect.succeed([instance]), | |
| 1528 | listUnavailable: Effect.succeed([]), | |
| 1529 | streamChanges: Stream.empty, | |
| 1530 | subscribeChanges: Effect.flatMap(PubSub.unbounded<void>(), PubSub.subscribe), | |
| 1531 | }, | |
| 1532 | ); | |
| 1533 | const scope = yield* Scope.make(); | |
| 1534 | yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void)); | |
| 1535 | const runtimeServices = yield* Layer.build( | |
| 1536 | ProviderRegistryLayer.layer.pipe( | |
| 1537 | Layer.provideMerge(layerInstanceRegistry), | |
| 1538 | Layer.provideMerge( | |
| 1539 | ServerConfig.layerTest(process.cwd(), { | |
| 1540 | prefix: "t3-provider-registry-background-refresh-", | |
| 1541 | }), | |
| 1542 | ), | |
| 1543 | Layer.provideMerge(NodeServices.layer), | |
| 1544 | ), | |
| 1545 | ).pipe(Scope.provide(scope)); | |
| 1546 | yield* Effect.gen(function* () { | |
| 1547 | const registry = yield* ProviderRegistry.ProviderRegistry; | |
| 1548 | assert.deepStrictEqual(yield* registry.getProviders, [initialProvider]); | |
| 1549 | assert.strictEqual(yield* Ref.get(refreshCalls), 0); | |
| 1550 | }).pipe(Effect.provide(runtimeServices)); | |
| 1551 | }), | |
| 1552 | ); | |
| 2284 | 1553 | |
| 2285 | yield* Effect.gen(function* () { | |
| 2286 | const registry = yield* ProviderRegistry.ProviderRegistry; | |
| 2287 | const config = yield* ServerConfig.ServerConfig; | |
| 2288 | const filePath = yield* resolveProviderStatusCachePath({ | |
| 2289 | cacheDir: config.providerStatusCacheDir, | |
| 2290 | instanceId: openCodeInstanceId, | |
| 2291 | }); | |
| 2292 | ||
| 2293 | const authoritativePersisted = yield* awaitPersistedProvider( | |
| 2294 | registry, | |
| 2295 | authoritativeProvider.checkedAt, | |
| 2296 | ); | |
| 2297 | yield* PubSub.publish(changes, authoritativeProvider); | |
| 2298 | yield* Fiber.join(authoritativePersisted); | |
| 2299 | let cachedProvider = yield* readProviderStatusCache(filePath); | |
| 1554 | it.effect("deduplicates cwd probes and clears snapshots when an instance rebuilds", () => | |
| 1555 | Effect.gen(function* () { | |
| 1556 | const driver = ProviderDriverKind.make("codex"); | |
| 1557 | const instanceId = ProviderInstanceId.make("codex"); | |
| 1558 | const machineProvider = { | |
| 1559 | instanceId, | |
| 1560 | driver, | |
| 1561 | status: "ready", | |
| 1562 | enabled: true, | |
| 1563 | installed: true, | |
| 1564 | auth: { status: "authenticated" }, | |
| 1565 | checkedAt: "2026-06-10T00:00:00.000Z", | |
| 1566 | version: "1.0.0", | |
| 1567 | models: [], | |
| 1568 | slashCommands: [{ name: "global" }], | |
| 1569 | skills: [{ name: "global", path: "/global/SKILL.md", enabled: true }], | |
| 1570 | } as const satisfies ServerProvider; | |
| 1571 | const scopedProvider = { | |
| 1572 | ...machineProvider, | |
| 1573 | checkedAt: "2026-06-10T00:01:00.000Z", | |
| 1574 | slashCommands: [{ name: "project" }], | |
| 1575 | skills: [{ name: "project", path: "/workspace/SKILL.md", enabled: true }], | |
| 1576 | } as const satisfies ServerProvider; | |
| 1577 | const pendingScopedProvider = { | |
| 1578 | ...scopedProvider, | |
| 1579 | status: "error", | |
| 1580 | installed: false, | |
| 1581 | slashCommands: [], | |
| 1582 | } as const satisfies ServerProvider; | |
| 1583 | const snapshotCalls = yield* Ref.make(0); | |
| 1584 | const scopedResult = yield* Ref.make<ProviderWorkspaceSnapshot>({ | |
| 1585 | ...scopedProvider, | |
| 1586 | status: "error", | |
| 1587 | slashCommands: [COMPACT_SLASH_COMMAND], | |
| 1588 | slashCommandsPending: true, | |
| 1589 | }); | |
| 1590 | const cacheInvalidations = yield* Ref.make(0); | |
| 1591 | const scanGate = yield* Ref.make<{ | |
| 1592 | readonly started: Deferred.Deferred<void>; | |
| 1593 | readonly release: Deferred.Deferred<void>; | |
| 1594 | } | null>(null); | |
| 1595 | const returnPendingSnapshot = yield* Ref.make(true); | |
| 1596 | const probeStarted = yield* Deferred.make<void>(); | |
| 1597 | const releaseProbe = yield* Deferred.make<void>(); | |
| 1598 | const makeInstance = ( | |
| 1599 | provider: ServerProvider, | |
| 1600 | snapshotForCwd: NonNullable<ProviderInstance["snapshotForCwd"]>, | |
| 1601 | ): ProviderInstance => ({ | |
| 1602 | instanceId, | |
| 1603 | driverKind: driver, | |
| 1604 | continuationIdentity: { | |
| 1605 | driverKind: driver, | |
| 1606 | continuationKey: "codex:instance:codex", | |
| 1607 | }, | |
| 1608 | displayName: undefined, | |
| 1609 | enabled: true, | |
| 1610 | snapshot: { | |
| 1611 | resolveMaintenance: () => | |
| 1612 | Effect.succeed( | |
| 1613 | makeManualOnlyProviderMaintenanceCapabilities({ | |
| 1614 | provider: driver, | |
| 1615 | packageName: null, | |
| 1616 | }), | |
| 1617 | ), | |
| 1618 | getSnapshot: Effect.succeed(provider), | |
| 1619 | refresh: Effect.succeed(provider), | |
| 1620 | streamChanges: Stream.empty, | |
| 1621 | applyUsageLimits: () => Effect.void, | |
| 1622 | }, | |
| 1623 | snapshotForCwd, | |
| 1624 | invalidateCaches: Ref.update(cacheInvalidations, (count) => count + 1), | |
| 1625 | orchestrationAdapter: {} as ProviderInstance["orchestrationAdapter"], | |
| 1626 | textGeneration: {} as ProviderInstance["textGeneration"], | |
| 1627 | }); | |
| 1628 | const firstInstance = makeInstance(machineProvider, () => | |
| 1629 | Effect.gen(function* () { | |
| 1630 | yield* Ref.update(snapshotCalls, (count) => count + 1); | |
| 1631 | if (yield* Ref.get(returnPendingSnapshot)) return pendingScopedProvider; | |
| 1632 | yield* Deferred.succeed(probeStarted, undefined); | |
| 1633 | yield* Deferred.await(releaseProbe); | |
| 1634 | const result = yield* Ref.get(scopedResult); | |
| 1635 | const gate = yield* Ref.getAndSet(scanGate, null); | |
| 1636 | if (gate) { | |
| 1637 | yield* Deferred.succeed(gate.started, undefined); | |
| 1638 | yield* Deferred.await(gate.release); | |
| 1639 | } | |
| 1640 | return result; | |
| 1641 | }), | |
| 1642 | ); | |
| 1643 | const rebuiltProvider = { | |
| 1644 | ...machineProvider, | |
| 1645 | checkedAt: "2026-06-10T00:02:00.000Z", | |
| 1646 | status: "warning", | |
| 1647 | installed: false, | |
| 1648 | auth: { status: "unknown" }, | |
| 1649 | } satisfies ServerProvider; | |
| 1650 | const rebuiltInstance = makeInstance(rebuiltProvider, () => | |
| 1651 | Ref.update(snapshotCalls, (count) => count + 1).pipe(Effect.as(scopedProvider)), | |
| 1652 | ); | |
| 1653 | const registryChanges = yield* PubSub.unbounded<void>(); | |
| 1654 | const instancesRef = yield* Ref.make<ReadonlyArray<ProviderInstance>>([firstInstance]); | |
| 1655 | const layerInstanceRegistry = Layer.succeed( | |
| 1656 | ProviderInstanceRegistry.ProviderInstanceRegistry, | |
| 1657 | { | |
| 1658 | getInstance: (requestedId) => | |
| 1659 | Ref.get(instancesRef).pipe( | |
| 1660 | Effect.map((instances) => | |
| 1661 | instances.find((instance) => instance.instanceId === requestedId), | |
| 1662 | ), | |
| 1663 | ), | |
| 1664 | listInstances: Ref.get(instancesRef), | |
| 1665 | listUnavailable: Effect.succeed([]), | |
| 1666 | streamChanges: Stream.fromPubSub(registryChanges), | |
| 1667 | subscribeChanges: PubSub.subscribe(registryChanges), | |
| 1668 | }, | |
| 1669 | ); | |
| 1670 | const scope = yield* Scope.make(); | |
| 1671 | yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void)); | |
| 1672 | const runtimeServices = yield* Layer.build( | |
| 1673 | ProviderRegistryLayer.layer.pipe( | |
| 1674 | Layer.provideMerge(layerInstanceRegistry), | |
| 1675 | Layer.provideMerge( | |
| 1676 | ServerConfig.layerTest(process.cwd(), { | |
| 1677 | prefix: "t3-provider-registry-workspace-snapshot-", | |
| 1678 | }), | |
| 1679 | ), | |
| 1680 | Layer.provideMerge(NodeServices.layer), | |
| 1681 | ), | |
| 1682 | ).pipe(Scope.provide(scope)); | |
| 1683 | ||
| 1684 | yield* Effect.gen(function* () { | |
| 1685 | const registry = yield* ProviderRegistry.ProviderRegistry; | |
| 1686 | yield* registry.refreshWorkspaceSnapshot({ instanceId, cwd: "/workspace" }); | |
| 1687 | assert.strictEqual((yield* registry.getProviders)[0]?.workspaceSnapshots, undefined); | |
| 1688 | yield* Ref.set(returnPendingSnapshot, false); | |
| 1689 | const workspaceUpdate = yield* registry.streamChanges.pipe( | |
| 1690 | Stream.runHead, | |
| 1691 | Effect.forkChild, | |
| 1692 | ); | |
| 1693 | yield* Effect.yieldNow; | |
| 1694 | const firstRefresh = yield* registry | |
| 1695 | .refreshWorkspaceSnapshot({ instanceId, cwd: "/workspace" }) | |
| 1696 | .pipe(Effect.forkChild); | |
| 1697 | yield* Deferred.await(probeStarted); | |
| 1698 | const duplicateRefresh = yield* registry | |
| 1699 | .refreshWorkspaceSnapshot({ instanceId, cwd: "/workspace" }) | |
| 1700 | .pipe(Effect.forkChild); | |
| 1701 | yield* Effect.yieldNow; | |
| 1702 | assert.strictEqual(yield* Ref.get(snapshotCalls), 2); | |
| 1703 | yield* Deferred.succeed(releaseProbe, undefined); | |
| 1704 | yield* Fiber.join(firstRefresh); | |
| 1705 | yield* Fiber.join(duplicateRefresh); | |
| 1706 | const published = yield* Fiber.join(workspaceUpdate); | |
| 1707 | assert.strictEqual(published._tag, "Some"); | |
| 1708 | const providers = yield* registry.getProviders; | |
| 1709 | assert.deepStrictEqual(providers[0]?.skills, machineProvider.skills); | |
| 1710 | assert.deepStrictEqual( | |
| 1711 | providers[0]?.workspaceSnapshots?.[0]?.skills, | |
| 1712 | scopedProvider.skills, | |
| 1713 | ); | |
| 1714 | assert.deepStrictEqual(providers[0]?.workspaceSnapshots?.[0]?.slashCommands, [ | |
| 1715 | COMPACT_SLASH_COMMAND, | |
| 1716 | ]); | |
| 1717 | assert.strictEqual(providers[0]?.workspaceSnapshots?.[0]?.slashCommandsPending, true); | |
| 1718 | yield* Ref.set(scopedResult, { | |
| 1719 | ...scopedProvider, | |
| 1720 | status: "error", | |
| 1721 | slashCommandsPending: false, | |
| 1722 | }); | |
| 1723 | yield* registry.refreshWorkspaceSnapshot({ instanceId, cwd: "/workspace" }); | |
| 1724 | assert.strictEqual(yield* Ref.get(snapshotCalls), 3); | |
| 1725 | assert.deepStrictEqual( | |
| 1726 | (yield* registry.getProviders)[0]?.workspaceSnapshots?.[0]?.slashCommands, | |
| 1727 | scopedProvider.slashCommands, | |
| 1728 | ); | |
| 1729 | assert.strictEqual( | |
| 1730 | (yield* registry.getProviders)[0]?.workspaceSnapshots?.[0]?.slashCommandsPending, | |
| 1731 | undefined, | |
| 1732 | ); | |
| 1733 | yield* registry.refreshWorkspaceSnapshot({ instanceId, cwd: "/workspace" }); | |
| 1734 | assert.strictEqual(yield* Ref.get(snapshotCalls), 3); | |
| 1735 | const newSkills = [ | |
| 1736 | ...scopedProvider.skills, | |
| 1737 | { name: "added", path: "/workspace/added/SKILL.md", enabled: true }, | |
| 1738 | ]; | |
| 1739 | yield* Ref.set(scopedResult, { ...scopedProvider, skills: newSkills }); | |
| 1740 | yield* registry.refreshWorkspaceSnapshot({ | |
| 1741 | instanceId, | |
| 1742 | cwd: "/workspace", | |
| 1743 | fresh: true, | |
| 1744 | }); | |
| 1745 | assert.strictEqual(yield* Ref.get(snapshotCalls), 4); | |
| 1746 | assert.strictEqual(yield* Ref.get(cacheInvalidations), 1); | |
| 1747 | assert.deepStrictEqual( | |
| 1748 | (yield* registry.getProviders)[0]?.workspaceSnapshots?.map((s) => s.skills), | |
| 1749 | [newSkills], | |
| 1750 | ); | |
| 2300 | 1751 | |
| 2301 | assert.deepStrictEqual(cachedProvider?.models, [authoritativeProvider.models[0]!]); | |
| 1752 | // A slow fresh scan that read older files must not overwrite a | |
| 1753 | // newer scan that finished first. | |
| 1754 | const slowStarted = yield* Deferred.make<void>(); | |
| 1755 | const releaseSlow = yield* Deferred.make<void>(); | |
| 1756 | yield* Ref.set(scanGate, { started: slowStarted, release: releaseSlow }); | |
| 1757 | yield* Ref.set(scopedResult, scopedProvider); | |
| 1758 | const slowScan = yield* registry | |
| 1759 | .refreshWorkspaceSnapshot({ instanceId, cwd: "/workspace", fresh: true }) | |
| 1760 | .pipe(Effect.forkChild); | |
| 1761 | yield* Deferred.await(slowStarted); | |
| 1762 | const latestSkills = [ | |
| 1763 | ...newSkills, | |
| 1764 | { name: "latest", path: "/workspace/latest/SKILL.md", enabled: true }, | |
| 1765 | ]; | |
| 1766 | yield* Ref.set(scopedResult, { ...scopedProvider, skills: latestSkills }); | |
| 1767 | yield* registry.refreshWorkspaceSnapshot({ | |
| 1768 | instanceId, | |
| 1769 | cwd: "/workspace", | |
| 1770 | fresh: true, | |
| 1771 | }); | |
| 1772 | yield* Deferred.succeed(releaseSlow, undefined); | |
| 1773 | yield* Fiber.join(slowScan); | |
| 1774 | assert.deepStrictEqual( | |
| 1775 | (yield* registry.getProviders)[0]?.workspaceSnapshots?.map((s) => s.skills), | |
| 1776 | [latestSkills], | |
| 1777 | ); | |
| 2302 | 1778 | |
| 2303 | const failedPersisted = yield* awaitPersistedProvider( | |
| 2304 | registry, | |
| 2305 | failedProvider.checkedAt, | |
| 2306 | ); | |
| 2307 | yield* PubSub.publish(changes, failedProvider); | |
| 2308 | yield* Fiber.join(failedPersisted); | |
| 2309 | cachedProvider = yield* readProviderStatusCache(filePath); | |
| 2310 | ||
| 2311 | assert.deepStrictEqual(cachedProvider?.models, [authoritativeProvider.models[0]!]); | |
| 2312 | assert.deepStrictEqual((yield* registry.getProviders)[0]?.models, [ | |
| 2313 | authoritativeProvider.models[0]!, | |
| 2314 | ]); | |
| 2315 | }).pipe(Effect.provide(runtimeServices)); | |
| 2316 | }), | |
| 2317 | ); | |
| 1779 | yield* Ref.set(instancesRef, [rebuiltInstance]); | |
| 1780 | yield* PubSub.publish(registryChanges, undefined); | |
| 1781 | let rebuilt = yield* registry.getProviders; | |
| 1782 | for ( | |
| 1783 | let attempt = 0; | |
| 1784 | attempt < 50 && rebuilt[0]?.checkedAt !== rebuiltProvider.checkedAt; | |
| 1785 | attempt += 1 | |
| 1786 | ) { | |
| 1787 | yield* Effect.yieldNow; | |
| 1788 | rebuilt = yield* registry.getProviders; | |
| 1789 | } | |
| 1790 | assert.strictEqual(rebuilt[0]?.checkedAt, rebuiltProvider.checkedAt); | |
| 1791 | assert.strictEqual(rebuilt[0]?.workspaceSnapshots, undefined); | |
| 1792 | }).pipe(Effect.provide(runtimeServices)); | |
| 1793 | }), | |
| 1794 | ); | |
| 2318 | 1795 | |
| 2319 | it.effect("returns the cached provider list when a manual refresh fails", () => | |
| 2320 | Effect.gen(function* () { | |
| 2321 | const codexDriver = ProviderDriverKind.make("codex"); | |
| 2322 | const codexInstanceId = ProviderInstanceId.make("codex"); | |
| 2323 | const cachedProvider = { | |
| 2324 | instanceId: codexInstanceId, | |
| 2325 | driver: codexDriver, | |
| 2326 | status: "ready", | |
| 2327 | enabled: true, | |
| 2328 | installed: true, | |
| 2329 | auth: { status: "authenticated" }, | |
| 2330 | checkedAt: "2026-04-29T10:00:00.000Z", | |
| 2331 | version: "1.0.0", | |
| 2332 | models: [], | |
| 2333 | slashCommands: [], | |
| 2334 | skills: [], | |
| 2335 | } as const satisfies ServerProvider; | |
| 2336 | const instance = { | |
| 1796 | it.effect("refreshes OpenCode catalogs and preserves other providers", () => | |
| 1797 | Effect.gen(function* () { | |
| 1798 | const codexDriver = ProviderDriverKind.make("codex"); | |
| 1799 | const openCodeDriver = ProviderDriverKind.make("opencode"); | |
| 1800 | const codexInstanceId = ProviderInstanceId.make("codex"); | |
| 1801 | const openCodeInstanceId = ProviderInstanceId.make("opencode"); | |
| 1802 | const codexRefreshCalls = yield* Ref.make(0); | |
| 1803 | const openCodeRefreshCalls = yield* Ref.make(0); | |
| 1804 | const codexProvider = { | |
| 1805 | instanceId: codexInstanceId, | |
| 1806 | driver: codexDriver, | |
| 1807 | status: "ready", | |
| 1808 | enabled: true, | |
| 1809 | installed: true, | |
| 1810 | auth: { status: "authenticated" }, | |
| 1811 | checkedAt: "2026-06-10T00:00:00.000Z", | |
| 1812 | version: "1.0.0", | |
| 1813 | models: [], | |
| 1814 | slashCommands: [], | |
| 1815 | skills: [], | |
| 1816 | } as const satisfies ServerProvider; | |
| 1817 | const failedOpenCodeProvider = { | |
| 1818 | instanceId: openCodeInstanceId, | |
| 1819 | driver: openCodeDriver, | |
| 1820 | status: "error", | |
| 1821 | enabled: true, | |
| 1822 | installed: true, | |
| 1823 | auth: { status: "unknown" }, | |
| 1824 | checkedAt: "2026-06-10T00:00:00.000Z", | |
| 1825 | version: "1.0.0", | |
| 1826 | message: "Failed to refresh OpenCode models.", | |
| 1827 | models: [], | |
| 1828 | slashCommands: [], | |
| 1829 | skills: [], | |
| 1830 | } as const satisfies ServerProvider; | |
| 1831 | const recoveredOpenCodeProvider = { | |
| 1832 | ...failedOpenCodeProvider, | |
| 1833 | status: "ready", | |
| 1834 | auth: { status: "authenticated" }, | |
| 1835 | checkedAt: "2026-06-10T00:01:00.000Z", | |
| 1836 | message: "One upstream provider connected through OpenCode.", | |
| 1837 | models: [ | |
| 1838 | { | |
| 1839 | slug: "github/gpt-5", | |
| 1840 | name: "GPT-5", | |
| 1841 | subProvider: "GitHub", | |
| 1842 | isCustom: false, | |
| 1843 | capabilities: null, | |
| 1844 | }, | |
| 1845 | ], | |
| 1846 | } as const satisfies ServerProvider; | |
| 1847 | const changedCatalogProvider = { | |
| 1848 | ...recoveredOpenCodeProvider, | |
| 1849 | checkedAt: "2026-06-10T00:02:00.000Z", | |
| 1850 | models: [ | |
| 1851 | { | |
| 1852 | slug: "anthropic/claude-sonnet-4", | |
| 1853 | name: "Claude Sonnet 4", | |
| 1854 | subProvider: "Anthropic", | |
| 1855 | isCustom: false, | |
| 1856 | capabilities: null, | |
| 1857 | }, | |
| 1858 | ], | |
| 1859 | } as const satisfies ServerProvider; | |
| 1860 | const catalogSnapshot = yield* Ref.make<ServerProvider>(recoveredOpenCodeProvider); | |
| 1861 | const instances = [ | |
| 1862 | { | |
| 2337 | 1863 | instanceId: codexInstanceId, |
| 2338 | 1864 | driverKind: codexDriver, |
| 2339 | 1865 | continuationIdentity: { |
| ... | ... | @@ -2350,96 +1876,339 @@ it.layer(Layer.mergeAll(TestNodeServices, ServerSettingsModule.layerTest(), Test |
| 2350 | 1876 | packageName: null, |
| 2351 | 1877 | }), |
| 2352 | 1878 | ), |
| 2353 | getSnapshot: Effect.succeed(cachedProvider), | |
| 2354 | refresh: Effect.die(new Error("simulated refresh failure")), | |
| 1879 | getSnapshot: Effect.succeed(codexProvider), | |
| 1880 | refresh: Ref.update(codexRefreshCalls, (count) => count + 1).pipe( | |
| 1881 | Effect.as(codexProvider), | |
| 1882 | ), | |
| 2355 | 1883 | streamChanges: Stream.empty, |
| 2356 | 1884 | applyUsageLimits: () => Effect.void, |
| 2357 | 1885 | }, |
| 2358 | 1886 | orchestrationAdapter: {} as ProviderInstance["orchestrationAdapter"], |
| 2359 | 1887 | textGeneration: {} as ProviderInstance["textGeneration"], |
| 2360 | } satisfies ProviderInstance; | |
| 2361 | const instanceRegistryLayer = Layer.succeed( | |
| 2362 | ProviderInstanceRegistry.ProviderInstanceRegistry, | |
| 2363 | { | |
| 2364 | getInstance: (instanceId) => | |
| 2365 | Effect.succeed(instanceId === codexInstanceId ? instance : undefined), | |
| 2366 | listInstances: Effect.succeed([instance]), | |
| 2367 | listUnavailable: Effect.succeed([]), | |
| 2368 | streamChanges: Stream.empty, | |
| 2369 | subscribeChanges: Effect.flatMap(PubSub.unbounded<void>(), (pubsub) => | |
| 2370 | PubSub.subscribe(pubsub), | |
| 1888 | }, | |
| 1889 | { | |
| 1890 | instanceId: openCodeInstanceId, | |
| 1891 | driverKind: openCodeDriver, | |
| 1892 | continuationIdentity: { | |
| 1893 | driverKind: openCodeDriver, | |
| 1894 | continuationKey: "opencode:instance:opencode", | |
| 1895 | }, | |
| 1896 | displayName: undefined, | |
| 1897 | enabled: true, | |
| 1898 | snapshot: { | |
| 1899 | resolveMaintenance: () => | |
| 1900 | Effect.succeed( | |
| 1901 | makeManualOnlyProviderMaintenanceCapabilities({ | |
| 1902 | provider: openCodeDriver, | |
| 1903 | packageName: null, | |
| 1904 | }), | |
| 1905 | ), | |
| 1906 | getSnapshot: Effect.succeed(failedOpenCodeProvider), | |
| 1907 | refresh: Ref.update(openCodeRefreshCalls, (count) => count + 1).pipe( | |
| 1908 | Effect.andThen(Ref.get(catalogSnapshot)), | |
| 2371 | 1909 | ), |
| 1910 | streamChanges: Stream.empty, | |
| 1911 | applyUsageLimits: () => Effect.void, | |
| 2372 | 1912 | }, |
| 1913 | orchestrationAdapter: {} as ProviderInstance["orchestrationAdapter"], | |
| 1914 | textGeneration: {} as ProviderInstance["textGeneration"], | |
| 1915 | }, | |
| 1916 | ] satisfies ReadonlyArray<ProviderInstance>; | |
| 1917 | const layerInstanceRegistry = Layer.succeed( | |
| 1918 | ProviderInstanceRegistry.ProviderInstanceRegistry, | |
| 1919 | { | |
| 1920 | getInstance: (instanceId) => | |
| 1921 | Effect.succeed(instances.find((instance) => instance.instanceId === instanceId)), | |
| 1922 | listInstances: Effect.succeed(instances), | |
| 1923 | listUnavailable: Effect.succeed([]), | |
| 1924 | streamChanges: Stream.empty, | |
| 1925 | subscribeChanges: Effect.flatMap(PubSub.unbounded<void>(), PubSub.subscribe), | |
| 1926 | }, | |
| 1927 | ); | |
| 1928 | const scope = yield* Scope.make(); | |
| 1929 | yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void)); | |
| 1930 | const runtimeServices = yield* Layer.build( | |
| 1931 | ProviderRegistryLayer.layer.pipe( | |
| 1932 | Layer.provideMerge(layerInstanceRegistry), | |
| 1933 | Layer.provideMerge( | |
| 1934 | ServerConfig.layerTest(process.cwd(), { | |
| 1935 | prefix: "t3-provider-registry-reconnect-refresh-", | |
| 1936 | }), | |
| 1937 | ), | |
| 1938 | Layer.provideMerge(NodeServices.layer), | |
| 1939 | ), | |
| 1940 | ).pipe(Scope.provide(scope)); | |
| 1941 | ||
| 1942 | yield* Effect.gen(function* () { | |
| 1943 | const registry = yield* ProviderRegistry.ProviderRegistry; | |
| 1944 | const initialProviders = yield* registry.getProviders; | |
| 1945 | assert.strictEqual( | |
| 1946 | initialProviders.find((provider) => provider.instanceId === openCodeInstanceId)?.status, | |
| 1947 | "error", | |
| 2373 | 1948 | ); |
| 2374 | const scope = yield* Scope.make(); | |
| 2375 | yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void)); | |
| 2376 | const runtimeServices = yield* Layer.build( | |
| 2377 | ProviderRegistryLive.pipe( | |
| 2378 | Layer.provideMerge(instanceRegistryLayer), | |
| 2379 | Layer.provideMerge( | |
| 2380 | ServerConfig.layerTest(process.cwd(), { | |
| 2381 | prefix: "t3-provider-registry-refresh-failure-", | |
| 1949 | ||
| 1950 | const recoveredProviders = yield* registry.refresh(); | |
| 1951 | assert.deepStrictEqual( | |
| 1952 | recoveredProviders.find((provider) => provider.instanceId === openCodeInstanceId) | |
| 1953 | ?.models, | |
| 1954 | recoveredOpenCodeProvider.models, | |
| 1955 | ); | |
| 1956 | assert.deepStrictEqual( | |
| 1957 | recoveredProviders.find((provider) => provider.instanceId === codexInstanceId), | |
| 1958 | withBundledCompatibility(codexProvider), | |
| 1959 | ); | |
| 1960 | ||
| 1961 | yield* Ref.set(catalogSnapshot, changedCatalogProvider); | |
| 1962 | const changedProviders = yield* registry.refresh(); | |
| 1963 | assert.deepStrictEqual( | |
| 1964 | changedProviders.find((provider) => provider.instanceId === openCodeInstanceId)?.models, | |
| 1965 | changedCatalogProvider.models, | |
| 1966 | ); | |
| 1967 | assert.deepStrictEqual( | |
| 1968 | changedProviders.find((provider) => provider.instanceId === codexInstanceId), | |
| 1969 | withBundledCompatibility(codexProvider), | |
| 1970 | ); | |
| 1971 | }).pipe(Effect.provide(runtimeServices)); | |
| 1972 | ||
| 1973 | assert.strictEqual(yield* Ref.get(codexRefreshCalls), 2); | |
| 1974 | assert.strictEqual(yield* Ref.get(openCodeRefreshCalls), 2); | |
| 1975 | }), | |
| 1976 | ); | |
| 1977 | ||
| 1978 | it("persists merged provider snapshots for the providers that were refreshed", () => { | |
| 1979 | const previousProviders = [ | |
| 1980 | { | |
| 1981 | instanceId: ProviderInstanceId.make("cursor"), | |
| 1982 | driver: ProviderDriverKind.make("cursor"), | |
| 1983 | status: "ready", | |
| 1984 | enabled: true, | |
| 1985 | installed: true, | |
| 1986 | auth: { status: "authenticated" }, | |
| 1987 | checkedAt: "2026-04-14T00:00:00.000Z", | |
| 1988 | version: "2026.04.09-f2b0fcd", | |
| 1989 | models: [ | |
| 1990 | { | |
| 1991 | slug: "claude-opus-4-6", | |
| 1992 | name: "Opus 4.6", | |
| 1993 | isCustom: false, | |
| 1994 | capabilities: createModelCapabilities({ | |
| 1995 | optionDescriptors: [ | |
| 1996 | selectDescriptor("reasoning", "Reasoning", [ | |
| 1997 | { id: "high", label: "High", isDefault: true }, | |
| 1998 | ]), | |
| 1999 | booleanDescriptor("fastMode", "Fast Mode"), | |
| 2000 | booleanDescriptor("thinking", "Thinking"), | |
| 2001 | ], | |
| 2002 | }), | |
| 2003 | }, | |
| 2004 | ], | |
| 2005 | slashCommands: [], | |
| 2006 | skills: [], | |
| 2007 | }, | |
| 2008 | { | |
| 2009 | instanceId: ProviderInstanceId.make("codex"), | |
| 2010 | driver: ProviderDriverKind.make("codex"), | |
| 2011 | status: "ready", | |
| 2012 | enabled: true, | |
| 2013 | installed: true, | |
| 2014 | auth: { status: "authenticated" }, | |
| 2015 | checkedAt: "2026-04-14T00:00:00.000Z", | |
| 2016 | version: "1.0.0", | |
| 2017 | models: [], | |
| 2018 | slashCommands: [], | |
| 2019 | skills: [], | |
| 2020 | }, | |
| 2021 | ] as const satisfies ReadonlyArray<ServerProvider>; | |
| 2022 | const refreshedCursor = { | |
| 2023 | ...previousProviders[0], | |
| 2024 | checkedAt: "2026-04-14T00:01:00.000Z", | |
| 2025 | models: [], | |
| 2026 | } satisfies ServerProvider; | |
| 2027 | ||
| 2028 | const mergedProviders = mergeProviderSnapshots(previousProviders, [refreshedCursor]); | |
| 2029 | const persistedProviders = selectProvidersByKind( | |
| 2030 | mergedProviders, | |
| 2031 | new Set([ProviderDriverKind.make("cursor")]), | |
| 2032 | ); | |
| 2033 | ||
| 2034 | assert.deepStrictEqual(persistedProviders, [ | |
| 2035 | { | |
| 2036 | ...refreshedCursor, | |
| 2037 | models: [...previousProviders[0].models], | |
| 2038 | }, | |
| 2039 | ]); | |
| 2040 | }); | |
| 2041 | ||
| 2042 | it.effect("persists the merged snapshot when a live update has empty models", () => | |
| 2043 | Effect.gen(function* () { | |
| 2044 | const cursorDriver = ProviderDriverKind.make("cursor"); | |
| 2045 | const cursorInstanceId = ProviderInstanceId.make("cursor"); | |
| 2046 | const initialProvider = { | |
| 2047 | instanceId: cursorInstanceId, | |
| 2048 | driver: cursorDriver, | |
| 2049 | status: "ready", | |
| 2050 | enabled: true, | |
| 2051 | installed: true, | |
| 2052 | auth: { status: "authenticated" }, | |
| 2053 | checkedAt: "2026-04-14T00:00:00.000Z", | |
| 2054 | version: "2026.04.09-f2b0fcd", | |
| 2055 | models: [ | |
| 2056 | { | |
| 2057 | slug: "claude-opus-4-6", | |
| 2058 | name: "Opus 4.6", | |
| 2059 | isCustom: false, | |
| 2060 | capabilities: createModelCapabilities({ | |
| 2061 | optionDescriptors: [ | |
| 2062 | selectDescriptor("reasoning", "Reasoning", [ | |
| 2063 | { id: "high", label: "High", isDefault: true }, | |
| 2064 | ]), | |
| 2065 | ], | |
| 2066 | }), | |
| 2067 | }, | |
| 2068 | ], | |
| 2069 | slashCommands: [], | |
| 2070 | skills: [], | |
| 2071 | } as const satisfies ServerProvider; | |
| 2072 | const refreshedProvider = { | |
| 2073 | ...initialProvider, | |
| 2074 | checkedAt: "2026-04-14T00:01:00.000Z", | |
| 2075 | models: [], | |
| 2076 | } satisfies ServerProvider; | |
| 2077 | const changes = yield* PubSub.unbounded<ServerProvider>(); | |
| 2078 | const instance = { | |
| 2079 | instanceId: cursorInstanceId, | |
| 2080 | driverKind: cursorDriver, | |
| 2081 | continuationIdentity: { | |
| 2082 | driverKind: cursorDriver, | |
| 2083 | continuationKey: "cursor:instance:cursor", | |
| 2084 | }, | |
| 2085 | displayName: undefined, | |
| 2086 | enabled: true, | |
| 2087 | snapshot: { | |
| 2088 | resolveMaintenance: () => | |
| 2089 | Effect.succeed( | |
| 2090 | makeManualOnlyProviderMaintenanceCapabilities({ | |
| 2091 | provider: cursorDriver, | |
| 2092 | packageName: null, | |
| 2382 | 2093 | }), |
| 2383 | 2094 | ), |
| 2384 | Layer.provideMerge(BackgroundPolicyAlwaysRunLayer), | |
| 2385 | Layer.provideMerge(NodeServices.layer), | |
| 2095 | getSnapshot: Effect.succeed(initialProvider), | |
| 2096 | refresh: Effect.succeed(refreshedProvider), | |
| 2097 | streamChanges: Stream.fromPubSub(changes), | |
| 2098 | applyUsageLimits: () => Effect.void, | |
| 2099 | }, | |
| 2100 | orchestrationAdapter: {} as ProviderInstance["orchestrationAdapter"], | |
| 2101 | textGeneration: {} as ProviderInstance["textGeneration"], | |
| 2102 | } satisfies ProviderInstance; | |
| 2103 | const layerInstanceRegistry = Layer.succeed( | |
| 2104 | ProviderInstanceRegistry.ProviderInstanceRegistry, | |
| 2105 | { | |
| 2106 | getInstance: (instanceId) => | |
| 2107 | Effect.succeed(instanceId === cursorInstanceId ? instance : undefined), | |
| 2108 | listInstances: Effect.succeed([instance]), | |
| 2109 | listUnavailable: Effect.succeed([]), | |
| 2110 | streamChanges: Stream.empty, | |
| 2111 | subscribeChanges: Effect.flatMap(PubSub.unbounded<void>(), (pubsub) => | |
| 2112 | PubSub.subscribe(pubsub), | |
| 2386 | 2113 | ), |
| 2387 | ).pipe(Scope.provide(scope)); | |
| 2114 | }, | |
| 2115 | ); | |
| 2116 | const scope = yield* Scope.make(); | |
| 2117 | yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void)); | |
| 2118 | const runtimeServices = yield* Layer.build( | |
| 2119 | ProviderRegistryLayer.layer.pipe( | |
| 2120 | Layer.provideMerge(layerInstanceRegistry), | |
| 2121 | Layer.provideMerge( | |
| 2122 | ServerConfig.layerTest(process.cwd(), { | |
| 2123 | prefix: "t3-provider-registry-merged-persist-", | |
| 2124 | }), | |
| 2125 | ), | |
| 2126 | Layer.provideMerge(layerBackgroundPolicyAlwaysRun), | |
| 2127 | Layer.provideMerge(NodeServices.layer), | |
| 2128 | ), | |
| 2129 | ).pipe(Scope.provide(scope)); | |
| 2388 | 2130 | |
| 2389 | yield* Effect.gen(function* () { | |
| 2390 | const registry = yield* ProviderRegistry.ProviderRegistry; | |
| 2131 | yield* Effect.gen(function* () { | |
| 2132 | const registry = yield* ProviderRegistry.ProviderRegistry; | |
| 2133 | const config = yield* ServerConfig.ServerConfig; | |
| 2134 | const filePath = yield* resolveProviderStatusCachePath({ | |
| 2135 | cacheDir: config.providerStatusCacheDir, | |
| 2136 | instanceId: cursorInstanceId, | |
| 2137 | }); | |
| 2391 | 2138 | |
| 2392 | assert.deepStrictEqual(yield* registry.getProviders, [ | |
| 2393 | withBundledCompatibility(cachedProvider), | |
| 2394 | ]); | |
| 2395 | assert.deepStrictEqual(yield* registry.refresh(codexDriver), [ | |
| 2396 | withBundledCompatibility(cachedProvider), | |
| 2397 | ]); | |
| 2398 | assert.deepStrictEqual(yield* registry.refreshInstance(codexInstanceId), [ | |
| 2399 | withBundledCompatibility(cachedProvider), | |
| 2400 | ]); | |
| 2401 | }).pipe(Effect.provide(runtimeServices)); | |
| 2402 | }), | |
| 2403 | ); | |
| 2139 | assert.deepStrictEqual((yield* registry.getProviders)[0]?.models, [ | |
| 2140 | ...initialProvider.models, | |
| 2141 | ]); | |
| 2142 | const persisted = yield* awaitPersistedProvider(registry, refreshedProvider.checkedAt); | |
| 2143 | yield* PubSub.publish(changes, refreshedProvider); | |
| 2144 | yield* Fiber.join(persisted); | |
| 2145 | const cachedProvider = yield* readProviderStatusCache(filePath); | |
| 2146 | ||
| 2147 | assert.deepStrictEqual( | |
| 2148 | cachedProvider, | |
| 2149 | withBundledCompatibility({ | |
| 2150 | ...refreshedProvider, | |
| 2151 | models: [...initialProvider.models], | |
| 2152 | }), | |
| 2153 | ); | |
| 2154 | }).pipe(Effect.provide(runtimeServices)); | |
| 2155 | }), | |
| 2156 | ); | |
| 2404 | 2157 | |
| 2405 | it.effect("keeps consuming registry changes after one sync fails", () => | |
| 2158 | it.effect( | |
| 2159 | "persists authoritative OpenCode removals without resurrecting them on a failed live refresh", | |
| 2160 | () => | |
| 2406 | 2161 | Effect.gen(function* () { |
| 2407 | const codexDriver = ProviderDriverKind.make("codex"); | |
| 2408 | const codexInstanceId = ProviderInstanceId.make("codex"); | |
| 2409 | const claudeDriver = ProviderDriverKind.make("claudeAgent"); | |
| 2410 | const claudeInstanceId = ProviderInstanceId.make("claudeAgent"); | |
| 2411 | const codexProvider = { | |
| 2412 | instanceId: codexInstanceId, | |
| 2413 | driver: codexDriver, | |
| 2162 | const openCodeDriver = ProviderDriverKind.make("opencode"); | |
| 2163 | const openCodeInstanceId = ProviderInstanceId.make("opencode"); | |
| 2164 | const initialProvider = { | |
| 2165 | instanceId: openCodeInstanceId, | |
| 2166 | driver: openCodeDriver, | |
| 2414 | 2167 | status: "ready", |
| 2415 | 2168 | enabled: true, |
| 2416 | 2169 | installed: true, |
| 2417 | 2170 | auth: { status: "authenticated" }, |
| 2418 | checkedAt: "2026-04-29T10:00:00.000Z", | |
| 2171 | checkedAt: "2026-07-17T00:00:00.000Z", | |
| 2419 | 2172 | version: "1.0.0", |
| 2420 | models: [], | |
| 2173 | models: [ | |
| 2174 | { | |
| 2175 | slug: "github/gpt-5", | |
| 2176 | name: "GPT-5", | |
| 2177 | subProvider: "GitHub", | |
| 2178 | isCustom: false, | |
| 2179 | capabilities: null, | |
| 2180 | }, | |
| 2181 | { | |
| 2182 | slug: "removed-plugin/model", | |
| 2183 | name: "Removed Plugin Model", | |
| 2184 | subProvider: "Removed Plugin", | |
| 2185 | isCustom: false, | |
| 2186 | capabilities: null, | |
| 2187 | }, | |
| 2188 | ], | |
| 2421 | 2189 | slashCommands: [], |
| 2422 | 2190 | skills: [], |
| 2423 | 2191 | } as const satisfies ServerProvider; |
| 2424 | const claudeProvider = { | |
| 2425 | instanceId: claudeInstanceId, | |
| 2426 | driver: claudeDriver, | |
| 2427 | status: "ready", | |
| 2428 | enabled: true, | |
| 2429 | installed: true, | |
| 2430 | auth: { status: "authenticated" }, | |
| 2431 | checkedAt: "2026-04-29T10:01:00.000Z", | |
| 2432 | version: "1.0.0", | |
| 2192 | const authoritativeProvider = { | |
| 2193 | ...initialProvider, | |
| 2194 | checkedAt: "2026-07-17T00:01:00.000Z", | |
| 2195 | models: [initialProvider.models[0]!], | |
| 2196 | } satisfies ServerProvider; | |
| 2197 | const failedProvider = { | |
| 2198 | ...authoritativeProvider, | |
| 2199 | status: "error", | |
| 2200 | auth: { status: "unknown" }, | |
| 2201 | checkedAt: "2026-07-17T00:02:00.000Z", | |
| 2433 | 2202 | models: [], |
| 2434 | slashCommands: [], | |
| 2435 | skills: [], | |
| 2436 | } as const satisfies ServerProvider; | |
| 2437 | const makeInstance = (provider: ServerProvider): ProviderInstance => ({ | |
| 2438 | instanceId: provider.instanceId, | |
| 2439 | driverKind: provider.driver, | |
| 2203 | message: "Failed to refresh OpenCode models.", | |
| 2204 | } satisfies ServerProvider; | |
| 2205 | const changes = yield* PubSub.unbounded<ServerProvider>(); | |
| 2206 | const instance = { | |
| 2207 | instanceId: openCodeInstanceId, | |
| 2208 | driverKind: openCodeDriver, | |
| 2440 | 2209 | continuationIdentity: { |
| 2441 | driverKind: provider.driver, | |
| 2442 | continuationKey: `${provider.driver}:instance:${provider.instanceId}`, | |
| 2210 | driverKind: openCodeDriver, | |
| 2211 | continuationKey: "opencode:instance:opencode", | |
| 2443 | 2212 | }, |
| 2444 | 2213 | displayName: undefined, |
| 2445 | 2214 | enabled: true, |
| ... | ... | @@ -2447,372 +2216,636 @@ it.layer(Layer.mergeAll(TestNodeServices, ServerSettingsModule.layerTest(), Test |
| 2447 | 2216 | resolveMaintenance: () => |
| 2448 | 2217 | Effect.succeed( |
| 2449 | 2218 | makeManualOnlyProviderMaintenanceCapabilities({ |
| 2450 | provider: provider.driver, | |
| 2219 | provider: openCodeDriver, | |
| 2451 | 2220 | packageName: null, |
| 2452 | 2221 | }), |
| 2453 | 2222 | ), |
| 2454 | getSnapshot: Effect.succeed(provider), | |
| 2455 | refresh: Effect.succeed(provider), | |
| 2456 | streamChanges: Stream.empty, | |
| 2223 | getSnapshot: Effect.succeed(initialProvider), | |
| 2224 | refresh: Effect.succeed(authoritativeProvider), | |
| 2225 | streamChanges: Stream.fromPubSub(changes), | |
| 2457 | 2226 | applyUsageLimits: () => Effect.void, |
| 2458 | 2227 | }, |
| 2459 | 2228 | orchestrationAdapter: {} as ProviderInstance["orchestrationAdapter"], |
| 2460 | 2229 | textGeneration: {} as ProviderInstance["textGeneration"], |
| 2461 | }); | |
| 2462 | const codexInstance = makeInstance(codexProvider); | |
| 2463 | const claudeInstance = makeInstance(claudeProvider); | |
| 2464 | const changes = yield* PubSub.unbounded<void>(); | |
| 2465 | const instancesRef = yield* Ref.make<ReadonlyArray<ProviderInstance>>([codexInstance]); | |
| 2466 | const failNextList = yield* Ref.make(false); | |
| 2467 | const wait = () => Effect.yieldNow; | |
| 2468 | const instanceRegistryLayer = Layer.succeed( | |
| 2230 | } satisfies ProviderInstance; | |
| 2231 | const layerInstanceRegistry = Layer.succeed( | |
| 2469 | 2232 | ProviderInstanceRegistry.ProviderInstanceRegistry, |
| 2470 | 2233 | { |
| 2471 | 2234 | getInstance: (instanceId) => |
| 2472 | Ref.get(instancesRef).pipe( | |
| 2473 | Effect.map((instances) => | |
| 2474 | instances.find((instance) => instance.instanceId === instanceId), | |
| 2475 | ), | |
| 2476 | ), | |
| 2477 | listInstances: Effect.gen(function* () { | |
| 2478 | const shouldFail = yield* Ref.get(failNextList); | |
| 2479 | if (shouldFail) { | |
| 2480 | yield* Ref.set(failNextList, false); | |
| 2481 | return yield* Effect.die(new Error("simulated registry list failure")); | |
| 2482 | } | |
| 2483 | return yield* Ref.get(instancesRef); | |
| 2484 | }), | |
| 2235 | Effect.succeed(instanceId === openCodeInstanceId ? instance : undefined), | |
| 2236 | listInstances: Effect.succeed([instance]), | |
| 2485 | 2237 | listUnavailable: Effect.succeed([]), |
| 2486 | streamChanges: Stream.fromPubSub(changes), | |
| 2487 | subscribeChanges: PubSub.subscribe(changes), | |
| 2238 | streamChanges: Stream.empty, | |
| 2239 | subscribeChanges: Effect.flatMap(PubSub.unbounded<void>(), (pubsub) => | |
| 2240 | PubSub.subscribe(pubsub), | |
| 2241 | ), | |
| 2488 | 2242 | }, |
| 2489 | 2243 | ); |
| 2490 | 2244 | const scope = yield* Scope.make(); |
| 2491 | 2245 | yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void)); |
| 2492 | 2246 | const runtimeServices = yield* Layer.build( |
| 2493 | ProviderRegistryLive.pipe( | |
| 2494 | Layer.provideMerge(instanceRegistryLayer), | |
| 2247 | ProviderRegistryLayer.layer.pipe( | |
| 2248 | Layer.provideMerge(layerInstanceRegistry), | |
| 2495 | 2249 | Layer.provideMerge( |
| 2496 | 2250 | ServerConfig.layerTest(process.cwd(), { |
| 2497 | prefix: "t3-provider-registry-sync-failure-", | |
| 2251 | prefix: "t3-provider-registry-opencode-authoritative-persist-", | |
| 2498 | 2252 | }), |
| 2499 | 2253 | ), |
| 2500 | Layer.provideMerge(BackgroundPolicyAlwaysRunLayer), | |
| 2501 | 2254 | Layer.provideMerge(NodeServices.layer), |
| 2502 | 2255 | ), |
| 2503 | 2256 | ).pipe(Scope.provide(scope)); |
| 2504 | 2257 | |
| 2505 | 2258 | yield* Effect.gen(function* () { |
| 2506 | 2259 | const registry = yield* ProviderRegistry.ProviderRegistry; |
| 2507 | assert.deepStrictEqual(yield* registry.getProviders, [ | |
| 2508 | withBundledCompatibility(codexProvider), | |
| 2509 | ]); | |
| 2260 | const config = yield* ServerConfig.ServerConfig; | |
| 2261 | const filePath = yield* resolveProviderStatusCachePath({ | |
| 2262 | cacheDir: config.providerStatusCacheDir, | |
| 2263 | instanceId: openCodeInstanceId, | |
| 2264 | }); | |
| 2510 | 2265 | |
| 2511 | yield* Ref.set(failNextList, true); | |
| 2512 | yield* PubSub.publish(changes, undefined); | |
| 2513 | ||
| 2514 | yield* Ref.set(instancesRef, [codexInstance, claudeInstance]); | |
| 2515 | yield* PubSub.publish(changes, undefined); | |
| 2516 | ||
| 2517 | let providers = yield* registry.getProviders; | |
| 2518 | for ( | |
| 2519 | let attempt = 0; | |
| 2520 | attempt < 50 && | |
| 2521 | !providers.some((provider) => provider.instanceId === claudeInstanceId); | |
| 2522 | attempt += 1 | |
| 2523 | ) { | |
| 2524 | yield* wait(); | |
| 2525 | providers = yield* registry.getProviders; | |
| 2526 | } | |
| 2266 | const authoritativePersisted = yield* awaitPersistedProvider( | |
| 2267 | registry, | |
| 2268 | authoritativeProvider.checkedAt, | |
| 2269 | ); | |
| 2270 | yield* PubSub.publish(changes, authoritativeProvider); | |
| 2271 | yield* Fiber.join(authoritativePersisted); | |
| 2272 | let cachedProvider = yield* readProviderStatusCache(filePath); | |
| 2527 | 2273 | |
| 2528 | assert.deepStrictEqual( | |
| 2529 | providers.map((provider) => provider.instanceId).toSorted(), | |
| 2530 | [codexInstanceId, claudeInstanceId].toSorted(), | |
| 2274 | assert.deepStrictEqual(cachedProvider?.models, [authoritativeProvider.models[0]!]); | |
| 2275 | ||
| 2276 | const failedPersisted = yield* awaitPersistedProvider( | |
| 2277 | registry, | |
| 2278 | failedProvider.checkedAt, | |
| 2531 | 2279 | ); |
| 2280 | yield* PubSub.publish(changes, failedProvider); | |
| 2281 | yield* Fiber.join(failedPersisted); | |
| 2282 | cachedProvider = yield* readProviderStatusCache(filePath); | |
| 2283 | ||
| 2284 | assert.deepStrictEqual(cachedProvider?.models, [authoritativeProvider.models[0]!]); | |
| 2285 | assert.deepStrictEqual((yield* registry.getProviders)[0]?.models, [ | |
| 2286 | authoritativeProvider.models[0]!, | |
| 2287 | ]); | |
| 2532 | 2288 | }).pipe(Effect.provide(runtimeServices)); |
| 2533 | 2289 | }), |
| 2534 | ); | |
| 2290 | ); | |
| 2535 | 2291 | |
| 2536 | // This test intentionally avoids `mockCommandSpawnerLayer` so the real | |
| 2537 | // `probeCodexAppServerProvider` path runs — including the full | |
| 2538 | // `codex app-server` RPC handshake via `CodexClient.layerChildProcess`. | |
| 2539 | // We point `binaryPath` at a name that cannot exist on any machine so | |
| 2540 | // the real `ChildProcessSpawner` deterministically returns ENOENT; the | |
| 2541 | // probe wraps that as `CodexAppServerSpawnError` and | |
| 2542 | // `checkCodexProviderStatus` turns it into the user-visible "not | |
| 2543 | // installed" error snapshot. If the aggregator's `syncLiveSources` | |
| 2544 | // breaks — the `codex_personal`-never-probes bug we are guarding | |
| 2545 | // against — that snapshot never lands in `getProviders` and the | |
| 2546 | // assertions below fail. | |
| 2547 | it.effect("propagates real Codex probe failures to the aggregator at boot", () => | |
| 2548 | Effect.gen(function* () { | |
| 2549 | const missingBinary = `t3code_codex_missing_`; | |
| 2550 | const serverSettings = yield* makeMutableServerSettingsService( | |
| 2551 | decodeServerSettings( | |
| 2552 | deepMerge(encodedDefaultServerSettings, { | |
| 2553 | providers: { | |
| 2554 | // Disable every built-in probe that would otherwise spawn | |
| 2555 | // on the CI host. `enabled: false` short-circuits each | |
| 2556 | // driver's probe *before* it touches the spawner, so the | |
| 2557 | // test environment stays isolated from the dev | |
| 2558 | // machine's PATH. | |
| 2559 | codex: { enabled: false }, | |
| 2560 | claudeAgent: { enabled: false }, | |
| 2561 | cursor: { enabled: false }, | |
| 2562 | grok: { enabled: false }, | |
| 2563 | opencode: { enabled: false }, | |
| 2564 | }, | |
| 2565 | // `providerInstances` keys are branded `ProviderInstanceId`; | |
| 2566 | // the branded index signature rejects plain string literals | |
| 2567 | // at the TS level even though the runtime schema happily | |
| 2568 | // accepts + decodes them. Cast the patch to `unknown` so | |
| 2569 | // the `Schema.decodeSync` below does the real validation. | |
| 2570 | providerInstances: { | |
| 2571 | // Matches the shape the user had in `.t3/dev/settings.json` | |
| 2572 | // when the bug was reported: a custom enabled Codex instance | |
| 2573 | // pointing at a binary the server has to actually spawn. | |
| 2574 | codex_personal: { | |
| 2575 | driver: "codex", | |
| 2576 | displayName: "Codex Personal", | |
| 2577 | enabled: true, | |
| 2578 | config: { | |
| 2579 | binaryPath: missingBinary, | |
| 2580 | homePath: `/tmp/${missingBinary}_home`, | |
| 2581 | }, | |
| 2582 | }, | |
| 2583 | } as unknown as ContractServerSettings["providerInstances"], | |
| 2584 | }), | |
| 2585 | ), | |
| 2586 | ); | |
| 2587 | const scope = yield* Scope.make(); | |
| 2588 | yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void)); | |
| 2589 | const providerRegistryLayer = ProviderRegistryLive.pipe( | |
| 2590 | Layer.provideMerge(ProviderInstanceRegistryHydrationLive), | |
| 2591 | Layer.provideMerge(AntigravityInstallation.AntigravityInstallation.layer), | |
| 2592 | Layer.provideMerge( | |
| 2593 | Layer.succeed(ServerSettingsModule.ServerSettingsService, serverSettings), | |
| 2292 | it.effect("returns the cached provider list when a manual refresh fails", () => | |
| 2293 | Effect.gen(function* () { | |
| 2294 | const codexDriver = ProviderDriverKind.make("codex"); | |
| 2295 | const codexInstanceId = ProviderInstanceId.make("codex"); | |
| 2296 | const cachedProvider = { | |
| 2297 | instanceId: codexInstanceId, | |
| 2298 | driver: codexDriver, | |
| 2299 | status: "ready", | |
| 2300 | enabled: true, | |
| 2301 | installed: true, | |
| 2302 | auth: { status: "authenticated" }, | |
| 2303 | checkedAt: "2026-04-29T10:00:00.000Z", | |
| 2304 | version: "1.0.0", | |
| 2305 | models: [], | |
| 2306 | slashCommands: [], | |
| 2307 | skills: [], | |
| 2308 | } as const satisfies ServerProvider; | |
| 2309 | const instance = { | |
| 2310 | instanceId: codexInstanceId, | |
| 2311 | driverKind: codexDriver, | |
| 2312 | continuationIdentity: { | |
| 2313 | driverKind: codexDriver, | |
| 2314 | continuationKey: "codex:instance:codex", | |
| 2315 | }, | |
| 2316 | displayName: undefined, | |
| 2317 | enabled: true, | |
| 2318 | snapshot: { | |
| 2319 | resolveMaintenance: () => | |
| 2320 | Effect.succeed( | |
| 2321 | makeManualOnlyProviderMaintenanceCapabilities({ | |
| 2322 | provider: codexDriver, | |
| 2323 | packageName: null, | |
| 2324 | }), | |
| 2325 | ), | |
| 2326 | getSnapshot: Effect.succeed(cachedProvider), | |
| 2327 | refresh: Effect.die(new Error("simulated refresh failure")), | |
| 2328 | streamChanges: Stream.empty, | |
| 2329 | applyUsageLimits: () => Effect.void, | |
| 2330 | }, | |
| 2331 | orchestrationAdapter: {} as ProviderInstance["orchestrationAdapter"], | |
| 2332 | textGeneration: {} as ProviderInstance["textGeneration"], | |
| 2333 | } satisfies ProviderInstance; | |
| 2334 | const layerInstanceRegistry = Layer.succeed( | |
| 2335 | ProviderInstanceRegistry.ProviderInstanceRegistry, | |
| 2336 | { | |
| 2337 | getInstance: (instanceId) => | |
| 2338 | Effect.succeed(instanceId === codexInstanceId ? instance : undefined), | |
| 2339 | listInstances: Effect.succeed([instance]), | |
| 2340 | listUnavailable: Effect.succeed([]), | |
| 2341 | streamChanges: Stream.empty, | |
| 2342 | subscribeChanges: Effect.flatMap(PubSub.unbounded<void>(), (pubsub) => | |
| 2343 | PubSub.subscribe(pubsub), | |
| 2594 | 2344 | ), |
| 2595 | Layer.provideMerge(ServerSecretStore.layer), | |
| 2345 | }, | |
| 2346 | ); | |
| 2347 | const scope = yield* Scope.make(); | |
| 2348 | yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void)); | |
| 2349 | const runtimeServices = yield* Layer.build( | |
| 2350 | ProviderRegistryLayer.layer.pipe( | |
| 2351 | Layer.provideMerge(layerInstanceRegistry), | |
| 2596 | 2352 | Layer.provideMerge( |
| 2597 | 2353 | ServerConfig.layerTest(process.cwd(), { |
| 2598 | prefix: "t3-provider-registry-", | |
| 2354 | prefix: "t3-provider-registry-refresh-failure-", | |
| 2599 | 2355 | }), |
| 2600 | 2356 | ), |
| 2601 | Layer.provideMerge(TestHttpClientLive), | |
| 2602 | Layer.provideMerge( | |
| 2603 | Layer.succeed( | |
| 2604 | ProviderEventLoggers.ProviderEventLoggers, | |
| 2605 | ProviderEventLoggers.NoOpProviderEventLoggers, | |
| 2357 | Layer.provideMerge(layerBackgroundPolicyAlwaysRun), | |
| 2358 | Layer.provideMerge(NodeServices.layer), | |
| 2359 | ), | |
| 2360 | ).pipe(Scope.provide(scope)); | |
| 2361 | ||
| 2362 | yield* Effect.gen(function* () { | |
| 2363 | const registry = yield* ProviderRegistry.ProviderRegistry; | |
| 2364 | ||
| 2365 | assert.deepStrictEqual(yield* registry.getProviders, [ | |
| 2366 | withBundledCompatibility(cachedProvider), | |
| 2367 | ]); | |
| 2368 | assert.deepStrictEqual(yield* registry.refresh(codexDriver), [ | |
| 2369 | withBundledCompatibility(cachedProvider), | |
| 2370 | ]); | |
| 2371 | assert.deepStrictEqual(yield* registry.refreshInstance(codexInstanceId), [ | |
| 2372 | withBundledCompatibility(cachedProvider), | |
| 2373 | ]); | |
| 2374 | }).pipe(Effect.provide(runtimeServices)); | |
| 2375 | }), | |
| 2376 | ); | |
| 2377 | ||
| 2378 | it.effect("keeps consuming registry changes after one sync fails", () => | |
| 2379 | Effect.gen(function* () { | |
| 2380 | const codexDriver = ProviderDriverKind.make("codex"); | |
| 2381 | const codexInstanceId = ProviderInstanceId.make("codex"); | |
| 2382 | const claudeDriver = ProviderDriverKind.make("claudeAgent"); | |
| 2383 | const claudeInstanceId = ProviderInstanceId.make("claudeAgent"); | |
| 2384 | const codexProvider = { | |
| 2385 | instanceId: codexInstanceId, | |
| 2386 | driver: codexDriver, | |
| 2387 | status: "ready", | |
| 2388 | enabled: true, | |
| 2389 | installed: true, | |
| 2390 | auth: { status: "authenticated" }, | |
| 2391 | checkedAt: "2026-04-29T10:00:00.000Z", | |
| 2392 | version: "1.0.0", | |
| 2393 | models: [], | |
| 2394 | slashCommands: [], | |
| 2395 | skills: [], | |
| 2396 | } as const satisfies ServerProvider; | |
| 2397 | const claudeProvider = { | |
| 2398 | instanceId: claudeInstanceId, | |
| 2399 | driver: claudeDriver, | |
| 2400 | status: "ready", | |
| 2401 | enabled: true, | |
| 2402 | installed: true, | |
| 2403 | auth: { status: "authenticated" }, | |
| 2404 | checkedAt: "2026-04-29T10:01:00.000Z", | |
| 2405 | version: "1.0.0", | |
| 2406 | models: [], | |
| 2407 | slashCommands: [], | |
| 2408 | skills: [], | |
| 2409 | } as const satisfies ServerProvider; | |
| 2410 | const makeInstance = (provider: ServerProvider): ProviderInstance => ({ | |
| 2411 | instanceId: provider.instanceId, | |
| 2412 | driverKind: provider.driver, | |
| 2413 | continuationIdentity: { | |
| 2414 | driverKind: provider.driver, | |
| 2415 | continuationKey: `${provider.driver}:instance:${provider.instanceId}`, | |
| 2416 | }, | |
| 2417 | displayName: undefined, | |
| 2418 | enabled: true, | |
| 2419 | snapshot: { | |
| 2420 | resolveMaintenance: () => | |
| 2421 | Effect.succeed( | |
| 2422 | makeManualOnlyProviderMaintenanceCapabilities({ | |
| 2423 | provider: provider.driver, | |
| 2424 | packageName: null, | |
| 2425 | }), | |
| 2606 | 2426 | ), |
| 2607 | ), | |
| 2608 | Layer.provideMerge(ModelManifest.layerTest), | |
| 2609 | Layer.provideMerge(ResetCreditCoordinator.layerTest), | |
| 2610 | Layer.provideMerge( | |
| 2611 | OpenCodeRuntime.OpenCodeRuntimeLive.pipe( | |
| 2612 | Layer.provide(OpenCodeServerLedger.layerTest), | |
| 2427 | getSnapshot: Effect.succeed(provider), | |
| 2428 | refresh: Effect.succeed(provider), | |
| 2429 | streamChanges: Stream.empty, | |
| 2430 | applyUsageLimits: () => Effect.void, | |
| 2431 | }, | |
| 2432 | orchestrationAdapter: {} as ProviderInstance["orchestrationAdapter"], | |
| 2433 | textGeneration: {} as ProviderInstance["textGeneration"], | |
| 2434 | }); | |
| 2435 | const codexInstance = makeInstance(codexProvider); | |
| 2436 | const claudeInstance = makeInstance(claudeProvider); | |
| 2437 | const changes = yield* PubSub.unbounded<void>(); | |
| 2438 | const instancesRef = yield* Ref.make<ReadonlyArray<ProviderInstance>>([codexInstance]); | |
| 2439 | const failNextList = yield* Ref.make(false); | |
| 2440 | const wait = () => Effect.yieldNow; | |
| 2441 | const layerInstanceRegistry = Layer.succeed( | |
| 2442 | ProviderInstanceRegistry.ProviderInstanceRegistry, | |
| 2443 | { | |
| 2444 | getInstance: (instanceId) => | |
| 2445 | Ref.get(instancesRef).pipe( | |
| 2446 | Effect.map((instances) => | |
| 2447 | instances.find((instance) => instance.instanceId === instanceId), | |
| 2448 | ), | |
| 2613 | 2449 | ), |
| 2450 | listInstances: Effect.gen(function* () { | |
| 2451 | const shouldFail = yield* Ref.get(failNextList); | |
| 2452 | if (shouldFail) { | |
| 2453 | yield* Ref.set(failNextList, false); | |
| 2454 | return yield* Effect.die(new Error("simulated registry list failure")); | |
| 2455 | } | |
| 2456 | return yield* Ref.get(instancesRef); | |
| 2457 | }), | |
| 2458 | listUnavailable: Effect.succeed([]), | |
| 2459 | streamChanges: Stream.fromPubSub(changes), | |
| 2460 | subscribeChanges: PubSub.subscribe(changes), | |
| 2461 | }, | |
| 2462 | ); | |
| 2463 | const scope = yield* Scope.make(); | |
| 2464 | yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void)); | |
| 2465 | const runtimeServices = yield* Layer.build( | |
| 2466 | ProviderRegistryLayer.layer.pipe( | |
| 2467 | Layer.provideMerge(layerInstanceRegistry), | |
| 2468 | Layer.provideMerge( | |
| 2469 | ServerConfig.layerTest(process.cwd(), { | |
| 2470 | prefix: "t3-provider-registry-sync-failure-", | |
| 2471 | }), | |
| 2614 | 2472 | ), |
| 2615 | Layer.provideMerge(BackgroundPolicyAlwaysRunLayer), | |
| 2616 | // NO spawner mock — `ChildProcessSpawner` is supplied by the | |
| 2617 | // outer `NodeServices.layer` on `it.layer(...)` and will | |
| 2618 | // genuinely spawn a subprocess. The missing-binary ENOENT is | |
| 2619 | // what exercises the same failure mode as a misconfigured | |
| 2620 | // production `binaryPath`. | |
| 2621 | ); | |
| 2622 | const runtimeServices = yield* Layer.build(providerRegistryLayer).pipe( | |
| 2623 | Scope.provide(scope), | |
| 2624 | ); | |
| 2473 | Layer.provideMerge(layerBackgroundPolicyAlwaysRun), | |
| 2474 | Layer.provideMerge(NodeServices.layer), | |
| 2475 | ), | |
| 2476 | ).pipe(Scope.provide(scope)); | |
| 2625 | 2477 | |
| 2626 | yield* Effect.gen(function* () { | |
| 2627 | const registry = yield* ProviderRegistry.ProviderRegistry; | |
| 2628 | let providers = yield* registry.getProviders; | |
| 2629 | for ( | |
| 2630 | let attempts = 0; | |
| 2631 | attempts < 50 && | |
| 2632 | providers.find((provider) => provider.instanceId === "codex_personal")?.status !== | |
| 2633 | "error"; | |
| 2634 | attempts += 1 | |
| 2635 | ) { | |
| 2636 | yield* Effect.yieldNow; | |
| 2637 | providers = yield* registry.getProviders; | |
| 2638 | } | |
| 2639 | const codexPersonal = providers.find( | |
| 2640 | (provider) => provider.instanceId === "codex_personal", | |
| 2641 | ); | |
| 2642 | assert.notStrictEqual( | |
| 2643 | codexPersonal, | |
| 2644 | undefined, | |
| 2645 | `Expected the aggregator to know about codex_personal; instead saw: ${providers | |
| 2646 | .map((provider) => provider.instanceId) | |
| 2647 | .join(", ")}`, | |
| 2648 | ); | |
| 2649 | assert.strictEqual( | |
| 2650 | codexPersonal?.status, | |
| 2651 | "error", | |
| 2652 | "Real Codex probe against a missing binary should surface as 'error' in the aggregator", | |
| 2653 | ); | |
| 2654 | assert.strictEqual(codexPersonal?.installed, false); | |
| 2655 | assert.include(codexPersonal?.message, missingBinary); | |
| 2656 | assert.include(codexPersonal?.message, "Settings → Providers → Codex → Binary path"); | |
| 2657 | }).pipe(Effect.provide(runtimeServices)); | |
| 2658 | }), | |
| 2659 | ); | |
| 2478 | yield* Effect.gen(function* () { | |
| 2479 | const registry = yield* ProviderRegistry.ProviderRegistry; | |
| 2480 | assert.deepStrictEqual(yield* registry.getProviders, [ | |
| 2481 | withBundledCompatibility(codexProvider), | |
| 2482 | ]); | |
| 2660 | 2483 | |
| 2661 | // A binary path change must rebuild Codex and publish its new probe result. | |
| 2662 | it.effect("re-probes when settings change the codex binaryPath", () => | |
| 2663 | Effect.gen(function* () { | |
| 2664 | const firstMissing = `t3code_codex_first_`; | |
| 2665 | const secondMissing = `t3code_codex_second_`; | |
| 2666 | const spawnedCommands: Array<string> = []; | |
| 2667 | const secondProbeStarted = yield* Deferred.make<void>(); | |
| 2668 | const releaseSecondProbe = yield* Deferred.make<void>(); | |
| 2669 | const allowLazySettingsStream = yield* Deferred.make<void>(); | |
| 2670 | const mutableServerSettings = yield* makeMutableServerSettingsService( | |
| 2671 | decodeServerSettings( | |
| 2672 | deepMerge(encodedDefaultServerSettings, { | |
| 2673 | providers: { | |
| 2674 | codex: { enabled: true, binaryPath: firstMissing }, | |
| 2675 | claudeAgent: { enabled: false }, | |
| 2676 | cursor: { enabled: false }, | |
| 2677 | grok: { enabled: false }, | |
| 2678 | opencode: { enabled: false }, | |
| 2484 | yield* Ref.set(failNextList, true); | |
| 2485 | yield* PubSub.publish(changes, undefined); | |
| 2486 | ||
| 2487 | yield* Ref.set(instancesRef, [codexInstance, claudeInstance]); | |
| 2488 | yield* PubSub.publish(changes, undefined); | |
| 2489 | ||
| 2490 | let providers = yield* registry.getProviders; | |
| 2491 | for ( | |
| 2492 | let attempt = 0; | |
| 2493 | attempt < 50 && !providers.some((provider) => provider.instanceId === claudeInstanceId); | |
| 2494 | attempt += 1 | |
| 2495 | ) { | |
| 2496 | yield* wait(); | |
| 2497 | providers = yield* registry.getProviders; | |
| 2498 | } | |
| 2499 | ||
| 2500 | assert.deepStrictEqual( | |
| 2501 | providers.map((provider) => provider.instanceId).toSorted(), | |
| 2502 | [codexInstanceId, claudeInstanceId].toSorted(), | |
| 2503 | ); | |
| 2504 | }).pipe(Effect.provide(runtimeServices)); | |
| 2505 | }), | |
| 2506 | ); | |
| 2507 | ||
| 2508 | // This test intentionally avoids `mockCommandSpawnerLayer` so the real | |
| 2509 | // `probeCodexAppServerProvider` path runs — including the full | |
| 2510 | // `codex app-server` RPC handshake via `CodexClient.layerChildProcess`. | |
| 2511 | // We point `binaryPath` at a name that cannot exist on any machine so | |
| 2512 | // the real `ChildProcessSpawner` deterministically returns ENOENT; the | |
| 2513 | // probe wraps that as `CodexAppServerSpawnError` and | |
| 2514 | // `checkCodexProviderStatus` turns it into the user-visible "not | |
| 2515 | // installed" error snapshot. If the aggregator's `syncLiveSources` | |
| 2516 | // breaks — the `codex_personal`-never-probes bug we are guarding | |
| 2517 | // against — that snapshot never lands in `getProviders` and the | |
| 2518 | // assertions below fail. | |
| 2519 | it.effect("propagates real Codex probe failures to the aggregator at boot", () => | |
| 2520 | Effect.gen(function* () { | |
| 2521 | const missingBinary = `t3code_codex_missing_`; | |
| 2522 | const serverSettings = yield* makeMutableServerSettingsService( | |
| 2523 | decodeServerSettings( | |
| 2524 | deepMerge(encodedDefaultServerSettings, { | |
| 2525 | providers: { | |
| 2526 | // Disable every built-in probe that would otherwise spawn | |
| 2527 | // on the CI host. `enabled: false` short-circuits each | |
| 2528 | // driver's probe *before* it touches the spawner, so the | |
| 2529 | // test environment stays isolated from the dev | |
| 2530 | // machine's PATH. | |
| 2531 | codex: { enabled: false }, | |
| 2532 | claudeAgent: { enabled: false }, | |
| 2533 | cursor: { enabled: false }, | |
| 2534 | grok: { enabled: false }, | |
| 2535 | opencode: { enabled: false }, | |
| 2536 | }, | |
| 2537 | // `providerInstances` keys are branded `ProviderInstanceId`; | |
| 2538 | // the branded index signature rejects plain string literals | |
| 2539 | // at the TS level even though the runtime schema happily | |
| 2540 | // accepts + decodes them. Cast the patch to `unknown` so | |
| 2541 | // the `Schema.decodeSync` below does the real validation. | |
| 2542 | providerInstances: { | |
| 2543 | // Matches the shape the user had in `.t3/dev/settings.json` | |
| 2544 | // when the bug was reported: a custom enabled Codex instance | |
| 2545 | // pointing at a binary the server has to actually spawn. | |
| 2546 | codex_personal: { | |
| 2547 | driver: "codex", | |
| 2548 | displayName: "Codex Personal", | |
| 2549 | enabled: true, | |
| 2550 | config: { | |
| 2551 | binaryPath: missingBinary, | |
| 2552 | homePath: `/tmp/${missingBinary}_home`, | |
| 2553 | }, | |
| 2679 | 2554 | }, |
| 2680 | }), | |
| 2555 | } as unknown as ContractServerSettings["providerInstances"], | |
| 2556 | }), | |
| 2557 | ), | |
| 2558 | ); | |
| 2559 | const scope = yield* Scope.make(); | |
| 2560 | yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void)); | |
| 2561 | const layerProviderRegistry = ProviderRegistryLayer.layer.pipe( | |
| 2562 | Layer.provideMerge(ProviderInstanceRegistryHydration.layer), | |
| 2563 | Layer.provideMerge(AntigravityInstallation.AntigravityInstallation.layer), | |
| 2564 | Layer.provideMerge( | |
| 2565 | Layer.succeed(ServerSettingsModule.ServerSettingsService, serverSettings), | |
| 2566 | ), | |
| 2567 | Layer.provideMerge(ServerSecretStore.layer), | |
| 2568 | Layer.provideMerge( | |
| 2569 | ServerConfig.layerTest(process.cwd(), { | |
| 2570 | prefix: "t3-provider-registry-", | |
| 2571 | }), | |
| 2572 | ), | |
| 2573 | Layer.provideMerge(layerTestHttpClient), | |
| 2574 | Layer.provideMerge( | |
| 2575 | Layer.succeed( | |
| 2576 | ProviderEventLoggers.ProviderEventLoggers, | |
| 2577 | ProviderEventLoggers.NoOpProviderEventLoggers, | |
| 2681 | 2578 | ), |
| 2579 | ), | |
| 2580 | Layer.provideMerge(ModelManifest.layerTest), | |
| 2581 | Layer.provideMerge(ResetCreditCoordinator.layerTest), | |
| 2582 | Layer.provideMerge( | |
| 2583 | OpenCodeRuntime.layer.pipe(Layer.provide(OpenCodeServerLedger.layerTest)), | |
| 2584 | ), | |
| 2585 | Layer.provideMerge(layerBackgroundPolicyAlwaysRun), | |
| 2586 | // NO spawner mock — `ChildProcessSpawner` is supplied by the | |
| 2587 | // outer `NodeServices.layer` on `it.layer(...)` and will | |
| 2588 | // genuinely spawn a subprocess. The missing-binary ENOENT is | |
| 2589 | // what exercises the same failure mode as a misconfigured | |
| 2590 | // production `binaryPath`. | |
| 2591 | ); | |
| 2592 | const runtimeServices = yield* Layer.build(layerProviderRegistry).pipe( | |
| 2593 | Scope.provide(scope), | |
| 2594 | ); | |
| 2595 | ||
| 2596 | yield* Effect.gen(function* () { | |
| 2597 | const registry = yield* ProviderRegistry.ProviderRegistry; | |
| 2598 | let providers = yield* registry.getProviders; | |
| 2599 | for ( | |
| 2600 | let attempts = 0; | |
| 2601 | attempts < 50 && | |
| 2602 | providers.find((provider) => provider.instanceId === "codex_personal")?.status !== | |
| 2603 | "error"; | |
| 2604 | attempts += 1 | |
| 2605 | ) { | |
| 2606 | yield* Effect.yieldNow; | |
| 2607 | providers = yield* registry.getProviders; | |
| 2608 | } | |
| 2609 | const codexPersonal = providers.find( | |
| 2610 | (provider) => provider.instanceId === "codex_personal", | |
| 2682 | 2611 | ); |
| 2683 | const serverSettings = { | |
| 2684 | ...mutableServerSettings, | |
| 2685 | streamChanges: Stream.unwrap( | |
| 2686 | Deferred.await(allowLazySettingsStream).pipe( | |
| 2687 | Effect.as(mutableServerSettings.streamChanges), | |
| 2688 | ), | |
| 2689 | ), | |
| 2690 | } satisfies ServerSettingsModule.ServerSettingsService["Service"]; | |
| 2691 | const scope = yield* Scope.make(); | |
| 2692 | yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void)); | |
| 2693 | const providerRegistryLayer = ProviderRegistryLive.pipe( | |
| 2694 | Layer.provideMerge(ProviderInstanceRegistryHydrationLive), | |
| 2695 | Layer.provideMerge(AntigravityInstallation.AntigravityInstallation.layer), | |
| 2696 | Layer.provideMerge( | |
| 2697 | Layer.succeed(ServerSettingsModule.ServerSettingsService, serverSettings), | |
| 2698 | ), | |
| 2699 | Layer.provideMerge(ServerSecretStore.layer), | |
| 2700 | Layer.provideMerge( | |
| 2701 | ServerConfig.layerTest(process.cwd(), { | |
| 2702 | prefix: "t3-provider-registry-", | |
| 2703 | }), | |
| 2612 | assert.notStrictEqual( | |
| 2613 | codexPersonal, | |
| 2614 | undefined, | |
| 2615 | `Expected the aggregator to know about codex_personal; instead saw: ${providers | |
| 2616 | .map((provider) => provider.instanceId) | |
| 2617 | .join(", ")}`, | |
| 2618 | ); | |
| 2619 | assert.strictEqual( | |
| 2620 | codexPersonal?.status, | |
| 2621 | "error", | |
| 2622 | "Real Codex probe against a missing binary should surface as 'error' in the aggregator", | |
| 2623 | ); | |
| 2624 | assert.strictEqual(codexPersonal?.installed, false); | |
| 2625 | assert.include(codexPersonal?.message, missingBinary); | |
| 2626 | assert.include(codexPersonal?.message, "Settings → Providers → Codex → Binary path"); | |
| 2627 | }).pipe(Effect.provide(runtimeServices)); | |
| 2628 | }), | |
| 2629 | ); | |
| 2630 | ||
| 2631 | // A binary path change must rebuild Codex and publish its new probe result. | |
| 2632 | it.effect("re-probes when settings change the codex binaryPath", () => | |
| 2633 | Effect.gen(function* () { | |
| 2634 | const firstMissing = `t3code_codex_first_`; | |
| 2635 | const secondMissing = `t3code_codex_second_`; | |
| 2636 | const spawnedCommands: Array<string> = []; | |
| 2637 | const secondProbeStarted = yield* Deferred.make<void>(); | |
| 2638 | const releaseSecondProbe = yield* Deferred.make<void>(); | |
| 2639 | const allowLazySettingsStream = yield* Deferred.make<void>(); | |
| 2640 | const mutableServerSettings = yield* makeMutableServerSettingsService( | |
| 2641 | decodeServerSettings( | |
| 2642 | deepMerge(encodedDefaultServerSettings, { | |
| 2643 | providers: { | |
| 2644 | codex: { enabled: true, binaryPath: firstMissing }, | |
| 2645 | claudeAgent: { enabled: false }, | |
| 2646 | cursor: { enabled: false }, | |
| 2647 | grok: { enabled: false }, | |
| 2648 | opencode: { enabled: false }, | |
| 2649 | }, | |
| 2650 | }), | |
| 2651 | ), | |
| 2652 | ); | |
| 2653 | const serverSettings = { | |
| 2654 | ...mutableServerSettings, | |
| 2655 | streamChanges: Stream.unwrap( | |
| 2656 | Deferred.await(allowLazySettingsStream).pipe( | |
| 2657 | Effect.as(mutableServerSettings.streamChanges), | |
| 2704 | 2658 | ), |
| 2705 | Layer.provideMerge(TestHttpClientLive), | |
| 2706 | Layer.provideMerge( | |
| 2707 | Layer.succeed( | |
| 2708 | ProviderEventLoggers.ProviderEventLoggers, | |
| 2709 | ProviderEventLoggers.NoOpProviderEventLoggers, | |
| 2710 | ), | |
| 2659 | ), | |
| 2660 | } satisfies ServerSettingsModule.ServerSettingsService["Service"]; | |
| 2661 | const scope = yield* Scope.make(); | |
| 2662 | yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void)); | |
| 2663 | const layerProviderRegistry = ProviderRegistryLayer.layer.pipe( | |
| 2664 | Layer.provideMerge(ProviderInstanceRegistryHydration.layer), | |
| 2665 | Layer.provideMerge(AntigravityInstallation.AntigravityInstallation.layer), | |
| 2666 | Layer.provideMerge( | |
| 2667 | Layer.succeed(ServerSettingsModule.ServerSettingsService, serverSettings), | |
| 2668 | ), | |
| 2669 | Layer.provideMerge(ServerSecretStore.layer), | |
| 2670 | Layer.provideMerge( | |
| 2671 | ServerConfig.layerTest(process.cwd(), { | |
| 2672 | prefix: "t3-provider-registry-", | |
| 2673 | }), | |
| 2674 | ), | |
| 2675 | Layer.provideMerge(layerTestHttpClient), | |
| 2676 | Layer.provideMerge( | |
| 2677 | Layer.succeed( | |
| 2678 | ProviderEventLoggers.ProviderEventLoggers, | |
| 2679 | ProviderEventLoggers.NoOpProviderEventLoggers, | |
| 2711 | 2680 | ), |
| 2712 | Layer.provideMerge(ModelManifest.layerTest), | |
| 2713 | Layer.provideMerge(ResetCreditCoordinator.layerTest), | |
| 2714 | Layer.provideMerge( | |
| 2715 | OpenCodeRuntime.OpenCodeRuntimeLive.pipe( | |
| 2716 | Layer.provide(OpenCodeServerLedger.layerTest), | |
| 2717 | ), | |
| 2681 | ), | |
| 2682 | Layer.provideMerge(ModelManifest.layerTest), | |
| 2683 | Layer.provideMerge(ResetCreditCoordinator.layerTest), | |
| 2684 | Layer.provideMerge( | |
| 2685 | OpenCodeRuntime.layer.pipe(Layer.provide(OpenCodeServerLedger.layerTest)), | |
| 2686 | ), | |
| 2687 | Layer.updateService(ChildProcessSpawner.ChildProcessSpawner, (spawner) => | |
| 2688 | ChildProcessSpawner.make((command) => { | |
| 2689 | if (command._tag !== "StandardCommand") return spawner.spawn(command); | |
| 2690 | spawnedCommands.push(command.command); | |
| 2691 | const beforeSpawn = | |
| 2692 | command.command === secondMissing | |
| 2693 | ? Deferred.succeed(secondProbeStarted, undefined).pipe( | |
| 2694 | Effect.andThen(Deferred.await(releaseSecondProbe)), | |
| 2695 | ) | |
| 2696 | : Effect.void; | |
| 2697 | return beforeSpawn.pipe(Effect.andThen(spawner.spawn(command))); | |
| 2698 | }), | |
| 2699 | ), | |
| 2700 | Layer.provideMerge(NodeServices.layer), | |
| 2701 | Layer.provideMerge(layerBackgroundPolicyAlwaysRun), | |
| 2702 | ); | |
| 2703 | const runtimeServices = yield* Layer.build(layerProviderRegistry).pipe( | |
| 2704 | Scope.provide(scope), | |
| 2705 | ); | |
| 2706 | ||
| 2707 | yield* Effect.gen(function* () { | |
| 2708 | const registry = yield* ProviderRegistry.ProviderRegistry; | |
| 2709 | const codexSnapshots = registry.streamChanges.pipe( | |
| 2710 | Stream.map((providers) => | |
| 2711 | providers.find((provider) => provider.instanceId === "codex"), | |
| 2718 | 2712 | ), |
| 2719 | Layer.updateService(ChildProcessSpawner.ChildProcessSpawner, (spawner) => | |
| 2720 | ChildProcessSpawner.make((command) => { | |
| 2721 | if (command._tag !== "StandardCommand") return spawner.spawn(command); | |
| 2722 | spawnedCommands.push(command.command); | |
| 2723 | const beforeSpawn = | |
| 2724 | command.command === secondMissing | |
| 2725 | ? Deferred.succeed(secondProbeStarted, undefined).pipe( | |
| 2726 | Effect.andThen(Deferred.await(releaseSecondProbe)), | |
| 2727 | ) | |
| 2728 | : Effect.void; | |
| 2729 | return beforeSpawn.pipe(Effect.andThen(spawner.spawn(command))); | |
| 2730 | }), | |
| 2713 | Stream.filter((provider): provider is ServerProvider => provider !== undefined), | |
| 2714 | ); | |
| 2715 | const firstError = yield* Stream.toPull( | |
| 2716 | codexSnapshots.pipe(Stream.filter((provider) => provider.status === "error")), | |
| 2717 | ); | |
| 2718 | const currentCodex = (yield* registry.getProviders).find( | |
| 2719 | (provider) => provider.instanceId === "codex", | |
| 2720 | ); | |
| 2721 | const initialCodex = | |
| 2722 | currentCodex?.status === "error" ? currentCodex : (yield* firstError)[0]; | |
| 2723 | assert.strictEqual(initialCodex?.status, "error"); | |
| 2724 | assert.strictEqual(initialCodex?.installed, false); | |
| 2725 | assert.deepStrictEqual(spawnedCommands, [firstMissing]); | |
| 2726 | ||
| 2727 | const pendingRebuild = yield* Stream.toPull( | |
| 2728 | codexSnapshots.pipe( | |
| 2729 | Stream.filter((provider) => provider.status === "warning" && !provider.installed), | |
| 2731 | 2730 | ), |
| 2732 | Layer.provideMerge(NodeServices.layer), | |
| 2733 | Layer.provideMerge(BackgroundPolicyAlwaysRunLayer), | |
| 2734 | 2731 | ); |
| 2735 | const runtimeServices = yield* Layer.build(providerRegistryLayer).pipe( | |
| 2736 | Scope.provide(scope), | |
| 2732 | yield* serverSettings.updateSettings({ | |
| 2733 | providers: { | |
| 2734 | codex: { enabled: true, binaryPath: secondMissing }, | |
| 2735 | }, | |
| 2736 | }); | |
| 2737 | // Start the lazy stream only after publishing. A watcher that did | |
| 2738 | // not subscribe before forking has already lost this update. | |
| 2739 | yield* Deferred.succeed(allowLazySettingsStream, undefined); | |
| 2740 | ||
| 2741 | // Hold the second probe until the aggregator sees the rebuilt | |
| 2742 | // instance. Its next error must come from the new executable. | |
| 2743 | yield* Deferred.await(secondProbeStarted); | |
| 2744 | yield* pendingRebuild; | |
| 2745 | const rebuiltError = yield* Stream.toPull( | |
| 2746 | codexSnapshots.pipe(Stream.filter((provider) => provider.status === "error")), | |
| 2737 | 2747 | ); |
| 2748 | yield* Deferred.succeed(releaseSecondProbe, undefined); | |
| 2749 | const [reprobedCodex] = yield* rebuiltError; | |
| 2750 | assert.deepStrictEqual(spawnedCommands, [firstMissing, secondMissing]); | |
| 2751 | assert.strictEqual(reprobedCodex?.status, "error"); | |
| 2752 | assert.strictEqual(reprobedCodex?.installed, false); | |
| 2753 | }).pipe(Effect.provide(runtimeServices)); | |
| 2754 | }), | |
| 2755 | ); | |
| 2738 | 2756 | |
| 2739 | yield* Effect.gen(function* () { | |
| 2740 | const registry = yield* ProviderRegistry.ProviderRegistry; | |
| 2741 | const codexSnapshots = registry.streamChanges.pipe( | |
| 2742 | Stream.map((providers) => | |
| 2743 | providers.find((provider) => provider.instanceId === "codex"), | |
| 2744 | ), | |
| 2745 | Stream.filter((provider): provider is ServerProvider => provider !== undefined), | |
| 2746 | ); | |
| 2747 | const firstError = yield* Stream.toPull( | |
| 2748 | codexSnapshots.pipe(Stream.filter((provider) => provider.status === "error")), | |
| 2749 | ); | |
| 2750 | const currentCodex = (yield* registry.getProviders).find( | |
| 2751 | (provider) => provider.instanceId === "codex", | |
| 2752 | ); | |
| 2753 | const initialCodex = | |
| 2754 | currentCodex?.status === "error" ? currentCodex : (yield* firstError)[0]; | |
| 2755 | assert.strictEqual(initialCodex?.status, "error"); | |
| 2756 | assert.strictEqual(initialCodex?.installed, false); | |
| 2757 | assert.deepStrictEqual(spawnedCommands, [firstMissing]); | |
| 2758 | ||
| 2759 | const pendingRebuild = yield* Stream.toPull( | |
| 2760 | codexSnapshots.pipe( | |
| 2761 | Stream.filter((provider) => provider.status === "warning" && !provider.installed), | |
| 2762 | ), | |
| 2763 | ); | |
| 2764 | yield* serverSettings.updateSettings({ | |
| 2757 | it.effect("includes unavailable instance snapshots in getProviders", () => | |
| 2758 | Effect.gen(function* () { | |
| 2759 | const serverSettings = yield* makeMutableServerSettingsService( | |
| 2760 | decodeServerSettings( | |
| 2761 | deepMerge(encodedDefaultServerSettings, { | |
| 2765 | 2762 | providers: { |
| 2766 | codex: { enabled: true, binaryPath: secondMissing }, | |
| 2763 | codex: { enabled: false }, | |
| 2764 | claudeAgent: { enabled: false }, | |
| 2765 | cursor: { enabled: false }, | |
| 2766 | grok: { enabled: false }, | |
| 2767 | opencode: { enabled: false }, | |
| 2767 | 2768 | }, |
| 2768 | }); | |
| 2769 | // Start the lazy stream only after publishing. A watcher that did | |
| 2770 | // not subscribe before forking has already lost this update. | |
| 2771 | yield* Deferred.succeed(allowLazySettingsStream, undefined); | |
| 2772 | ||
| 2773 | // Hold the second probe until the aggregator sees the rebuilt | |
| 2774 | // instance. Its next error must come from the new executable. | |
| 2775 | yield* Deferred.await(secondProbeStarted); | |
| 2776 | yield* pendingRebuild; | |
| 2777 | const rebuiltError = yield* Stream.toPull( | |
| 2778 | codexSnapshots.pipe(Stream.filter((provider) => provider.status === "error")), | |
| 2779 | ); | |
| 2780 | yield* Deferred.succeed(releaseSecondProbe, undefined); | |
| 2781 | const [reprobedCodex] = yield* rebuiltError; | |
| 2782 | assert.deepStrictEqual(spawnedCommands, [firstMissing, secondMissing]); | |
| 2783 | assert.strictEqual(reprobedCodex?.status, "error"); | |
| 2784 | assert.strictEqual(reprobedCodex?.installed, false); | |
| 2785 | }).pipe(Effect.provide(runtimeServices)); | |
| 2786 | }), | |
| 2787 | ); | |
| 2769 | providerInstances: { | |
| 2770 | ghost_main: { | |
| 2771 | driver: "ghostDriver", | |
| 2772 | displayName: "A fork-only driver we don't ship", | |
| 2773 | enabled: false, | |
| 2774 | config: { arbitrary: "payload" }, | |
| 2775 | }, | |
| 2776 | } as unknown as ContractServerSettings["providerInstances"], | |
| 2777 | }), | |
| 2778 | ), | |
| 2779 | ); | |
| 2780 | const scope = yield* Scope.make(); | |
| 2781 | yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void)); | |
| 2782 | const layerProviderRegistry = ProviderRegistryLayer.layer.pipe( | |
| 2783 | Layer.provideMerge(ProviderInstanceRegistryHydration.layer), | |
| 2784 | Layer.provideMerge(AntigravityInstallation.AntigravityInstallation.layer), | |
| 2785 | Layer.provideMerge( | |
| 2786 | Layer.succeed(ServerSettingsModule.ServerSettingsService, serverSettings), | |
| 2787 | ), | |
| 2788 | Layer.provideMerge(ServerSecretStore.layer), | |
| 2789 | Layer.provideMerge( | |
| 2790 | ServerConfig.layerTest(process.cwd(), { | |
| 2791 | prefix: "t3-provider-registry-", | |
| 2792 | }), | |
| 2793 | ), | |
| 2794 | Layer.provideMerge(layerTestHttpClient), | |
| 2795 | Layer.provideMerge( | |
| 2796 | Layer.succeed( | |
| 2797 | ProviderEventLoggers.ProviderEventLoggers, | |
| 2798 | ProviderEventLoggers.NoOpProviderEventLoggers, | |
| 2799 | ), | |
| 2800 | ), | |
| 2801 | Layer.provideMerge(ModelManifest.layerTest), | |
| 2802 | Layer.provideMerge(ResetCreditCoordinator.layerTest), | |
| 2803 | Layer.provideMerge( | |
| 2804 | OpenCodeRuntime.layer.pipe(Layer.provide(OpenCodeServerLedger.layerTest)), | |
| 2805 | ), | |
| 2806 | Layer.provideMerge(NodeServices.layer), | |
| 2807 | Layer.provideMerge(layerBackgroundPolicyAlwaysRun), | |
| 2808 | ); | |
| 2809 | const runtimeServices = yield* Layer.build(layerProviderRegistry).pipe( | |
| 2810 | Scope.provide(scope), | |
| 2811 | ); | |
| 2812 | ||
| 2813 | yield* Effect.gen(function* () { | |
| 2814 | const registry = yield* ProviderRegistry.ProviderRegistry; | |
| 2815 | const providers = yield* registry.getProviders; | |
| 2816 | const ghost = providers.find((provider) => provider.instanceId === "ghost_main"); | |
| 2817 | ||
| 2818 | assert.notStrictEqual(ghost, undefined); | |
| 2819 | assert.strictEqual(ghost?.driver, "ghostDriver"); | |
| 2820 | assert.strictEqual(ghost?.availability, "unavailable"); | |
| 2821 | assert.match(ghost?.unavailableReason ?? "", /ghostDriver/); | |
| 2822 | }).pipe(Effect.provide(runtimeServices)); | |
| 2823 | }), | |
| 2824 | ); | |
| 2788 | 2825 | |
| 2789 | it.effect("includes unavailable instance snapshots in getProviders", () => | |
| 2826 | it.effect( | |
| 2827 | "keeps Cursor disabled and skips provider probing when settings use their defaults", | |
| 2828 | () => | |
| 2790 | 2829 | Effect.gen(function* () { |
| 2791 | 2830 | const serverSettings = yield* makeMutableServerSettingsService( |
| 2792 | 2831 | decodeServerSettings( |
| 2793 | 2832 | deepMerge(encodedDefaultServerSettings, { |
| 2794 | 2833 | providers: { |
| 2795 | codex: { enabled: false }, | |
| 2796 | claudeAgent: { enabled: false }, | |
| 2797 | cursor: { enabled: false }, | |
| 2798 | grok: { enabled: false }, | |
| 2799 | opencode: { enabled: false }, | |
| 2800 | }, | |
| 2801 | providerInstances: { | |
| 2802 | ghost_main: { | |
| 2803 | driver: "ghostDriver", | |
| 2804 | displayName: "A fork-only driver we don't ship", | |
| 2834 | codex: { | |
| 2835 | enabled: false, | |
| 2836 | }, | |
| 2837 | grok: { | |
| 2805 | 2838 | enabled: false, |
| 2806 | config: { arbitrary: "payload" }, | |
| 2807 | 2839 | }, |
| 2808 | } as unknown as ContractServerSettings["providerInstances"], | |
| 2840 | }, | |
| 2809 | 2841 | }), |
| 2810 | 2842 | ), |
| 2811 | 2843 | ); |
| 2844 | let cursorSpawned = false; | |
| 2812 | 2845 | const scope = yield* Scope.make(); |
| 2813 | 2846 | yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void)); |
| 2814 | const providerRegistryLayer = ProviderRegistryLive.pipe( | |
| 2815 | Layer.provideMerge(ProviderInstanceRegistryHydrationLive), | |
| 2847 | const layerProviderRegistry = ProviderRegistryLayer.layer.pipe( | |
| 2848 | Layer.provideMerge(ProviderInstanceRegistryHydration.layer), | |
| 2816 | 2849 | Layer.provideMerge(AntigravityInstallation.AntigravityInstallation.layer), |
| 2817 | 2850 | Layer.provideMerge( |
| 2818 | 2851 | Layer.succeed(ServerSettingsModule.ServerSettingsService, serverSettings), |
| ... | ... | @@ -2823,7 +2856,7 @@ it.layer(Layer.mergeAll(TestNodeServices, ServerSettingsModule.layerTest(), Test |
| 2823 | 2856 | prefix: "t3-provider-registry-", |
| 2824 | 2857 | }), |
| 2825 | 2858 | ), |
| 2826 | Layer.provideMerge(TestHttpClientLive), | |
| 2859 | Layer.provideMerge(layerTestHttpClient), | |
| 2827 | 2860 | Layer.provideMerge( |
| 2828 | 2861 | Layer.succeed( |
| 2829 | 2862 | ProviderEventLoggers.ProviderEventLoggers, |
| ... | ... | @@ -2833,546 +2866,473 @@ it.layer(Layer.mergeAll(TestNodeServices, ServerSettingsModule.layerTest(), Test |
| 2833 | 2866 | Layer.provideMerge(ModelManifest.layerTest), |
| 2834 | 2867 | Layer.provideMerge(ResetCreditCoordinator.layerTest), |
| 2835 | 2868 | Layer.provideMerge( |
| 2836 | OpenCodeRuntime.OpenCodeRuntimeLive.pipe( | |
| 2837 | Layer.provide(OpenCodeServerLedger.layerTest), | |
| 2838 | ), | |
| 2869 | OpenCodeRuntime.layer.pipe(Layer.provide(OpenCodeServerLedger.layerTest)), | |
| 2870 | ), | |
| 2871 | Layer.provideMerge(layerBackgroundPolicyAlwaysRun), | |
| 2872 | Layer.provideMerge( | |
| 2873 | layerMockCommandSpawner((command, args) => { | |
| 2874 | if (command === "cursor-agent") { | |
| 2875 | cursorSpawned = true; | |
| 2876 | } | |
| 2877 | const joined = args.join(" "); | |
| 2878 | if (joined === "--version") { | |
| 2879 | return { | |
| 2880 | stdout: `${command} 1.0.0\n`, | |
| 2881 | stderr: "", | |
| 2882 | code: 0, | |
| 2883 | }; | |
| 2884 | } | |
| 2885 | if (joined === "auth status") { | |
| 2886 | return { | |
| 2887 | stdout: '{"authenticated":true}\n', | |
| 2888 | stderr: "", | |
| 2889 | code: 0, | |
| 2890 | }; | |
| 2891 | } | |
| 2892 | throw new Error(`Unexpected args: ${command} ${joined}`); | |
| 2893 | }), | |
| 2839 | 2894 | ), |
| 2840 | Layer.provideMerge(NodeServices.layer), | |
| 2841 | Layer.provideMerge(BackgroundPolicyAlwaysRunLayer), | |
| 2842 | ); | |
| 2843 | const runtimeServices = yield* Layer.build(providerRegistryLayer).pipe( | |
| 2844 | Scope.provide(scope), | |
| 2845 | 2895 | ); |
| 2896 | const runtimeServices = yield* Layer.build( | |
| 2897 | Layer.mergeAll( | |
| 2898 | Layer.succeed(ServerSettingsModule.ServerSettingsService, serverSettings), | |
| 2899 | layerProviderRegistry, | |
| 2900 | ), | |
| 2901 | ).pipe(Scope.provide(scope)); | |
| 2846 | 2902 | |
| 2847 | 2903 | yield* Effect.gen(function* () { |
| 2848 | 2904 | const registry = yield* ProviderRegistry.ProviderRegistry; |
| 2849 | 2905 | const providers = yield* registry.getProviders; |
| 2850 | const ghost = providers.find((provider) => provider.instanceId === "ghost_main"); | |
| 2906 | const cursorProvider = providers.find( | |
| 2907 | (provider) => provider.instanceId === ProviderInstanceId.make("cursor"), | |
| 2908 | ); | |
| 2851 | 2909 | |
| 2852 | assert.notStrictEqual(ghost, undefined); | |
| 2853 | assert.strictEqual(ghost?.driver, "ghostDriver"); | |
| 2854 | assert.strictEqual(ghost?.availability, "unavailable"); | |
| 2855 | assert.match(ghost?.unavailableReason ?? "", /ghostDriver/); | |
| 2910 | assert.deepStrictEqual(providers.map((provider) => provider.instanceId).toSorted(), [ | |
| 2911 | "antigravity", | |
| 2912 | "claudeAgent", | |
| 2913 | "codex", | |
| 2914 | "cursor", | |
| 2915 | "grok", | |
| 2916 | "opencode", | |
| 2917 | "pi", | |
| 2918 | ]); | |
| 2919 | assert.strictEqual(cursorProvider?.enabled, false); | |
| 2920 | assert.strictEqual(cursorProvider?.status, "disabled"); | |
| 2921 | assert.strictEqual(cursorProvider?.message, "Cursor is disabled in T3 Code settings."); | |
| 2922 | assert.strictEqual(cursorSpawned, false); | |
| 2856 | 2923 | }).pipe(Effect.provide(runtimeServices)); |
| 2857 | 2924 | }), |
| 2858 | ); | |
| 2925 | ); | |
| 2859 | 2926 | |
| 2860 | it.effect( | |
| 2861 | "keeps Cursor disabled and skips provider probing when settings use their defaults", | |
| 2862 | () => | |
| 2863 | Effect.gen(function* () { | |
| 2864 | const serverSettings = yield* makeMutableServerSettingsService( | |
| 2865 | decodeServerSettings( | |
| 2866 | deepMerge(encodedDefaultServerSettings, { | |
| 2867 | providers: { | |
| 2868 | codex: { | |
| 2869 | enabled: false, | |
| 2870 | }, | |
| 2871 | grok: { | |
| 2872 | enabled: false, | |
| 2873 | }, | |
| 2874 | }, | |
| 2875 | }), | |
| 2876 | ), | |
| 2877 | ); | |
| 2878 | let cursorSpawned = false; | |
| 2879 | const scope = yield* Scope.make(); | |
| 2880 | yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void)); | |
| 2881 | const providerRegistryLayer = ProviderRegistryLive.pipe( | |
| 2882 | Layer.provideMerge(ProviderInstanceRegistryHydrationLive), | |
| 2883 | Layer.provideMerge(AntigravityInstallation.AntigravityInstallation.layer), | |
| 2884 | Layer.provideMerge( | |
| 2885 | Layer.succeed(ServerSettingsModule.ServerSettingsService, serverSettings), | |
| 2886 | ), | |
| 2887 | Layer.provideMerge(ServerSecretStore.layer), | |
| 2888 | Layer.provideMerge( | |
| 2889 | ServerConfig.layerTest(process.cwd(), { | |
| 2890 | prefix: "t3-provider-registry-", | |
| 2891 | }), | |
| 2892 | ), | |
| 2893 | Layer.provideMerge(TestHttpClientLive), | |
| 2894 | Layer.provideMerge( | |
| 2895 | Layer.succeed( | |
| 2896 | ProviderEventLoggers.ProviderEventLoggers, | |
| 2897 | ProviderEventLoggers.NoOpProviderEventLoggers, | |
| 2898 | ), | |
| 2899 | ), | |
| 2900 | Layer.provideMerge(ModelManifest.layerTest), | |
| 2901 | Layer.provideMerge(ResetCreditCoordinator.layerTest), | |
| 2902 | Layer.provideMerge( | |
| 2903 | OpenCodeRuntime.OpenCodeRuntimeLive.pipe( | |
| 2904 | Layer.provide(OpenCodeServerLedger.layerTest), | |
| 2905 | ), | |
| 2906 | ), | |
| 2907 | Layer.provideMerge(BackgroundPolicyAlwaysRunLayer), | |
| 2908 | Layer.provideMerge( | |
| 2909 | mockCommandSpawnerLayer((command, args) => { | |
| 2910 | if (command === "cursor-agent") { | |
| 2911 | cursorSpawned = true; | |
| 2912 | } | |
| 2913 | const joined = args.join(" "); | |
| 2914 | if (joined === "--version") { | |
| 2915 | return { | |
| 2916 | stdout: `${command} 1.0.0\n`, | |
| 2917 | stderr: "", | |
| 2918 | code: 0, | |
| 2919 | }; | |
| 2920 | } | |
| 2921 | if (joined === "auth status") { | |
| 2922 | return { | |
| 2923 | stdout: '{"authenticated":true}\n', | |
| 2924 | stderr: "", | |
| 2925 | code: 0, | |
| 2926 | }; | |
| 2927 | } | |
| 2928 | throw new Error(`Unexpected args: ${command} ${joined}`); | |
| 2929 | }), | |
| 2930 | ), | |
| 2931 | ); | |
| 2932 | const runtimeServices = yield* Layer.build( | |
| 2933 | Layer.mergeAll( | |
| 2934 | Layer.succeed(ServerSettingsModule.ServerSettingsService, serverSettings), | |
| 2935 | providerRegistryLayer, | |
| 2936 | ), | |
| 2937 | ).pipe(Scope.provide(scope)); | |
| 2927 | it.effect("skips codex probes entirely when the provider is disabled", () => | |
| 2928 | Effect.gen(function* () { | |
| 2929 | const status = yield* checkCodexProviderStatus(disabledCodexSettings).pipe( | |
| 2930 | Effect.provide(layerFailingSpawner("spawn codex ENOENT")), | |
| 2931 | ); | |
| 2932 | assert.strictEqual(status.enabled, false); | |
| 2933 | assert.strictEqual(status.status, "disabled"); | |
| 2934 | assert.strictEqual(status.installed, false); | |
| 2935 | assert.strictEqual(status.message, "Codex is disabled in T3 Code settings."); | |
| 2936 | }), | |
| 2937 | ); | |
| 2938 | }); | |
| 2938 | 2939 | |
| 2939 | yield* Effect.gen(function* () { | |
| 2940 | const registry = yield* ProviderRegistry.ProviderRegistry; | |
| 2941 | const providers = yield* registry.getProviders; | |
| 2942 | const cursorProvider = providers.find( | |
| 2943 | (provider) => provider.instanceId === ProviderInstanceId.make("cursor"), | |
| 2944 | ); | |
| 2940 | // ── checkClaudeProviderStatus tests ────────────────────────── | |
| 2945 | 2941 | |
| 2946 | assert.deepStrictEqual(providers.map((provider) => provider.instanceId).toSorted(), [ | |
| 2947 | "antigravity", | |
| 2948 | "claudeAgent", | |
| 2949 | "codex", | |
| 2950 | "cursor", | |
| 2951 | "grok", | |
| 2952 | "opencode", | |
| 2953 | "pi", | |
| 2954 | ]); | |
| 2955 | assert.strictEqual(cursorProvider?.enabled, false); | |
| 2956 | assert.strictEqual(cursorProvider?.status, "disabled"); | |
| 2957 | assert.strictEqual( | |
| 2958 | cursorProvider?.message, | |
| 2959 | "Cursor is disabled in T3 Code settings.", | |
| 2960 | ); | |
| 2961 | assert.strictEqual(cursorSpawned, false); | |
| 2962 | }).pipe(Effect.provide(runtimeServices)); | |
| 2942 | describe("checkClaudeProviderStatus", () => { | |
| 2943 | it.effect("returns ready when claude is installed and authenticated", () => | |
| 2944 | Effect.gen(function* () { | |
| 2945 | const status = yield* checkClaudeProviderStatus( | |
| 2946 | defaultClaudeSettings, | |
| 2947 | claudeCapabilities(), | |
| 2948 | ); | |
| 2949 | assert.strictEqual(status.status, "ready"); | |
| 2950 | assert.strictEqual(status.installed, true); | |
| 2951 | assert.strictEqual(status.auth.status, "authenticated"); | |
| 2952 | }).pipe( | |
| 2953 | Effect.provide( | |
| 2954 | layerMockSpawner((args) => { | |
| 2955 | const joined = args.join(" "); | |
| 2956 | if (joined === "--version") return { stdout: "1.0.0\n", stderr: "", code: 0 }; | |
| 2957 | if (joined === "auth status") | |
| 2958 | return { | |
| 2959 | stdout: '{"loggedIn":true,"authMethod":"claude.ai"}\n', | |
| 2960 | stderr: "", | |
| 2961 | code: 0, | |
| 2962 | }; | |
| 2963 | throw new Error(`Unexpected args: ${joined}`); | |
| 2963 | 2964 | }), |
| 2964 | ); | |
| 2965 | ||
| 2966 | it.effect("skips codex probes entirely when the provider is disabled", () => | |
| 2967 | Effect.gen(function* () { | |
| 2968 | const status = yield* checkCodexProviderStatus(disabledCodexSettings).pipe( | |
| 2969 | Effect.provide(failingSpawnerLayer("spawn codex ENOENT")), | |
| 2970 | ); | |
| 2971 | assert.strictEqual(status.enabled, false); | |
| 2972 | assert.strictEqual(status.status, "disabled"); | |
| 2973 | assert.strictEqual(status.installed, false); | |
| 2974 | assert.strictEqual(status.message, "Codex is disabled in T3 Code settings."); | |
| 2975 | }), | |
| 2976 | ); | |
| 2977 | }); | |
| 2978 | ||
| 2979 | // ── checkClaudeProviderStatus tests ────────────────────────── | |
| 2980 | ||
| 2981 | describe("checkClaudeProviderStatus", () => { | |
| 2982 | it.effect("returns ready when claude is installed and authenticated", () => | |
| 2983 | Effect.gen(function* () { | |
| 2984 | const status = yield* checkClaudeProviderStatus( | |
| 2985 | defaultClaudeSettings, | |
| 2986 | claudeCapabilities(), | |
| 2987 | ); | |
| 2988 | assert.strictEqual(status.status, "ready"); | |
| 2989 | assert.strictEqual(status.installed, true); | |
| 2990 | assert.strictEqual(status.auth.status, "authenticated"); | |
| 2991 | }).pipe( | |
| 2992 | Effect.provide( | |
| 2993 | mockSpawnerLayer((args) => { | |
| 2994 | const joined = args.join(" "); | |
| 2995 | if (joined === "--version") return { stdout: "1.0.0\n", stderr: "", code: 0 }; | |
| 2996 | if (joined === "auth status") | |
| 2997 | return { | |
| 2998 | stdout: '{"loggedIn":true,"authMethod":"claude.ai"}\n', | |
| 2999 | stderr: "", | |
| 3000 | code: 0, | |
| 3001 | }; | |
| 3002 | throw new Error(`Unexpected args: ${joined}`); | |
| 3003 | }), | |
| 3004 | ), | |
| 3005 | ), | |
| 3006 | ); | |
| 3007 | ||
| 3008 | it.effect("returns ready and labels Bedrock-backed Claude as authenticated", () => | |
| 3009 | Effect.gen(function* () { | |
| 3010 | // Bedrock authenticates via external AWS credentials, so the SDK init | |
| 3011 | // reports only `apiProvider` with no subscription or token. | |
| 3012 | const status = yield* checkClaudeProviderStatus( | |
| 3013 | defaultClaudeSettings, | |
| 3014 | claudeCapabilities({ apiProvider: "bedrock" }), | |
| 3015 | ); | |
| 3016 | assert.strictEqual(status.status, "ready"); | |
| 3017 | assert.strictEqual(status.installed, true); | |
| 3018 | assert.strictEqual(status.auth.status, "authenticated"); | |
| 3019 | assert.strictEqual(status.auth.type, "bedrock"); | |
| 3020 | assert.strictEqual(status.auth.label, "Amazon Bedrock"); | |
| 3021 | }).pipe( | |
| 3022 | Effect.provide( | |
| 3023 | mockSpawnerLayer((args) => { | |
| 3024 | const joined = args.join(" "); | |
| 3025 | if (joined === "--version") return { stdout: "1.0.0\n", stderr: "", code: 0 }; | |
| 3026 | throw new Error(`Unexpected args: ${joined}`); | |
| 3027 | }), | |
| 3028 | ), | |
| 3029 | 2965 | ), |
| 3030 | ); | |
| 2966 | ), | |
| 2967 | ); | |
| 3031 | 2968 | |
| 3032 | it.effect("returns a display label for claude subscription types", () => | |
| 3033 | Effect.gen(function* () { | |
| 3034 | const status = yield* checkClaudeProviderStatus( | |
| 3035 | defaultClaudeSettings, | |
| 3036 | claudeCapabilities({ subscriptionType: "maxplan" }), | |
| 3037 | ); | |
| 3038 | assert.strictEqual(status.status, "ready"); | |
| 3039 | assert.strictEqual(status.auth.status, "authenticated"); | |
| 3040 | assert.strictEqual(status.auth.type, "maxplan"); | |
| 3041 | assert.strictEqual(status.auth.label, "Claude Max Subscription"); | |
| 3042 | }).pipe( | |
| 3043 | Effect.provide( | |
| 3044 | mockSpawnerLayer((args) => { | |
| 3045 | const joined = args.join(" "); | |
| 3046 | if (joined === "--version") return { stdout: "1.0.0\n", stderr: "", code: 0 }; | |
| 3047 | if (joined === "auth status") | |
| 3048 | return { | |
| 3049 | stdout: '{"loggedIn":true,"authMethod":"claude.ai"}\n', | |
| 3050 | stderr: "", | |
| 3051 | code: 0, | |
| 3052 | }; | |
| 3053 | throw new Error(`Unexpected args: ${joined}`); | |
| 3054 | }), | |
| 3055 | ), | |
| 2969 | it.effect("returns ready and labels Bedrock-backed Claude as authenticated", () => | |
| 2970 | Effect.gen(function* () { | |
| 2971 | // Bedrock authenticates via external AWS credentials, so the SDK init | |
| 2972 | // reports only `apiProvider` with no subscription or token. | |
| 2973 | const status = yield* checkClaudeProviderStatus( | |
| 2974 | defaultClaudeSettings, | |
| 2975 | claudeCapabilities({ apiProvider: "bedrock" }), | |
| 2976 | ); | |
| 2977 | assert.strictEqual(status.status, "ready"); | |
| 2978 | assert.strictEqual(status.installed, true); | |
| 2979 | assert.strictEqual(status.auth.status, "authenticated"); | |
| 2980 | assert.strictEqual(status.auth.type, "bedrock"); | |
| 2981 | assert.strictEqual(status.auth.label, "Amazon Bedrock"); | |
| 2982 | }).pipe( | |
| 2983 | Effect.provide( | |
| 2984 | layerMockSpawner((args) => { | |
| 2985 | const joined = args.join(" "); | |
| 2986 | if (joined === "--version") return { stdout: "1.0.0\n", stderr: "", code: 0 }; | |
| 2987 | throw new Error(`Unexpected args: ${joined}`); | |
| 2988 | }), | |
| 3056 | 2989 | ), |
| 3057 | ); | |
| 2990 | ), | |
| 2991 | ); | |
| 3058 | 2992 | |
| 3059 | it.effect("reads banked resets only for subscription logins", () => | |
| 3060 | Effect.gen(function* () { | |
| 3061 | const check = (overrides: Partial<TestClaudeCapabilities>) => | |
| 3062 | checkClaudeProviderStatus( | |
| 3063 | defaultClaudeSettings, | |
| 3064 | () => | |
| 3065 | Effect.succeed({ | |
| 3066 | email: undefined, | |
| 3067 | subscriptionType: undefined, | |
| 3068 | tokenSource: undefined, | |
| 3069 | apiProvider: undefined, | |
| 3070 | slashCommands: [], | |
| 3071 | usage: { rate_limits_available: true, rate_limits: {} }, | |
| 3072 | ...overrides, | |
| 3073 | }), | |
| 3074 | undefined, | |
| 3075 | undefined, | |
| 3076 | undefined, | |
| 3077 | undefined, | |
| 3078 | () => Effect.succeed({ availableCount: 2 }), | |
| 3079 | ); | |
| 3080 | const subscription = yield* check({ subscriptionType: "max" }); | |
| 3081 | const bedrock = yield* check({ apiProvider: "bedrock" }); | |
| 3082 | assert.deepStrictEqual(subscription.usageLimits?.resetCredits, { availableCount: 2 }); | |
| 3083 | assert.strictEqual(bedrock.usageLimits?.resetCredits, undefined); | |
| 3084 | }).pipe( | |
| 3085 | Effect.provide( | |
| 3086 | mockSpawnerLayer((args) => { | |
| 3087 | const joined = args.join(" "); | |
| 3088 | if (joined === "--version") return { stdout: "1.0.0\n", stderr: "", code: 0 }; | |
| 3089 | throw new Error(`Unexpected args: ${joined}`); | |
| 3090 | }), | |
| 3091 | ), | |
| 2993 | it.effect("returns a display label for claude subscription types", () => | |
| 2994 | Effect.gen(function* () { | |
| 2995 | const status = yield* checkClaudeProviderStatus( | |
| 2996 | defaultClaudeSettings, | |
| 2997 | claudeCapabilities({ subscriptionType: "maxplan" }), | |
| 2998 | ); | |
| 2999 | assert.strictEqual(status.status, "ready"); | |
| 3000 | assert.strictEqual(status.auth.status, "authenticated"); | |
| 3001 | assert.strictEqual(status.auth.type, "maxplan"); | |
| 3002 | assert.strictEqual(status.auth.label, "Claude Max Subscription"); | |
| 3003 | }).pipe( | |
| 3004 | Effect.provide( | |
| 3005 | layerMockSpawner((args) => { | |
| 3006 | const joined = args.join(" "); | |
| 3007 | if (joined === "--version") return { stdout: "1.0.0\n", stderr: "", code: 0 }; | |
| 3008 | if (joined === "auth status") | |
| 3009 | return { | |
| 3010 | stdout: '{"loggedIn":true,"authMethod":"claude.ai"}\n', | |
| 3011 | stderr: "", | |
| 3012 | code: 0, | |
| 3013 | }; | |
| 3014 | throw new Error(`Unexpected args: ${joined}`); | |
| 3015 | }), | |
| 3092 | 3016 | ), |
| 3093 | ); | |
| 3017 | ), | |
| 3018 | ); | |
| 3094 | 3019 | |
| 3095 | it.effect("does not duplicate Claude in full subscription labels", () => | |
| 3096 | Effect.gen(function* () { | |
| 3097 | const status = yield* checkClaudeProviderStatus( | |
| 3020 | it.effect("reads banked resets only for subscription logins", () => | |
| 3021 | Effect.gen(function* () { | |
| 3022 | const check = (overrides: Partial<TestClaudeCapabilities>) => | |
| 3023 | checkClaudeProviderStatus( | |
| 3098 | 3024 | defaultClaudeSettings, |
| 3099 | claudeCapabilities({ | |
| 3100 | subscriptionType: "Claude Max Subscription", | |
| 3101 | }), | |
| 3025 | () => | |
| 3026 | Effect.succeed({ | |
| 3027 | email: undefined, | |
| 3028 | subscriptionType: undefined, | |
| 3029 | tokenSource: undefined, | |
| 3030 | apiProvider: undefined, | |
| 3031 | slashCommands: [], | |
| 3032 | usage: { rate_limits_available: true, rate_limits: {} }, | |
| 3033 | ...overrides, | |
| 3034 | }), | |
| 3035 | undefined, | |
| 3036 | undefined, | |
| 3037 | undefined, | |
| 3038 | undefined, | |
| 3039 | () => Effect.succeed({ availableCount: 2 }), | |
| 3102 | 3040 | ); |
| 3103 | assert.strictEqual(status.auth.status, "authenticated"); | |
| 3104 | assert.strictEqual(status.auth.type, "Claude Max Subscription"); | |
| 3105 | assert.strictEqual(status.auth.label, "Claude Max Subscription"); | |
| 3106 | }).pipe( | |
| 3107 | Effect.provide( | |
| 3108 | mockSpawnerLayer((args) => { | |
| 3109 | const joined = args.join(" "); | |
| 3110 | if (joined === "--version") return { stdout: "1.0.0\n", stderr: "", code: 0 }; | |
| 3111 | throw new Error(`Unexpected args: ${joined}`); | |
| 3112 | }), | |
| 3113 | ), | |
| 3041 | const subscription = yield* check({ subscriptionType: "max" }); | |
| 3042 | const bedrock = yield* check({ apiProvider: "bedrock" }); | |
| 3043 | assert.deepStrictEqual(subscription.usageLimits?.resetCredits, { availableCount: 2 }); | |
| 3044 | assert.strictEqual(bedrock.usageLimits?.resetCredits, undefined); | |
| 3045 | }).pipe( | |
| 3046 | Effect.provide( | |
| 3047 | layerMockSpawner((args) => { | |
| 3048 | const joined = args.join(" "); | |
| 3049 | if (joined === "--version") return { stdout: "1.0.0\n", stderr: "", code: 0 }; | |
| 3050 | throw new Error(`Unexpected args: ${joined}`); | |
| 3051 | }), | |
| 3114 | 3052 | ), |
| 3115 | ); | |
| 3053 | ), | |
| 3054 | ); | |
| 3116 | 3055 | |
| 3117 | it.effect("does not duplicate Claude in provider-prefixed subscription names", () => | |
| 3118 | Effect.gen(function* () { | |
| 3119 | const status = yield* checkClaudeProviderStatus( | |
| 3120 | defaultClaudeSettings, | |
| 3121 | claudeCapabilities({ | |
| 3122 | subscriptionType: "Claude Max", | |
| 3123 | }), | |
| 3124 | ); | |
| 3125 | assert.strictEqual(status.auth.status, "authenticated"); | |
| 3126 | assert.strictEqual(status.auth.type, "Claude Max"); | |
| 3127 | assert.strictEqual(status.auth.label, "Claude Max Subscription"); | |
| 3128 | }).pipe( | |
| 3129 | Effect.provide( | |
| 3130 | mockSpawnerLayer((args) => { | |
| 3131 | const joined = args.join(" "); | |
| 3132 | if (joined === "--version") return { stdout: "1.0.0\n", stderr: "", code: 0 }; | |
| 3133 | throw new Error(`Unexpected args: ${joined}`); | |
| 3134 | }), | |
| 3135 | ), | |
| 3056 | it.effect("does not duplicate Claude in full subscription labels", () => | |
| 3057 | Effect.gen(function* () { | |
| 3058 | const status = yield* checkClaudeProviderStatus( | |
| 3059 | defaultClaudeSettings, | |
| 3060 | claudeCapabilities({ | |
| 3061 | subscriptionType: "Claude Max Subscription", | |
| 3062 | }), | |
| 3063 | ); | |
| 3064 | assert.strictEqual(status.auth.status, "authenticated"); | |
| 3065 | assert.strictEqual(status.auth.type, "Claude Max Subscription"); | |
| 3066 | assert.strictEqual(status.auth.label, "Claude Max Subscription"); | |
| 3067 | }).pipe( | |
| 3068 | Effect.provide( | |
| 3069 | layerMockSpawner((args) => { | |
| 3070 | const joined = args.join(" "); | |
| 3071 | if (joined === "--version") return { stdout: "1.0.0\n", stderr: "", code: 0 }; | |
| 3072 | throw new Error(`Unexpected args: ${joined}`); | |
| 3073 | }), | |
| 3136 | 3074 | ), |
| 3137 | ); | |
| 3075 | ), | |
| 3076 | ); | |
| 3138 | 3077 | |
| 3139 | it.effect("returns claude auth email from initialization result", () => | |
| 3140 | Effect.gen(function* () { | |
| 3141 | const status = yield* checkClaudeProviderStatus( | |
| 3142 | defaultClaudeSettings, | |
| 3143 | claudeCapabilities({ email: "claude@example.com" }), | |
| 3144 | ); | |
| 3145 | assert.strictEqual(status.auth.status, "authenticated"); | |
| 3146 | assert.strictEqual(status.auth.email, "claude@example.com"); | |
| 3147 | }).pipe( | |
| 3148 | Effect.provide( | |
| 3149 | mockSpawnerLayer((args) => { | |
| 3150 | const joined = args.join(" "); | |
| 3151 | if (joined === "--version") return { stdout: "1.0.0\n", stderr: "", code: 0 }; | |
| 3152 | if (joined === "auth status") | |
| 3153 | return { | |
| 3154 | stdout: | |
| 3155 | '{"loggedIn":true,"authMethod":"claude.ai","account":{"email":"claude@example.com"}}\n', | |
| 3156 | stderr: "", | |
| 3157 | code: 0, | |
| 3158 | }; | |
| 3159 | throw new Error(`Unexpected args: ${joined}`); | |
| 3160 | }), | |
| 3161 | ), | |
| 3078 | it.effect("does not duplicate Claude in provider-prefixed subscription names", () => | |
| 3079 | Effect.gen(function* () { | |
| 3080 | const status = yield* checkClaudeProviderStatus( | |
| 3081 | defaultClaudeSettings, | |
| 3082 | claudeCapabilities({ | |
| 3083 | subscriptionType: "Claude Max", | |
| 3084 | }), | |
| 3085 | ); | |
| 3086 | assert.strictEqual(status.auth.status, "authenticated"); | |
| 3087 | assert.strictEqual(status.auth.type, "Claude Max"); | |
| 3088 | assert.strictEqual(status.auth.label, "Claude Max Subscription"); | |
| 3089 | }).pipe( | |
| 3090 | Effect.provide( | |
| 3091 | layerMockSpawner((args) => { | |
| 3092 | const joined = args.join(" "); | |
| 3093 | if (joined === "--version") return { stdout: "1.0.0\n", stderr: "", code: 0 }; | |
| 3094 | throw new Error(`Unexpected args: ${joined}`); | |
| 3095 | }), | |
| 3162 | 3096 | ), |
| 3163 | ); | |
| 3164 | ||
| 3165 | it.effect("runs Claude status probes with the configured CLAUDE_CONFIG_DIR", () => { | |
| 3166 | const claudeConfigDir = "/tmp/t3code-claude-home"; | |
| 3167 | const recorded = recordingMockSpawnerLayer((args) => { | |
| 3168 | const joined = args.join(" "); | |
| 3169 | if (joined === "--version") return { stdout: "1.0.0\n", stderr: "", code: 0 }; | |
| 3170 | if (joined === "auth status") | |
| 3171 | return { | |
| 3172 | stdout: '{"loggedIn":true,"authMethod":"claude.ai"}\n', | |
| 3173 | stderr: "", | |
| 3174 | code: 0, | |
| 3175 | }; | |
| 3176 | throw new Error(`Unexpected args: ${joined}`); | |
| 3177 | }); | |
| 3097 | ), | |
| 3098 | ); | |
| 3178 | 3099 | |
| 3179 | return Effect.gen(function* () { | |
| 3180 | const status = yield* checkClaudeProviderStatus( | |
| 3181 | { | |
| 3182 | ...defaultClaudeSettings, | |
| 3183 | homePath: claudeConfigDir, | |
| 3184 | }, | |
| 3185 | claudeCapabilities(), | |
| 3186 | ); | |
| 3187 | assert.strictEqual(status.status, "ready"); | |
| 3188 | // The home is resolved through the host Path before it reaches the env. | |
| 3189 | assert.deepStrictEqual( | |
| 3190 | recorded.commands.map((command) => command.env?.CLAUDE_CONFIG_DIR), | |
| 3191 | [(yield* Path.Path).resolve(claudeConfigDir)], | |
| 3192 | ); | |
| 3193 | }).pipe(Effect.provide(recorded.layer)); | |
| 3100 | it.effect("returns claude auth email from initialization result", () => | |
| 3101 | Effect.gen(function* () { | |
| 3102 | const status = yield* checkClaudeProviderStatus( | |
| 3103 | defaultClaudeSettings, | |
| 3104 | claudeCapabilities({ email: "claude@example.com" }), | |
| 3105 | ); | |
| 3106 | assert.strictEqual(status.auth.status, "authenticated"); | |
| 3107 | assert.strictEqual(status.auth.email, "claude@example.com"); | |
| 3108 | }).pipe( | |
| 3109 | Effect.provide( | |
| 3110 | layerMockSpawner((args) => { | |
| 3111 | const joined = args.join(" "); | |
| 3112 | if (joined === "--version") return { stdout: "1.0.0\n", stderr: "", code: 0 }; | |
| 3113 | if (joined === "auth status") | |
| 3114 | return { | |
| 3115 | stdout: | |
| 3116 | '{"loggedIn":true,"authMethod":"claude.ai","account":{"email":"claude@example.com"}}\n', | |
| 3117 | stderr: "", | |
| 3118 | code: 0, | |
| 3119 | }; | |
| 3120 | throw new Error(`Unexpected args: ${joined}`); | |
| 3121 | }), | |
| 3122 | ), | |
| 3123 | ), | |
| 3124 | ); | |
| 3125 | ||
| 3126 | it.effect("runs Claude status probes with the configured CLAUDE_CONFIG_DIR", () => { | |
| 3127 | const claudeConfigDir = "/tmp/t3code-claude-home"; | |
| 3128 | const recorded = recordingMockSpawnerLayer((args) => { | |
| 3129 | const joined = args.join(" "); | |
| 3130 | if (joined === "--version") return { stdout: "1.0.0\n", stderr: "", code: 0 }; | |
| 3131 | if (joined === "auth status") | |
| 3132 | return { | |
| 3133 | stdout: '{"loggedIn":true,"authMethod":"claude.ai"}\n', | |
| 3134 | stderr: "", | |
| 3135 | code: 0, | |
| 3136 | }; | |
| 3137 | throw new Error(`Unexpected args: ${joined}`); | |
| 3194 | 3138 | }); |
| 3195 | 3139 | |
| 3196 | it.effect("includes probed claude slash commands in the provider snapshot", () => | |
| 3197 | Effect.gen(function* () { | |
| 3198 | const status = yield* checkClaudeProviderStatus( | |
| 3199 | defaultClaudeSettings, | |
| 3200 | claudeCapabilities({ | |
| 3201 | subscriptionType: "maxplan", | |
| 3202 | slashCommands: [ | |
| 3203 | { | |
| 3204 | name: "review", | |
| 3205 | description: "Review a pull request", | |
| 3206 | input: { hint: "pr-or-branch" }, | |
| 3207 | }, | |
| 3208 | ], | |
| 3209 | }), | |
| 3210 | ); | |
| 3140 | return Effect.gen(function* () { | |
| 3141 | const status = yield* checkClaudeProviderStatus( | |
| 3142 | { | |
| 3143 | ...defaultClaudeSettings, | |
| 3144 | homePath: claudeConfigDir, | |
| 3145 | }, | |
| 3146 | claudeCapabilities(), | |
| 3147 | ); | |
| 3148 | assert.strictEqual(status.status, "ready"); | |
| 3149 | // The home is resolved through the host Path before it reaches the env. | |
| 3150 | assert.deepStrictEqual( | |
| 3151 | recorded.commands.map((command) => command.env?.CLAUDE_CONFIG_DIR), | |
| 3152 | [(yield* Path.Path).resolve(claudeConfigDir)], | |
| 3153 | ); | |
| 3154 | }).pipe(Effect.provide(recorded.layer)); | |
| 3155 | }); | |
| 3211 | 3156 | |
| 3212 | assert.deepStrictEqual(status.slashCommands.slice(1), [ | |
| 3213 | { | |
| 3214 | name: "review", | |
| 3215 | description: "Review a pull request", | |
| 3216 | input: { hint: "pr-or-branch" }, | |
| 3217 | }, | |
| 3218 | ]); | |
| 3219 | }).pipe( | |
| 3220 | Effect.provide( | |
| 3221 | mockSpawnerLayer((args) => { | |
| 3222 | const joined = args.join(" "); | |
| 3223 | if (joined === "--version") return { stdout: "1.0.0\n", stderr: "", code: 0 }; | |
| 3224 | if (joined === "auth status") | |
| 3225 | return { | |
| 3226 | stdout: '{"loggedIn":true,"authMethod":"claude.ai"}\n', | |
| 3227 | stderr: "", | |
| 3228 | code: 0, | |
| 3229 | }; | |
| 3230 | throw new Error(`Unexpected args: ${joined}`); | |
| 3231 | }), | |
| 3232 | ), | |
| 3157 | it.effect("includes probed claude slash commands in the provider snapshot", () => | |
| 3158 | Effect.gen(function* () { | |
| 3159 | const status = yield* checkClaudeProviderStatus( | |
| 3160 | defaultClaudeSettings, | |
| 3161 | claudeCapabilities({ | |
| 3162 | subscriptionType: "maxplan", | |
| 3163 | slashCommands: [ | |
| 3164 | { | |
| 3165 | name: "review", | |
| 3166 | description: "Review a pull request", | |
| 3167 | input: { hint: "pr-or-branch" }, | |
| 3168 | }, | |
| 3169 | ], | |
| 3170 | }), | |
| 3171 | ); | |
| 3172 | ||
| 3173 | assert.deepStrictEqual(status.slashCommands.slice(1), [ | |
| 3174 | { | |
| 3175 | name: "review", | |
| 3176 | description: "Review a pull request", | |
| 3177 | input: { hint: "pr-or-branch" }, | |
| 3178 | }, | |
| 3179 | ]); | |
| 3180 | }).pipe( | |
| 3181 | Effect.provide( | |
| 3182 | layerMockSpawner((args) => { | |
| 3183 | const joined = args.join(" "); | |
| 3184 | if (joined === "--version") return { stdout: "1.0.0\n", stderr: "", code: 0 }; | |
| 3185 | if (joined === "auth status") | |
| 3186 | return { | |
| 3187 | stdout: '{"loggedIn":true,"authMethod":"claude.ai"}\n', | |
| 3188 | stderr: "", | |
| 3189 | code: 0, | |
| 3190 | }; | |
| 3191 | throw new Error(`Unexpected args: ${joined}`); | |
| 3192 | }), | |
| 3233 | 3193 | ), |
| 3234 | ); | |
| 3194 | ), | |
| 3195 | ); | |
| 3235 | 3196 | |
| 3236 | it.effect("deduplicates probed claude slash commands by name", () => | |
| 3237 | Effect.gen(function* () { | |
| 3238 | const status = yield* checkClaudeProviderStatus( | |
| 3239 | defaultClaudeSettings, | |
| 3240 | claudeCapabilities({ | |
| 3241 | subscriptionType: "maxplan", | |
| 3242 | slashCommands: [ | |
| 3243 | { | |
| 3244 | name: "ui", | |
| 3245 | description: "Explore and refine UI", | |
| 3246 | }, | |
| 3247 | { | |
| 3248 | name: "ui", | |
| 3249 | input: { hint: "component-or-screen" }, | |
| 3250 | }, | |
| 3251 | ], | |
| 3252 | }), | |
| 3253 | ); | |
| 3197 | it.effect("deduplicates probed claude slash commands by name", () => | |
| 3198 | Effect.gen(function* () { | |
| 3199 | const status = yield* checkClaudeProviderStatus( | |
| 3200 | defaultClaudeSettings, | |
| 3201 | claudeCapabilities({ | |
| 3202 | subscriptionType: "maxplan", | |
| 3203 | slashCommands: [ | |
| 3204 | { | |
| 3205 | name: "ui", | |
| 3206 | description: "Explore and refine UI", | |
| 3207 | }, | |
| 3208 | { | |
| 3209 | name: "ui", | |
| 3210 | input: { hint: "component-or-screen" }, | |
| 3211 | }, | |
| 3212 | ], | |
| 3213 | }), | |
| 3214 | ); | |
| 3254 | 3215 | |
| 3255 | assert.deepStrictEqual(status.slashCommands, [ | |
| 3256 | COMPACT_SLASH_COMMAND, | |
| 3257 | { | |
| 3258 | name: "ui", | |
| 3259 | description: "Explore and refine UI", | |
| 3260 | input: { hint: "component-or-screen" }, | |
| 3261 | }, | |
| 3262 | ]); | |
| 3263 | }).pipe( | |
| 3264 | Effect.provide( | |
| 3265 | mockSpawnerLayer((args) => { | |
| 3266 | const joined = args.join(" "); | |
| 3267 | if (joined === "--version") return { stdout: "1.0.0\n", stderr: "", code: 0 }; | |
| 3268 | if (joined === "auth status") | |
| 3269 | return { | |
| 3270 | stdout: '{"loggedIn":true,"authMethod":"claude.ai"}\n', | |
| 3271 | stderr: "", | |
| 3272 | code: 0, | |
| 3273 | }; | |
| 3274 | throw new Error(`Unexpected args: ${joined}`); | |
| 3275 | }), | |
| 3276 | ), | |
| 3216 | assert.deepStrictEqual(status.slashCommands, [ | |
| 3217 | COMPACT_SLASH_COMMAND, | |
| 3218 | { | |
| 3219 | name: "ui", | |
| 3220 | description: "Explore and refine UI", | |
| 3221 | input: { hint: "component-or-screen" }, | |
| 3222 | }, | |
| 3223 | ]); | |
| 3224 | }).pipe( | |
| 3225 | Effect.provide( | |
| 3226 | layerMockSpawner((args) => { | |
| 3227 | const joined = args.join(" "); | |
| 3228 | if (joined === "--version") return { stdout: "1.0.0\n", stderr: "", code: 0 }; | |
| 3229 | if (joined === "auth status") | |
| 3230 | return { | |
| 3231 | stdout: '{"loggedIn":true,"authMethod":"claude.ai"}\n', | |
| 3232 | stderr: "", | |
| 3233 | code: 0, | |
| 3234 | }; | |
| 3235 | throw new Error(`Unexpected args: ${joined}`); | |
| 3236 | }), | |
| 3277 | 3237 | ), |
| 3278 | ); | |
| 3238 | ), | |
| 3239 | ); | |
| 3279 | 3240 | |
| 3280 | it.effect("returns an api key label for claude api key auth", () => | |
| 3281 | Effect.gen(function* () { | |
| 3282 | const status = yield* checkClaudeProviderStatus( | |
| 3283 | defaultClaudeSettings, | |
| 3284 | claudeCapabilities({ tokenSource: "ANTHROPIC_AUTH_TOKEN" }), | |
| 3285 | ); | |
| 3286 | assert.strictEqual(status.status, "ready"); | |
| 3287 | assert.strictEqual(status.auth.status, "authenticated"); | |
| 3288 | assert.strictEqual(status.auth.type, "apiKey"); | |
| 3289 | assert.strictEqual(status.auth.label, "Claude API Key"); | |
| 3290 | }).pipe( | |
| 3291 | Effect.provide( | |
| 3292 | mockSpawnerLayer((args) => { | |
| 3293 | const joined = args.join(" "); | |
| 3294 | if (joined === "--version") return { stdout: "1.0.0\n", stderr: "", code: 0 }; | |
| 3295 | if (joined === "auth status") | |
| 3296 | return { | |
| 3297 | stdout: '{"loggedIn":true,"authMethod":"api-key"}\n', | |
| 3298 | stderr: "", | |
| 3299 | code: 0, | |
| 3300 | }; | |
| 3301 | throw new Error(`Unexpected args: ${joined}`); | |
| 3302 | }), | |
| 3303 | ), | |
| 3241 | it.effect("returns an api key label for claude api key auth", () => | |
| 3242 | Effect.gen(function* () { | |
| 3243 | const status = yield* checkClaudeProviderStatus( | |
| 3244 | defaultClaudeSettings, | |
| 3245 | claudeCapabilities({ tokenSource: "ANTHROPIC_AUTH_TOKEN" }), | |
| 3246 | ); | |
| 3247 | assert.strictEqual(status.status, "ready"); | |
| 3248 | assert.strictEqual(status.auth.status, "authenticated"); | |
| 3249 | assert.strictEqual(status.auth.type, "apiKey"); | |
| 3250 | assert.strictEqual(status.auth.label, "Claude API Key"); | |
| 3251 | }).pipe( | |
| 3252 | Effect.provide( | |
| 3253 | layerMockSpawner((args) => { | |
| 3254 | const joined = args.join(" "); | |
| 3255 | if (joined === "--version") return { stdout: "1.0.0\n", stderr: "", code: 0 }; | |
| 3256 | if (joined === "auth status") | |
| 3257 | return { | |
| 3258 | stdout: '{"loggedIn":true,"authMethod":"api-key"}\n', | |
| 3259 | stderr: "", | |
| 3260 | code: 0, | |
| 3261 | }; | |
| 3262 | throw new Error(`Unexpected args: ${joined}`); | |
| 3263 | }), | |
| 3304 | 3264 | ), |
| 3305 | ); | |
| 3306 | ||
| 3307 | it.effect("returns unavailable when claude is missing", () => | |
| 3308 | Effect.gen(function* () { | |
| 3309 | const status = yield* checkClaudeProviderStatus( | |
| 3310 | defaultClaudeSettings, | |
| 3311 | claudeCapabilities(), | |
| 3312 | ); | |
| 3313 | assert.strictEqual(status.status, "error"); | |
| 3314 | assert.strictEqual(status.installed, false); | |
| 3315 | assert.strictEqual(status.auth.status, "unknown"); | |
| 3316 | assert.strictEqual(status.message, "Claude Agent CLI (`claude`) was not found on PATH."); | |
| 3317 | }).pipe(Effect.provide(failingSpawnerLayer("spawn claude ENOENT"))), | |
| 3318 | ); | |
| 3265 | ), | |
| 3266 | ); | |
| 3319 | 3267 | |
| 3320 | it.effect("returns error when version check fails with non-zero exit code", () => { | |
| 3321 | const secretStderr = "Something went wrong: secret-token-value"; | |
| 3322 | return Effect.gen(function* () { | |
| 3323 | const status = yield* checkClaudeProviderStatus( | |
| 3324 | defaultClaudeSettings, | |
| 3325 | claudeCapabilities(), | |
| 3326 | ); | |
| 3327 | assert.strictEqual(status.status, "error"); | |
| 3328 | assert.strictEqual(status.installed, true); | |
| 3329 | assert.strictEqual(status.message, "Claude Agent CLI is installed but failed to run."); | |
| 3330 | assert.ok(!(status.message ?? "").includes(secretStderr)); | |
| 3331 | }).pipe( | |
| 3332 | Effect.provide( | |
| 3333 | mockSpawnerLayer((args) => { | |
| 3334 | const joined = args.join(" "); | |
| 3335 | if (joined === "--version") | |
| 3336 | return { | |
| 3337 | stdout: "", | |
| 3338 | stderr: secretStderr, | |
| 3339 | code: 1, | |
| 3340 | }; | |
| 3341 | throw new Error(`Unexpected args: ${joined}`); | |
| 3342 | }), | |
| 3343 | ), | |
| 3268 | it.effect("returns unavailable when claude is missing", () => | |
| 3269 | Effect.gen(function* () { | |
| 3270 | const status = yield* checkClaudeProviderStatus( | |
| 3271 | defaultClaudeSettings, | |
| 3272 | claudeCapabilities(), | |
| 3344 | 3273 | ); |
| 3345 | }); | |
| 3346 | ||
| 3347 | it.effect("returns warning when the Claude initialization result is unavailable", () => | |
| 3348 | Effect.gen(function* () { | |
| 3349 | const status = yield* checkClaudeProviderStatus( | |
| 3350 | defaultClaudeSettings, | |
| 3351 | noClaudeCapabilities, | |
| 3352 | ); | |
| 3353 | assert.strictEqual(status.status, "warning"); | |
| 3354 | assert.strictEqual(status.installed, true); | |
| 3355 | assert.strictEqual(status.auth.status, "unknown"); | |
| 3356 | assert.strictEqual( | |
| 3357 | status.message, | |
| 3358 | "Could not verify Claude authentication status from initialization result.", | |
| 3359 | ); | |
| 3360 | }).pipe( | |
| 3361 | Effect.provide( | |
| 3362 | mockSpawnerLayer((args) => { | |
| 3363 | const joined = args.join(" "); | |
| 3364 | if (joined === "--version") return { stdout: "1.0.0\n", stderr: "", code: 0 }; | |
| 3365 | if (joined === "auth status") | |
| 3366 | return { | |
| 3367 | stdout: '{"loggedIn":false}\n', | |
| 3368 | stderr: "", | |
| 3369 | code: 1, | |
| 3370 | }; | |
| 3371 | throw new Error(`Unexpected args: ${joined}`); | |
| 3372 | }), | |
| 3373 | ), | |
| 3274 | assert.strictEqual(status.status, "error"); | |
| 3275 | assert.strictEqual(status.installed, false); | |
| 3276 | assert.strictEqual(status.auth.status, "unknown"); | |
| 3277 | assert.strictEqual(status.message, "Claude Agent CLI (`claude`) was not found on PATH."); | |
| 3278 | }).pipe(Effect.provide(layerFailingSpawner("spawn claude ENOENT"))), | |
| 3279 | ); | |
| 3280 | ||
| 3281 | it.effect("returns error when version check fails with non-zero exit code", () => { | |
| 3282 | const secretStderr = "Something went wrong: secret-token-value"; | |
| 3283 | return Effect.gen(function* () { | |
| 3284 | const status = yield* checkClaudeProviderStatus( | |
| 3285 | defaultClaudeSettings, | |
| 3286 | claudeCapabilities(), | |
| 3287 | ); | |
| 3288 | assert.strictEqual(status.status, "error"); | |
| 3289 | assert.strictEqual(status.installed, true); | |
| 3290 | assert.strictEqual(status.message, "Claude Agent CLI is installed but failed to run."); | |
| 3291 | assert.ok(!(status.message ?? "").includes(secretStderr)); | |
| 3292 | }).pipe( | |
| 3293 | Effect.provide( | |
| 3294 | layerMockSpawner((args) => { | |
| 3295 | const joined = args.join(" "); | |
| 3296 | if (joined === "--version") | |
| 3297 | return { | |
| 3298 | stdout: "", | |
| 3299 | stderr: secretStderr, | |
| 3300 | code: 1, | |
| 3301 | }; | |
| 3302 | throw new Error(`Unexpected args: ${joined}`); | |
| 3303 | }), | |
| 3374 | 3304 | ), |
| 3375 | 3305 | ); |
| 3376 | 3306 | }); |
| 3377 | }, | |
| 3378 | ); | |
| 3307 | ||
| 3308 | it.effect("returns warning when the Claude initialization result is unavailable", () => | |
| 3309 | Effect.gen(function* () { | |
| 3310 | const status = yield* checkClaudeProviderStatus( | |
| 3311 | defaultClaudeSettings, | |
| 3312 | noClaudeCapabilities, | |
| 3313 | ); | |
| 3314 | assert.strictEqual(status.status, "warning"); | |
| 3315 | assert.strictEqual(status.installed, true); | |
| 3316 | assert.strictEqual(status.auth.status, "unknown"); | |
| 3317 | assert.strictEqual( | |
| 3318 | status.message, | |
| 3319 | "Could not verify Claude authentication status from initialization result.", | |
| 3320 | ); | |
| 3321 | }).pipe( | |
| 3322 | Effect.provide( | |
| 3323 | layerMockSpawner((args) => { | |
| 3324 | const joined = args.join(" "); | |
| 3325 | if (joined === "--version") return { stdout: "1.0.0\n", stderr: "", code: 0 }; | |
| 3326 | if (joined === "auth status") | |
| 3327 | return { | |
| 3328 | stdout: '{"loggedIn":false}\n', | |
| 3329 | stderr: "", | |
| 3330 | code: 1, | |
| 3331 | }; | |
| 3332 | throw new Error(`Unexpected args: ${joined}`); | |
| 3333 | }), | |
| 3334 | ), | |
| 3335 | ), | |
| 3336 | ); | |
| 3337 | }); | |
| 3338 | }); |
apps/server/src/provider/Layers/ProviderRegistry.ts+1-1| ... | ... | @@ -326,7 +326,7 @@ const buildSnapshotSource = (instance: ProviderInstance): ProviderSnapshotSource |
| 326 | 326 | streamChanges: instance.snapshot.streamChanges, |
| 327 | 327 | }); |
| 328 | 328 | |
| 329 | export const ProviderRegistryLive = Layer.effect( | |
| 329 | export const layer = Layer.effect( | |
| 330 | 330 | ProviderRegistry.ProviderRegistry, |
| 331 | 331 | Effect.gen(function* () { |
| 332 | 332 | const instanceRegistry = yield* ProviderInstanceRegistry.ProviderInstanceRegistry; |
apps/server/src/provider/Layers/ProviderUsageLimitsIngestion.ts+1-1| ... | ... | @@ -4,4 +4,4 @@ |
| 4 | 4 | */ |
| 5 | 5 | import * as Layer from "effect/Layer"; |
| 6 | 6 | |
| 7 | export const ProviderUsageLimitsIngestionLive = Layer.empty; | |
| 7 | export const layer = Layer.empty; |
apps/server/src/provider/ModelManifest.test.ts+14-14| ... | ... | @@ -333,13 +333,13 @@ const INVALID_REMOTE_MANIFESTS: ReadonlyArray<ModelManifest.ModelManifestData> = |
| 333 | 333 | }), |
| 334 | 334 | ]; |
| 335 | 335 | |
| 336 | const httpClientLayer = (handler: () => Response) => | |
| 336 | const layerHttpClient = (handler: () => Response) => | |
| 337 | 337 | Layer.succeed( |
| 338 | 338 | HttpClient.HttpClient, |
| 339 | 339 | HttpClient.make((request) => Effect.succeed(HttpClientResponse.fromWeb(request, handler()))), |
| 340 | 340 | ); |
| 341 | 341 | |
| 342 | const serviceLayers = (input: { | |
| 342 | const layerService = (input: { | |
| 343 | 343 | readonly prefix: string; |
| 344 | 344 | readonly response: () => Response; |
| 345 | 345 | readonly settings?: Parameters<typeof ServerSettings.layerTest>[0]; |
| ... | ... | @@ -347,7 +347,7 @@ const serviceLayers = (input: { |
| 347 | 347 | ServerConfig.layerTest(process.cwd(), { prefix: input.prefix }).pipe( |
| 348 | 348 | Layer.provideMerge(NodeServices.layer), |
| 349 | 349 | Layer.provideMerge(ServerSettings.layerTest(input.settings ?? {})), |
| 350 | Layer.provideMerge(httpClientLayer(input.response)), | |
| 350 | Layer.provideMerge(layerHttpClient(input.response)), | |
| 351 | 351 | ); |
| 352 | 352 | |
| 353 | 353 | describe("ModelManifest service", () => { |
| ... | ... | @@ -373,7 +373,7 @@ describe("ModelManifest service", () => { |
| 373 | 373 | }).pipe( |
| 374 | 374 | Effect.scoped, |
| 375 | 375 | Effect.provide( |
| 376 | serviceLayers({ | |
| 376 | layerService({ | |
| 377 | 377 | prefix: "model-manifest-force-refresh-test", |
| 378 | 378 | response: () => Response.json(fetchCount++ === 0 ? REMOTE_MANIFEST : updated), |
| 379 | 379 | }), |
| ... | ... | @@ -395,7 +395,7 @@ describe("ModelManifest service", () => { |
| 395 | 395 | }).pipe( |
| 396 | 396 | Effect.scoped, |
| 397 | 397 | Effect.provide( |
| 398 | serviceLayers({ | |
| 398 | layerService({ | |
| 399 | 399 | prefix: "model-manifest-force-retry-test", |
| 400 | 400 | response: () => |
| 401 | 401 | fetchCount++ === 1 |
| ... | ... | @@ -418,7 +418,7 @@ describe("ModelManifest service", () => { |
| 418 | 418 | }).pipe( |
| 419 | 419 | Effect.scoped, |
| 420 | 420 | Effect.provide( |
| 421 | serviceLayers({ | |
| 421 | layerService({ | |
| 422 | 422 | prefix: "model-manifest-force-initial-retry-test", |
| 423 | 423 | response: () => |
| 424 | 424 | fetchCount++ === 0 |
| ... | ... | @@ -442,7 +442,7 @@ describe("ModelManifest service", () => { |
| 442 | 442 | }).pipe( |
| 443 | 443 | Effect.scoped, |
| 444 | 444 | Effect.provide( |
| 445 | serviceLayers({ | |
| 445 | layerService({ | |
| 446 | 446 | prefix: "model-manifest-fetch-test", |
| 447 | 447 | response: () => Response.json(REMOTE_MANIFEST), |
| 448 | 448 | }), |
| ... | ... | @@ -466,7 +466,7 @@ describe("ModelManifest service", () => { |
| 466 | 466 | }).pipe( |
| 467 | 467 | Effect.scoped, |
| 468 | 468 | Effect.provide( |
| 469 | serviceLayers({ | |
| 469 | layerService({ | |
| 470 | 470 | prefix: "model-manifest-stale-fetch-test", |
| 471 | 471 | response: () => Response.json(remote), |
| 472 | 472 | }), |
| ... | ... | @@ -481,7 +481,7 @@ describe("ModelManifest service", () => { |
| 481 | 481 | }).pipe( |
| 482 | 482 | Effect.scoped, |
| 483 | 483 | Effect.provide( |
| 484 | serviceLayers({ | |
| 484 | layerService({ | |
| 485 | 485 | prefix: "model-manifest-malformed-test", |
| 486 | 486 | response: () => Response.json({ version: 999, nonsense: true }), |
| 487 | 487 | }), |
| ... | ... | @@ -508,7 +508,7 @@ describe("ModelManifest service", () => { |
| 508 | 508 | }).pipe( |
| 509 | 509 | Effect.scoped, |
| 510 | 510 | Effect.provide( |
| 511 | serviceLayers({ | |
| 511 | layerService({ | |
| 512 | 512 | prefix: "model-manifest-last-good-test", |
| 513 | 513 | response: () => Response.json(responses[responseIndex]), |
| 514 | 514 | }), |
| ... | ... | @@ -548,7 +548,7 @@ describe("ModelManifest service", () => { |
| 548 | 548 | }).pipe( |
| 549 | 549 | Effect.scoped, |
| 550 | 550 | Effect.provide( |
| 551 | serviceLayers({ | |
| 551 | layerService({ | |
| 552 | 552 | prefix: "model-manifest-newer-bundle-test", |
| 553 | 553 | response: () => Response.json(REMOTE_MANIFEST), |
| 554 | 554 | }), |
| ... | ... | @@ -561,7 +561,7 @@ describe("ModelManifest service", () => { |
| 561 | 561 | let fetchCount = 0; |
| 562 | 562 | const service = yield* ModelManifest.make.pipe( |
| 563 | 563 | Effect.provide( |
| 564 | httpClientLayer(() => { | |
| 564 | layerHttpClient(() => { | |
| 565 | 565 | fetchCount += 1; |
| 566 | 566 | return Response.json(REMOTE_MANIFEST); |
| 567 | 567 | }), |
| ... | ... | @@ -573,7 +573,7 @@ describe("ModelManifest service", () => { |
| 573 | 573 | }).pipe( |
| 574 | 574 | Effect.scoped, |
| 575 | 575 | Effect.provide( |
| 576 | serviceLayers({ | |
| 576 | layerService({ | |
| 577 | 577 | prefix: "model-manifest-optout-test", |
| 578 | 578 | response: () => Response.json(REMOTE_MANIFEST), |
| 579 | 579 | settings: { enableProviderUpdateChecks: false }, |
| ... | ... | @@ -607,7 +607,7 @@ it.effect("caches valid compatibility policies and keeps them after a malformed |
| 607 | 607 | }).pipe( |
| 608 | 608 | Effect.scoped, |
| 609 | 609 | Effect.provide( |
| 610 | serviceLayers({ | |
| 610 | layerService({ | |
| 611 | 611 | prefix: "model-manifest-compatibility-test", |
| 612 | 612 | response: () => |
| 613 | 613 | Response.json( |
apps/server/src/provider/OpenCodeServerLedger.test.ts+2-2| ... | ... | @@ -195,7 +195,7 @@ describe.skipIf(observedPlatforms.length === 0)("OpenCode server startup", () => |
| 195 | 195 | const spawned = yield* Deferred.make<ChildProcessSpawner.ChildProcessHandle>(); |
| 196 | 196 | const recording = yield* Deferred.make<number>(); |
| 197 | 197 | const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; |
| 198 | const runtimeLayer = OpenCodeRuntime.OpenCodeRuntimeLive.pipe( | |
| 198 | const layerRuntime = OpenCodeRuntime.layer.pipe( | |
| 199 | 199 | Layer.provide( |
| 200 | 200 | Layer.succeed( |
| 201 | 201 | ChildProcessSpawner.ChildProcessSpawner, |
| ... | ... | @@ -228,7 +228,7 @@ describe.skipIf(observedPlatforms.length === 0)("OpenCode server startup", () => |
| 228 | 228 | runtime.startOpenCodeServerProcess({ binaryPath, directory: tempDir, port: 4096 }), |
| 229 | 229 | ), |
| 230 | 230 | Effect.provideService(Scope.Scope, serverScope), |
| 231 | Effect.provide(runtimeLayer), | |
| 231 | Effect.provide(layerRuntime), | |
| 232 | 232 | Effect.forkChild, |
| 233 | 233 | ); |
| 234 | 234 | const pgid = yield* Deferred.await(recording); |
apps/server/src/provider/acp/AcpRegistrySupport.test.ts+30-30| ... | ... | @@ -50,7 +50,7 @@ function settings(input: Partial<AcpRegistrySettings> = {}): AcpRegistrySettings |
| 50 | 50 | }); |
| 51 | 51 | } |
| 52 | 52 | |
| 53 | function resolverLayer( | |
| 53 | function layerResolver( | |
| 54 | 54 | execute: Parameters<typeof HttpClient.make>[0], |
| 55 | 55 | environment: NodeJS.ProcessEnv = process.env, |
| 56 | 56 | ) { |
| ... | ... | @@ -253,7 +253,7 @@ describe("AcpRegistrySupport", () => { |
| 253 | 253 | }).pipe( |
| 254 | 254 | Effect.scoped, |
| 255 | 255 | Effect.provide( |
| 256 | resolverLayer((request) => { | |
| 256 | layerResolver((request) => { | |
| 257 | 257 | requests.push(request.url); |
| 258 | 258 | return Effect.die("unexpected registry request for a local provider"); |
| 259 | 259 | }), |
| ... | ... | @@ -310,7 +310,7 @@ describe("AcpRegistrySupport", () => { |
| 310 | 310 | } |
| 311 | 311 | }).pipe( |
| 312 | 312 | Effect.scoped, |
| 313 | Effect.provide(resolverLayer(() => Effect.die("unexpected registry request"))), | |
| 313 | Effect.provide(layerResolver(() => Effect.die("unexpected registry request"))), | |
| 314 | 314 | ), |
| 315 | 315 | ); |
| 316 | 316 | |
| ... | ... | @@ -353,7 +353,7 @@ describe("AcpRegistrySupport", () => { |
| 353 | 353 | } |
| 354 | 354 | }).pipe( |
| 355 | 355 | Effect.scoped, |
| 356 | Effect.provide(resolverLayer(() => Effect.die("unexpected registry request"))), | |
| 356 | Effect.provide(layerResolver(() => Effect.die("unexpected registry request"))), | |
| 357 | 357 | ), |
| 358 | 358 | ); |
| 359 | 359 | |
| ... | ... | @@ -402,7 +402,7 @@ describe("AcpRegistrySupport", () => { |
| 402 | 402 | }).pipe( |
| 403 | 403 | Effect.scoped, |
| 404 | 404 | Effect.provide( |
| 405 | resolverLayer((request) => { | |
| 405 | layerResolver((request) => { | |
| 406 | 406 | requests.push(request.url); |
| 407 | 407 | return Effect.succeed( |
| 408 | 408 | HttpClientResponse.fromWeb(request, new Response(makeRegistry(agent))), |
| ... | ... | @@ -443,7 +443,7 @@ describe("AcpRegistrySupport", () => { |
| 443 | 443 | }).pipe( |
| 444 | 444 | Effect.scoped, |
| 445 | 445 | Effect.provide( |
| 446 | resolverLayer((request) => | |
| 446 | layerResolver((request) => | |
| 447 | 447 | Effect.succeed(HttpClientResponse.fromWeb(request, new Response(makeRegistry(agent)))), |
| 448 | 448 | ), |
| 449 | 449 | ), |
| ... | ... | @@ -483,7 +483,7 @@ describe("AcpRegistrySupport", () => { |
| 483 | 483 | }).pipe( |
| 484 | 484 | Effect.scoped, |
| 485 | 485 | Effect.provide( |
| 486 | resolverLayer((request) => | |
| 486 | layerResolver((request) => | |
| 487 | 487 | Effect.succeed(HttpClientResponse.fromWeb(request, new Response(makeRegistry(agent)))), |
| 488 | 488 | ), |
| 489 | 489 | ), |
| ... | ... | @@ -519,7 +519,7 @@ describe("AcpRegistrySupport", () => { |
| 519 | 519 | }).pipe( |
| 520 | 520 | Effect.scoped, |
| 521 | 521 | Effect.provide( |
| 522 | resolverLayer((request) => | |
| 522 | layerResolver((request) => | |
| 523 | 523 | Effect.succeed(HttpClientResponse.fromWeb(request, new Response(makeRegistry(agent)))), |
| 524 | 524 | ), |
| 525 | 525 | ), |
| ... | ... | @@ -572,7 +572,7 @@ describe("AcpRegistrySupport", () => { |
| 572 | 572 | }).pipe( |
| 573 | 573 | Effect.scoped, |
| 574 | 574 | Effect.provide( |
| 575 | resolverLayer((request) => | |
| 575 | layerResolver((request) => | |
| 576 | 576 | Effect.succeed(HttpClientResponse.fromWeb(request, new Response(makeRegistry(agent)))), |
| 577 | 577 | ), |
| 578 | 578 | ), |
| ... | ... | @@ -616,7 +616,7 @@ describe("AcpRegistrySupport", () => { |
| 616 | 616 | }).pipe( |
| 617 | 617 | Effect.scoped, |
| 618 | 618 | Effect.provide( |
| 619 | resolverLayer((request) => { | |
| 619 | layerResolver((request) => { | |
| 620 | 620 | requests.push(request.url); |
| 621 | 621 | const response = |
| 622 | 622 | request.url === registryUrl |
| ... | ... | @@ -704,7 +704,7 @@ describe("AcpRegistrySupport", () => { |
| 704 | 704 | }).pipe( |
| 705 | 705 | Effect.scoped, |
| 706 | 706 | Effect.provide( |
| 707 | resolverLayer((request) => { | |
| 707 | layerResolver((request) => { | |
| 708 | 708 | requests.push(request.url); |
| 709 | 709 | return Effect.succeed( |
| 710 | 710 | HttpClientResponse.fromWeb( |
| ... | ... | @@ -822,7 +822,7 @@ describe("AcpRegistrySupport", () => { |
| 822 | 822 | }).pipe( |
| 823 | 823 | Effect.scoped, |
| 824 | 824 | Effect.provide( |
| 825 | resolverLayer((request) => | |
| 825 | layerResolver((request) => | |
| 826 | 826 | Effect.succeed( |
| 827 | 827 | HttpClientResponse.fromWeb( |
| 828 | 828 | request, |
| ... | ... | @@ -863,7 +863,7 @@ describe("AcpRegistrySupport", () => { |
| 863 | 863 | }).pipe( |
| 864 | 864 | Effect.scoped, |
| 865 | 865 | Effect.provide( |
| 866 | resolverLayer((request) => { | |
| 866 | layerResolver((request) => { | |
| 867 | 867 | requests += 1; |
| 868 | 868 | return Effect.yieldNow.pipe( |
| 869 | 869 | Effect.as(HttpClientResponse.fromWeb(request, new Response(makeRegistry(agent)))), |
| ... | ... | @@ -900,7 +900,7 @@ describe("AcpRegistrySupport", () => { |
| 900 | 900 | }).pipe( |
| 901 | 901 | Effect.scoped, |
| 902 | 902 | Effect.provide( |
| 903 | resolverLayer( | |
| 903 | layerResolver( | |
| 904 | 904 | (request) => |
| 905 | 905 | Effect.succeed( |
| 906 | 906 | HttpClientResponse.fromWeb( |
| ... | ... | @@ -942,7 +942,7 @@ describe("AcpRegistrySupport", () => { |
| 942 | 942 | }).pipe( |
| 943 | 943 | Effect.scoped, |
| 944 | 944 | Effect.provide( |
| 945 | resolverLayer((request) => | |
| 945 | layerResolver((request) => | |
| 946 | 946 | Effect.succeed( |
| 947 | 947 | HttpClientResponse.fromWeb( |
| 948 | 948 | request, |
| ... | ... | @@ -977,7 +977,7 @@ describe("AcpRegistrySupport", () => { |
| 977 | 977 | }).pipe( |
| 978 | 978 | Effect.scoped, |
| 979 | 979 | Effect.provide( |
| 980 | resolverLayer((request) => | |
| 980 | layerResolver((request) => | |
| 981 | 981 | Effect.succeed(HttpClientResponse.fromWeb(request, new Response(makeRegistry(agent)))), |
| 982 | 982 | ), |
| 983 | 983 | ), |
| ... | ... | @@ -1012,7 +1012,7 @@ describe("AcpRegistrySupport", () => { |
| 1012 | 1012 | }).pipe( |
| 1013 | 1013 | Effect.scoped, |
| 1014 | 1014 | Effect.provide( |
| 1015 | resolverLayer( | |
| 1015 | layerResolver( | |
| 1016 | 1016 | (request) => |
| 1017 | 1017 | Effect.succeed( |
| 1018 | 1018 | HttpClientResponse.fromWeb( |
| ... | ... | @@ -1056,7 +1056,7 @@ describe("AcpRegistrySupport", () => { |
| 1056 | 1056 | }).pipe( |
| 1057 | 1057 | Effect.scoped, |
| 1058 | 1058 | Effect.provide( |
| 1059 | resolverLayer((request) => | |
| 1059 | layerResolver((request) => | |
| 1060 | 1060 | Effect.succeed( |
| 1061 | 1061 | HttpClientResponse.fromWeb( |
| 1062 | 1062 | request, |
| ... | ... | @@ -1101,7 +1101,7 @@ describe("AcpRegistrySupport", () => { |
| 1101 | 1101 | }).pipe( |
| 1102 | 1102 | Effect.scoped, |
| 1103 | 1103 | Effect.provide( |
| 1104 | resolverLayer((request) => { | |
| 1104 | layerResolver((request) => { | |
| 1105 | 1105 | requests.push(request.url); |
| 1106 | 1106 | return Effect.succeed( |
| 1107 | 1107 | HttpClientResponse.fromWeb(request, new Response(makeRegistry(agent))), |
| ... | ... | @@ -1141,7 +1141,7 @@ describe("AcpRegistrySupport", () => { |
| 1141 | 1141 | }).pipe( |
| 1142 | 1142 | Effect.scoped, |
| 1143 | 1143 | Effect.provide( |
| 1144 | resolverLayer((request) => | |
| 1144 | layerResolver((request) => | |
| 1145 | 1145 | Effect.succeed( |
| 1146 | 1146 | HttpClientResponse.fromWeb(request, new Response("unavailable", { status: 503 })), |
| 1147 | 1147 | ), |
| ... | ... | @@ -1177,7 +1177,7 @@ describe("AcpRegistrySupport", () => { |
| 1177 | 1177 | }).pipe( |
| 1178 | 1178 | Effect.scoped, |
| 1179 | 1179 | Effect.provide( |
| 1180 | resolverLayer((request) => { | |
| 1180 | layerResolver((request) => { | |
| 1181 | 1181 | requests.push(request.url); |
| 1182 | 1182 | return Effect.succeed( |
| 1183 | 1183 | HttpClientResponse.fromWeb(request, new Response(makeRegistry(agent))), |
| ... | ... | @@ -1211,7 +1211,7 @@ describe("AcpRegistrySupport", () => { |
| 1211 | 1211 | }).pipe( |
| 1212 | 1212 | Effect.scoped, |
| 1213 | 1213 | Effect.provide( |
| 1214 | resolverLayer((request) => { | |
| 1214 | layerResolver((request) => { | |
| 1215 | 1215 | requests += 1; |
| 1216 | 1216 | return Effect.succeed( |
| 1217 | 1217 | HttpClientResponse.fromWeb(request, new Response(makeRegistry(agent))), |
| ... | ... | @@ -1240,7 +1240,7 @@ describe("AcpRegistrySupport", () => { |
| 1240 | 1240 | }).pipe( |
| 1241 | 1241 | Effect.scoped, |
| 1242 | 1242 | Effect.provide( |
| 1243 | resolverLayer((request) => { | |
| 1243 | layerResolver((request) => { | |
| 1244 | 1244 | requests += 1; |
| 1245 | 1245 | return Effect.succeed(HttpClientResponse.fromWeb(request, new Response("unused"))); |
| 1246 | 1246 | }), |
| ... | ... | @@ -1279,7 +1279,7 @@ describe("AcpRegistrySupport", () => { |
| 1279 | 1279 | }).pipe( |
| 1280 | 1280 | Effect.scoped, |
| 1281 | 1281 | Effect.provide( |
| 1282 | resolverLayer( | |
| 1282 | layerResolver( | |
| 1283 | 1283 | (request) => |
| 1284 | 1284 | Effect.succeed(HttpClientResponse.fromWeb(request, new Response(makeRegistry(agent)))), |
| 1285 | 1285 | { PATH: "" }, |
| ... | ... | @@ -1329,7 +1329,7 @@ describe("AcpRegistrySupport", () => { |
| 1329 | 1329 | }).pipe( |
| 1330 | 1330 | Effect.scoped, |
| 1331 | 1331 | Effect.provide( |
| 1332 | resolverLayer((request) => | |
| 1332 | layerResolver((request) => | |
| 1333 | 1333 | Effect.succeed(HttpClientResponse.fromWeb(request, new Response(makeRegistry(agent)))), |
| 1334 | 1334 | ), |
| 1335 | 1335 | ), |
| ... | ... | @@ -1395,7 +1395,7 @@ describe("AcpRegistrySupport", () => { |
| 1395 | 1395 | }).pipe( |
| 1396 | 1396 | Effect.scoped, |
| 1397 | 1397 | Effect.provide( |
| 1398 | resolverLayer((request) => | |
| 1398 | layerResolver((request) => | |
| 1399 | 1399 | Effect.succeed(HttpClientResponse.fromWeb(request, new Response("unused"))), |
| 1400 | 1400 | ), |
| 1401 | 1401 | ), |
| ... | ... | @@ -1433,7 +1433,7 @@ describe("AcpRegistrySupport", () => { |
| 1433 | 1433 | }); |
| 1434 | 1434 | }).pipe( |
| 1435 | 1435 | Effect.scoped, |
| 1436 | Effect.provide(resolverLayer(() => Effect.die("unexpected HTTP request"))), | |
| 1436 | Effect.provide(layerResolver(() => Effect.die("unexpected HTTP request"))), | |
| 1437 | 1437 | ), |
| 1438 | 1438 | ); |
| 1439 | 1439 | |
| ... | ... | @@ -1508,7 +1508,7 @@ describe("AcpRegistrySupport", () => { |
| 1508 | 1508 | }).pipe( |
| 1509 | 1509 | Effect.scoped, |
| 1510 | 1510 | Effect.provide( |
| 1511 | resolverLayer((request) => { | |
| 1511 | layerResolver((request) => { | |
| 1512 | 1512 | if (request.url === registryUrl) { |
| 1513 | 1513 | return Effect.succeed( |
| 1514 | 1514 | HttpClientResponse.fromWeb(request, new Response(makeRegistry(agent))), |
| ... | ... | @@ -1563,7 +1563,7 @@ describe("AcpRegistrySupport", () => { |
| 1563 | 1563 | }).pipe( |
| 1564 | 1564 | Effect.scoped, |
| 1565 | 1565 | Effect.provide( |
| 1566 | resolverLayer((request) => | |
| 1566 | layerResolver((request) => | |
| 1567 | 1567 | Effect.succeed( |
| 1568 | 1568 | HttpClientResponse.fromWeb( |
| 1569 | 1569 | request, |
| ... | ... | @@ -1607,7 +1607,7 @@ describe("AcpRegistrySupport", () => { |
| 1607 | 1607 | }).pipe( |
| 1608 | 1608 | Effect.scoped, |
| 1609 | 1609 | Effect.provide( |
| 1610 | resolverLayer((request) => | |
| 1610 | layerResolver((request) => | |
| 1611 | 1611 | Effect.succeed( |
| 1612 | 1612 | HttpClientResponse.fromWeb( |
| 1613 | 1613 | request, |
apps/server/src/provider/makeManagedServerProvider.test.ts+18-18| ... | ... | @@ -99,7 +99,7 @@ const refreshedSnapshotSecond: ServerProvider = { |
| 99 | 99 | message: "Refreshed provider availability again.", |
| 100 | 100 | }; |
| 101 | 101 | |
| 102 | function makeBackgroundPolicyLayer(shouldRunScopeWork: boolean) { | |
| 102 | function layerBackgroundPolicy(shouldRunScopeWork: boolean) { | |
| 103 | 103 | return Layer.mock(BackgroundPolicy.BackgroundPolicy)({ |
| 104 | 104 | reportClientActivity: () => Effect.void, |
| 105 | 105 | removeRpcClient: () => Effect.void, |
| ... | ... | @@ -130,11 +130,11 @@ function makeBackgroundPolicyLayer(shouldRunScopeWork: boolean) { |
| 130 | 130 | }); |
| 131 | 131 | } |
| 132 | 132 | |
| 133 | const BackgroundPolicyAlwaysRunLayer = makeBackgroundPolicyLayer(true); | |
| 134 | const BackgroundPolicyNeverRunLayer = makeBackgroundPolicyLayer(false); | |
| 135 | const ServerSettingsTestLayer = ServerSettings.layerTest(); | |
| 136 | const AlwaysRunTestLayer = Layer.merge(BackgroundPolicyAlwaysRunLayer, ServerSettingsTestLayer); | |
| 137 | const NeverRunTestLayer = Layer.merge(BackgroundPolicyNeverRunLayer, ServerSettingsTestLayer); | |
| 133 | const layerBackgroundPolicyAlwaysRun = layerBackgroundPolicy(true); | |
| 134 | const layerBackgroundPolicyNeverRun = layerBackgroundPolicy(false); | |
| 135 | const layerServerSettingsTest = ServerSettings.layerTest(); | |
| 136 | const layerAlwaysRunTest = Layer.merge(layerBackgroundPolicyAlwaysRun, layerServerSettingsTest); | |
| 137 | const layerNeverRunTest = Layer.merge(layerBackgroundPolicyNeverRun, layerServerSettingsTest); | |
| 138 | 138 | |
| 139 | 139 | const enrichedSnapshotSecond: ServerProvider = { |
| 140 | 140 | ...refreshedSnapshotSecond, |
| ... | ... | @@ -189,7 +189,7 @@ describe("makeManagedServerProvider", () => { |
| 189 | 189 | assert.deepStrictEqual(latest, refreshedSnapshot); |
| 190 | 190 | assert.strictEqual(yield* Ref.get(checkCalls), 1); |
| 191 | 191 | }), |
| 192 | ).pipe(Effect.provide(AlwaysRunTestLayer)), | |
| 192 | ).pipe(Effect.provide(layerAlwaysRunTest)), | |
| 193 | 193 | ); |
| 194 | 194 | |
| 195 | 195 | it.effect("skips periodic provider refreshes without foreground provider-status demand", () => |
| ... | ... | @@ -220,7 +220,7 @@ describe("makeManagedServerProvider", () => { |
| 220 | 220 | |
| 221 | 221 | assert.strictEqual(yield* Ref.get(checkCalls), 1); |
| 222 | 222 | }), |
| 223 | ).pipe(Effect.provide(Layer.mergeAll(NeverRunTestLayer, TestClock.layer()))), | |
| 223 | ).pipe(Effect.provide(Layer.mergeAll(layerNeverRunTest, TestClock.layer()))), | |
| 224 | 224 | ); |
| 225 | 225 | |
| 226 | 226 | it.effect("disables periodic provider refreshes when the explicit interval is zero", () => |
| ... | ... | @@ -247,7 +247,7 @@ describe("makeManagedServerProvider", () => { |
| 247 | 247 | |
| 248 | 248 | assert.strictEqual(yield* Ref.get(checkCalls), 1); |
| 249 | 249 | }), |
| 250 | ).pipe(Effect.provide(Layer.mergeAll(AlwaysRunTestLayer, TestClock.layer()))), | |
| 250 | ).pipe(Effect.provide(Layer.mergeAll(layerAlwaysRunTest, TestClock.layer()))), | |
| 251 | 251 | ); |
| 252 | 252 | |
| 253 | 253 | it.effect("keeps manual refresh when interval refresh is disabled", () => |
| ... | ... | @@ -281,7 +281,7 @@ describe("makeManagedServerProvider", () => { |
| 281 | 281 | yield* provider.refresh; |
| 282 | 282 | assert.strictEqual(yield* Ref.get(checkCalls), 2); |
| 283 | 283 | }), |
| 284 | ).pipe(Effect.provide(Layer.mergeAll(AlwaysRunTestLayer, TestClock.layer()))), | |
| 284 | ).pipe(Effect.provide(Layer.mergeAll(layerAlwaysRunTest, TestClock.layer()))), | |
| 285 | 285 | ); |
| 286 | 286 | |
| 287 | 287 | it.effect("wakes a sleeping provider refresh loop when its interval changes", () => |
| ... | ... | @@ -293,7 +293,7 @@ describe("makeManagedServerProvider", () => { |
| 293 | 293 | }; |
| 294 | 294 | const serverSettingsRef = yield* Ref.make(initialServerSettings); |
| 295 | 295 | const serverSettingsChanges = yield* PubSub.unbounded<typeof initialServerSettings>(); |
| 296 | const serverSettingsLayer = Layer.succeed( | |
| 296 | const layerServerSettings = Layer.succeed( | |
| 297 | 297 | ServerSettings.ServerSettingsService, |
| 298 | 298 | ServerSettings.ServerSettingsService.of({ |
| 299 | 299 | start: Effect.void, |
| ... | ... | @@ -326,7 +326,7 @@ describe("makeManagedServerProvider", () => { |
| 326 | 326 | ), |
| 327 | 327 | Effect.as(refreshedSnapshot), |
| 328 | 328 | ), |
| 329 | }).pipe(Effect.provide(Layer.merge(BackgroundPolicyAlwaysRunLayer, serverSettingsLayer))); | |
| 329 | }).pipe(Effect.provide(Layer.merge(layerBackgroundPolicyAlwaysRun, layerServerSettings))); | |
| 330 | 330 | |
| 331 | 331 | yield* Deferred.await(initialCheckDone); |
| 332 | 332 | const nextServerSettings = { |
| ... | ... | @@ -388,7 +388,7 @@ describe("makeManagedServerProvider", () => { |
| 388 | 388 | assert.deepStrictEqual(latest, refreshedSnapshotSecond); |
| 389 | 389 | assert.strictEqual(yield* Ref.get(checkCalls), 2); |
| 390 | 390 | }), |
| 391 | ).pipe(Effect.provide(AlwaysRunTestLayer)), | |
| 391 | ).pipe(Effect.provide(layerAlwaysRunTest)), | |
| 392 | 392 | ); |
| 393 | 393 | |
| 394 | 394 | it.effect("can update settings and disable periodic checks without probing again", () => |
| ... | ... | @@ -423,7 +423,7 @@ describe("makeManagedServerProvider", () => { |
| 423 | 423 | assert.strictEqual(yield* Ref.get(checkCalls), 1); |
| 424 | 424 | assert.strictEqual(yield* Ref.get(enrichmentCalls), 2); |
| 425 | 425 | }), |
| 426 | ).pipe(Effect.provide(Layer.mergeAll(AlwaysRunTestLayer, TestClock.layer()))), | |
| 426 | ).pipe(Effect.provide(Layer.mergeAll(layerAlwaysRunTest, TestClock.layer()))), | |
| 427 | 427 | ); |
| 428 | 428 | |
| 429 | 429 | it.effect("streams supplemental snapshot updates after the base provider check completes", () => |
| ... | ... | @@ -461,7 +461,7 @@ describe("makeManagedServerProvider", () => { |
| 461 | 461 | assert.deepStrictEqual(updates, [refreshedSnapshot, enrichedSnapshot]); |
| 462 | 462 | assert.deepStrictEqual(latest, enrichedSnapshot); |
| 463 | 463 | }), |
| 464 | ).pipe(Effect.provide(AlwaysRunTestLayer)), | |
| 464 | ).pipe(Effect.provide(layerAlwaysRunTest)), | |
| 465 | 465 | ); |
| 466 | 466 | |
| 467 | 467 | it.effect("ignores stale enrichment callbacks after a newer refresh advances generation", () => |
| ... | ... | @@ -522,7 +522,7 @@ describe("makeManagedServerProvider", () => { |
| 522 | 522 | ]); |
| 523 | 523 | assert.deepStrictEqual(latest, enrichedSnapshotSecond); |
| 524 | 524 | }), |
| 525 | ).pipe(Effect.provide(AlwaysRunTestLayer)), | |
| 525 | ).pipe(Effect.provide(layerAlwaysRunTest)), | |
| 526 | 526 | ); |
| 527 | 527 | |
| 528 | 528 | it.effect("applies runtime usage updates onto the published snapshot", () => |
| ... | ... | @@ -584,7 +584,7 @@ describe("makeManagedServerProvider", () => { |
| 584 | 584 | }); |
| 585 | 585 | assert.deepStrictEqual(yield* provider.getSnapshot, update); |
| 586 | 586 | }), |
| 587 | ).pipe(Effect.provide(AlwaysRunTestLayer)), | |
| 587 | ).pipe(Effect.provide(layerAlwaysRunTest)), | |
| 588 | 588 | ); |
| 589 | 589 | |
| 590 | 590 | it.effect("keeps live usage windows across a failed probe and a stale enrichment", () => |
| ... | ... | @@ -655,6 +655,6 @@ describe("makeManagedServerProvider", () => { |
| 655 | 655 | assert.strictEqual(refreshed.message, refreshedSnapshotSecond.message); |
| 656 | 656 | assert.deepStrictEqual(refreshed.usageLimits?.windows, [liveWindow]); |
| 657 | 657 | }), |
| 658 | ).pipe(Effect.provide(AlwaysRunTestLayer)), | |
| 658 | ).pipe(Effect.provide(layerAlwaysRunTest)), | |
| 659 | 659 | ); |
| 660 | 660 | }); |
apps/server/src/provider/opencode2/OpenCode2Client.events.test.ts+8-6| ... | ... | @@ -54,7 +54,7 @@ const newerServerStream = Effect.gen(function* () { |
| 54 | 54 | ]; |
| 55 | 55 | }); |
| 56 | 56 | |
| 57 | const serving = (events: ReadonlyArray<unknown>) => | |
| 57 | const layerServing = (events: ReadonlyArray<unknown>) => | |
| 58 | 58 | Layer.succeed( |
| 59 | 59 | HttpClient.HttpClient, |
| 60 | 60 | HttpClient.make((request) => |
| ... | ... | @@ -75,7 +75,7 @@ const connect = Effect.gen(function* () { |
| 75 | 75 | }); |
| 76 | 76 | |
| 77 | 77 | /** Sends `events`, then keeps the connection open without another byte, like a frozen server. */ |
| 78 | const servingThenSilent = (events: ReadonlyArray<unknown>) => | |
| 78 | const layerServingThenSilent = (events: ReadonlyArray<unknown>) => | |
| 79 | 79 | Layer.succeed( |
| 80 | 80 | HttpClient.HttpClient, |
| 81 | 81 | HttpClient.make((request) => |
| ... | ... | @@ -103,7 +103,9 @@ describe("OpenCode2Client events", () => { |
| 103 | 103 | const { events } = yield* connect.pipe( |
| 104 | 104 | Effect.provide( |
| 105 | 105 | OpenCode2Client.layer.pipe( |
| 106 | Layer.provide(servingThenSilent([{ id: "evt_1", type: "server.connected", data: {} }])), | |
| 106 | Layer.provide( | |
| 107 | layerServingThenSilent([{ id: "evt_1", type: "server.connected", data: {} }]), | |
| 108 | ), | |
| 107 | 109 | ), |
| 108 | 110 | ), |
| 109 | 111 | ); |
| ... | ... | @@ -124,7 +126,7 @@ describe("OpenCode2Client events", () => { |
| 124 | 126 | Effect.provide( |
| 125 | 127 | OpenCode2Client.layer.pipe( |
| 126 | 128 | Layer.provide( |
| 127 | serving([ | |
| 129 | layerServing([ | |
| 128 | 130 | { |
| 129 | 131 | id: "evt_1", |
| 130 | 132 | created: 1, |
| ... | ... | @@ -167,7 +169,7 @@ describe("OpenCode2Client events", () => { |
| 167 | 169 | Effect.gen(function* () { |
| 168 | 170 | const events = yield* newerServerStream; |
| 169 | 171 | const { events: subscribe } = yield* connect.pipe( |
| 170 | Effect.provide(OpenCode2Client.layer.pipe(Layer.provide(serving(events)))), | |
| 172 | Effect.provide(OpenCode2Client.layer.pipe(Layer.provide(layerServing(events)))), | |
| 171 | 173 | ); |
| 172 | 174 | const types = yield* (yield* subscribe).pipe( |
| 173 | 175 | Stream.map((event) => event.type), |
| ... | ... | @@ -185,7 +187,7 @@ describe("OpenCode2Client events", () => { |
| 185 | 187 | Effect.gen(function* () { |
| 186 | 188 | const events = yield* newerServerStream; |
| 187 | 189 | const { client } = yield* connect.pipe( |
| 188 | Effect.provide(OpenCode2Client.layer.pipe(Layer.provide(serving(events)))), | |
| 190 | Effect.provide(OpenCode2Client.layer.pipe(Layer.provide(layerServing(events)))), | |
| 189 | 191 | ); |
| 190 | 192 | const failure = yield* client.event.subscribe().pipe(Stream.runDrain, Effect.flip); |
| 191 | 193 | assert.strictEqual(failure._tag, "ClientError"); |
apps/server/src/provider/opencode2/OpenCode2Client.test.ts+2-2| ... | ... | @@ -12,7 +12,7 @@ const INFO_BODY = '{"version":"2.0.18","pid":4242,"urls":[],"paths":{"tmp":"/tmp |
| 12 | 12 | /** Sends one request through the client and returns the Authorization header it carried. */ |
| 13 | 13 | const authorizationFor = (password: string | Redacted.Redacted) => { |
| 14 | 14 | const seen: Array<string | undefined> = []; |
| 15 | const capturing = Layer.succeed( | |
| 15 | const layerCapturing = Layer.succeed( | |
| 16 | 16 | HttpClient.HttpClient, |
| 17 | 17 | HttpClient.make((request) => |
| 18 | 18 | Effect.sync(() => { |
| ... | ... | @@ -29,7 +29,7 @@ const authorizationFor = (password: string | Redacted.Redacted) => { |
| 29 | 29 | const { client } = yield* opencode.connect({ baseUrl: "http://127.0.0.1:4096", password }); |
| 30 | 30 | yield* client.server.info(); |
| 31 | 31 | return seen[0]; |
| 32 | }).pipe(Effect.provide(OpenCode2Client.layer.pipe(Layer.provide(capturing)))); | |
| 32 | }).pipe(Effect.provide(OpenCode2Client.layer.pipe(Layer.provide(layerCapturing)))); | |
| 33 | 33 | }; |
| 34 | 34 | |
| 35 | 35 | const utf8Basic = (password: string) => |
apps/server/src/provider/opencode2/OpenCode2Server.live.test.ts+1-1| ... | ... | @@ -229,7 +229,7 @@ describe.runIf(binaryPath !== undefined)("OpenCode2Server live", () => { |
| 229 | 229 | Effect.provide( |
| 230 | 230 | Layer.mergeAll( |
| 231 | 231 | OpenCode2Client.layer, |
| 232 | OpenCodeRuntime.OpenCodeRuntimeLive.pipe(Layer.provide(OpenCodeServerLedger.layerTest)), | |
| 232 | OpenCodeRuntime.layer.pipe(Layer.provide(OpenCodeServerLedger.layerTest)), | |
| 233 | 233 | ).pipe(Layer.provideMerge(Layer.mergeAll(NodeServices.layer, FetchHttpClient.layer))), |
| 234 | 234 | ), |
| 235 | 235 | ), |
apps/server/src/provider/opencode2/OpenCode2Server.test.ts+19-13| ... | ... | @@ -27,7 +27,7 @@ const INFO_BODY = |
| 27 | 27 | const UNAUTHORIZED_BODY = '{"_tag":"UnauthorizedError","message":"Authentication required"}'; |
| 28 | 28 | const SPA_BODY = "<!doctype html><html><head><title>OpenCode</title></head></html>"; |
| 29 | 29 | |
| 30 | const serverReplying = (reply: { | |
| 30 | const layerServerReplying = (reply: { | |
| 31 | 31 | readonly status: number; |
| 32 | 32 | readonly contentType?: string; |
| 33 | 33 | readonly body: string; |
| ... | ... | @@ -58,7 +58,7 @@ describe("OpenCode2Server.verifyServer", () => { |
| 58 | 58 | it.effect("returns the version of an authenticated OpenCode 2 server", () => |
| 59 | 59 | Effect.gen(function* () { |
| 60 | 60 | const version = yield* verify( |
| 61 | serverReplying({ status: 200, contentType: "application/json", body: INFO_BODY }), | |
| 61 | layerServerReplying({ status: 200, contentType: "application/json", body: INFO_BODY }), | |
| 62 | 62 | ); |
| 63 | 63 | assert.strictEqual(version, "2.0.18"); |
| 64 | 64 | }), |
| ... | ... | @@ -67,7 +67,11 @@ describe("OpenCode2Server.verifyServer", () => { |
| 67 | 67 | it.effect("reports a rejected password, not a wrong server", () => |
| 68 | 68 | Effect.gen(function* () { |
| 69 | 69 | const error = yield* verify( |
| 70 | serverReplying({ status: 401, contentType: "application/json", body: UNAUTHORIZED_BODY }), | |
| 70 | layerServerReplying({ | |
| 71 | status: 401, | |
| 72 | contentType: "application/json", | |
| 73 | body: UNAUTHORIZED_BODY, | |
| 74 | }), | |
| 71 | 75 | ).pipe(Effect.flip); |
| 72 | 76 | assert.include(error.detail, "rejected the server password"); |
| 73 | 77 | }), |
| ... | ... | @@ -76,7 +80,7 @@ describe("OpenCode2Server.verifyServer", () => { |
| 76 | 80 | // 1.x rejects a wrong password with an empty 401, which the client cannot decode. |
| 77 | 81 | it.effect("reports an empty-body 401 as a rejected password", () => |
| 78 | 82 | Effect.gen(function* () { |
| 79 | const error = yield* verify(serverReplying({ status: 401, body: "" })).pipe(Effect.flip); | |
| 83 | const error = yield* verify(layerServerReplying({ status: 401, body: "" })).pipe(Effect.flip); | |
| 80 | 84 | assert.include(error.detail, "rejected the server password"); |
| 81 | 85 | }), |
| 82 | 86 | ); |
| ... | ... | @@ -85,7 +89,7 @@ describe("OpenCode2Server.verifyServer", () => { |
| 85 | 89 | Effect.gen(function* () { |
| 86 | 90 | for (const status of [500, 502]) { |
| 87 | 91 | const error = yield* verify( |
| 88 | serverReplying({ status, contentType: "text/plain", body: "upstream failed" }), | |
| 92 | layerServerReplying({ status, contentType: "text/plain", body: "upstream failed" }), | |
| 89 | 93 | ).pipe(Effect.flip); |
| 90 | 94 | assert.include(error.detail, `returned HTTP ${status}`); |
| 91 | 95 | } |
| ... | ... | @@ -96,7 +100,7 @@ describe("OpenCode2Server.verifyServer", () => { |
| 96 | 100 | it.effect("rejects a server that answers with the web UI's HTML", () => |
| 97 | 101 | Effect.gen(function* () { |
| 98 | 102 | const error = yield* verify( |
| 99 | serverReplying({ status: 200, contentType: "text/html", body: SPA_BODY }), | |
| 103 | layerServerReplying({ status: 200, contentType: "text/html", body: SPA_BODY }), | |
| 100 | 104 | ).pipe(Effect.flip); |
| 101 | 105 | assert.include(error.detail, "is not an OpenCode 2 server"); |
| 102 | 106 | }), |
| ... | ... | @@ -129,24 +133,26 @@ describe("OpenCode2Server error details", () => { |
| 129 | 133 | Effect.provide( |
| 130 | 134 | Layer.mergeAll( |
| 131 | 135 | OpenCode2Client.layer.pipe(Layer.provide(httpClient)), |
| 132 | OpenCodeRuntime.OpenCodeRuntimeLive.pipe(Layer.provide(OpenCodeServerLedger.layerTest)), | |
| 136 | OpenCodeRuntime.layer.pipe(Layer.provide(OpenCodeServerLedger.layerTest)), | |
| 133 | 137 | ).pipe(Layer.provideMerge(NodeServices.layer)), |
| 134 | 138 | ), |
| 135 | 139 | ); |
| 136 | 140 | |
| 137 | 141 | it.effect("never include the server URL", () => |
| 138 | 142 | Effect.gen(function* () { |
| 139 | const hanging = Layer.succeed( | |
| 143 | const layerHanging = Layer.succeed( | |
| 140 | 144 | HttpClient.HttpClient, |
| 141 | 145 | HttpClient.make(() => Effect.never), |
| 142 | 146 | ); |
| 143 | const timedOut = yield* detailFor(hanging).pipe(Effect.forkChild); | |
| 147 | const timedOut = yield* detailFor(layerHanging).pipe(Effect.forkChild); | |
| 144 | 148 | yield* TestClock.adjust("10 seconds"); |
| 145 | 149 | const details = [ |
| 146 | 150 | yield* detailFor(FetchHttpClient.layer), |
| 147 | yield* detailFor(serverReplying({ status: 401, body: "" })), | |
| 148 | yield* detailFor(serverReplying({ status: 502, contentType: "text/plain", body: "" })), | |
| 149 | yield* detailFor(serverReplying({ status: 200, contentType: "text/html", body: SPA_BODY })), | |
| 151 | yield* detailFor(layerServerReplying({ status: 401, body: "" })), | |
| 152 | yield* detailFor(layerServerReplying({ status: 502, contentType: "text/plain", body: "" })), | |
| 153 | yield* detailFor( | |
| 154 | layerServerReplying({ status: 200, contentType: "text/html", body: SPA_BODY }), | |
| 155 | ), | |
| 150 | 156 | yield* Fiber.join(timedOut), |
| 151 | 157 | ]; |
| 152 | 158 | assert.deepStrictEqual(details, [ |
| ... | ... | @@ -244,7 +250,7 @@ describe("OpenCode2Server spawned server", () => { |
| 244 | 250 | Effect.provide( |
| 245 | 251 | Layer.mergeAll( |
| 246 | 252 | OpenCode2Client.layer, |
| 247 | OpenCodeRuntime.OpenCodeRuntimeLive.pipe(Layer.provide(OpenCodeServerLedger.layerTest)), | |
| 253 | OpenCodeRuntime.layer.pipe(Layer.provide(OpenCodeServerLedger.layerTest)), | |
| 248 | 254 | ).pipe(Layer.provideMerge(Layer.mergeAll(NodeServices.layer, FetchHttpClient.layer))), |
| 249 | 255 | ), |
| 250 | 256 | ), |
apps/server/src/provider/opencodeRuntime.environment.test.ts+1-1| ... | ... | @@ -227,7 +227,7 @@ server.listen(0, "127.0.0.1", () => { |
| 227 | 227 | }).pipe( |
| 228 | 228 | Effect.scoped, |
| 229 | 229 | Effect.provide([ |
| 230 | OpenCodeRuntime.OpenCodeRuntimeLive.pipe( | |
| 230 | OpenCodeRuntime.layer.pipe( | |
| 231 | 231 | Layer.provide(OpenCodeServerLedger.layerTest), |
| 232 | 232 | Layer.provideMerge(NodeServices.layer), |
| 233 | 233 | ), |
apps/server/src/provider/opencodeRuntime.inventory.test.ts+2-2| ... | ... | @@ -18,12 +18,12 @@ import { |
| 18 | 18 | import * as OpenCodeRuntime from "./opencodeRuntime.ts"; |
| 19 | 19 | import * as OpenCodeServerLedger from "./OpenCodeServerLedger.ts"; |
| 20 | 20 | |
| 21 | const testLayer = OpenCodeRuntime.OpenCodeRuntimeLive.pipe( | |
| 21 | const layerTest = OpenCodeRuntime.layer.pipe( | |
| 22 | 22 | Layer.provide(OpenCodeServerLedger.layerTest), |
| 23 | 23 | Layer.provideMerge(NodeServices.layer), |
| 24 | 24 | ); |
| 25 | 25 | |
| 26 | it.layer(testLayer)("OpenCodeRuntime inventory", (it) => { | |
| 26 | it.layer(layerTest)("OpenCodeRuntime inventory", (it) => { | |
| 27 | 27 | it.effect("aborts pending SDK requests when inventory loading is interrupted", () => |
| 28 | 28 | Effect.gen(function* () { |
| 29 | 29 | const runtime = yield* OpenCodeRuntime.OpenCodeRuntime; |
apps/server/src/provider/opencodeRuntime.ts+1-1| ... | ... | @@ -1106,6 +1106,6 @@ export class OpenCodeRuntime extends Context.Service<OpenCodeRuntime, OpenCodeRu |
| 1106 | 1106 | "t3/provider/opencodeRuntime", |
| 1107 | 1107 | ) {} |
| 1108 | 1108 | |
| 1109 | export const OpenCodeRuntimeLive = Layer.effect(OpenCodeRuntime, makeOpenCodeRuntime).pipe( | |
| 1109 | export const layer = Layer.effect(OpenCodeRuntime, makeOpenCodeRuntime).pipe( | |
| 1110 | 1110 | Layer.provide(NetService.layer), |
| 1111 | 1111 | ); |
apps/server/src/provider/providerCompatibility.test.ts+3-3| ... | ... | @@ -10,7 +10,7 @@ import * as Ref from "effect/Ref"; |
| 10 | 10 | import * as Stream from "effect/Stream"; |
| 11 | 11 | import * as ServerConfig from "../config.ts"; |
| 12 | 12 | import * as ModelManifest from "./ModelManifest.ts"; |
| 13 | import { ProviderRegistryLive } from "./Layers/ProviderRegistry.ts"; | |
| 13 | import * as ProviderRegistryLayer from "./Layers/ProviderRegistry.ts"; | |
| 14 | 14 | import * as ProviderRegistry from "./Services/ProviderRegistry.ts"; |
| 15 | 15 | import * as ProviderInstanceRegistry from "./Services/ProviderInstanceRegistry.ts"; |
| 16 | 16 | import type { ProviderInstance } from "./ProviderDriver.ts"; |
| ... | ... | @@ -323,7 +323,7 @@ it.effect("a remote policy refresh preserves a newer health result on the regist |
| 323 | 323 | })), |
| 324 | 324 | ), |
| 325 | 325 | ); |
| 326 | const dependencies = Layer.mergeAll( | |
| 326 | const layerDependencies = Layer.mergeAll( | |
| 327 | 327 | Layer.succeed(ModelManifest.ModelManifest, { |
| 328 | 328 | current: Ref.get(manifest), |
| 329 | 329 | refresh, |
| ... | ... | @@ -362,7 +362,7 @@ it.effect("a remote policy refresh preserves a newer health result on the regist |
| 362 | 362 | assert.strictEqual(updated?.compatibilityAdvisory?.status, "supported"); |
| 363 | 363 | assert.strictEqual(updated?.status, "error"); |
| 364 | 364 | assert.strictEqual(updated?.message, "Authentication failed"); |
| 365 | }).pipe(Effect.provide(ProviderRegistryLive.pipe(Layer.provide(dependencies)))); | |
| 365 | }).pipe(Effect.provide(ProviderRegistryLayer.layer.pipe(Layer.provide(layerDependencies)))); | |
| 366 | 366 | }).pipe(Effect.scoped), |
| 367 | 367 | ); |
| 368 | 368 |
apps/server/src/provider/providerMaintenanceRunner.test.ts+52-52| ... | ... | @@ -42,7 +42,7 @@ const encoder = new TextEncoder(); |
| 42 | 42 | // `{ command, args }` assertions below hold deterministically on any host |
| 43 | 43 | // (including Windows). Windows-specific resolution is covered by the dedicated |
| 44 | 44 | // win32 case at the end of this suite. |
| 45 | const NonWindowsPlatform = Layer.succeed(HostProcessPlatform, "linux"); | |
| 45 | const layerNonWindowsPlatform = Layer.succeed(HostProcessPlatform, "linux"); | |
| 46 | 46 | |
| 47 | 47 | function lifecycleFor(provider: ProviderDriverKind): ProviderMaintenanceCapabilities { |
| 48 | 48 | if (provider === NATIVE_CLI_DRIVER) { |
| ... | ... | @@ -92,7 +92,7 @@ const baseOpenCodeProvider: ServerProvider = { |
| 92 | 92 | driver: OPENCODE_DRIVER, |
| 93 | 93 | }; |
| 94 | 94 | |
| 95 | const latestVersionHttpClient = (version: string) => | |
| 95 | const layerLatestVersionHttpClient = (version: string) => | |
| 96 | 96 | Layer.succeed( |
| 97 | 97 | HttpClient.HttpClient, |
| 98 | 98 | HttpClient.make((request) => |
| ... | ... | @@ -126,7 +126,7 @@ function mockHandle(result: { |
| 126 | 126 | }); |
| 127 | 127 | } |
| 128 | 128 | |
| 129 | function mockSpawnerLayer( | |
| 129 | function layerMockSpawner( | |
| 130 | 130 | handler: ( |
| 131 | 131 | command: string, |
| 132 | 132 | args: ReadonlyArray<string>, |
| ... | ... | @@ -265,9 +265,9 @@ describe("providerMaintenanceRunner", () => { |
| 265 | 265 | }).pipe( |
| 266 | 266 | Effect.provide( |
| 267 | 267 | Layer.mergeAll( |
| 268 | NonWindowsPlatform, | |
| 269 | latestVersionHttpClient("0.0.0"), | |
| 270 | mockSpawnerLayer((command, args) => { | |
| 268 | layerNonWindowsPlatform, | |
| 269 | layerLatestVersionHttpClient("0.0.0"), | |
| 270 | layerMockSpawner((command, args) => { | |
| 271 | 271 | calls.push({ command, args }); |
| 272 | 272 | return { stdout: "updated" }; |
| 273 | 273 | }), |
| ... | ... | @@ -294,9 +294,9 @@ describe("providerMaintenanceRunner", () => { |
| 294 | 294 | }).pipe( |
| 295 | 295 | Effect.provide( |
| 296 | 296 | Layer.mergeAll( |
| 297 | NonWindowsPlatform, | |
| 298 | latestVersionHttpClient("0.0.0"), | |
| 299 | mockSpawnerLayer(() => ({ stdout: "updated" })), | |
| 297 | layerNonWindowsPlatform, | |
| 298 | layerLatestVersionHttpClient("0.0.0"), | |
| 299 | layerMockSpawner(() => ({ stdout: "updated" })), | |
| 300 | 300 | ), |
| 301 | 301 | ), |
| 302 | 302 | ); |
| ... | ... | @@ -321,9 +321,9 @@ describe("providerMaintenanceRunner", () => { |
| 321 | 321 | }).pipe( |
| 322 | 322 | Effect.provide( |
| 323 | 323 | Layer.mergeAll( |
| 324 | NonWindowsPlatform, | |
| 325 | latestVersionHttpClient("0.0.0"), | |
| 326 | mockSpawnerLayer(() => ({ stdout: "updated" })), | |
| 324 | layerNonWindowsPlatform, | |
| 325 | layerLatestVersionHttpClient("0.0.0"), | |
| 326 | layerMockSpawner(() => ({ stdout: "updated" })), | |
| 327 | 327 | ), |
| 328 | 328 | ), |
| 329 | 329 | ); |
| ... | ... | @@ -354,9 +354,9 @@ describe("providerMaintenanceRunner", () => { |
| 354 | 354 | }).pipe( |
| 355 | 355 | Effect.provide( |
| 356 | 356 | Layer.mergeAll( |
| 357 | NonWindowsPlatform, | |
| 358 | latestVersionHttpClient("0.0.0"), | |
| 359 | mockSpawnerLayer((_command, _args, options) => { | |
| 357 | layerNonWindowsPlatform, | |
| 358 | layerLatestVersionHttpClient("0.0.0"), | |
| 359 | layerMockSpawner((_command, _args, options) => { | |
| 360 | 360 | seen.push(options.env); |
| 361 | 361 | return { stdout: "updated" }; |
| 362 | 362 | }), |
| ... | ... | @@ -395,9 +395,9 @@ describe("providerMaintenanceRunner", () => { |
| 395 | 395 | }).pipe( |
| 396 | 396 | Effect.provide( |
| 397 | 397 | Layer.mergeAll( |
| 398 | NonWindowsPlatform, | |
| 399 | latestVersionHttpClient("0.0.0"), | |
| 400 | mockSpawnerLayer((command, args) => { | |
| 398 | layerNonWindowsPlatform, | |
| 399 | layerLatestVersionHttpClient("0.0.0"), | |
| 400 | layerMockSpawner((command, args) => { | |
| 401 | 401 | calls.push({ command, args }); |
| 402 | 402 | return { stdout: "updated" }; |
| 403 | 403 | }), |
| ... | ... | @@ -440,9 +440,9 @@ describe("providerMaintenanceRunner", () => { |
| 440 | 440 | }).pipe( |
| 441 | 441 | Effect.provide( |
| 442 | 442 | Layer.mergeAll( |
| 443 | NonWindowsPlatform, | |
| 444 | latestVersionHttpClient("0.0.0"), | |
| 445 | mockSpawnerLayer((command) => { | |
| 443 | layerNonWindowsPlatform, | |
| 444 | layerLatestVersionHttpClient("0.0.0"), | |
| 445 | layerMockSpawner((command) => { | |
| 446 | 446 | calls.push(command); |
| 447 | 447 | return { stdout: "updated" }; |
| 448 | 448 | }), |
| ... | ... | @@ -490,9 +490,9 @@ describe("providerMaintenanceRunner", () => { |
| 490 | 490 | }).pipe( |
| 491 | 491 | Effect.provide( |
| 492 | 492 | Layer.mergeAll( |
| 493 | NonWindowsPlatform, | |
| 494 | latestVersionHttpClient("0.0.0"), | |
| 495 | mockSpawnerLayer((command, args) => { | |
| 493 | layerNonWindowsPlatform, | |
| 494 | layerLatestVersionHttpClient("0.0.0"), | |
| 495 | layerMockSpawner((command, args) => { | |
| 496 | 496 | calls.push({ command, args }); |
| 497 | 497 | return { stdout: "updated" }; |
| 498 | 498 | }), |
| ... | ... | @@ -521,9 +521,9 @@ describe("providerMaintenanceRunner", () => { |
| 521 | 521 | }).pipe( |
| 522 | 522 | Effect.provide( |
| 523 | 523 | Layer.mergeAll( |
| 524 | NonWindowsPlatform, | |
| 525 | latestVersionHttpClient("0.0.0"), | |
| 526 | mockSpawnerLayer((command, args) => { | |
| 524 | layerNonWindowsPlatform, | |
| 525 | layerLatestVersionHttpClient("0.0.0"), | |
| 526 | layerMockSpawner((command, args) => { | |
| 527 | 527 | calls.push({ command, args }); |
| 528 | 528 | return { stdout: "updated" }; |
| 529 | 529 | }), |
| ... | ... | @@ -594,9 +594,9 @@ describe("providerMaintenanceRunner", () => { |
| 594 | 594 | }).pipe( |
| 595 | 595 | Effect.provide( |
| 596 | 596 | Layer.mergeAll( |
| 597 | NonWindowsPlatform, | |
| 598 | latestVersionHttpClient("0.124.0-alpha.3"), | |
| 599 | mockSpawnerLayer((command, args) => { | |
| 597 | layerNonWindowsPlatform, | |
| 598 | layerLatestVersionHttpClient("0.124.0-alpha.3"), | |
| 599 | layerMockSpawner((command, args) => { | |
| 600 | 600 | calls.push({ command, args }); |
| 601 | 601 | return { stdout: "updated" }; |
| 602 | 602 | }), |
| ... | ... | @@ -619,9 +619,9 @@ describe("providerMaintenanceRunner", () => { |
| 619 | 619 | }).pipe( |
| 620 | 620 | Effect.provide( |
| 621 | 621 | Layer.mergeAll( |
| 622 | NonWindowsPlatform, | |
| 623 | latestVersionHttpClient("0.0.0"), | |
| 624 | mockSpawnerLayer(() => ({ stderr: "permission denied", code: 1 })), | |
| 622 | layerNonWindowsPlatform, | |
| 623 | layerLatestVersionHttpClient("0.0.0"), | |
| 624 | layerMockSpawner(() => ({ stderr: "permission denied", code: 1 })), | |
| 625 | 625 | ), |
| 626 | 626 | ), |
| 627 | 627 | ), |
| ... | ... | @@ -645,9 +645,9 @@ describe("providerMaintenanceRunner", () => { |
| 645 | 645 | }).pipe( |
| 646 | 646 | Effect.provide( |
| 647 | 647 | Layer.mergeAll( |
| 648 | NonWindowsPlatform, | |
| 649 | latestVersionHttpClient("9.9.9"), | |
| 650 | mockSpawnerLayer(() => ({ stdout: "updated" })), | |
| 648 | layerNonWindowsPlatform, | |
| 649 | layerLatestVersionHttpClient("9.9.9"), | |
| 650 | layerMockSpawner(() => ({ stdout: "updated" })), | |
| 651 | 651 | ), |
| 652 | 652 | ), |
| 653 | 653 | ), |
| ... | ... | @@ -684,9 +684,9 @@ describe("providerMaintenanceRunner", () => { |
| 684 | 684 | }).pipe( |
| 685 | 685 | Effect.provide( |
| 686 | 686 | Layer.mergeAll( |
| 687 | NonWindowsPlatform, | |
| 688 | latestVersionHttpClient("0.0.0"), | |
| 689 | mockSpawnerLayer(() => { | |
| 687 | layerNonWindowsPlatform, | |
| 688 | layerLatestVersionHttpClient("0.0.0"), | |
| 689 | layerMockSpawner(() => { | |
| 690 | 690 | startedLatch.resolve(); |
| 691 | 691 | return { |
| 692 | 692 | stdout: "updated", |
| ... | ... | @@ -761,9 +761,9 @@ describe("providerMaintenanceRunner", () => { |
| 761 | 761 | }).pipe( |
| 762 | 762 | Effect.provide( |
| 763 | 763 | Layer.mergeAll( |
| 764 | NonWindowsPlatform, | |
| 765 | latestVersionHttpClient("2.0.0"), | |
| 766 | mockSpawnerLayer((_command, args) => { | |
| 764 | layerNonWindowsPlatform, | |
| 765 | layerLatestVersionHttpClient("2.0.0"), | |
| 766 | layerMockSpawner((_command, args) => { | |
| 767 | 767 | calls.push(args.join(" ")); |
| 768 | 768 | if (calls.length === 1) { |
| 769 | 769 | firstStartedLatch.resolve(); |
| ... | ... | @@ -805,9 +805,9 @@ describe("providerMaintenanceRunner", () => { |
| 805 | 805 | }).pipe( |
| 806 | 806 | Effect.provide( |
| 807 | 807 | Layer.mergeAll( |
| 808 | NonWindowsPlatform, | |
| 809 | latestVersionHttpClient("0.0.0"), | |
| 810 | mockSpawnerLayer((_command, args) => { | |
| 808 | layerNonWindowsPlatform, | |
| 809 | layerLatestVersionHttpClient("0.0.0"), | |
| 810 | layerMockSpawner((_command, args) => { | |
| 811 | 811 | calls.push(args.join(" ")); |
| 812 | 812 | return { stdout: "updated" }; |
| 813 | 813 | }), |
| ... | ... | @@ -860,9 +860,9 @@ describe("providerMaintenanceRunner", () => { |
| 860 | 860 | }).pipe( |
| 861 | 861 | Effect.provide( |
| 862 | 862 | Layer.mergeAll( |
| 863 | NonWindowsPlatform, | |
| 864 | latestVersionHttpClient("0.0.0"), | |
| 865 | mockSpawnerLayer(() => ({ stdout: "updated" })), | |
| 863 | layerNonWindowsPlatform, | |
| 864 | layerLatestVersionHttpClient("0.0.0"), | |
| 865 | layerMockSpawner(() => ({ stdout: "updated" })), | |
| 866 | 866 | ), |
| 867 | 867 | ), |
| 868 | 868 | ), |
| ... | ... | @@ -908,7 +908,7 @@ describe("providerMaintenanceRunner", () => { |
| 908 | 908 | Layer.succeed(SpawnExecutableResolution, (command) => |
| 909 | 909 | command === "npm" ? "C:\\fake\\npm\\npm.cmd" : undefined, |
| 910 | 910 | ), |
| 911 | latestVersionHttpClient("0.0.0"), | |
| 911 | layerLatestVersionHttpClient("0.0.0"), | |
| 912 | 912 | Layer.succeed( |
| 913 | 913 | ChildProcessSpawner.ChildProcessSpawner, |
| 914 | 914 | ChildProcessSpawner.make((command) => { |
| ... | ... | @@ -1009,9 +1009,9 @@ it.effect("refuses incompatible latest versions and unapproved or unpinnable tar |
| 1009 | 1009 | }).pipe( |
| 1010 | 1010 | Effect.provide( |
| 1011 | 1011 | Layer.mergeAll( |
| 1012 | NonWindowsPlatform, | |
| 1013 | latestVersionHttpClient("3.0.0"), | |
| 1014 | mockSpawnerLayer((_command, args) => { | |
| 1012 | layerNonWindowsPlatform, | |
| 1013 | layerLatestVersionHttpClient("3.0.0"), | |
| 1014 | layerMockSpawner((_command, args) => { | |
| 1015 | 1015 | calls.push(args.join(" ")); |
| 1016 | 1016 | return { stdout: "installed" }; |
| 1017 | 1017 | }), |
apps/server/src/provider/testUtils/providerRegistryMock.ts+1-1| ... | ... | @@ -18,5 +18,5 @@ const makeProviderRegistryMock = ( |
| 18 | 18 | streamChanges: Stream.empty, |
| 19 | 19 | }); |
| 20 | 20 | |
| 21 | export const makeProviderRegistryLayer = (providers: ReadonlyArray<ServerProvider> = []) => | |
| 21 | export const layer = (providers: ReadonlyArray<ServerProvider> = []) => | |
| 22 | 22 | Layer.succeed(ProviderRegistry.ProviderRegistry, makeProviderRegistryMock(providers)); |
apps/server/src/pullRequest/PullRequestService.test.ts+2-2| ... | ... | @@ -25,7 +25,7 @@ import * as ServerSettingsService from "../serverSettings.ts"; |
| 25 | 25 | import { PullRequestOperationError } from "@t3tools/contracts"; |
| 26 | 26 | |
| 27 | 27 | import * as ProjectService from "../project/ProjectService.ts"; |
| 28 | import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts"; | |
| 28 | import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts"; | |
| 29 | 29 | import * as PullRequestFilesViewed from "../persistence/PullRequestFilesViewed.ts"; |
| 30 | 30 | import * as RepositoryIdentityResolver from "../project/RepositoryIdentityResolver.ts"; |
| 31 | 31 | import * as SourceControlProviderRegistry from "../sourceControl/SourceControlProviderRegistry.ts"; |
| ... | ... | @@ -505,7 +505,7 @@ function makeService(input: { |
| 505 | 505 | SourceControlRateLimit.layer, |
| 506 | 506 | // The real store over a database of its own, so the environment-kept marks are exercised |
| 507 | 507 | // through the SQL that holds them rather than through a stand-in that agrees with itself. |
| 508 | PullRequestFilesViewed.layer.pipe(Layer.provide(SqlitePersistenceMemory)), | |
| 508 | PullRequestFilesViewed.layer.pipe(Layer.provide(SqlitePersistence.layerMemory)), | |
| 509 | 509 | Layer.effect(PullRequestReadCache.PullRequestReadCache, PullRequestReadCache.make).pipe( |
| 510 | 510 | Layer.provide(KeyValueStore.layerMemory), |
| 511 | 511 | Layer.provide(NodeServices.layer), |
apps/server/src/pullRequest/http.ts+1-1| ... | ... | @@ -6,7 +6,7 @@ import { annotateEnvironmentRequest, requireEnvironmentScope } from "../auth/htt |
| 6 | 6 | import * as PullRequestService from "./PullRequestService.ts"; |
| 7 | 7 | |
| 8 | 8 | /** The patch is often the largest PR payload and benefits from HTTP compression and flow control. */ |
| 9 | export const pullRequestHttpApiLayer = HttpApiBuilder.group( | |
| 9 | export const layer = HttpApiBuilder.group( | |
| 10 | 10 | EnvironmentHttpApi, |
| 11 | 11 | "pullRequests", |
| 12 | 12 | Effect.fnUntraced(function* (handlers) { |
apps/server/src/pullRequest/linkedThreads.test.ts+2-2| ... | ... | @@ -3,7 +3,7 @@ import * as Effect from "effect/Effect"; |
| 3 | 3 | import * as Schema from "effect/Schema"; |
| 4 | 4 | import * as SqlClient from "effect/sql/SqlClient"; |
| 5 | 5 | |
| 6 | import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts"; | |
| 6 | import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts"; | |
| 7 | 7 | import { listLinkedPullRequestThreads } from "./linkedThreads.ts"; |
| 8 | 8 | |
| 9 | 9 | const encodePayload = Schema.encodeEffect(Schema.fromJsonString(Schema.Unknown)); |
| ... | ... | @@ -180,5 +180,5 @@ it.effect( |
| 180 | 180 | }), |
| 181 | 181 | { threads: [] }, |
| 182 | 182 | ); |
| 183 | }).pipe(Effect.provide(SqlitePersistenceMemory)), | |
| 183 | }).pipe(Effect.provide(SqlitePersistence.layerMemory)), | |
| 184 | 184 | ); |
apps/server/src/resourceTelemetry/ResourceTelemetry.test.ts+35-35| ... | ... | @@ -152,7 +152,7 @@ describe("ResourceTelemetry", () => { |
| 152 | 152 | childWriteBytes: 1_000, |
| 153 | 153 | }); |
| 154 | 154 | const demandChanges = yield* Ref.make<ReadonlyArray<boolean>>([]); |
| 155 | const nativeLayer = NativeTelemetryClient.layerTest({ | |
| 155 | const layerNative = NativeTelemetryClient.layerTest({ | |
| 156 | 156 | sampleNow: Effect.succeed(nativeGeneration(sample, 0)), |
| 157 | 157 | health: Effect.succeed({ |
| 158 | 158 | status: "healthy", |
| ... | ... | @@ -163,13 +163,13 @@ describe("ResourceTelemetry", () => { |
| 163 | 163 | sampleIntervalMs: 1_000, |
| 164 | 164 | }), |
| 165 | 165 | }); |
| 166 | const desktopLayer = DesktopTelemetryReceiver.layerTest({ | |
| 166 | const layerDesktop = DesktopTelemetryReceiver.layerTest({ | |
| 167 | 167 | latest: Effect.succeedSome(desktopSnapshot(sampledAtUnixMs)), |
| 168 | 168 | setDiagnosticsDemand: (enabled) => |
| 169 | 169 | Ref.update(demandChanges, (changes) => [...changes, enabled]), |
| 170 | 170 | }); |
| 171 | const telemetryLayer = ResourceTelemetry.layer.pipe( | |
| 172 | Layer.provide(Layer.mergeAll(nativeLayer, desktopLayer, ResourceAttribution.layer)), | |
| 171 | const layerTelemetry = ResourceTelemetry.layer.pipe( | |
| 172 | Layer.provide(Layer.mergeAll(layerNative, layerDesktop, ResourceAttribution.layer)), | |
| 173 | 173 | ); |
| 174 | 174 | |
| 175 | 175 | const live = yield* Stream.runHead( |
| ... | ... | @@ -177,7 +177,7 @@ describe("ResourceTelemetry", () => { |
| 177 | 177 | const telemetry = yield* ResourceTelemetry.ResourceTelemetry; |
| 178 | 178 | return telemetry.changes; |
| 179 | 179 | }).pipe(Stream.unwrap), |
| 180 | ).pipe(Effect.provide(telemetryLayer)); | |
| 180 | ).pipe(Effect.provide(layerTelemetry)); | |
| 181 | 181 | |
| 182 | 182 | expect(Option.isSome(live)).toBe(true); |
| 183 | 183 | expect(yield* Ref.get(demandChanges)).toEqual([true, false]); |
| ... | ... | @@ -188,7 +188,7 @@ describe("ResourceTelemetry", () => { |
| 188 | 188 | Effect.gen(function* () { |
| 189 | 189 | const sampledAtUnixMs = DateTime.toEpochMillis(yield* DateTime.now); |
| 190 | 190 | const demandChanges = yield* Ref.make<ReadonlyArray<boolean>>([]); |
| 191 | const nativeLayer = NativeTelemetryClient.layerTest({ | |
| 191 | const layerNative = NativeTelemetryClient.layerTest({ | |
| 192 | 192 | sampleNow: Effect.never, |
| 193 | 193 | health: Effect.succeed({ |
| 194 | 194 | status: "healthy", |
| ... | ... | @@ -199,13 +199,13 @@ describe("ResourceTelemetry", () => { |
| 199 | 199 | sampleIntervalMs: 1_000, |
| 200 | 200 | }), |
| 201 | 201 | }); |
| 202 | const desktopLayer = DesktopTelemetryReceiver.layerTest({ | |
| 202 | const layerDesktop = DesktopTelemetryReceiver.layerTest({ | |
| 203 | 203 | latest: Effect.succeedSome(desktopSnapshot(sampledAtUnixMs)), |
| 204 | 204 | setDiagnosticsDemand: (enabled) => |
| 205 | 205 | Ref.update(demandChanges, (changes) => [...changes, enabled]), |
| 206 | 206 | }); |
| 207 | const telemetryLayer = ResourceTelemetry.layer.pipe( | |
| 208 | Layer.provide(Layer.mergeAll(nativeLayer, desktopLayer, ResourceAttribution.layer)), | |
| 207 | const layerTelemetry = ResourceTelemetry.layer.pipe( | |
| 208 | Layer.provide(Layer.mergeAll(layerNative, layerDesktop, ResourceAttribution.layer)), | |
| 209 | 209 | ); |
| 210 | 210 | |
| 211 | 211 | const resultFiber = yield* Stream.runHead( |
| ... | ... | @@ -213,7 +213,7 @@ describe("ResourceTelemetry", () => { |
| 213 | 213 | const telemetry = yield* ResourceTelemetry.ResourceTelemetry; |
| 214 | 214 | return telemetry.changes; |
| 215 | 215 | }).pipe(Stream.unwrap), |
| 216 | ).pipe(Effect.timeoutOption("10 millis"), Effect.provide(telemetryLayer), Effect.forkChild); | |
| 216 | ).pipe(Effect.timeoutOption("10 millis"), Effect.provide(layerTelemetry), Effect.forkChild); | |
| 217 | 217 | yield* Effect.yieldNow; |
| 218 | 218 | yield* TestClock.adjust("10 millis"); |
| 219 | 219 | const result = yield* Fiber.join(resultFiber); |
| ... | ... | @@ -239,7 +239,7 @@ describe("ResourceTelemetry", () => { |
| 239 | 239 | ...desktopSnapshot(sampledAtUnixMs), |
| 240 | 240 | electronProcesses: [], |
| 241 | 241 | }; |
| 242 | const nativeLayer = NativeTelemetryClient.layerTest({ | |
| 242 | const layerNative = NativeTelemetryClient.layerTest({ | |
| 243 | 243 | sampleNow: Effect.succeed(nativeGeneration(sample, 0)), |
| 244 | 244 | health: Effect.succeed({ |
| 245 | 245 | status: "healthy", |
| ... | ... | @@ -250,17 +250,17 @@ describe("ResourceTelemetry", () => { |
| 250 | 250 | sampleIntervalMs: 1_000, |
| 251 | 251 | }), |
| 252 | 252 | }); |
| 253 | const desktopLayer = DesktopTelemetryReceiver.layerTest({ | |
| 253 | const layerDesktop = DesktopTelemetryReceiver.layerTest({ | |
| 254 | 254 | latest: Effect.succeedSome(desktop), |
| 255 | 255 | }); |
| 256 | const telemetryLayer = ResourceTelemetry.layer.pipe( | |
| 257 | Layer.provide(Layer.mergeAll(nativeLayer, desktopLayer, ResourceAttribution.layer)), | |
| 256 | const layerTelemetry = ResourceTelemetry.layer.pipe( | |
| 257 | Layer.provide(Layer.mergeAll(layerNative, layerDesktop, ResourceAttribution.layer)), | |
| 258 | 258 | ); |
| 259 | 259 | |
| 260 | 260 | const snapshot = yield* Effect.gen(function* () { |
| 261 | 261 | const telemetry = yield* ResourceTelemetry.ResourceTelemetry; |
| 262 | 262 | return yield* telemetry.refresh; |
| 263 | }).pipe(Effect.provide(telemetryLayer)); | |
| 263 | }).pipe(Effect.provide(layerTelemetry)); | |
| 264 | 264 | |
| 265 | 265 | expect(snapshot.processes.find((entry) => entry.identity.pid === 5_000)?.category).toBe( |
| 266 | 266 | "electron-main", |
| ... | ... | @@ -288,7 +288,7 @@ describe("ResourceTelemetry", () => { |
| 288 | 288 | }); |
| 289 | 289 | const nativeSnapshots = |
| 290 | 290 | yield* PubSub.unbounded<NativeTelemetryClient.NativeTelemetrySnapshot>(); |
| 291 | const nativeLayer = NativeTelemetryClient.layerTest({ | |
| 291 | const layerNative = NativeTelemetryClient.layerTest({ | |
| 292 | 292 | snapshots: Stream.fromPubSub(nativeSnapshots), |
| 293 | 293 | sampleNow: Effect.never, |
| 294 | 294 | health: Effect.succeed({ |
| ... | ... | @@ -300,10 +300,10 @@ describe("ResourceTelemetry", () => { |
| 300 | 300 | sampleIntervalMs: 1_000, |
| 301 | 301 | }), |
| 302 | 302 | }); |
| 303 | const telemetryLayer = ResourceTelemetry.layer.pipe( | |
| 303 | const layerTelemetry = ResourceTelemetry.layer.pipe( | |
| 304 | 304 | Layer.provide( |
| 305 | 305 | Layer.mergeAll( |
| 306 | nativeLayer, | |
| 306 | layerNative, | |
| 307 | 307 | DesktopTelemetryReceiver.layerTest(), |
| 308 | 308 | ResourceAttribution.layer, |
| 309 | 309 | ), |
| ... | ... | @@ -326,7 +326,7 @@ describe("ResourceTelemetry", () => { |
| 326 | 326 | expect(DateTime.toEpochMillis(Option.getOrThrow(received).readAt)).toBe( |
| 327 | 327 | current.sampledAtUnixMs, |
| 328 | 328 | ); |
| 329 | }).pipe(Effect.provide(telemetryLayer)); | |
| 329 | }).pipe(Effect.provide(layerTelemetry)); | |
| 330 | 330 | }), |
| 331 | 331 | ), |
| 332 | 332 | ); |
| ... | ... | @@ -343,7 +343,7 @@ describe("ResourceTelemetry", () => { |
| 343 | 343 | yield* PubSub.sliding<DesktopTelemetryReceiver.DesktopTelemetryReceiverHealth>(4); |
| 344 | 344 | const healthSubscribed = yield* Deferred.make<void>(); |
| 345 | 345 | const finishDesktopSnapshot = yield* Deferred.make<void>(); |
| 346 | const desktopLayer = DesktopTelemetryReceiver.layerTest({ | |
| 346 | const layerDesktop = DesktopTelemetryReceiver.layerTest({ | |
| 347 | 347 | health: Ref.get(health), |
| 348 | 348 | subscribeHealth: Effect.gen(function* () { |
| 349 | 349 | const subscription = yield* PubSub.subscribe(healthChanges); |
| ... | ... | @@ -361,11 +361,11 @@ describe("ResourceTelemetry", () => { |
| 361 | 361 | }), |
| 362 | 362 | ), |
| 363 | 363 | }); |
| 364 | const telemetryLayer = ResourceTelemetry.layer.pipe( | |
| 364 | const layerTelemetry = ResourceTelemetry.layer.pipe( | |
| 365 | 365 | Layer.provide( |
| 366 | 366 | Layer.mergeAll( |
| 367 | 367 | NativeTelemetryClient.layerTest(), |
| 368 | desktopLayer, | |
| 368 | layerDesktop, | |
| 369 | 369 | ResourceAttribution.layer, |
| 370 | 370 | ), |
| 371 | 371 | ), |
| ... | ... | @@ -377,7 +377,7 @@ describe("ResourceTelemetry", () => { |
| 377 | 377 | if (snapshot.health.desktop.status === "degraded") return snapshot; |
| 378 | 378 | yield* Effect.yieldNow; |
| 379 | 379 | } |
| 380 | }).pipe(Effect.provide(telemetryLayer), Effect.timeout("1 second"), Effect.forkChild); | |
| 380 | }).pipe(Effect.provide(layerTelemetry), Effect.timeout("1 second"), Effect.forkChild); | |
| 381 | 381 | |
| 382 | 382 | yield* Deferred.await(healthSubscribed); |
| 383 | 383 | const degraded: DesktopTelemetryReceiver.DesktopTelemetryReceiverHealth = { |
| ... | ... | @@ -407,7 +407,7 @@ describe("ResourceTelemetry", () => { |
| 407 | 407 | const rebuildStarted = yield* Deferred.make<void>(); |
| 408 | 408 | const finishRebuild = yield* Deferred.make<void>(); |
| 409 | 409 | const attributionReads = yield* Ref.make(0); |
| 410 | const attributionLayer = Layer.succeed( | |
| 410 | const layerAttribution = Layer.succeed( | |
| 411 | 411 | ResourceAttribution.ResourceAttribution, |
| 412 | 412 | ResourceAttribution.ResourceAttribution.of({ |
| 413 | 413 | record: () => Effect.void, |
| ... | ... | @@ -428,7 +428,7 @@ describe("ResourceTelemetry", () => { |
| 428 | 428 | ), |
| 429 | 429 | }), |
| 430 | 430 | ); |
| 431 | const nativeLayer = NativeTelemetryClient.layerTest({ | |
| 431 | const layerNative = NativeTelemetryClient.layerTest({ | |
| 432 | 432 | sampleNow: Effect.never, |
| 433 | 433 | health: Effect.succeed({ |
| 434 | 434 | status: "healthy", |
| ... | ... | @@ -439,11 +439,11 @@ describe("ResourceTelemetry", () => { |
| 439 | 439 | sampleIntervalMs: 1_000, |
| 440 | 440 | }), |
| 441 | 441 | }); |
| 442 | const desktopLayer = DesktopTelemetryReceiver.layerTest({ | |
| 442 | const layerDesktop = DesktopTelemetryReceiver.layerTest({ | |
| 443 | 443 | changes: Stream.fromPubSub(desktopChanges), |
| 444 | 444 | }); |
| 445 | const telemetryLayer = ResourceTelemetry.layer.pipe( | |
| 446 | Layer.provide(Layer.mergeAll(nativeLayer, desktopLayer, attributionLayer)), | |
| 445 | const layerTelemetry = ResourceTelemetry.layer.pipe( | |
| 446 | Layer.provide(Layer.mergeAll(layerNative, layerDesktop, layerAttribution)), | |
| 447 | 447 | ); |
| 448 | 448 | |
| 449 | 449 | yield* Effect.gen(function* () { |
| ... | ... | @@ -461,7 +461,7 @@ describe("ResourceTelemetry", () => { |
| 461 | 461 | nextDesktopSnapshot.sampledAtUnixMs, |
| 462 | 462 | ); |
| 463 | 463 | expect(subscription.latest.power).toEqual(nextDesktopSnapshot.power); |
| 464 | }).pipe(Effect.provide(telemetryLayer)); | |
| 464 | }).pipe(Effect.provide(layerTelemetry)); | |
| 465 | 465 | }), |
| 466 | 466 | ), |
| 467 | 467 | ); |
| ... | ... | @@ -520,7 +520,7 @@ describe("ResourceTelemetry", () => { |
| 520 | 520 | }); |
| 521 | 521 | const nativeHealthChanges = |
| 522 | 522 | yield* PubSub.sliding<NativeTelemetryClient.NativeTelemetryClientHealth>(4); |
| 523 | const nativeLayer = NativeTelemetryClient.layerTest({ | |
| 523 | const layerNative = NativeTelemetryClient.layerTest({ | |
| 524 | 524 | setExternalProcesses: (processes) => Ref.set(externalProcesses, processes), |
| 525 | 525 | readHistory: () => Effect.succeed(samples.slice(0, 2)), |
| 526 | 526 | sampleNow: Ref.modify(sampleIndex, (index) => { |
| ... | ... | @@ -538,7 +538,7 @@ describe("ResourceTelemetry", () => { |
| 538 | 538 | }; |
| 539 | 539 | }), |
| 540 | 540 | }); |
| 541 | const desktopLayer = DesktopTelemetryReceiver.layerTest({ | |
| 541 | const layerDesktop = DesktopTelemetryReceiver.layerTest({ | |
| 542 | 542 | latest: Effect.succeedSome(desktopSnapshot(startedAt)), |
| 543 | 543 | health: Effect.succeed({ |
| 544 | 544 | status: "healthy", |
| ... | ... | @@ -546,10 +546,10 @@ describe("ResourceTelemetry", () => { |
| 546 | 546 | lastError: Option.none(), |
| 547 | 547 | }), |
| 548 | 548 | }); |
| 549 | const attributionLayer = ResourceAttribution.layer; | |
| 550 | const dependencies = Layer.mergeAll(nativeLayer, desktopLayer, attributionLayer); | |
| 551 | const telemetryLayer = ResourceTelemetry.layer.pipe(Layer.provide(dependencies)); | |
| 552 | const layer = Layer.mergeAll(dependencies, telemetryLayer); | |
| 549 | const layerAttribution = ResourceAttribution.layer; | |
| 550 | const layerDependencies = Layer.mergeAll(layerNative, layerDesktop, layerAttribution); | |
| 551 | const layerTelemetry = ResourceTelemetry.layer.pipe(Layer.provide(layerDependencies)); | |
| 552 | const layer = Layer.mergeAll(layerDependencies, layerTelemetry); | |
| 553 | 553 | |
| 554 | 554 | yield* Effect.gen(function* () { |
| 555 | 555 | const telemetry = yield* ResourceTelemetry.ResourceTelemetry; |
apps/server/src/review/ReviewService.test.ts+7-7| ... | ... | @@ -11,7 +11,7 @@ import * as GitVcsDriver from "../vcs/GitVcsDriver.ts"; |
| 11 | 11 | import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts"; |
| 12 | 12 | import * as ReviewService from "./ReviewService.ts"; |
| 13 | 13 | |
| 14 | function makeLayer(input: { | |
| 14 | function layer(input: { | |
| 15 | 15 | readonly workspaceRoot: string; |
| 16 | 16 | readonly baseDir: string; |
| 17 | 17 | readonly detectCalls?: Array<{ readonly cwd: string }>; |
| ... | ... | @@ -54,7 +54,7 @@ describe("ReviewService", () => { |
| 54 | 54 | const error = yield* Effect.gen(function* () { |
| 55 | 55 | const review = yield* ReviewService.ReviewService; |
| 56 | 56 | return yield* review.getDiffPreview({ cwd: outsideRoot }).pipe(Effect.flip); |
| 57 | }).pipe(Effect.provide(makeLayer({ workspaceRoot, baseDir, detectCalls }))); | |
| 57 | }).pipe(Effect.provide(layer({ workspaceRoot, baseDir, detectCalls }))); | |
| 58 | 58 | |
| 59 | 59 | assert.strictEqual(error._tag, "VcsRepositoryDetectionError"); |
| 60 | 60 | assert.strictEqual(error.operation, "ReviewService.getDiffPreview"); |
| ... | ... | @@ -87,7 +87,7 @@ describe("ReviewService", () => { |
| 87 | 87 | newPath: "file.ts", |
| 88 | 88 | }) |
| 89 | 89 | .pipe(Effect.flip); |
| 90 | }).pipe(Effect.provide(makeLayer({ workspaceRoot, baseDir, detectCalls }))); | |
| 90 | }).pipe(Effect.provide(layer({ workspaceRoot, baseDir, detectCalls }))); | |
| 91 | 91 | |
| 92 | 92 | assert.strictEqual(error._tag, "VcsRepositoryDetectionError"); |
| 93 | 93 | assert.strictEqual(error.operation, "ReviewService.getDiffFileContents"); |
| ... | ... | @@ -112,7 +112,7 @@ describe("ReviewService", () => { |
| 112 | 112 | return yield* review.getDiffPreview({ cwd: previous }); |
| 113 | 113 | }).pipe( |
| 114 | 114 | Effect.provide( |
| 115 | makeLayer({ | |
| 115 | layer({ | |
| 116 | 116 | workspaceRoot, |
| 117 | 117 | baseDir, |
| 118 | 118 | worktreesDirectory: "/", |
| ... | ... | @@ -128,7 +128,7 @@ describe("ReviewService", () => { |
| 128 | 128 | const error = yield* Effect.gen(function* () { |
| 129 | 129 | const review = yield* ReviewService.ReviewService; |
| 130 | 130 | return yield* review.getDiffPreview({ cwd: outsideRoot }).pipe(Effect.flip); |
| 131 | }).pipe(Effect.provide(makeLayer({ workspaceRoot, baseDir, worktreesDirectory }))); | |
| 131 | }).pipe(Effect.provide(layer({ workspaceRoot, baseDir, worktreesDirectory }))); | |
| 132 | 132 | assert.strictEqual(error._tag, "VcsRepositoryDetectionError"); |
| 133 | 133 | } |
| 134 | 134 | }).pipe(Effect.provide(NodeServices.layer)), |
| ... | ... | @@ -144,7 +144,7 @@ describe("ReviewService", () => { |
| 144 | 144 | const result = yield* Effect.gen(function* () { |
| 145 | 145 | const review = yield* ReviewService.ReviewService; |
| 146 | 146 | return yield* review.getDiffPreview({ cwd: workspaceRoot }); |
| 147 | }).pipe(Effect.provide(makeLayer({ workspaceRoot, baseDir, detectCalls }))); | |
| 147 | }).pipe(Effect.provide(layer({ workspaceRoot, baseDir, detectCalls }))); | |
| 148 | 148 | |
| 149 | 149 | assert.strictEqual(result.cwd, workspaceRoot); |
| 150 | 150 | assert.deepStrictEqual(result.sources, []); |
| ... | ... | @@ -163,7 +163,7 @@ describe("ReviewService", () => { |
| 163 | 163 | const error = yield* Effect.gen(function* () { |
| 164 | 164 | const review = yield* ReviewService.ReviewService; |
| 165 | 165 | return yield* review.getDiffPreview({ cwd: invalidCwd }).pipe(Effect.flip); |
| 166 | }).pipe(Effect.provide(makeLayer({ workspaceRoot, baseDir, detectCalls }))); | |
| 166 | }).pipe(Effect.provide(layer({ workspaceRoot, baseDir, detectCalls }))); | |
| 167 | 167 | |
| 168 | 168 | assert.strictEqual(error._tag, "VcsRepositoryDetectionError"); |
| 169 | 169 | if (error._tag !== "VcsRepositoryDetectionError") return; |
apps/server/src/scheduledTasks/ScheduledTaskService.schedule.test.ts+7-7| ... | ... | @@ -11,14 +11,14 @@ import * as TestClock from "effect/testing/TestClock"; |
| 11 | 11 | import * as ThreadLaunchService from "../orchestration-v2/ThreadLaunchService.ts"; |
| 12 | 12 | import * as ThreadManagementService from "../orchestration-v2/ThreadManagementService.ts"; |
| 13 | 13 | import * as SecretRequests from "../secrets/SecretRequests.ts"; |
| 14 | import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts"; | |
| 14 | import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts"; | |
| 15 | 15 | import * as ScheduledTaskService from "./ScheduledTaskService.ts"; |
| 16 | 16 | |
| 17 | 17 | const decodeUpsertInput = Schema.decodeUnknownEffect(ScheduledTaskUpsertInput); |
| 18 | 18 | |
| 19 | 19 | it.effect("rejects a stale form save after deletion while preserving explicit-id creates", () => |
| 20 | 20 | Effect.gen(function* () { |
| 21 | const dependencies = Layer.mergeAll( | |
| 21 | const layerDependencies = Layer.mergeAll( | |
| 22 | 22 | NodeCrypto.layer, |
| 23 | 23 | Scheduler.layer, |
| 24 | 24 | Layer.mock(ThreadLaunchService.ThreadLaunchService)({}), |
| ... | ... | @@ -49,8 +49,8 @@ it.effect("rejects a stale form save after deletion while preserving explicit-id |
| 49 | 49 | expect((yield* service.list()).tasks).toEqual([]); |
| 50 | 50 | |
| 51 | 51 | expect((yield* service.upsert(input)).task.id).toBe(created.task.id); |
| 52 | }).pipe(Effect.provide(ScheduledTaskService.layer.pipe(Layer.provide(dependencies)))); | |
| 53 | }).pipe(Effect.provide(SqlitePersistenceMemory)), | |
| 52 | }).pipe(Effect.provide(ScheduledTaskService.layer.pipe(Layer.provide(layerDependencies)))); | |
| 53 | }).pipe(Effect.provide(SqlitePersistence.layerMemory)), | |
| 54 | 54 | ); |
| 55 | 55 | |
| 56 | 56 | it.effect("preserves a due run when a save only pads the scheduled hour", () => |
| ... | ... | @@ -61,7 +61,7 @@ it.effect("preserves a due run when a save only pads the scheduled hour", () => |
| 61 | 61 | ); |
| 62 | 62 | yield* TestClock.setTime(DateTime.toEpochMillis(dueAt) - 1_000); |
| 63 | 63 | |
| 64 | const dependencies = Layer.mergeAll( | |
| 64 | const layerDependencies = Layer.mergeAll( | |
| 65 | 65 | NodeCrypto.layer, |
| 66 | 66 | Scheduler.layer, |
| 67 | 67 | Layer.mock(ThreadLaunchService.ThreadLaunchService)({}), |
| ... | ... | @@ -105,6 +105,6 @@ it.effect("preserves a due run when a save only pads the scheduled hour", () => |
| 105 | 105 | expect(rescheduled.task.nextRunAt).toBe( |
| 106 | 106 | DateTime.formatIso(DateTime.toUtc(DateTime.add(dueAt, { minutes: 30 }))), |
| 107 | 107 | ); |
| 108 | }).pipe(Effect.provide(ScheduledTaskService.layer.pipe(Layer.provide(dependencies)))); | |
| 109 | }).pipe(Effect.provide(SqlitePersistenceMemory)), | |
| 108 | }).pipe(Effect.provide(ScheduledTaskService.layer.pipe(Layer.provide(layerDependencies)))); | |
| 109 | }).pipe(Effect.provide(SqlitePersistence.layerMemory)), | |
| 110 | 110 | ); |
apps/server/src/scheduledTasks/ScheduledTaskService.test.ts+4-4| ... | ... | @@ -18,7 +18,7 @@ import * as TestClock from "effect/testing/TestClock"; |
| 18 | 18 | import * as ThreadLaunchService from "../orchestration-v2/ThreadLaunchService.ts"; |
| 19 | 19 | import * as ThreadManagementService from "../orchestration-v2/ThreadManagementService.ts"; |
| 20 | 20 | import * as SecretRequests from "../secrets/SecretRequests.ts"; |
| 21 | import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts"; | |
| 21 | import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts"; | |
| 22 | 22 | import * as ScheduledTaskService from "./ScheduledTaskService.ts"; |
| 23 | 23 | |
| 24 | 24 | const isScheduledTaskError = Schema.is(ScheduledTaskError); |
| ... | ... | @@ -154,7 +154,7 @@ it.effect("loads only due tasks and skips corrupt due rows without decoding sett |
| 154 | 154 | last_run_status: string; |
| 155 | 155 | }>`SELECT last_run_status FROM scheduled_tasks WHERE task_id = 'running'`; |
| 156 | 156 | assert.equal(running[0]?.last_run_status, "running"); |
| 157 | }).pipe(Effect.provide(SqlitePersistenceMemory)), | |
| 157 | }).pipe(Effect.provide(SqlitePersistence.layerMemory)), | |
| 158 | 158 | ); |
| 159 | 159 | |
| 160 | 160 | it.effect( |
| ... | ... | @@ -268,7 +268,7 @@ it.effect( |
| 268 | 268 | assert.isNull(byId.get("due-huge")?.next_run_at); |
| 269 | 269 | assert.equal(byId.get("due-bad-date")?.last_run_status, "never"); |
| 270 | 270 | assert.equal(byId.get("due-bad-date")?.run_count, 0); |
| 271 | }).pipe(Effect.provide(SqlitePersistenceMemory)), | |
| 271 | }).pipe(Effect.provide(SqlitePersistence.layerMemory)), | |
| 272 | 272 | ); |
| 273 | 273 | |
| 274 | 274 | it.effect( |
| ... | ... | @@ -349,5 +349,5 @@ it.effect( |
| 349 | 349 | yield* Deferred.succeed(releaseLast, undefined); |
| 350 | 350 | }), |
| 351 | 351 | ); |
| 352 | }).pipe(Effect.provide(SqlitePersistenceMemory)), | |
| 352 | }).pipe(Effect.provide(SqlitePersistence.layerMemory)), | |
| 353 | 353 | ); |
apps/server/src/scheduledTasks/ScheduledTaskService.webhook.test.ts+4-4| ... | ... | @@ -17,7 +17,7 @@ import * as TestClock from "effect/testing/TestClock"; |
| 17 | 17 | import * as ThreadLaunchService from "../orchestration-v2/ThreadLaunchService.ts"; |
| 18 | 18 | import * as ThreadManagementService from "../orchestration-v2/ThreadManagementService.ts"; |
| 19 | 19 | import * as SecretRequests from "../secrets/SecretRequests.ts"; |
| 20 | import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts"; | |
| 20 | import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts"; | |
| 21 | 21 | import * as Scheduler from "../scheduling/Scheduler.ts"; |
| 22 | 22 | import * as ScheduledTaskService from "./ScheduledTaskService.ts"; |
| 23 | 23 | |
| ... | ... | @@ -75,7 +75,7 @@ const withService = <A, E>( |
| 75 | 75 | Effect.gen(function* () { |
| 76 | 76 | const launches = yield* Queue.unbounded<LaunchInput>(); |
| 77 | 77 | const secretsByRef = new Map<string, string>(); |
| 78 | const dependencies = Layer.mergeAll( | |
| 78 | const layerDependencies = Layer.mergeAll( | |
| 79 | 79 | NodePlatformCrypto.layer, |
| 80 | 80 | Scheduler.layer, |
| 81 | 81 | Layer.mock(ThreadLaunchService.ThreadLaunchService)({ |
| ... | ... | @@ -103,8 +103,8 @@ const withService = <A, E>( |
| 103 | 103 | return yield* Effect.gen(function* () { |
| 104 | 104 | const service = yield* ScheduledTaskService.ScheduledTaskService; |
| 105 | 105 | return yield* body({ service, launches, secretsByRef }); |
| 106 | }).pipe(Effect.provide(ScheduledTaskService.layer.pipe(Layer.provide(dependencies)))); | |
| 107 | }).pipe(Effect.provide(SqlitePersistenceMemory)); | |
| 106 | }).pipe(Effect.provide(ScheduledTaskService.layer.pipe(Layer.provide(layerDependencies)))); | |
| 107 | }).pipe(Effect.provide(SqlitePersistence.layerMemory)); | |
| 108 | 108 | |
| 109 | 109 | it.effect("dispatches exactly the rendered prompt and logs the delivery", () => |
| 110 | 110 | withService(({ service, launches }) => |
apps/server/src/scheduledTasks/webhookRoute.test.ts+3-2| ... | ... | @@ -27,7 +27,8 @@ import * as ServerSecretStore from "../auth/ServerSecretStore.ts"; |
| 27 | 27 | import { CLOUD_MINT_PUBLIC_KEY, RELAY_ISSUER_SECRET } from "../cloud/config.ts"; |
| 28 | 28 | import * as ServerEnvironment from "../environment/ServerEnvironment.ts"; |
| 29 | 29 | import * as RelayDeliveryProof from "./RelayDeliveryProof.ts"; |
| 30 | import { WEBHOOK_MAX_BODY_BYTES, webhookHttpApiLayer } from "./webhookRoute.ts"; | |
| 30 | import { WEBHOOK_MAX_BODY_BYTES } from "./webhookRoute.ts"; | |
| 31 | import * as WebhookRoute from "./webhookRoute.ts"; | |
| 31 | 32 | |
| 32 | 33 | class WebhookTestApi extends HttpApi.make("environment").add(EnvironmentHttpApi.groups.webhooks) {} |
| 33 | 34 | |
| ... | ... | @@ -77,7 +78,7 @@ const handlerFor = ( |
| 77 | 78 | ) => |
| 78 | 79 | HttpRouter.toWebHandler( |
| 79 | 80 | HttpApiBuilder.layer(WebhookTestApi).pipe( |
| 80 | Layer.provide(webhookHttpApiLayer), | |
| 81 | Layer.provide(WebhookRoute.layer), | |
| 81 | 82 | Layer.provide(RelayDeliveryProof.layer), |
| 82 | 83 | Layer.provide(Layer.mock(ScheduledTaskService)({ triggerWebhook: trigger })), |
| 83 | 84 | Layer.provide( |
apps/server/src/scheduledTasks/webhookRoute.ts+1-1| ... | ... | @@ -23,7 +23,7 @@ const WEBHOOK_OUTCOME_HEADER = "x-t3-hook-outcome"; |
| 23 | 23 | const json = (status: number, body: Record<string, string>, outcome: string) => |
| 24 | 24 | HttpServerResponse.jsonUnsafe(body, { status, headers: { [WEBHOOK_OUTCOME_HEADER]: outcome } }); |
| 25 | 25 | |
| 26 | export const webhookHttpApiLayer = HttpApiBuilder.group( | |
| 26 | export const layer = HttpApiBuilder.group( | |
| 27 | 27 | EnvironmentHttpApi, |
| 28 | 28 | "webhooks", |
| 29 | 29 | Effect.fnUntraced(function* (handlers) { |
apps/server/src/scheduling/Scheduler.integration.test.ts+7-7| ... | ... | @@ -22,7 +22,7 @@ import * as ProjectionStore from "../orchestration-v2/ProjectionStore.ts"; |
| 22 | 22 | import * as ThreadLaunchService from "../orchestration-v2/ThreadLaunchService.ts"; |
| 23 | 23 | import * as ThreadManagementService from "../orchestration-v2/ThreadManagementService.ts"; |
| 24 | 24 | import * as UsageLimitRecoveryWorker from "../orchestration-v2/UsageLimitRecoveryWorker.ts"; |
| 25 | import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts"; | |
| 25 | import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts"; | |
| 26 | 26 | import * as ScheduledTasks from "../scheduledTasks/ScheduledTaskService.ts"; |
| 27 | 27 | import * as SecretRequests from "../secrets/SecretRequests.ts"; |
| 28 | 28 | import * as ServerSettings from "../serverSettings.ts"; |
| ... | ... | @@ -86,7 +86,7 @@ it.effect.each(["on time", "after restart"])( |
| 86 | 86 | const current = yield* Ref.make(thread); |
| 87 | 87 | const commands = yield* Ref.make<ReadonlyArray<OrchestrationV2ServerCommand>>([]); |
| 88 | 88 | const receipts = yield* Queue.unbounded<"task" | "retry">(); |
| 89 | const dependencies = Layer.mergeAll( | |
| 89 | const layerDependencies = Layer.mergeAll( | |
| 90 | 90 | NodeCrypto.layer, |
| 91 | 91 | Layer.mock(ThreadLaunchService.ThreadLaunchService)({ |
| 92 | 92 | launch: () => |
| ... | ... | @@ -115,10 +115,10 @@ it.effect.each(["on time", "after restart"])( |
| 115 | 115 | }), |
| 116 | 116 | Layer.mock(SecretRequests.SecretRequests)({}), |
| 117 | 117 | ); |
| 118 | const workers = Layer.mergeAll( | |
| 118 | const layerWorkers = Layer.mergeAll( | |
| 119 | 119 | ScheduledTasks.layer, |
| 120 | UsageLimitRecoveryWorker.workerLive, | |
| 121 | ).pipe(Layer.provide(dependencies), Layer.provide(Scheduler.layer)); | |
| 120 | UsageLimitRecoveryWorker.layer, | |
| 121 | ).pipe(Layer.provide(layerDependencies), Layer.provide(Scheduler.layer)); | |
| 122 | 122 | yield* Effect.gen(function* () { |
| 123 | 123 | const tasks = yield* ScheduledTasks.ScheduledTaskService; |
| 124 | 124 | const { task } = yield* tasks.upsert({ |
| ... | ... | @@ -151,6 +151,6 @@ it.effect.each(["on time", "after restart"])( |
| 151 | 151 | usageLimitRecoveryRequestId: thread.limitRecovery!.requestId, |
| 152 | 152 | text: "Continue where you left off.", |
| 153 | 153 | }); |
| 154 | }).pipe(Effect.provide(workers)); | |
| 155 | }).pipe(Effect.provide(SqlitePersistenceMemory)), | |
| 154 | }).pipe(Effect.provide(layerWorkers)); | |
| 155 | }).pipe(Effect.provide(SqlitePersistence.layerMemory)), | |
| 156 | 156 | ); |
apps/server/src/secrets/SecretRequests.test.ts+2-2| ... | ... | @@ -48,7 +48,7 @@ const withService = <A, E>( |
| 48 | 48 | let secretStatus = "pending"; |
| 49 | 49 | let failedRecords = options.failedRecords ?? 0; |
| 50 | 50 | const requestThreadId = options.threadId ?? threadId; |
| 51 | const dependencies = Layer.mergeAll( | |
| 51 | const layerDependencies = Layer.mergeAll( | |
| 52 | 52 | NodeCrypto.layer, |
| 53 | 53 | NodeServices.layer, |
| 54 | 54 | Layer.succeed( |
| ... | ... | @@ -130,7 +130,7 @@ const withService = <A, E>( |
| 130 | 130 | return yield* Effect.gen(function* () { |
| 131 | 131 | const service = yield* SecretRequests.SecretRequests; |
| 132 | 132 | return yield* body({ service, stored, dispatched }); |
| 133 | }).pipe(Effect.provide(SecretRequests.layer.pipe(Layer.provide(dependencies)))); | |
| 133 | }).pipe(Effect.provide(SecretRequests.layer.pipe(Layer.provide(layerDependencies)))); | |
| 134 | 134 | }); |
| 135 | 135 | |
| 136 | 136 | /** The secret values in the store, leaving out the server's own salt. */ |
apps/server/src/server.ts+194-217| ... | ... | @@ -25,22 +25,14 @@ import * as HttpApiBuilder from "effect/http-api/HttpApiBuilder"; |
| 25 | 25 | import * as BackgroundPolicy from "./background/BackgroundPolicy.ts"; |
| 26 | 26 | import * as HostPowerMonitor from "./background/HostPowerMonitor.ts"; |
| 27 | 27 | import * as ServerConfig from "./config.ts"; |
| 28 | import { | |
| 29 | otlpTracesProxyRouteLayer, | |
| 30 | assetRouteLayer, | |
| 31 | attachmentUploadRouteLayer, | |
| 32 | serverEnvironmentHttpApiLayer, | |
| 33 | staticAndDevRouteLayer, | |
| 34 | browserApiCorsLayer, | |
| 35 | httpCompressionLayer, | |
| 36 | withUntracedRequests, | |
| 37 | } from "./http.ts"; | |
| 28 | import { withUntracedRequests } from "./http.ts"; | |
| 29 | import * as ServerHttp from "./http.ts"; | |
| 38 | 30 | import { guardHttpResponseWriteErrors } from "./httpResponseErrorGuard.ts"; |
| 39 | 31 | import { fixPath } from "./os-jank.ts"; |
| 40 | import { websocketRpcRouteLayer } from "./ws.ts"; | |
| 32 | import * as Ws from "./ws.ts"; | |
| 41 | 33 | import * as ExternalLauncher from "./process/externalLauncher.ts"; |
| 42 | 34 | import * as NodePtyAdapter from "./terminal/NodePtyAdapter.ts"; |
| 43 | import { pullRequestHttpApiLayer } from "./pullRequest/http.ts"; | |
| 35 | import * as PullRequestHttp from "./pullRequest/http.ts"; | |
| 44 | 36 | import * as PullRequestProviderRegistry from "./pullRequest/PullRequestProviderRegistry.ts"; |
| 45 | 37 | import * as PullRequestService from "./pullRequest/PullRequestService.ts"; |
| 46 | 38 | import * as SqlitePersistence from "./persistence/Layers/Sqlite.ts"; |
| ... | ... | @@ -53,7 +45,7 @@ import * as ResetCreditCoordinator from "./provider/Layers/resetCreditCoordinato |
| 53 | 45 | import * as ProviderEventLoggers from "./provider/Layers/ProviderEventLoggers.ts"; |
| 54 | 46 | import * as OpenCodeRuntime from "./provider/opencodeRuntime.ts"; |
| 55 | 47 | import * as OpenCodeServerLedger from "./provider/OpenCodeServerLedger.ts"; |
| 56 | import { AcpRegistryCatalogLive } from "./provider/Layers/AcpRegistryCatalog.ts"; | |
| 48 | import * as AcpRegistryCatalog from "./provider/Layers/AcpRegistryCatalog.ts"; | |
| 57 | 49 | import * as CheckpointDiffQuery from "./checkpointing/CheckpointDiffQuery.ts"; |
| 58 | 50 | import * as CheckpointStore from "./checkpointing/CheckpointStore.ts"; |
| 59 | 51 | import * as AzureDevOpsCli from "./sourceControl/AzureDevOpsCli.ts"; |
| ... | ... | @@ -62,13 +54,13 @@ import * as GitHubCli from "./sourceControl/GitHubCli.ts"; |
| 62 | 54 | import * as GitLabCli from "./sourceControl/GitLabCli.ts"; |
| 63 | 55 | import * as ForgejoCli from "./sourceControl/ForgejoCli.ts"; |
| 64 | 56 | import * as TextGeneration from "./textGeneration/TextGeneration.ts"; |
| 65 | import { ProviderInstanceRegistryHydrationLive } from "./provider/Layers/ProviderInstanceRegistryHydration.ts"; | |
| 57 | import * as ProviderInstanceRegistryHydration from "./provider/Layers/ProviderInstanceRegistryHydration.ts"; | |
| 66 | 58 | import * as TerminalManager from "./terminal/Manager.ts"; |
| 67 | 59 | import * as McpHttpServer from "./mcp/McpHttpServer.ts"; |
| 68 | 60 | import * as McpSessionRegistry from "./mcp/McpSessionRegistry.ts"; |
| 69 | 61 | import * as PreviewAutomationBroker from "./mcp/PreviewAutomationBroker.ts"; |
| 70 | 62 | import * as DeviceService from "./device/DeviceService.ts"; |
| 71 | import { deviceHubProxyRouteLayer } from "./device/DeviceHubProxy.ts"; | |
| 63 | import * as DeviceHubProxy from "./device/DeviceHubProxy.ts"; | |
| 72 | 64 | import * as PreviewManager from "./preview/Manager.ts"; |
| 73 | 65 | import * as PortScanner from "./preview/PortScanner.ts"; |
| 74 | 66 | import * as ProcessRunner from "./processRunner.ts"; |
| ... | ... | @@ -78,7 +70,7 @@ import * as Keybindings from "./keybindings.ts"; |
| 78 | 70 | import * as ServerRuntimeStartup from "./serverRuntimeStartup.ts"; |
| 79 | 71 | import * as AgentAwarenessRelay from "./relay/AgentAwarenessRelay.ts"; |
| 80 | 72 | import { hasCloudPublicConfig } from "./cloud/publicConfig.ts"; |
| 81 | import { ProviderRegistryLive } from "./provider/Layers/ProviderRegistry.ts"; | |
| 73 | import * as ProviderRegistryLayer from "./provider/Layers/ProviderRegistry.ts"; | |
| 82 | 74 | import * as ServerSettings from "./serverSettings.ts"; |
| 83 | 75 | import * as ProjectEnrichmentService from "./project/ProjectEnrichmentService.ts"; |
| 84 | 76 | import * as NativeAppIconResolver from "./assets/NativeAppIconResolver.ts"; |
| ... | ... | @@ -87,7 +79,7 @@ import * as CodexInstallation from "./provider/CodexInstallation.ts"; |
| 87 | 79 | import * as ProviderInstanceRegistry from "./provider/Services/ProviderInstanceRegistry.ts"; |
| 88 | 80 | import * as ProviderAdapterRegistry from "./orchestration-v2/ProviderAdapterRegistry.ts"; |
| 89 | 81 | import * as ProviderRegistry from "./provider/Services/ProviderRegistry.ts"; |
| 90 | import { ProviderUsageLimitsIngestionLive } from "./provider/Layers/ProviderUsageLimitsIngestion.ts"; | |
| 82 | import * as ProviderUsageLimitsIngestion from "./provider/Layers/ProviderUsageLimitsIngestion.ts"; | |
| 91 | 83 | import * as UsageLimitSources from "./usage/UsageLimitSources.ts"; |
| 92 | 84 | import * as ProjectFaviconResolver from "./project/ProjectFaviconResolver.ts"; |
| 93 | 85 | import * as T3ProjectFileLoader from "./project/T3ProjectFileLoader.ts"; |
| ... | ... | @@ -109,16 +101,16 @@ import * as PullRequestReadCache from "./pullRequest/PullRequestReadCache.ts"; |
| 109 | 101 | import * as SourceControlRateLimit from "./sourceControl/SourceControlRateLimit.ts"; |
| 110 | 102 | import * as SourceControlRepositoryService from "./sourceControl/SourceControlRepositoryService.ts"; |
| 111 | 103 | import * as WorktreeSetupTracker from "./project/WorktreeSetupTracker.ts"; |
| 112 | import { ObservabilityLive } from "./observability/Layers/Observability.ts"; | |
| 104 | import * as Observability from "./observability/Layers/Observability.ts"; | |
| 113 | 105 | import * as HeapSnapshot from "./observability/HeapSnapshot.ts"; |
| 114 | 106 | import * as EventLoopMonitor from "./observability/EventLoopMonitor.ts"; |
| 115 | 107 | import * as ServerEnvironment from "./environment/ServerEnvironment.ts"; |
| 116 | 108 | import * as DirectEndpoints from "./environment/DirectEndpoints.ts"; |
| 117 | 109 | import * as RemoteOpenTargets from "./environment/RemoteOpenTargets.ts"; |
| 118 | import { authHttpApiLayer, environmentAuthenticatedAuthLayer } from "./auth/http.ts"; | |
| 110 | import * as AuthHttp from "./auth/http.ts"; | |
| 119 | 111 | import * as ReplayMarkers from "./auth/replayMarkers.ts"; |
| 120 | 112 | import * as ServerSecretStore from "./auth/ServerSecretStore.ts"; |
| 121 | import { webhookHttpApiLayer } from "./scheduledTasks/webhookRoute.ts"; | |
| 113 | import * as WebhookRoute from "./scheduledTasks/webhookRoute.ts"; | |
| 122 | 114 | import * as RelayDeliveryProof from "./scheduledTasks/RelayDeliveryProof.ts"; |
| 123 | 115 | import * as CloudPreferences from "./cloud/CloudPreferences.ts"; |
| 124 | 116 | import * as HeldHooksWaker from "./relay/HeldHooksWaker.ts"; |
| ... | ... | @@ -133,7 +125,6 @@ import { |
| 133 | 125 | } from "./cloud/config.ts"; |
| 134 | 126 | import * as EnvironmentAuth from "./auth/EnvironmentAuth.ts"; |
| 135 | 127 | import { |
| 136 | connectHttpApiLayer, | |
| 137 | 128 | pendingServiceUpdateExists, |
| 138 | 129 | reconcileDesiredCloudLinkIfStillDesired, |
| 139 | 130 | recoverManagedCloudTunnel, |
| ... | ... | @@ -141,7 +132,8 @@ import { |
| 141 | 132 | startManagedCloudTunnelIfOriginConfirmed, |
| 142 | 133 | releaseManagedTunnelOnShutdown, |
| 143 | 134 | } from "./cloud/http.ts"; |
| 144 | import { serverRelayBrokerTracingLayer } from "./cloud/relayTracing.ts"; | |
| 135 | import * as CloudHttp from "./cloud/http.ts"; | |
| 136 | import * as RelayTracing from "./cloud/relayTracing.ts"; | |
| 145 | 137 | import { shouldRetryCloudLink } from "./cloud/relayResponse.ts"; |
| 146 | 138 | import * as CloudManagedEndpointRuntime from "./cloud/ManagedEndpointRuntime.ts"; |
| 147 | 139 | import { |
| ... | ... | @@ -165,12 +157,7 @@ import * as ResourceAttribution from "./resourceTelemetry/ResourceAttribution.ts |
| 165 | 157 | import * as ResourceMonitorBinary from "./resourceTelemetry/ResourceMonitorBinary.ts"; |
| 166 | 158 | import * as ResourceTelemetry from "./resourceTelemetry/ResourceTelemetry.ts"; |
| 167 | 159 | import * as UsageService from "./usage/UsageService.ts"; |
| 168 | import { | |
| 169 | OrchestrationEventInfrastructureLayerLive, | |
| 170 | OrchestrationV2ProductionLayerLive, | |
| 171 | ProjectServiceLayerLive, | |
| 172 | ProjectSetupScriptRunnerLayerLive, | |
| 173 | } from "./orchestration-v2/runtimeLayer.ts"; | |
| 160 | import * as RuntimeLayer from "./orchestration-v2/runtimeLayer.ts"; | |
| 174 | 161 | import * as ProjectStore from "./orchestration-v2/ProjectStore.ts"; |
| 175 | 162 | import * as ThreadSearch from "./orchestration-v2/ThreadSearch.ts"; |
| 176 | 163 | import * as ResourceCleanupService from "./orchestration-v2/ResourceCleanupService.ts"; |
| ... | ... | @@ -183,8 +170,8 @@ import { |
| 183 | 170 | makePersistedServerRuntimeState, |
| 184 | 171 | persistServerRuntimeState, |
| 185 | 172 | } from "./serverRuntimeState.ts"; |
| 186 | import { orchestrationHttpApiLayer } from "./orchestration-v2/http.ts"; | |
| 187 | import { projectHttpApiLayer } from "./project/http.ts"; | |
| 173 | import * as OrchestrationHttp from "./orchestration-v2/http.ts"; | |
| 174 | import * as ProjectHttp from "./project/http.ts"; | |
| 188 | 175 | import * as NetService from "@t3tools/shared/Net"; |
| 189 | 176 | import * as RelayClient from "@t3tools/shared/relayClient"; |
| 190 | 177 | import { disableTailscaleServe, ensureTailscaleServe } from "@t3tools/tailscale"; |
| ... | ... | @@ -201,63 +188,63 @@ const HTTP_ROUTER_CONFIG = { |
| 201 | 188 | // already closes the websocket gracefully. Do not add an artificial drain before |
| 202 | 189 | // those finalizers get a chance to run. |
| 203 | 190 | const HTTP_PREEMPTIVE_SHUTDOWN_GRACE_MS = 0; |
| 204 | const ResourceAttributionLayerLive = ResourceAttribution.layer; | |
| 205 | const ApplicationObservabilityLive = EventLoopMonitor.layer.pipe( | |
| 206 | Layer.provideMerge(ObservabilityLive), | |
| 207 | Layer.provideMerge(ResourceAttributionLayerLive), | |
| 191 | const layerResourceAttribution = ResourceAttribution.layer; | |
| 192 | const layerApplicationObservability = EventLoopMonitor.layer.pipe( | |
| 193 | Layer.provideMerge(Observability.layer), | |
| 194 | Layer.provideMerge(layerResourceAttribution), | |
| 208 | 195 | ); |
| 209 | 196 | |
| 210 | const PtyAdapterLive = NodePtyAdapter.layer; | |
| 197 | const layerPtyAdapter = NodePtyAdapter.layer; | |
| 211 | 198 | |
| 212 | const ServerSettingsLayerLive = ServerSettings.layer.pipe( | |
| 199 | const layerServerSettings = ServerSettings.layer.pipe( | |
| 213 | 200 | Layer.provide(ServerSecretStore.layer), |
| 214 | 201 | Layer.provideMerge(SqlitePersistence.layerConfig), |
| 215 | 202 | ); |
| 216 | 203 | |
| 217 | const NativeTelemetryLayerLive = NativeTelemetryClient.layer.pipe( | |
| 204 | const layerNativeTelemetry = NativeTelemetryClient.layer.pipe( | |
| 218 | 205 | Layer.provide(ResourceMonitorBinary.layer), |
| 219 | 206 | ); |
| 220 | const DesktopTelemetryReceiverLayerLive = DesktopTelemetryReceiver.layer.pipe( | |
| 221 | Layer.provideMerge(ServerSettingsLayerLive), | |
| 207 | const layerDesktopTelemetryReceiver = DesktopTelemetryReceiver.layer.pipe( | |
| 208 | Layer.provideMerge(layerServerSettings), | |
| 222 | 209 | ); |
| 223 | 210 | |
| 224 | const ResourceTelemetryLayerLive = ResourceTelemetry.layer.pipe( | |
| 225 | Layer.provideMerge(NativeTelemetryLayerLive), | |
| 226 | Layer.provideMerge(DesktopTelemetryReceiverLayerLive), | |
| 211 | const layerResourceTelemetry = ResourceTelemetry.layer.pipe( | |
| 212 | Layer.provideMerge(layerNativeTelemetry), | |
| 213 | Layer.provideMerge(layerDesktopTelemetryReceiver), | |
| 227 | 214 | ); |
| 228 | 215 | |
| 229 | const HostPowerMonitorLayerLive = HostPowerMonitor.layer.pipe( | |
| 230 | Layer.provide(DesktopTelemetryReceiverLayerLive), | |
| 216 | const layerHostPowerMonitor = HostPowerMonitor.layer.pipe( | |
| 217 | Layer.provide(layerDesktopTelemetryReceiver), | |
| 231 | 218 | ); |
| 232 | 219 | |
| 233 | 220 | // Reuses DesktopTelemetryReceiverLayerLive: a fresh receiver layer here |
| 234 | 221 | // would open a second reader on the desktop telemetry fd. |
| 235 | const DesktopAppUpdateLayerLive = DesktopAppUpdate.layer.pipe( | |
| 236 | Layer.provide(DesktopTelemetryReceiverLayerLive), | |
| 222 | const layerDesktopAppUpdate = DesktopAppUpdate.layer.pipe( | |
| 223 | Layer.provide(layerDesktopTelemetryReceiver), | |
| 237 | 224 | ); |
| 238 | 225 | |
| 239 | const BackgroundLayerLive = BackgroundPolicy.layer.pipe( | |
| 240 | Layer.provide(HostPowerMonitorLayerLive), | |
| 241 | Layer.provideMerge(ServerSettingsLayerLive), | |
| 226 | const layerBackground = BackgroundPolicy.layer.pipe( | |
| 227 | Layer.provide(layerHostPowerMonitor), | |
| 228 | Layer.provideMerge(layerServerSettings), | |
| 242 | 229 | ); |
| 243 | 230 | |
| 244 | const UsageLayerLive = UsageService.layer.pipe(Layer.provide(ServerSettingsLayerLive)); | |
| 231 | const layerUsage = UsageService.layer.pipe(Layer.provide(layerServerSettings)); | |
| 245 | 232 | |
| 246 | const ResourceDiagnosticsLayerLive = Layer.mergeAll( | |
| 233 | const layerResourceDiagnostics = Layer.mergeAll( | |
| 247 | 234 | HostResources.layer, |
| 248 | ResourceTelemetryLayerLive, | |
| 249 | ProcessDiagnostics.layer.pipe(Layer.provide(ResourceTelemetryLayerLive)), | |
| 250 | ProcessResourceMonitor.layer.pipe(Layer.provide(ResourceTelemetryLayerLive)), | |
| 235 | layerResourceTelemetry, | |
| 236 | ProcessDiagnostics.layer.pipe(Layer.provide(layerResourceTelemetry)), | |
| 237 | ProcessResourceMonitor.layer.pipe(Layer.provide(layerResourceTelemetry)), | |
| 251 | 238 | ); |
| 252 | 239 | |
| 253 | const RelayClientLive = Layer.unwrap( | |
| 240 | const layerRelayClient = Layer.unwrap( | |
| 254 | 241 | Effect.gen(function* () { |
| 255 | 242 | const config = yield* ServerConfig.ServerConfig; |
| 256 | 243 | return RelayClient.layerCloudflared({ baseDir: config.baseDir }); |
| 257 | 244 | }), |
| 258 | 245 | ); |
| 259 | 246 | |
| 260 | const HttpServerLive = Layer.unwrap( | |
| 247 | const layerHttpServer = Layer.unwrap( | |
| 261 | 248 | Effect.gen(function* () { |
| 262 | 249 | const config = yield* ServerConfig.ServerConfig; |
| 263 | 250 | return NodeHttpServer.layer(() => guardHttpResponseWriteErrors(NodeHttp.createServer()), { |
| ... | ... | @@ -275,15 +262,13 @@ const HttpServerLive = Layer.unwrap( |
| 275 | 262 | }), |
| 276 | 263 | ); |
| 277 | 264 | |
| 278 | const PlatformServicesLive = NodeServices.layer; | |
| 265 | const layerPlatformServices = NodeServices.layer; | |
| 279 | 266 | |
| 280 | const PersistenceLayerLive = Layer.empty.pipe(Layer.provideMerge(SqlitePersistence.layerConfig)); | |
| 267 | const layerPersistence = Layer.empty.pipe(Layer.provideMerge(SqlitePersistence.layerConfig)); | |
| 281 | 268 | |
| 282 | const VcsDriverRegistryLayerLive = VcsDriverRegistry.layer.pipe( | |
| 283 | Layer.provide(VcsProjectConfig.layer), | |
| 284 | ); | |
| 269 | const layerVcsDriverRegistry = VcsDriverRegistry.layer.pipe(Layer.provide(VcsProjectConfig.layer)); | |
| 285 | 270 | |
| 286 | const SourceControlProviderRegistryLayerLive = SourceControlProviderRegistry.layer.pipe( | |
| 271 | const layerSourceControlProviderRegistry = SourceControlProviderRegistry.layer.pipe( | |
| 287 | 272 | Layer.provide( |
| 288 | 273 | Layer.mergeAll( |
| 289 | 274 | AzureDevOpsCli.layer, |
| ... | ... | @@ -294,10 +279,10 @@ const SourceControlProviderRegistryLayerLive = SourceControlProviderRegistry.lay |
| 294 | 279 | ), |
| 295 | 280 | ), |
| 296 | 281 | Layer.provideMerge(GitVcsDriver.layer), |
| 297 | Layer.provideMerge(VcsDriverRegistryLayerLive), | |
| 282 | Layer.provideMerge(layerVcsDriverRegistry), | |
| 298 | 283 | ); |
| 299 | 284 | |
| 300 | const RepositoryIdentityResolverLayerLive = Layer.effect( | |
| 285 | const layerRepositoryIdentityResolver = Layer.effect( | |
| 301 | 286 | RepositoryIdentityResolver.RepositoryIdentityResolver, |
| 302 | 287 | Effect.gen(function* () { |
| 303 | 288 | const registry = yield* SourceControlProviderRegistry.SourceControlProviderRegistry; |
| ... | ... | @@ -331,136 +316,130 @@ const RepositoryIdentityResolverLayerLive = Layer.effect( |
| 331 | 316 | }), |
| 332 | 317 | }); |
| 333 | 318 | }), |
| 334 | ).pipe(Layer.provide(SourceControlProviderRegistryLayerLive), Layer.provide(ProcessRunner.layer)); | |
| 319 | ).pipe(Layer.provide(layerSourceControlProviderRegistry), Layer.provide(ProcessRunner.layer)); | |
| 335 | 320 | |
| 336 | const PullRequestServiceLive = PullRequestService.layer.pipe( | |
| 321 | const layerPullRequestService = PullRequestService.layer.pipe( | |
| 337 | 322 | Layer.provide(PullRequestProviderRegistry.layer), |
| 338 | 323 | // Where the viewed-file marks live for a host that keeps none of its own. |
| 339 | 324 | Layer.provide(PullRequestFilesViewed.layer), |
| 340 | 325 | Layer.provide(PullRequestReadCache.layer), |
| 341 | Layer.provide(SourceControlProviderRegistryLayerLive), | |
| 326 | Layer.provide(layerSourceControlProviderRegistry), | |
| 342 | 327 | Layer.provide(SourceControlRateLimit.layer), |
| 343 | 328 | ); |
| 344 | 329 | |
| 345 | const GitManagerLayerLive = GitManager.layer.pipe( | |
| 330 | const layerGitManager = GitManager.layer.pipe( | |
| 346 | 331 | // Per-project git settings resolve the acting thread's project. |
| 347 | 332 | Layer.provide(Layer.merge(ProjectionStoreV2.layer, ProjectStore.layer)), |
| 348 | Layer.provideMerge(ProjectSetupScriptRunnerLayerLive), | |
| 333 | Layer.provideMerge(RuntimeLayer.layerProjectSetupScriptRunner), | |
| 349 | 334 | Layer.provideMerge(WorktreeSetupTracker.layer), |
| 350 | 335 | Layer.provideMerge(GitVcsDriver.layer), |
| 351 | Layer.provideMerge(SourceControlProviderRegistryLayerLive), | |
| 352 | Layer.provideMerge( | |
| 353 | TextGeneration.layer.pipe(Layer.provide(SourceControlProviderRegistryLayerLive)), | |
| 354 | ), | |
| 336 | Layer.provideMerge(layerSourceControlProviderRegistry), | |
| 337 | Layer.provideMerge(TextGeneration.layer.pipe(Layer.provide(layerSourceControlProviderRegistry))), | |
| 355 | 338 | ); |
| 356 | 339 | |
| 357 | const GitLayerLive = Layer.empty.pipe( | |
| 358 | Layer.provideMerge(GitManagerLayerLive), | |
| 340 | const layerGit = Layer.empty.pipe( | |
| 341 | Layer.provideMerge(layerGitManager), | |
| 359 | 342 | Layer.provideMerge(GitVcsDriver.layer), |
| 360 | 343 | ); |
| 361 | 344 | |
| 362 | const GitWorkflowLayerLive = GitWorkflowService.layer.pipe( | |
| 363 | Layer.provideMerge(VcsDriverRegistryLayerLive), | |
| 364 | Layer.provideMerge(GitLayerLive), | |
| 345 | const layerGitWorkflow = GitWorkflowService.layer.pipe( | |
| 346 | Layer.provideMerge(layerVcsDriverRegistry), | |
| 347 | Layer.provideMerge(layerGit), | |
| 365 | 348 | ); |
| 366 | 349 | |
| 367 | const SourceControlRepositoryServiceLayerLive = SourceControlRepositoryService.layer.pipe( | |
| 350 | const layerSourceControlRepositoryService = SourceControlRepositoryService.layer.pipe( | |
| 368 | 351 | Layer.provideMerge(GitVcsDriver.layer), |
| 369 | Layer.provideMerge(SourceControlProviderRegistryLayerLive), | |
| 352 | Layer.provideMerge(layerSourceControlProviderRegistry), | |
| 370 | 353 | ); |
| 371 | 354 | |
| 372 | const ProjectCloneTrackerLayerLive = ProjectCloneTracker.layer.pipe( | |
| 373 | Layer.provide(SourceControlRepositoryServiceLayerLive), | |
| 355 | const layerProjectCloneTracker = ProjectCloneTracker.layer.pipe( | |
| 356 | Layer.provide(layerSourceControlRepositoryService), | |
| 374 | 357 | ); |
| 375 | 358 | |
| 376 | const ReviewLayerLive = ReviewService.layer.pipe( | |
| 359 | const layerReview = ReviewService.layer.pipe( | |
| 377 | 360 | Layer.provideMerge(GitVcsDriver.layer), |
| 378 | Layer.provideMerge(VcsDriverRegistryLayerLive), | |
| 361 | Layer.provideMerge(layerVcsDriverRegistry), | |
| 379 | 362 | ); |
| 380 | 363 | |
| 381 | const VcsLayerLive = Layer.empty.pipe( | |
| 364 | const layerVcs = Layer.empty.pipe( | |
| 382 | 365 | Layer.provideMerge(VcsProjectConfig.layer), |
| 383 | Layer.provideMerge(VcsDriverRegistryLayerLive), | |
| 384 | Layer.provideMerge(VcsProvisioningService.layer.pipe(Layer.provide(VcsDriverRegistryLayerLive))), | |
| 385 | Layer.provideMerge(GitWorkflowLayerLive), | |
| 386 | Layer.provideMerge(ReviewLayerLive), | |
| 387 | Layer.provideMerge(SourceControlRepositoryServiceLayerLive), | |
| 388 | Layer.provideMerge(ProjectCloneTrackerLayerLive), | |
| 366 | Layer.provideMerge(layerVcsDriverRegistry), | |
| 367 | Layer.provideMerge(VcsProvisioningService.layer.pipe(Layer.provide(layerVcsDriverRegistry))), | |
| 368 | Layer.provideMerge(layerGitWorkflow), | |
| 369 | Layer.provideMerge(layerReview), | |
| 370 | Layer.provideMerge(layerSourceControlRepositoryService), | |
| 371 | Layer.provideMerge(layerProjectCloneTracker), | |
| 389 | 372 | Layer.provideMerge( |
| 390 | 373 | VcsStatusBroadcaster.layer.pipe( |
| 391 | Layer.provide(GitWorkflowLayerLive), | |
| 374 | Layer.provide(layerGitWorkflow), | |
| 392 | 375 | // Auto-pull reads the project row. The orchestration runtime also |
| 393 | 376 | // consumes the broadcaster (run finalization), so the policy cannot read |
| 394 | 377 | // the store from the runtime's output. |
| 395 | 378 | Layer.provide( |
| 396 | VcsStatusBroadcaster.autoPullPolicyLayer.pipe(Layer.provide(ProjectStore.layer)), | |
| 379 | VcsStatusBroadcaster.layerAutoPullPolicy.pipe(Layer.provide(ProjectStore.layer)), | |
| 397 | 380 | ), |
| 398 | 381 | ), |
| 399 | 382 | ), |
| 400 | 383 | ); |
| 401 | 384 | |
| 402 | const CheckpointStoreLayerLive = CheckpointStore.layer.pipe( | |
| 403 | Layer.provide(VcsDriverRegistryLayerLive), | |
| 404 | ); | |
| 385 | const layerCheckpointStore = CheckpointStore.layer.pipe(Layer.provide(layerVcsDriverRegistry)); | |
| 405 | 386 | |
| 406 | const PortScannerLayerLive = PortScanner.layer.pipe(Layer.provide(ProcessRunner.layer)); | |
| 387 | const layerPortScanner = PortScanner.layer.pipe(Layer.provide(ProcessRunner.layer)); | |
| 407 | 388 | |
| 408 | const TerminalLayerLive = TerminalManager.layer.pipe( | |
| 409 | Layer.provide(PtyAdapterLive), | |
| 410 | Layer.provide(PortScannerLayerLive), | |
| 411 | Layer.provide(NativeTelemetryLayerLive), | |
| 389 | const layerTerminal = TerminalManager.layer.pipe( | |
| 390 | Layer.provide(layerPtyAdapter), | |
| 391 | Layer.provide(layerPortScanner), | |
| 392 | Layer.provide(layerNativeTelemetry), | |
| 412 | 393 | ); |
| 413 | 394 | |
| 414 | const PreviewLayerLive = Layer.empty.pipe( | |
| 395 | const layerPreview = Layer.empty.pipe( | |
| 415 | 396 | Layer.provideMerge(PreviewManager.layer), |
| 416 | Layer.provideMerge(PortScannerLayerLive), | |
| 397 | Layer.provideMerge(layerPortScanner), | |
| 417 | 398 | ); |
| 418 | 399 | |
| 419 | const DeviceLayerLive = DeviceService.layer.pipe( | |
| 420 | Layer.provide(ServerSettingsLayerLive), | |
| 400 | const layerDevice = DeviceService.layer.pipe( | |
| 401 | Layer.provide(layerServerSettings), | |
| 421 | 402 | Layer.provide(ProcessRunner.layer), |
| 422 | 403 | Layer.provide(NetService.layer), |
| 423 | 404 | ); |
| 424 | 405 | |
| 425 | const WorkspaceEntriesLayerLive = WorkspaceEntries.layer.pipe(Layer.provide(WorkspacePaths.layer)); | |
| 406 | const layerWorkspaceEntries = WorkspaceEntries.layer.pipe(Layer.provide(WorkspacePaths.layer)); | |
| 426 | 407 | |
| 427 | const WorkspaceFileSystemLayerLive = WorkspaceFileSystem.layer.pipe( | |
| 408 | const layerWorkspaceFileSystem = WorkspaceFileSystem.layer.pipe( | |
| 428 | 409 | Layer.provide(WorkspacePaths.layer), |
| 429 | Layer.provide(WorkspaceEntriesLayerLive), | |
| 410 | Layer.provide(layerWorkspaceEntries), | |
| 430 | 411 | ); |
| 431 | 412 | |
| 432 | const WorkspaceLayerLive = Layer.mergeAll( | |
| 413 | const layerWorkspace = Layer.mergeAll( | |
| 433 | 414 | WorkspacePaths.layer, |
| 434 | WorkspaceEntriesLayerLive, | |
| 435 | WorkspaceFileSystemLayerLive, | |
| 415 | layerWorkspaceEntries, | |
| 416 | layerWorkspaceFileSystem, | |
| 436 | 417 | ); |
| 437 | 418 | |
| 438 | const ProjectFaviconResolverLayerLive = ProjectFaviconResolver.layer.pipe( | |
| 419 | const layerProjectFaviconResolver = ProjectFaviconResolver.layer.pipe( | |
| 439 | 420 | Layer.provide(WorkspacePaths.layer), |
| 440 | 421 | Layer.provide(T3ProjectFileLoader.layer), |
| 441 | 422 | ); |
| 442 | 423 | |
| 443 | const ServerEnvironmentLayerLive = ServerEnvironment.layer.pipe( | |
| 444 | Layer.provide(ServerSecretStore.layer), | |
| 445 | ); | |
| 424 | const layerServerEnvironment = ServerEnvironment.layer.pipe(Layer.provide(ServerSecretStore.layer)); | |
| 446 | 425 | |
| 447 | const AuthLayerLive = EnvironmentAuth.layer.pipe( | |
| 448 | Layer.provideMerge(PersistenceLayerLive), | |
| 449 | Layer.provide(ServerEnvironmentLayerLive), | |
| 426 | const layerAuth = EnvironmentAuth.layer.pipe( | |
| 427 | Layer.provideMerge(layerPersistence), | |
| 428 | Layer.provide(layerServerEnvironment), | |
| 450 | 429 | Layer.provide(ServerSecretStore.layer), |
| 451 | 430 | ); |
| 452 | 431 | |
| 453 | const CloudManagedEndpointRuntimeLive = Layer.mergeAll( | |
| 454 | RelayClientLive, | |
| 432 | const layerCloudManagedEndpointRuntime = Layer.mergeAll( | |
| 433 | layerRelayClient, | |
| 455 | 434 | CloudManagedEndpointRuntime.layer.pipe( |
| 456 | 435 | Layer.provide(ServerSecretStore.layer), |
| 457 | Layer.provide(RelayClientLive), | |
| 436 | Layer.provide(layerRelayClient), | |
| 458 | 437 | ), |
| 459 | 438 | ); |
| 460 | 439 | |
| 461 | 440 | // Webhook URLs go through the relay only when the managed tunnel it forwards |
| 462 | 441 | // to is configured; otherwise clients show the environment-relative path. |
| 463 | const ScheduledTaskWebhookOriginLive = Layer.effect( | |
| 442 | const layerScheduledTaskWebhookOrigin = Layer.effect( | |
| 464 | 443 | ScheduledTaskWebhookOrigin, |
| 465 | 444 | Effect.gen(function* () { |
| 466 | 445 | const secrets = yield* ServerSecretStore.ServerSecretStore; |
| ... | ... | @@ -484,38 +463,38 @@ const ScheduledTaskWebhookOriginLive = Layer.effect( |
| 484 | 463 | }), |
| 485 | 464 | ); |
| 486 | 465 | |
| 487 | const OrchestrationV2RuntimeLayerLive = OrchestrationV2ProductionLayerLive.pipe( | |
| 488 | Layer.provide(ScheduledTaskWebhookOriginLive), | |
| 489 | Layer.provide(ProviderEventIngestor.analyticsLive), | |
| 490 | Layer.provide(CheckpointStoreLayerLive), | |
| 491 | Layer.provide(GitWorkflowLayerLive), | |
| 492 | Layer.provide(ResourceCleanupService.live), | |
| 466 | const layerOrchestrationV2Runtime = RuntimeLayer.layerProduction.pipe( | |
| 467 | Layer.provide(layerScheduledTaskWebhookOrigin), | |
| 468 | Layer.provide(ProviderEventIngestor.layerAnalytics), | |
| 469 | Layer.provide(layerCheckpointStore), | |
| 470 | Layer.provide(layerGitWorkflow), | |
| 471 | Layer.provide(ResourceCleanupService.layer), | |
| 493 | 472 | Layer.provide( |
| 494 | RunFinalizationService.observerLive.pipe( | |
| 473 | RunFinalizationService.layerObserver.pipe( | |
| 495 | 474 | Layer.provide(ProjectionStoreV2.layer), |
| 496 | Layer.provide(PullRequestServiceLive), | |
| 497 | Layer.provide(ProjectServiceLayerLive), | |
| 475 | Layer.provide(layerPullRequestService), | |
| 476 | Layer.provide(RuntimeLayer.layerProjectService), | |
| 498 | 477 | ), |
| 499 | 478 | ), |
| 500 | 479 | ); |
| 501 | 480 | |
| 502 | const OrchestrationApplicationLayerLive = CheckpointDiffQuery.layer.pipe( | |
| 503 | Layer.provideMerge(CheckpointStoreLayerLive), | |
| 504 | Layer.provideMerge(OrchestrationV2RuntimeLayerLive), | |
| 481 | const layerOrchestrationApplication = CheckpointDiffQuery.layer.pipe( | |
| 482 | Layer.provideMerge(layerCheckpointStore), | |
| 483 | Layer.provideMerge(layerOrchestrationV2Runtime), | |
| 505 | 484 | ); |
| 506 | 485 | |
| 507 | 486 | // Automatic thread settlement (#8600): a server-owned sweep evaluates |
| 508 | 487 | // inactivity and merged pull requests, then settles through the orchestrator |
| 509 | 488 | // so every client sees the same shelf. |
| 510 | const ThreadSettlementWorkerLive = Layer.effectDiscard( | |
| 489 | const layerThreadSettlementWorker = Layer.effectDiscard( | |
| 511 | 490 | ThreadSettlementService.make.pipe(Effect.flatMap((service) => service.start())), |
| 512 | ).pipe(Layer.provide(PullRequestServiceLive), Layer.provide(ProjectionStoreV2.layer)); | |
| 491 | ).pipe(Layer.provide(layerPullRequestService), Layer.provide(ProjectionStoreV2.layer)); | |
| 513 | 492 | |
| 514 | const ThreadPullRequestWorkerLive = Layer.effectDiscard( | |
| 493 | const layerThreadPullRequestWorker = Layer.effectDiscard( | |
| 515 | 494 | ThreadPullRequestService.make.pipe(Effect.flatMap((service) => service.start())), |
| 516 | ).pipe(Layer.provide(PullRequestServiceLive)); | |
| 495 | ).pipe(Layer.provide(layerPullRequestService)); | |
| 517 | 496 | |
| 518 | const ProviderInstallationRefreshLive = Layer.effectDiscard( | |
| 497 | const layerProviderInstallationRefresh = Layer.effectDiscard( | |
| 519 | 498 | Effect.gen(function* () { |
| 520 | 499 | const antigravity = yield* AntigravityInstallation.AntigravityInstallation; |
| 521 | 500 | const codex = yield* CodexInstallation.CodexInstallation; |
| ... | ... | @@ -547,15 +526,15 @@ const ProviderInstallationRefreshLive = Layer.effectDiscard( |
| 547 | 526 | }), |
| 548 | 527 | ); |
| 549 | 528 | |
| 550 | const RuntimeCoreDependenciesBaseLive = Layer.mergeAll( | |
| 529 | const layerRuntimeCoreDependenciesBase = Layer.mergeAll( | |
| 551 | 530 | AgentAwarenessRelay.layer, |
| 552 | 531 | // Asks T3 Connect to deliver webhooks it held while this environment was offline. |
| 553 | 532 | HeldHooksWaker.layer, |
| 554 | ThreadSettlementWorkerLive, | |
| 533 | layerThreadSettlementWorker, | |
| 555 | 534 | Layer.effectDiscard(StorageCleanup.make.pipe(Effect.flatMap((service) => service.start()))).pipe( |
| 556 | 535 | Layer.provide(ProjectionStoreV2.layer), |
| 557 | 536 | ), |
| 558 | ThreadPullRequestWorkerLive, | |
| 537 | layerThreadPullRequestWorker, | |
| 559 | 538 | Layer.effectDiscard( |
| 560 | 539 | Effect.gen(function* () { |
| 561 | 540 | const service = yield* PullRequestSyncReactor.PullRequestSyncReactor; |
| ... | ... | @@ -563,7 +542,7 @@ const RuntimeCoreDependenciesBaseLive = Layer.mergeAll( |
| 563 | 542 | }), |
| 564 | 543 | ).pipe( |
| 565 | 544 | Layer.provideMerge(PullRequestSyncReactor.layer), |
| 566 | Layer.provide(PullRequestServiceLive), | |
| 545 | Layer.provide(layerPullRequestService), | |
| 567 | 546 | Layer.provide(ProjectionStoreV2.layer), |
| 568 | 547 | ), |
| 569 | 548 | Layer.effectDiscard( |
| ... | ... | @@ -573,38 +552,38 @@ const RuntimeCoreDependenciesBaseLive = Layer.mergeAll( |
| 573 | 552 | }), |
| 574 | 553 | ).pipe( |
| 575 | 554 | Layer.provide(PullRequestWatchReactor.layer), |
| 576 | Layer.provide(PullRequestServiceLive), | |
| 555 | Layer.provide(layerPullRequestService), | |
| 577 | 556 | Layer.provide(ProjectionStoreV2.layer), |
| 578 | 557 | ), |
| 579 | 558 | // Subscribes to `account.rate-limits.updated` so usage bars track live |
| 580 | 559 | // telemetry instead of waiting for the next status probe. |
| 581 | ProviderUsageLimitsIngestionLive, | |
| 582 | ProviderInstallationRefreshLive, | |
| 560 | ProviderUsageLimitsIngestion.layer, | |
| 561 | layerProviderInstallationRefresh, | |
| 583 | 562 | ReplayMarkers.layer, |
| 584 | 563 | ).pipe( |
| 585 | 564 | // Core Services |
| 586 | Layer.provideMerge(OrchestrationApplicationLayerLive), | |
| 587 | Layer.provideMerge(OrchestrationEventInfrastructureLayerLive), | |
| 565 | Layer.provideMerge(layerOrchestrationApplication), | |
| 566 | Layer.provideMerge(RuntimeLayer.layerEventInfrastructure), | |
| 588 | 567 | Layer.provideMerge(Layer.merge(ProjectStore.layer, ThreadSearch.layer)), |
| 589 | Layer.provideMerge(ServerSettingsLayerLive), | |
| 568 | Layer.provideMerge(layerServerSettings), | |
| 590 | 569 | // The asset route uses the registry's GitHub credential for private PR media. |
| 591 | Layer.provideMerge(Layer.mergeAll(SourceControlProviderRegistryLayerLive, GitHubCli.layer)), | |
| 592 | Layer.provideMerge(GitLayerLive), | |
| 593 | Layer.provideMerge(VcsLayerLive), | |
| 594 | Layer.provideMerge(Layer.mergeAll(TerminalLayerLive, PreviewLayerLive, DeviceLayerLive)), | |
| 595 | Layer.provideMerge(PersistenceLayerLive), | |
| 570 | Layer.provideMerge(Layer.mergeAll(layerSourceControlProviderRegistry, GitHubCli.layer)), | |
| 571 | Layer.provideMerge(layerGit), | |
| 572 | Layer.provideMerge(layerVcs), | |
| 573 | Layer.provideMerge(Layer.mergeAll(layerTerminal, layerPreview, layerDevice)), | |
| 574 | Layer.provideMerge(layerPersistence), | |
| 596 | 575 | // Both read a user-owned file out of the state directory and stream changes |
| 597 | 576 | // to clients; neither depends on the other. |
| 598 | 577 | Layer.provideMerge( |
| 599 | 578 | Layer.mergeAll(Keybindings.layer, EnvironmentTheme.layer, UsageLimitSources.layer), |
| 600 | 579 | ), |
| 601 | Layer.provideMerge(ProviderRegistryLive), | |
| 580 | Layer.provideMerge(ProviderRegistryLayer.layer), | |
| 602 | 581 | // The instance registry is the new routing keystone — text generation, |
| 603 | 582 | // adapter lookup, and runtime ingestion all resolve `ProviderInstanceId` |
| 604 | 583 | // through this layer. Built-in drivers come from `BUILT_IN_DRIVERS`; |
| 605 | 584 | // `providerInstances` hydration merges `settings.providers.<kind>` |
| 606 | 585 | // with explicit `providerInstances` entries on boot. |
| 607 | Layer.provideMerge(ProviderInstanceRegistryHydrationLive), | |
| 586 | Layer.provideMerge(ProviderInstanceRegistryHydration.layer), | |
| 608 | 587 | Layer.provideMerge( |
| 609 | 588 | Layer.mergeAll( |
| 610 | 589 | AntigravityInstallation.AntigravityInstallation.layer, |
| ... | ... | @@ -613,11 +592,11 @@ const RuntimeCoreDependenciesBaseLive = Layer.mergeAll( |
| 613 | 592 | ), |
| 614 | 593 | ); |
| 615 | 594 | |
| 616 | const RuntimeCoreDependenciesLive = RuntimeCoreDependenciesBaseLive.pipe( | |
| 617 | Layer.provideMerge(PtyAdapterLive), | |
| 595 | const layerRuntimeCoreDependencies = layerRuntimeCoreDependenciesBase.pipe( | |
| 596 | Layer.provideMerge(layerPtyAdapter), | |
| 618 | 597 | // Search, prepare, status inspection, and turn launch share one registry |
| 619 | 598 | // cache so every client and provider instance sees the same prepared agents. |
| 620 | Layer.provideMerge(AcpRegistryCatalogLive.pipe(Layer.provide(ServerSettingsLayerLive))), | |
| 599 | Layer.provideMerge(AcpRegistryCatalog.layer.pipe(Layer.provide(layerServerSettings))), | |
| 621 | 600 | // Shared native/canonical NDJSON writers used by both the per-instance |
| 622 | 601 | // V2 drivers and the orchestration runtime. Provide resource attribution so |
| 623 | 602 | // the rewritten telemetry pipeline can account for logical NDJSON writes. |
| ... | ... | @@ -634,15 +613,13 @@ const RuntimeCoreDependenciesLive = RuntimeCoreDependenciesBaseLive.pipe( |
| 634 | 613 | // the rewritten registry reads snapshots off the instance registry and |
| 635 | 614 | // no longer transitively provides it. Exposing it at the runtime level |
| 636 | 615 | // keeps a single Live for all opencode consumers. |
| 637 | Layer.provideMerge( | |
| 638 | OpenCodeRuntime.OpenCodeRuntimeLive.pipe(Layer.provide(OpenCodeServerLedger.layer)), | |
| 639 | ), | |
| 640 | Layer.provideMerge(WorkspaceLayerLive), | |
| 616 | Layer.provideMerge(OpenCodeRuntime.layer.pipe(Layer.provide(OpenCodeServerLedger.layer))), | |
| 617 | Layer.provideMerge(layerWorkspace), | |
| 641 | 618 | Layer.provideMerge(ProjectEnrichmentService.layer), |
| 642 | Layer.provideMerge(Layer.mergeAll(NativeAppIconResolver.layer, ProjectFaviconResolverLayerLive)), | |
| 643 | Layer.provideMerge(RepositoryIdentityResolverLayerLive), | |
| 644 | Layer.provideMerge(ServerEnvironmentLayerLive), | |
| 645 | Layer.provideMerge(AuthLayerLive), | |
| 619 | Layer.provideMerge(Layer.mergeAll(NativeAppIconResolver.layer, layerProjectFaviconResolver)), | |
| 620 | Layer.provideMerge(layerRepositoryIdentityResolver), | |
| 621 | Layer.provideMerge(layerServerEnvironment), | |
| 622 | Layer.provideMerge(layerAuth), | |
| 646 | 623 | Layer.provideMerge(ServerSecretStore.layer), |
| 647 | 624 | Layer.provideMerge( |
| 648 | 625 | Layer.mergeAll( |
| ... | ... | @@ -650,16 +627,16 @@ const RuntimeCoreDependenciesLive = RuntimeCoreDependenciesBaseLive.pipe( |
| 650 | 627 | Layer.provide(ServerSecretStore.layer), |
| 651 | 628 | Layer.provide(ExternalLauncher.layer), |
| 652 | 629 | ), |
| 653 | CloudManagedEndpointRuntimeLive, | |
| 630 | layerCloudManagedEndpointRuntime, | |
| 654 | 631 | ), |
| 655 | 632 | ), |
| 656 | 633 | ); |
| 657 | 634 | |
| 658 | const RuntimeDependenciesLive = RuntimeCoreDependenciesLive.pipe( | |
| 635 | const layerRuntimeDependencies = layerRuntimeCoreDependencies.pipe( | |
| 659 | 636 | // Misc. |
| 660 | Layer.provideMerge(BackgroundLayerLive), | |
| 661 | Layer.provideMerge(ResourceDiagnosticsLayerLive), | |
| 662 | Layer.provideMerge(UsageLayerLive), | |
| 637 | Layer.provideMerge(layerBackground), | |
| 638 | Layer.provideMerge(layerResourceDiagnostics), | |
| 639 | Layer.provideMerge(layerUsage), | |
| 663 | 640 | Layer.provideMerge(TraceDiagnostics.layer), |
| 664 | 641 | Layer.provideMerge(AnalyticsService.layer), |
| 665 | 642 | Layer.provideMerge(ExternalLauncher.layer), |
| ... | ... | @@ -669,7 +646,7 @@ const RuntimeDependenciesLive = RuntimeCoreDependenciesLive.pipe( |
| 669 | 646 | Layer.provide(NetService.layer), |
| 670 | 647 | ); |
| 671 | 648 | |
| 672 | const commandReadinessLayer = HttpRouter.middleware( | |
| 649 | const layerCommandReadiness = HttpRouter.middleware( | |
| 673 | 650 | (httpEffect) => |
| 674 | 651 | Effect.flatMap(ServerRuntimeStartup.ServerRuntimeStartup, (startup) => |
| 675 | 652 | startup.awaitCommandReady.pipe(Effect.orDie, Effect.andThen(httpEffect)), |
| ... | ... | @@ -677,24 +654,24 @@ const commandReadinessLayer = HttpRouter.middleware( |
| 677 | 654 | { global: true }, |
| 678 | 655 | ); |
| 679 | 656 | |
| 680 | const makeRoutesLayer = Layer.mergeAll( | |
| 657 | const layerMakeRoutes = Layer.mergeAll( | |
| 681 | 658 | Layer.mergeAll( |
| 682 | 659 | HttpApiBuilder.layer(EnvironmentHttpApi).pipe( |
| 683 | Layer.provide(authHttpApiLayer), | |
| 684 | Layer.provide(connectHttpApiLayer.pipe(Layer.provide(CloudPreferences.layer))), | |
| 685 | Layer.provide(orchestrationHttpApiLayer), | |
| 686 | Layer.provide(pullRequestHttpApiLayer), | |
| 687 | Layer.provide(projectHttpApiLayer), | |
| 688 | Layer.provide(serverEnvironmentHttpApiLayer), | |
| 689 | Layer.provide(webhookHttpApiLayer.pipe(Layer.provide(RelayDeliveryProof.layer))), | |
| 690 | Layer.provide(environmentAuthenticatedAuthLayer), | |
| 660 | Layer.provide(AuthHttp.layer), | |
| 661 | Layer.provide(CloudHttp.layer.pipe(Layer.provide(CloudPreferences.layer))), | |
| 662 | Layer.provide(OrchestrationHttp.layer), | |
| 663 | Layer.provide(PullRequestHttp.layer), | |
| 664 | Layer.provide(ProjectHttp.layer), | |
| 665 | Layer.provide(ServerHttp.layerServerEnvironmentHttpApi), | |
| 666 | Layer.provide(WebhookRoute.layer.pipe(Layer.provide(RelayDeliveryProof.layer))), | |
| 667 | Layer.provide(AuthHttp.layerAuthenticatedAuth), | |
| 691 | 668 | ), |
| 692 | otlpTracesProxyRouteLayer, | |
| 693 | assetRouteLayer, | |
| 694 | attachmentUploadRouteLayer, | |
| 695 | deviceHubProxyRouteLayer, | |
| 696 | staticAndDevRouteLayer, | |
| 697 | websocketRpcRouteLayer, | |
| 669 | ServerHttp.layerOtlpTracesProxyRoute, | |
| 670 | ServerHttp.layerAssetRoute, | |
| 671 | ServerHttp.layerAttachmentUploadRoute, | |
| 672 | DeviceHubProxy.layer, | |
| 673 | ServerHttp.layerStaticAndDevRoute, | |
| 674 | Ws.layer, | |
| 698 | 675 | ), |
| 699 | 676 | // The MCP session registry is provided globally (shared with V2 provider |
| 700 | 677 | // sessions) rather than inline here. The orchestrator toolkit resolves |
| ... | ... | @@ -707,36 +684,36 @@ const makeRoutesLayer = Layer.mergeAll( |
| 707 | 684 | ).pipe( |
| 708 | 685 | // Both transports consume the same service instance, so caches single-flight across clients |
| 709 | 686 | // and mutations observed on WebSocket invalidate patches subsequently read over HTTP. |
| 710 | Layer.provide(PullRequestServiceLive), | |
| 687 | Layer.provide(layerPullRequestService), | |
| 711 | 688 | Layer.provide(PreviewAutomationBroker.layer), |
| 712 | Layer.provide(ServerSelfUpdate.layer.pipe(Layer.provide(DesktopAppUpdateLayerLive))), | |
| 713 | Layer.provide(commandReadinessLayer), | |
| 714 | Layer.provide(browserApiCorsLayer), | |
| 715 | Layer.provide(httpCompressionLayer), | |
| 689 | Layer.provide(ServerSelfUpdate.layer.pipe(Layer.provide(layerDesktopAppUpdate))), | |
| 690 | Layer.provide(layerCommandReadiness), | |
| 691 | Layer.provide(ServerHttp.layerBrowserApiCors), | |
| 692 | Layer.provide(ServerHttp.layerHttpCompression), | |
| 716 | 693 | ); |
| 717 | 694 | |
| 718 | const makeServerLayer = Layer.unwrap( | |
| 695 | const layerMakeServer = Layer.unwrap( | |
| 719 | 696 | Effect.gen(function* () { |
| 720 | 697 | const config = yield* ServerConfig.ServerConfig; |
| 721 | 698 | const activation = yield* Deferred.make<void>(); |
| 722 | 699 | const awaitActivation = Deferred.await(activation); |
| 723 | const activationLayer = Layer.succeed(ServerActivation.ServerActivation, awaitActivation); | |
| 700 | const layerActivation = Layer.succeed(ServerActivation.ServerActivation, awaitActivation); | |
| 724 | 701 | const runtimeStateParked = yield* Deferred.make<void>(); |
| 725 | 702 | const tailscaleParked = yield* Deferred.make<void>(); |
| 726 | 703 | const cloudLinkParked = yield* Deferred.make<void>(); |
| 727 | 704 | const routesReady = yield* Deferred.make<void>(); |
| 728 | const launcherLayer = ServiceLauncherClient.layer; | |
| 705 | const layerLauncher = ServiceLauncherClient.layer; | |
| 729 | 706 | |
| 730 | 707 | yield* fixPath(); |
| 731 | 708 | |
| 732 | const httpListeningLayer = Layer.effectDiscard( | |
| 709 | const layerHttpListening = Layer.effectDiscard( | |
| 733 | 710 | Effect.gen(function* () { |
| 734 | 711 | yield* HttpServer.HttpServer; |
| 735 | 712 | const startup = yield* ServerRuntimeStartup.ServerRuntimeStartup; |
| 736 | 713 | yield* startup.markHttpListening; |
| 737 | 714 | }), |
| 738 | 715 | ); |
| 739 | const runtimeStateLayer = Layer.effectDiscard( | |
| 716 | const layerRuntimeState = Layer.effectDiscard( | |
| 740 | 717 | Effect.acquireRelease( |
| 741 | 718 | Effect.gen(function* () { |
| 742 | 719 | yield* Deferred.succeed(runtimeStateParked, undefined).pipe(Effect.orDie); |
| ... | ... | @@ -770,7 +747,7 @@ const makeServerLayer = Layer.unwrap( |
| 770 | 747 | ), |
| 771 | 748 | ), |
| 772 | 749 | ); |
| 773 | const tailscaleServeLayer = config.tailscaleServeEnabled | |
| 750 | const layerTailscaleServe = config.tailscaleServeEnabled | |
| 774 | 751 | ? Layer.effectDiscard( |
| 775 | 752 | Effect.acquireRelease( |
| 776 | 753 | Effect.gen(function* () { |
| ... | ... | @@ -823,7 +800,7 @@ const makeServerLayer = Layer.unwrap( |
| 823 | 800 | ), |
| 824 | 801 | ) |
| 825 | 802 | : Layer.empty; |
| 826 | const cloudDesiredLinkReconcileLayer = Layer.effectDiscard( | |
| 803 | const layerCloudDesiredLinkReconcile = Layer.effectDiscard( | |
| 827 | 804 | Effect.gen(function* () { |
| 828 | 805 | const releaseManagedTunnel = releaseManagedTunnelOnShutdown().pipe( |
| 829 | 806 | Effect.timeout("10 seconds"), |
| ... | ... | @@ -1037,7 +1014,7 @@ const makeServerLayer = Layer.unwrap( |
| 1037 | 1014 | }), |
| 1038 | 1015 | ); |
| 1039 | 1016 | |
| 1040 | const runtimeServicesLive = ServerRuntimeStartup.layerWithOptions({ | |
| 1017 | const layerRuntimeServices = ServerRuntimeStartup.layerWithOptions({ | |
| 1041 | 1018 | activate: Deferred.succeed(activation, undefined).pipe(Effect.asVoid), |
| 1042 | 1019 | abort: (error) => Deferred.die(activation, error).pipe(Effect.asVoid), |
| 1043 | 1020 | awaitAuxiliaryParked: Effect.all( |
| ... | ... | @@ -1049,42 +1026,42 @@ const makeServerLayer = Layer.unwrap( |
| 1049 | 1026 | ], |
| 1050 | 1027 | { concurrency: "unbounded" }, |
| 1051 | 1028 | ).pipe(Effect.asVoid), |
| 1052 | }).pipe(Layer.provideMerge(RuntimeDependenciesLive), Layer.provide(launcherLayer)); | |
| 1029 | }).pipe(Layer.provideMerge(layerRuntimeDependencies), Layer.provide(layerLauncher)); | |
| 1053 | 1030 | |
| 1054 | const routesLayer = HttpRouter.serve(makeRoutesLayer.pipe(Layer.provide(launcherLayer)), { | |
| 1031 | const layerRoutes = HttpRouter.serve(layerMakeRoutes.pipe(Layer.provide(layerLauncher)), { | |
| 1055 | 1032 | disableLogger: !config.logWebSocketEvents, |
| 1056 | 1033 | routerConfig: HTTP_ROUTER_CONFIG, |
| 1057 | 1034 | }).pipe( |
| 1058 | 1035 | withUntracedRequests, |
| 1059 | 1036 | Layer.tap(() => Deferred.succeed(routesReady, undefined).pipe(Effect.orDie)), |
| 1060 | 1037 | ); |
| 1061 | const serverApplicationLayer = Layer.mergeAll( | |
| 1062 | routesLayer, | |
| 1063 | httpListeningLayer, | |
| 1064 | runtimeStateLayer.pipe(Layer.provide(launcherLayer)), | |
| 1065 | tailscaleServeLayer, | |
| 1066 | cloudDesiredLinkReconcileLayer, | |
| 1038 | const layerServerApplication = Layer.mergeAll( | |
| 1039 | layerRoutes, | |
| 1040 | layerHttpListening, | |
| 1041 | layerRuntimeState.pipe(Layer.provide(layerLauncher)), | |
| 1042 | layerTailscaleServe, | |
| 1043 | layerCloudDesiredLinkReconcile, | |
| 1067 | 1044 | HeapSnapshot.layer, |
| 1068 | 1045 | ); |
| 1069 | 1046 | |
| 1070 | return serverApplicationLayer.pipe( | |
| 1071 | Layer.provideMerge(runtimeServicesLive), | |
| 1047 | return layerServerApplication.pipe( | |
| 1048 | Layer.provideMerge(layerRuntimeServices), | |
| 1072 | 1049 | Layer.provideMerge( |
| 1073 | 1050 | McpSessionRegistry.layer.pipe( |
| 1074 | 1051 | Layer.provide(ServerEnvironment.layer.pipe(Layer.provide(ServerSecretStore.layer))), |
| 1075 | 1052 | ), |
| 1076 | 1053 | ), |
| 1077 | Layer.provide(activationLayer), | |
| 1078 | Layer.provideMerge(serverRelayBrokerTracingLayer), | |
| 1079 | Layer.provideMerge(HttpServerLive), | |
| 1080 | Layer.provide(ApplicationObservabilityLive), | |
| 1054 | Layer.provide(layerActivation), | |
| 1055 | Layer.provideMerge(RelayTracing.layerServerRelayBroker), | |
| 1056 | Layer.provideMerge(layerHttpServer), | |
| 1057 | Layer.provide(layerApplicationObservability), | |
| 1081 | 1058 | Layer.provideMerge(FetchHttpClient.layer), |
| 1082 | 1059 | // PR reads, Git operations, and WebSocket discovery share one process limiter. |
| 1083 | 1060 | Layer.provide(VcsProcess.layer), |
| 1084 | Layer.provideMerge(PlatformServicesLive), | |
| 1061 | Layer.provideMerge(layerPlatformServices), | |
| 1085 | 1062 | ); |
| 1086 | 1063 | }), |
| 1087 | 1064 | ); |
| 1088 | 1065 | |
| 1089 | 1066 | // The CLI supplies configuration. |
| 1090 | export const runServer = Layer.launch(makeServerLayer); | |
| 1067 | export const runServer = Layer.launch(layerMakeServer); |
apps/server/src/serverLogger.test.ts+11-11| ... | ... | @@ -13,7 +13,7 @@ import { DEFAULT_SIGNAL_EXPORT } from "@t3tools/shared/observability"; |
| 13 | 13 | import * as OtelEnvironment from "@t3tools/shared/otelEnvironment"; |
| 14 | 14 | |
| 15 | 15 | import * as ServerConfig from "./config.ts"; |
| 16 | import { ServerLoggerLive } from "./serverLogger.ts"; | |
| 16 | import * as ServerLogger from "./serverLogger.ts"; | |
| 17 | 17 | |
| 18 | 18 | interface ExportedRequest { |
| 19 | 19 | readonly url: string; |
| ... | ... | @@ -22,7 +22,7 @@ interface ExportedRequest { |
| 22 | 22 | } |
| 23 | 23 | |
| 24 | 24 | /** Answers every export with a 200 and keeps what was posted for assertions. */ |
| 25 | const collectorLayer = (requests: Array<ExportedRequest>) => | |
| 25 | const layerCollector = (requests: Array<ExportedRequest>) => | |
| 26 | 26 | Layer.succeed( |
| 27 | 27 | HttpClient.HttpClient, |
| 28 | 28 | HttpClient.make((request) => |
| ... | ... | @@ -38,7 +38,7 @@ const collectorLayer = (requests: Array<ExportedRequest>) => |
| 38 | 38 | ), |
| 39 | 39 | ); |
| 40 | 40 | |
| 41 | const configLayer = (overrides: Partial<ServerConfig.ServerConfig["Service"]>) => | |
| 41 | const layerConfig = (overrides: Partial<ServerConfig.ServerConfig["Service"]>) => | |
| 42 | 42 | Layer.effect( |
| 43 | 43 | ServerConfig.ServerConfig, |
| 44 | 44 | Effect.gen(function* () { |
| ... | ... | @@ -93,9 +93,9 @@ const logThrough = (overrides: Partial<ServerConfig.ServerConfig["Service"]>) => |
| 93 | 93 | const requests: Array<ExportedRequest> = []; |
| 94 | 94 | yield* Effect.log("server logger under test").pipe( |
| 95 | 95 | Effect.provide( |
| 96 | ServerLoggerLive.pipe( | |
| 97 | Layer.provide(configLayer(overrides)), | |
| 98 | Layer.provide(collectorLayer(requests)), | |
| 96 | ServerLogger.layer.pipe( | |
| 97 | Layer.provide(layerConfig(overrides)), | |
| 98 | Layer.provide(layerCollector(requests)), | |
| 99 | 99 | ), |
| 100 | 100 | ), |
| 101 | 101 | ); |
| ... | ... | @@ -110,7 +110,7 @@ const logInSpanThrough = (overrides: Partial<ServerConfig.ServerConfig["Service" |
| 110 | 110 | Effect.gen(function* () { |
| 111 | 111 | const requests: Array<ExportedRequest> = []; |
| 112 | 112 | const spans: Array<Tracer.NativeSpan> = []; |
| 113 | const tracerLayer = Layer.succeed( | |
| 113 | const layerTracer = Layer.succeed( | |
| 114 | 114 | Tracer.Tracer, |
| 115 | 115 | Tracer.make({ |
| 116 | 116 | span: (spanOptions) => { |
| ... | ... | @@ -124,11 +124,11 @@ const logInSpanThrough = (overrides: Partial<ServerConfig.ServerConfig["Service" |
| 124 | 124 | Effect.withSpan("server-logger-test"), |
| 125 | 125 | Effect.provide( |
| 126 | 126 | Layer.mergeAll( |
| 127 | ServerLoggerLive.pipe( | |
| 128 | Layer.provide(configLayer(overrides)), | |
| 129 | Layer.provide(collectorLayer(requests)), | |
| 127 | ServerLogger.layer.pipe( | |
| 128 | Layer.provide(layerConfig(overrides)), | |
| 129 | Layer.provide(layerCollector(requests)), | |
| 130 | 130 | ), |
| 131 | tracerLayer, | |
| 131 | layerTracer, | |
| 132 | 132 | ), |
| 133 | 133 | ), |
| 134 | 134 | ); |
apps/server/src/serverLogger.ts+6-6| ... | ... | @@ -1,4 +1,4 @@ |
| 1 | import { otlpSerializationLayer } from "@t3tools/shared/observability"; | |
| 1 | import * as SharedObservability from "@t3tools/shared/observability"; | |
| 2 | 2 | import * as Effect from "effect/Effect"; |
| 3 | 3 | import * as Layer from "effect/Layer"; |
| 4 | 4 | import * as Logger from "effect/Logger"; |
| ... | ... | @@ -8,9 +8,9 @@ import * as OtlpLogger from "effect/observability/OtlpLogger"; |
| 8 | 8 | |
| 9 | 9 | import * as ServerConfig from "./config.ts"; |
| 10 | 10 | |
| 11 | export const ServerLoggerLive = Effect.gen(function* () { | |
| 11 | export const layer = Effect.gen(function* () { | |
| 12 | 12 | const config = yield* ServerConfig.ServerConfig; |
| 13 | const minimumLogLevelLayer = Layer.succeed(References.MinimumLogLevel, config.logLevel); | |
| 13 | const layerMinimumLogLevel = Layer.succeed(References.MinimumLogLevel, config.logLevel); | |
| 14 | 14 | |
| 15 | 15 | const logs = config.otlpLogsExport; |
| 16 | 16 | const otlpLogger = |
| ... | ... | @@ -35,15 +35,15 @@ export const ServerLoggerLive = Effect.gen(function* () { |
| 35 | 35 | // Recording events on spans is also the shape OpenTelemetry is deprecating, |
| 36 | 36 | // in favor of the log-based events this logger emits: |
| 37 | 37 | // https://opentelemetry.io/blog/2026/deprecating-span-events/ |
| 38 | const loggerLayer = Logger.layer( | |
| 38 | const layerLogger = Logger.layer( | |
| 39 | 39 | otlpLogger === undefined |
| 40 | 40 | ? [Logger.consolePretty(), Logger.tracerLogger] |
| 41 | 41 | : [Logger.consolePretty(), otlpLogger], |
| 42 | 42 | { mergeWithExisting: false }, |
| 43 | 43 | ).pipe( |
| 44 | 44 | Layer.provide(OtlpExporter.layerFlusher), |
| 45 | Layer.provide(otlpSerializationLayer(logs.protocol)), | |
| 45 | Layer.provide(SharedObservability.layerOtlpSerialization(logs.protocol)), | |
| 46 | 46 | ); |
| 47 | 47 | |
| 48 | return Layer.mergeAll(loggerLayer, minimumLogLevelLayer); | |
| 48 | return Layer.mergeAll(layerLogger, layerMinimumLogLevel); | |
| 49 | 49 | }).pipe(Layer.unwrap); |
apps/server/src/serverSettings.test.ts+71-71| ... | ... | @@ -27,7 +27,7 @@ import * as TestClock from "effect/testing/TestClock"; |
| 27 | 27 | import * as SqlClient from "effect/sql/SqlClient"; |
| 28 | 28 | import * as ServerSecretStore from "./auth/ServerSecretStore.ts"; |
| 29 | 29 | import * as ServerConfig from "./config.ts"; |
| 30 | import { SqlitePersistenceMemory } from "./persistence/Layers/Sqlite.ts"; | |
| 30 | import * as SqlitePersistence from "./persistence/Layers/Sqlite.ts"; | |
| 31 | 31 | import { writeFileStringAtomically } from "./atomicWrite.ts"; |
| 32 | 32 | import * as ServerSettingsModule from "./serverSettings.ts"; |
| 33 | 33 | import { resolveProviderInstanceTerminalEnvironment } from "./terminal/Manager.ts"; |
| ... | ... | @@ -36,10 +36,10 @@ const decodeSettingsPatch = Schema.decodeUnknownEffect(ServerSettingsPatch); |
| 36 | 36 | const decodeServerSettings = Schema.decodeUnknownEffect(ServerSettings); |
| 37 | 37 | const decodeServerSettingsJson = Schema.decodeUnknownEffect(Schema.fromJsonString(ServerSettings)); |
| 38 | 38 | |
| 39 | const makeServerSettingsLayer = () => | |
| 39 | const layerServerSettings = () => | |
| 40 | 40 | ServerSettingsModule.layer.pipe( |
| 41 | 41 | Layer.provide(ServerSecretStore.layer), |
| 42 | Layer.provideMerge(Layer.fresh(SqlitePersistenceMemory)), | |
| 42 | Layer.provideMerge(Layer.fresh(SqlitePersistence.layerMemory)), | |
| 43 | 43 | Layer.provideMerge( |
| 44 | 44 | Layer.fresh( |
| 45 | 45 | ServerConfig.layerTest(process.cwd(), { |
| ... | ... | @@ -50,10 +50,10 @@ const makeServerSettingsLayer = () => |
| 50 | 50 | ); |
| 51 | 51 | |
| 52 | 52 | /** Like `makeServerSettingsLayer`, but also exposes the secret store for assertions. */ |
| 53 | const makeServerSettingsLayerWithSecrets = () => | |
| 53 | const layerServerSettingsWithSecrets = () => | |
| 54 | 54 | ServerSettingsModule.layer.pipe( |
| 55 | 55 | Layer.provideMerge(ServerSecretStore.layer), |
| 56 | Layer.provideMerge(Layer.fresh(SqlitePersistenceMemory)), | |
| 56 | Layer.provideMerge(Layer.fresh(SqlitePersistence.layerMemory)), | |
| 57 | 57 | Layer.provideMerge( |
| 58 | 58 | Layer.fresh( |
| 59 | 59 | ServerConfig.layerTest(process.cwd(), { |
| ... | ... | @@ -63,7 +63,7 @@ const makeServerSettingsLayerWithSecrets = () => |
| 63 | 63 | ), |
| 64 | 64 | ); |
| 65 | 65 | |
| 66 | const makeFailingSecretStoreLayer = (cause: ServerSecretStore.SecretStoreError) => | |
| 66 | const layerFailingSecretStore = (cause: ServerSecretStore.SecretStoreError) => | |
| 67 | 67 | Layer.succeed( |
| 68 | 68 | ServerSecretStore.ServerSecretStore, |
| 69 | 69 | ServerSecretStore.ServerSecretStore.of({ |
| ... | ... | @@ -130,7 +130,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 130 | 130 | ); |
| 131 | 131 | assert.equal(persisted.responseStreamingMode, "turn"); |
| 132 | 132 | assert.deepEqual(persisted.projectSettingsOverrides, settings.projectSettingsOverrides); |
| 133 | }).pipe(Effect.provide(makeServerSettingsLayer())), | |
| 133 | }).pipe(Effect.provide(layerServerSettings())), | |
| 134 | 134 | ); |
| 135 | 135 | |
| 136 | 136 | it.effect("saves through a symlinked settings file without replacing the link", () => |
| ... | ... | @@ -152,7 +152,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 152 | 152 | yield* fs.readFileString(linkedSettingsPath), |
| 153 | 153 | ); |
| 154 | 154 | assert.equal(persisted.responseStreamingMode, "paragraph"); |
| 155 | }).pipe(Effect.provide(makeServerSettingsLayer())), | |
| 155 | }).pipe(Effect.provide(layerServerSettings())), | |
| 156 | 156 | ); |
| 157 | 157 | |
| 158 | 158 | it.effect("reloads when the destination of a symlinked settings file changes", () => |
| ... | ... | @@ -178,7 +178,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 178 | 178 | const change = yield* changes.pipe(Stream.runHead, Effect.timeout("2 seconds")); |
| 179 | 179 | assert.equal(Option.getOrUndefined(change)?.responseStreamingMode, "paragraph"); |
| 180 | 180 | }), |
| 181 | ).pipe(TestClock.withLive, Effect.provide(makeServerSettingsLayer())), | |
| 181 | ).pipe(TestClock.withLive, Effect.provide(layerServerSettings())), | |
| 182 | 182 | ); |
| 183 | 183 | |
| 184 | 184 | it.effect("follows a settings link that is repointed to another directory", () => |
| ... | ... | @@ -213,7 +213,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 213 | 213 | const edited = yield* editChanges.pipe(Stream.runHead, Effect.timeout("2 seconds")); |
| 214 | 214 | assert.equal(Option.getOrUndefined(edited)?.responseStreamingMode, "turn"); |
| 215 | 215 | }), |
| 216 | ).pipe(TestClock.withLive, Effect.provide(makeServerSettingsLayer())), | |
| 216 | ).pipe(TestClock.withLive, Effect.provide(layerServerSettings())), | |
| 217 | 217 | ); |
| 218 | 218 | |
| 219 | 219 | it.effect("reloads when a dangling settings link gets its destination", () => |
| ... | ... | @@ -238,7 +238,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 238 | 238 | const change = yield* changes.pipe(Stream.runHead, Effect.timeout("2 seconds")); |
| 239 | 239 | assert.equal(Option.getOrUndefined(change)?.responseStreamingMode, "paragraph"); |
| 240 | 240 | }), |
| 241 | ).pipe(TestClock.withLive, Effect.provide(makeServerSettingsLayer())), | |
| 241 | ).pipe(TestClock.withLive, Effect.provide(layerServerSettings())), | |
| 242 | 242 | ); |
| 243 | 243 | |
| 244 | 244 | it.effect("preserves context when reading a provider environment secret fails", () => { |
| ... | ... | @@ -253,15 +253,15 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 253 | 253 | resource: "provider environment secret", |
| 254 | 254 | cause: platformCause, |
| 255 | 255 | }); |
| 256 | const configLayer = Layer.fresh( | |
| 256 | const layerConfig = Layer.fresh( | |
| 257 | 257 | ServerConfig.layerTest(process.cwd(), { |
| 258 | 258 | prefix: "t3code-server-settings-secret-failure-test-", |
| 259 | 259 | }), |
| 260 | 260 | ); |
| 261 | const settingsLayer = ServerSettingsModule.layer.pipe( | |
| 262 | Layer.provide(makeFailingSecretStoreLayer(cause)), | |
| 263 | Layer.provideMerge(Layer.fresh(SqlitePersistenceMemory)), | |
| 264 | Layer.provideMerge(configLayer), | |
| 261 | const layerSettings = ServerSettingsModule.layer.pipe( | |
| 262 | Layer.provide(layerFailingSecretStore(cause)), | |
| 263 | Layer.provideMerge(Layer.fresh(SqlitePersistence.layerMemory)), | |
| 264 | Layer.provideMerge(layerConfig), | |
| 265 | 265 | ); |
| 266 | 266 | |
| 267 | 267 | return Effect.gen(function* () { |
| ... | ... | @@ -283,7 +283,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 283 | 283 | }); |
| 284 | 284 | assert.strictEqual(error.cause, cause); |
| 285 | 285 | assert.notInclude(error.message, cause.message); |
| 286 | }).pipe(Effect.provide(settingsLayer)); | |
| 286 | }).pipe(Effect.provide(layerSettings)); | |
| 287 | 287 | }); |
| 288 | 288 | |
| 289 | 289 | it.effect("identifies provider history query failures", () => |
| ... | ... | @@ -300,7 +300,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 300 | 300 | operation: "read-provider-history", |
| 301 | 301 | settingsPath: serverConfig.settingsPath, |
| 302 | 302 | }); |
| 303 | }).pipe(Effect.provide(makeServerSettingsLayer())), | |
| 303 | }).pipe(Effect.provide(layerServerSettings())), | |
| 304 | 304 | ); |
| 305 | 305 | |
| 306 | 306 | it.effect("decodes nested settings patches", () => |
| ... | ... | @@ -414,7 +414,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 414 | 414 | ], |
| 415 | 415 | ), |
| 416 | 416 | ); |
| 417 | }).pipe(Effect.provide(makeServerSettingsLayer())), | |
| 417 | }).pipe(Effect.provide(layerServerSettings())), | |
| 418 | 418 | ); |
| 419 | 419 | |
| 420 | 420 | it.effect("creates provider instances atomically without overwriting a concurrent add", () => |
| ... | ... | @@ -445,7 +445,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 445 | 445 | (yield* serverSettings.getSettings).providerInstances[instanceId]?.displayName ?? "", |
| 446 | 446 | ), |
| 447 | 447 | ); |
| 448 | }).pipe(Effect.provide(makeServerSettingsLayer())), | |
| 448 | }).pipe(Effect.provide(layerServerSettings())), | |
| 449 | 449 | ); |
| 450 | 450 | |
| 451 | 451 | it.effect("pauses provider-instance mutations while a settings snapshot is in use", () => |
| ... | ... | @@ -489,7 +489,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 489 | 489 | (yield* serverSettings.getSettings).providerInstances[instanceId]?.displayName, |
| 490 | 490 | "Kilo", |
| 491 | 491 | ); |
| 492 | }).pipe(Effect.provide(makeServerSettingsLayer())), | |
| 492 | }).pipe(Effect.provide(layerServerSettings())), | |
| 493 | 493 | ); |
| 494 | 494 | |
| 495 | 495 | it.effect("buffers changes after a subscription is acquired but before it is consumed", () => |
| ... | ... | @@ -512,7 +512,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 512 | 512 | "/usr/local/bin/codex-next", |
| 513 | 513 | ); |
| 514 | 514 | }), |
| 515 | ).pipe(Effect.provide(makeServerSettingsLayer())), | |
| 515 | ).pipe(Effect.provide(layerServerSettings())), | |
| 516 | 516 | ); |
| 517 | 517 | |
| 518 | 518 | it.effect("persists custom usage prices and removes them from the settings file", () => |
| ... | ... | @@ -538,7 +538,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 538 | 538 | const restored = yield* readPersisted; |
| 539 | 539 | assert.deepStrictEqual(restored.usagePriceOverrides, {}); |
| 540 | 540 | }), |
| 541 | ).pipe(Effect.provide(makeServerSettingsLayer())), | |
| 541 | ).pipe(Effect.provide(layerServerSettings())), | |
| 542 | 542 | ); |
| 543 | 543 | |
| 544 | 544 | it.effect("persists and broadcasts thread settlement settings", () => |
| ... | ... | @@ -566,7 +566,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 566 | 566 | assert.strictEqual(persisted.sidebarAutoSettleAfterDays, null); |
| 567 | 567 | assert.isFalse(persisted.sidebarAutoSettleOnMerge); |
| 568 | 568 | }), |
| 569 | ).pipe(Effect.provide(makeServerSettingsLayer())), | |
| 569 | ).pipe(Effect.provide(layerServerSettings())), | |
| 570 | 570 | ); |
| 571 | 571 | |
| 572 | 572 | it.effect("preserves model when switching providers via textGenerationModelSelection", () => |
| ... | ... | @@ -604,7 +604,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 604 | 604 | { id: "reasoningEffort", value: "high" }, |
| 605 | 605 | ]), |
| 606 | 606 | ); |
| 607 | }).pipe(Effect.provide(makeServerSettingsLayer())), | |
| 607 | }).pipe(Effect.provide(layerServerSettings())), | |
| 608 | 608 | ); |
| 609 | 609 | |
| 610 | 610 | it.effect("preserves custom provider instance text generation selections", () => |
| ... | ... | @@ -629,7 +629,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 629 | 629 | instanceId: ProviderInstanceId.make("claude_openrouter"), |
| 630 | 630 | model: "openai/gpt-5.5", |
| 631 | 631 | }); |
| 632 | }).pipe(Effect.provide(makeServerSettingsLayer())), | |
| 632 | }).pipe(Effect.provide(layerServerSettings())), | |
| 633 | 633 | ); |
| 634 | 634 | |
| 635 | 635 | it.effect( |
| ... | ... | @@ -662,7 +662,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 662 | 662 | instanceId, |
| 663 | 663 | model: "openai/gpt-5.5", |
| 664 | 664 | }); |
| 665 | }).pipe(Effect.provide(makeServerSettingsLayer())), | |
| 665 | }).pipe(Effect.provide(layerServerSettings())), | |
| 666 | 666 | ); |
| 667 | 667 | |
| 668 | 668 | it.effect("preserves enabled text generation selections for non-built-in drivers", () => |
| ... | ... | @@ -688,7 +688,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 688 | 688 | instanceId, |
| 689 | 689 | model: "openai/gpt-5.5", |
| 690 | 690 | }); |
| 691 | }).pipe(Effect.provide(makeServerSettingsLayer())), | |
| 691 | }).pipe(Effect.provide(layerServerSettings())), | |
| 692 | 692 | ); |
| 693 | 693 | |
| 694 | 694 | it.effect( |
| ... | ... | @@ -755,7 +755,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 755 | 755 | ServerSettingsModule.resolveSourceControlWriterModelSelection(restored), |
| 756 | 756 | sourceControlWriterModelSelection, |
| 757 | 757 | ); |
| 758 | }).pipe(Effect.provide(makeServerSettingsLayer())), | |
| 758 | }).pipe(Effect.provide(layerServerSettings())), | |
| 759 | 759 | ); |
| 760 | 760 | |
| 761 | 761 | it.effect("drops stale text generation options when resetting model selection", () => |
| ... | ... | @@ -788,7 +788,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 788 | 788 | instanceId: DEFAULT_SERVER_SETTINGS.textGenerationModelSelection.instanceId, |
| 789 | 789 | model: DEFAULT_SERVER_SETTINGS.textGenerationModelSelection.model, |
| 790 | 790 | }); |
| 791 | }).pipe(Effect.provide(makeServerSettingsLayer())), | |
| 791 | }).pipe(Effect.provide(layerServerSettings())), | |
| 792 | 792 | ); |
| 793 | 793 | |
| 794 | 794 | it.effect("replaces provider instance maps when clearing optional fields", () => |
| ... | ... | @@ -825,7 +825,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 825 | 825 | enabled: true, |
| 826 | 826 | config: { homePath: "~/.codex" }, |
| 827 | 827 | }); |
| 828 | }).pipe(Effect.provide(makeServerSettingsLayer())), | |
| 828 | }).pipe(Effect.provide(layerServerSettings())), | |
| 829 | 829 | ); |
| 830 | 830 | |
| 831 | 831 | it.effect("enables previously used providers from sparse settings files", () => |
| ... | ... | @@ -845,7 +845,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 845 | 845 | assert.isTrue(settings.providers.opencode.enabled); |
| 846 | 846 | assert.isFalse(settings.providers.cursor.enabled); |
| 847 | 847 | assert.equal(settings.providers.opencode.serverUrl, "http://127.0.0.1:4096"); |
| 848 | }).pipe(Effect.provide(makeServerSettingsLayer())), | |
| 848 | }).pipe(Effect.provide(layerServerSettings())), | |
| 849 | 849 | ); |
| 850 | 850 | |
| 851 | 851 | it.effect("preserves existing provider instances without explicit enabled flags", () => |
| ... | ... | @@ -870,7 +870,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 870 | 870 | const unused = settings.providerInstances[ProviderInstanceId.make("opencode_unused")]; |
| 871 | 871 | assert.isDefined(unused); |
| 872 | 872 | assert.isFalse(resolveProviderInstanceEnabled(unused)); |
| 873 | }).pipe(Effect.provide(makeServerSettingsLayer())), | |
| 873 | }).pipe(Effect.provide(layerServerSettings())), | |
| 874 | 874 | ); |
| 875 | 875 | |
| 876 | 876 | it.effect("preserves explicit provider disables in existing settings files", () => |
| ... | ... | @@ -894,7 +894,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 894 | 894 | assert.isFalse(settings.providerInstances[ProviderInstanceId.make("grok")]?.enabled); |
| 895 | 895 | assert.isFalse(settings.providerInstances[ProviderInstanceId.make("opencode")]?.enabled); |
| 896 | 896 | assert.isFalse(settings.providerInstances[ProviderInstanceId.make("cursor")]?.enabled); |
| 897 | }).pipe(Effect.provide(makeServerSettingsLayer())), | |
| 897 | }).pipe(Effect.provide(layerServerSettings())), | |
| 898 | 898 | ); |
| 899 | 899 | |
| 900 | 900 | it.effect("skips a disabled provider instance when picking the text generation fallback", () => |
| ... | ... | @@ -912,7 +912,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 912 | 912 | const settings = yield* serverSettings.getSettings; |
| 913 | 913 | |
| 914 | 914 | assert.equal(settings.textGenerationModelSelection.instanceId, "claudeAgent"); |
| 915 | }).pipe(Effect.provide(makeServerSettingsLayer())), | |
| 915 | }).pipe(Effect.provide(layerServerSettings())), | |
| 916 | 916 | ); |
| 917 | 917 | |
| 918 | 918 | it.effect("keeps unused providers disabled in existing sparse settings files", () => |
| ... | ... | @@ -927,7 +927,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 927 | 927 | assert.isFalse(settings.providers.grok.enabled); |
| 928 | 928 | assert.isFalse(settings.providers.opencode.enabled); |
| 929 | 929 | assert.isFalse(settings.providers.cursor.enabled); |
| 930 | }).pipe(Effect.provide(makeServerSettingsLayer())), | |
| 930 | }).pipe(Effect.provide(layerServerSettings())), | |
| 931 | 931 | ); |
| 932 | 932 | |
| 933 | 933 | it.effect("preserves provider history when no settings file exists", () => |
| ... | ... | @@ -940,7 +940,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 940 | 940 | assert.isTrue(settings.providers.grok.enabled); |
| 941 | 941 | assert.isFalse(settings.providers.opencode.enabled); |
| 942 | 942 | assert.isFalse(settings.providers.cursor.enabled); |
| 943 | }).pipe(Effect.provide(makeServerSettingsLayer())), | |
| 943 | }).pipe(Effect.provide(layerServerSettings())), | |
| 944 | 944 | ); |
| 945 | 945 | |
| 946 | 946 | it.effect("preserves provider history when the settings file is invalid", () => |
| ... | ... | @@ -956,7 +956,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 956 | 956 | assert.isTrue(settings.providers.cursor.enabled); |
| 957 | 957 | assert.isFalse(settings.providers.grok.enabled); |
| 958 | 958 | assert.isFalse(settings.providers.opencode.enabled); |
| 959 | }).pipe(Effect.provide(makeServerSettingsLayer())), | |
| 959 | }).pipe(Effect.provide(layerServerSettings())), | |
| 960 | 960 | ); |
| 961 | 961 | |
| 962 | 962 | it.effect("preserves valid provider flags when another settings field is invalid", () => |
| ... | ... | @@ -975,7 +975,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 975 | 975 | assert.isFalse(settings.providers.cursor.enabled); |
| 976 | 976 | assert.isTrue(settings.providers.grok.enabled); |
| 977 | 977 | assert.isFalse(settings.providers.opencode.enabled); |
| 978 | }).pipe(Effect.provide(makeServerSettingsLayer())), | |
| 978 | }).pipe(Effect.provide(layerServerSettings())), | |
| 979 | 979 | ); |
| 980 | 980 | |
| 981 | 981 | it.effect("restores providers from persisted runtime sessions", () => |
| ... | ... | @@ -1006,7 +1006,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 1006 | 1006 | assert.isFalse(settings.providers.grok.enabled); |
| 1007 | 1007 | assert.isTrue(settings.providers.opencode.enabled); |
| 1008 | 1008 | assert.isFalse(settings.providers.cursor.enabled); |
| 1009 | }).pipe(Effect.provide(makeServerSettingsLayer())), | |
| 1009 | }).pipe(Effect.provide(layerServerSettings())), | |
| 1010 | 1010 | ); |
| 1011 | 1011 | |
| 1012 | 1012 | it.effect("persists explicit disables after a provider has been used", () => |
| ... | ... | @@ -1026,7 +1026,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 1026 | 1026 | const raw = yield* fileSystem.readFileString(serverConfig.settingsPath); |
| 1027 | 1027 | // @effect-diagnostics-next-line preferSchemaOverJson:off |
| 1028 | 1028 | assert.isFalse(JSON.parse(raw).providers.grok.enabled); |
| 1029 | }).pipe(Effect.provide(makeServerSettingsLayer())), | |
| 1029 | }).pipe(Effect.provide(layerServerSettings())), | |
| 1030 | 1030 | ); |
| 1031 | 1031 | |
| 1032 | 1032 | it.effect("persists explicit provider enables before their first use", () => |
| ... | ... | @@ -1050,7 +1050,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 1050 | 1050 | assert.isTrue(persisted.providers.cursor.enabled); |
| 1051 | 1051 | assert.isTrue(persisted.providers.grok.enabled); |
| 1052 | 1052 | assert.isTrue(persisted.providers.opencode.enabled); |
| 1053 | }).pipe(Effect.provide(makeServerSettingsLayer())), | |
| 1053 | }).pipe(Effect.provide(layerServerSettings())), | |
| 1054 | 1054 | ); |
| 1055 | 1055 | |
| 1056 | 1056 | it.effect("keeps optional providers disabled after a new installation writes settings", () => |
| ... | ... | @@ -1088,7 +1088,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 1088 | 1088 | assert.isFalse(persisted.providers.grok.enabled); |
| 1089 | 1089 | assert.isFalse(persisted.providers.opencode.enabled); |
| 1090 | 1090 | assert.isUndefined(persisted.providerInstances.grok.enabled); |
| 1091 | }).pipe(Effect.provide(makeServerSettingsLayer())), | |
| 1091 | }).pipe(Effect.provide(layerServerSettings())), | |
| 1092 | 1092 | ); |
| 1093 | 1093 | |
| 1094 | 1094 | it.effect("folds a legacy in-config enabled flag into the envelope on load", () => |
| ... | ... | @@ -1124,7 +1124,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 1124 | 1124 | driver: ProviderDriverKind.make("cursor"), |
| 1125 | 1125 | config: { enabled: "nope" }, |
| 1126 | 1126 | }); |
| 1127 | }).pipe(Effect.provide(makeServerSettingsLayer())), | |
| 1127 | }).pipe(Effect.provide(layerServerSettings())), | |
| 1128 | 1128 | ); |
| 1129 | 1129 | |
| 1130 | 1130 | it.effect("folds in-config enabled flags arriving through updates", () => |
| ... | ... | @@ -1147,7 +1147,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 1147 | 1147 | enabled: false, |
| 1148 | 1148 | config: { binaryPath: "/opt/grok" }, |
| 1149 | 1149 | }); |
| 1150 | }).pipe(Effect.provide(makeServerSettingsLayer())), | |
| 1150 | }).pipe(Effect.provide(layerServerSettings())), | |
| 1151 | 1151 | ); |
| 1152 | 1152 | |
| 1153 | 1153 | it.effect("trims provider path settings when updates are applied", () => |
| ... | ... | @@ -1195,7 +1195,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 1195 | 1195 | serverPassword: "secret-password", |
| 1196 | 1196 | customModels: [], |
| 1197 | 1197 | }); |
| 1198 | }).pipe(Effect.provide(makeServerSettingsLayer())), | |
| 1198 | }).pipe(Effect.provide(layerServerSettings())), | |
| 1199 | 1199 | ); |
| 1200 | 1200 | |
| 1201 | 1201 | it.effect("trims observability settings when updates are applied", () => |
| ... | ... | @@ -1217,7 +1217,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 1217 | 1217 | otlpMetricsUrl: "http://localhost:4318/v1/metrics", |
| 1218 | 1218 | otlpLogsUrl: "http://localhost:4318/v1/logs", |
| 1219 | 1219 | }); |
| 1220 | }).pipe(Effect.provide(makeServerSettingsLayer())), | |
| 1220 | }).pipe(Effect.provide(layerServerSettings())), | |
| 1221 | 1221 | ); |
| 1222 | 1222 | |
| 1223 | 1223 | it.effect("defaults blank binary paths to provider executables", () => |
| ... | ... | @@ -1237,7 +1237,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 1237 | 1237 | |
| 1238 | 1238 | assert.equal(next.providers.codex.binaryPath, "codex"); |
| 1239 | 1239 | assert.equal(next.providers.claudeAgent.binaryPath, "claude"); |
| 1240 | }).pipe(Effect.provide(makeServerSettingsLayer())), | |
| 1240 | }).pipe(Effect.provide(layerServerSettings())), | |
| 1241 | 1241 | ); |
| 1242 | 1242 | |
| 1243 | 1243 | it.effect("writes non-default settings and explicit optional provider defaults to disk", () => |
| ... | ... | @@ -1299,7 +1299,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 1299 | 1299 | }, |
| 1300 | 1300 | automaticGitFetchInterval: 10_000, |
| 1301 | 1301 | }); |
| 1302 | }).pipe(Effect.provide(makeServerSettingsLayer())), | |
| 1302 | }).pipe(Effect.provide(layerServerSettings())), | |
| 1303 | 1303 | ); |
| 1304 | 1304 | |
| 1305 | 1305 | it.effect("keeps the inline value on disk when secret migration fails", () => { |
| ... | ... | @@ -1307,16 +1307,16 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 1307 | 1307 | resource: "provider environment secret", |
| 1308 | 1308 | cause: new Error("Secret storage unavailable"), |
| 1309 | 1309 | }); |
| 1310 | const secretLayer = Layer.effect( | |
| 1310 | const layerSecret = Layer.effect( | |
| 1311 | 1311 | ServerSecretStore.ServerSecretStore, |
| 1312 | 1312 | Effect.map(ServerSecretStore.ServerSecretStore, (store) => ({ |
| 1313 | 1313 | ...store, |
| 1314 | 1314 | set: () => Effect.fail(cause), |
| 1315 | 1315 | })), |
| 1316 | 1316 | ).pipe(Layer.provide(ServerSecretStore.layer)); |
| 1317 | const settingsLayer = ServerSettingsModule.layer.pipe( | |
| 1318 | Layer.provide(secretLayer), | |
| 1319 | Layer.provideMerge(Layer.fresh(SqlitePersistenceMemory)), | |
| 1317 | const layerSettings = ServerSettingsModule.layer.pipe( | |
| 1318 | Layer.provide(layerSecret), | |
| 1319 | Layer.provideMerge(Layer.fresh(SqlitePersistence.layerMemory)), | |
| 1320 | 1320 | Layer.provideMerge( |
| 1321 | 1321 | Layer.fresh( |
| 1322 | 1322 | ServerConfig.layerTest(process.cwd(), { |
| ... | ... | @@ -1352,7 +1352,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 1352 | 1352 | settings.providerInstances[instanceId]?.environment?.[0]?.value, |
| 1353 | 1353 | "inline-test-token", |
| 1354 | 1354 | ); |
| 1355 | }).pipe(Effect.provide(settingsLayer)); | |
| 1355 | }).pipe(Effect.provide(layerSettings)); | |
| 1356 | 1356 | }); |
| 1357 | 1357 | |
| 1358 | 1358 | it.effect.each( |
| ... | ... | @@ -1421,7 +1421,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 1421 | 1421 | ), |
| 1422 | 1422 | ); |
| 1423 | 1423 | assert.equal(reloaded.providerInstances[instanceId]?.environment?.[0]?.value, expected); |
| 1424 | }).pipe(Effect.provide(makeServerSettingsLayer())), | |
| 1424 | }).pipe(Effect.provide(layerServerSettings())), | |
| 1425 | 1425 | ); |
| 1426 | 1426 | |
| 1427 | 1427 | it.effect.each([true, false])( |
| ... | ... | @@ -1449,7 +1449,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 1449 | 1449 | next.providerInstances[instanceId]?.environment?.find((v) => v.sensitive)?.value, |
| 1450 | 1450 | sensitiveLast ? "secret-last" : "", |
| 1451 | 1451 | ); |
| 1452 | }).pipe(Effect.provide(makeServerSettingsLayer())), | |
| 1452 | }).pipe(Effect.provide(layerServerSettings())), | |
| 1453 | 1453 | ); |
| 1454 | 1454 | |
| 1455 | 1455 | it.effect("stores sensitive provider instance environment values outside settings.json", () => |
| ... | ... | @@ -1513,7 +1513,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 1513 | 1513 | roundTripped.providerInstances[instanceId]?.environment?.[0]?.value, |
| 1514 | 1514 | "sk-or-secret", |
| 1515 | 1515 | ); |
| 1516 | }).pipe(Effect.provide(makeServerSettingsLayer())), | |
| 1516 | }).pipe(Effect.provide(layerServerSettings())), | |
| 1517 | 1517 | ); |
| 1518 | 1518 | |
| 1519 | 1519 | it.effect( |
| ... | ... | @@ -1563,7 +1563,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 1563 | 1563 | ServerSettingsModule.redactServerSettingsForClient(cleared).bitbucket.accessToken, |
| 1564 | 1564 | "", |
| 1565 | 1565 | ); |
| 1566 | }).pipe(Effect.provide(makeServerSettingsLayerWithSecrets())), | |
| 1566 | }).pipe(Effect.provide(layerServerSettingsWithSecrets())), | |
| 1567 | 1567 | ); |
| 1568 | 1568 | |
| 1569 | 1569 | it.effect("removes a Bitbucket secret once its token is cleared by hand in settings.json", () => |
| ... | ... | @@ -1579,7 +1579,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 1579 | 1579 | yield* serverSettings.updateSettings({ cursorKeychainUsageEnabled: true }); |
| 1580 | 1580 | |
| 1581 | 1581 | assert.isTrue(Option.isNone(yield* secrets.get("bitbucket-access-token"))); |
| 1582 | }).pipe(Effect.provide(makeServerSettingsLayerWithSecrets())), | |
| 1582 | }).pipe(Effect.provide(layerServerSettingsWithSecrets())), | |
| 1583 | 1583 | ); |
| 1584 | 1584 | |
| 1585 | 1585 | it.effect("moves a hand-edited Bitbucket token into the secret store when settings load", () => |
| ... | ... | @@ -1606,7 +1606,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 1606 | 1606 | Option.isSome(stored) ? new TextDecoder().decode(stored.value) : null, |
| 1607 | 1607 | "hand-edited-token", |
| 1608 | 1608 | ); |
| 1609 | }).pipe(Effect.provide(makeServerSettingsLayerWithSecrets())), | |
| 1609 | }).pipe(Effect.provide(layerServerSettingsWithSecrets())), | |
| 1610 | 1610 | ); |
| 1611 | 1611 | |
| 1612 | 1612 | it.effect( |
| ... | ... | @@ -1635,7 +1635,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 1635 | 1635 | yield* fileSystem.readFileString(serverConfig.settingsPath), |
| 1636 | 1636 | "hand-edited-token", |
| 1637 | 1637 | ); |
| 1638 | }).pipe(Effect.provide(makeServerSettingsLayer())), | |
| 1638 | }).pipe(Effect.provide(layerServerSettings())), | |
| 1639 | 1639 | ); |
| 1640 | 1640 | |
| 1641 | 1641 | it.effect("materializes provider secrets for terminal environment resolution", () => |
| ... | ... | @@ -1670,7 +1670,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 1670 | 1670 | assert.match(environment.CODEX_HOME ?? "", /[\\/][.]codex-terminal$/); |
| 1671 | 1671 | assert.notInclude(persisted, "sk-terminal-secret"); |
| 1672 | 1672 | assert.include(persisted, '"valueRedacted": true'); |
| 1673 | }).pipe(Effect.provide(makeServerSettingsLayer())), | |
| 1673 | }).pipe(Effect.provide(layerServerSettings())), | |
| 1674 | 1674 | ); |
| 1675 | 1675 | it.effect("rolls back provider secret changes when the settings file commit fails", () => |
| 1676 | 1676 | Effect.gen(function* () { |
| ... | ... | @@ -1691,7 +1691,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 1691 | 1691 | : fileSystem.rename(fromPath, toPath), |
| 1692 | 1692 | }); |
| 1693 | 1693 | const instanceId = ProviderInstanceId.make("codex_write_failure"); |
| 1694 | const settingsLayer = makeServerSettingsLayer().pipe( | |
| 1694 | const layerSettings = layerServerSettings().pipe( | |
| 1695 | 1695 | Layer.provideMerge(Layer.succeed(FileSystem.FileSystem, failingFileSystem)), |
| 1696 | 1696 | ); |
| 1697 | 1697 | |
| ... | ... | @@ -1736,7 +1736,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 1736 | 1736 | ?.value, |
| 1737 | 1737 | "sk-kept", |
| 1738 | 1738 | ); |
| 1739 | }).pipe(Effect.provide(settingsLayer)); | |
| 1739 | }).pipe(Effect.provide(layerSettings)); | |
| 1740 | 1740 | }), |
| 1741 | 1741 | ); |
| 1742 | 1742 | |
| ... | ... | @@ -1746,7 +1746,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 1746 | 1746 | const textDecoder = new TextDecoder(); |
| 1747 | 1747 | const secrets = new Map<string, Uint8Array>(); |
| 1748 | 1748 | let rejectNewSecret = false; |
| 1749 | const secretStoreLayer = Layer.succeed( | |
| 1749 | const layerSecretStore = Layer.succeed( | |
| 1750 | 1750 | ServerSecretStore.ServerSecretStore, |
| 1751 | 1751 | ServerSecretStore.ServerSecretStore.of({ |
| 1752 | 1752 | get: (name) => |
| ... | ... | @@ -1799,9 +1799,9 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 1799 | 1799 | }), |
| 1800 | 1800 | }), |
| 1801 | 1801 | ); |
| 1802 | const settingsLayer = ServerSettingsModule.layer.pipe( | |
| 1803 | Layer.provideMerge(Layer.fresh(SqlitePersistenceMemory)), | |
| 1804 | Layer.provide(secretStoreLayer), | |
| 1802 | const layerSettings = ServerSettingsModule.layer.pipe( | |
| 1803 | Layer.provideMerge(Layer.fresh(SqlitePersistence.layerMemory)), | |
| 1804 | Layer.provide(layerSecretStore), | |
| 1805 | 1805 | Layer.provideMerge( |
| 1806 | 1806 | Layer.fresh( |
| 1807 | 1807 | ServerConfig.layerTest(process.cwd(), { |
| ... | ... | @@ -1844,7 +1844,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 1844 | 1844 | ?.value, |
| 1845 | 1845 | "sk-kept", |
| 1846 | 1846 | ); |
| 1847 | }).pipe(Effect.provide(settingsLayer)); | |
| 1847 | }).pipe(Effect.provide(layerSettings)); | |
| 1848 | 1848 | }, |
| 1849 | 1849 | ); |
| 1850 | 1850 | |
| ... | ... | @@ -1928,7 +1928,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 1928 | 1928 | ); |
| 1929 | 1929 | assert.isTrue(persisted.projectSettingsFolded); |
| 1930 | 1930 | assert.isUndefined(persisted.projectSettingsOverrides[legacyProject]); |
| 1931 | }).pipe(Effect.provide(makeServerSettingsLayer())), | |
| 1931 | }).pipe(Effect.provide(layerServerSettings())), | |
| 1932 | 1932 | ); |
| 1933 | 1933 | |
| 1934 | 1934 | it.effect("leaves an unreadable settings.json untouched instead of folding over it", () => |
| ... | ... | @@ -1954,6 +1954,6 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 1954 | 1954 | assert.deepEqual(settings.projectSettingsOverrides, {}); |
| 1955 | 1955 | // The user's file is still there to repair; nothing was written over it. |
| 1956 | 1956 | assert.equal(yield* fileSystem.readFileString(serverConfig.settingsPath), broken); |
| 1957 | }).pipe(Effect.provide(makeServerSettingsLayer())), | |
| 1957 | }).pipe(Effect.provide(layerServerSettings())), | |
| 1958 | 1958 | ); |
| 1959 | 1959 | }); |
apps/server/src/sourceControl/AzureDevOpsCli.test.ts+2-2| ... | ... | @@ -21,13 +21,13 @@ const processOutput = (stdout: string): VcsProcess.VcsProcessOutput => ({ |
| 21 | 21 | |
| 22 | 22 | const mockRun = vi.fn<VcsProcess.VcsProcess["Service"]["run"]>(); |
| 23 | 23 | |
| 24 | const supportLayer = Layer.mergeAll( | |
| 24 | const layerSupport = Layer.mergeAll( | |
| 25 | 25 | Layer.mock(VcsProcess.VcsProcess)({ |
| 26 | 26 | run: mockRun, |
| 27 | 27 | }), |
| 28 | 28 | NodeServices.layer, |
| 29 | 29 | ); |
| 30 | const layer = Layer.mergeAll(AzureDevOpsCli.layer.pipe(Layer.provide(supportLayer)), supportLayer); | |
| 30 | const layer = Layer.mergeAll(AzureDevOpsCli.layer.pipe(Layer.provide(layerSupport)), layerSupport); | |
| 31 | 31 | |
| 32 | 32 | afterEach(() => { |
| 33 | 33 | mockRun.mockReset(); |
apps/server/src/sourceControl/PrTemplateDetection.test.ts+2-2| ... | ... | @@ -27,7 +27,7 @@ const TEMPLATE_DIRECTORIES = [ |
| 27 | 27 | "docs/PULL_REQUEST_TEMPLATE", |
| 28 | 28 | ] as const; |
| 29 | 29 | |
| 30 | const PrTemplateDetectionTestLayer = GitVcsDriver.layer.pipe( | |
| 30 | const layerPrTemplateDetectionTest = GitVcsDriver.layer.pipe( | |
| 31 | 31 | Layer.provide( |
| 32 | 32 | ServerConfig.layerTest(process.cwd(), { |
| 33 | 33 | prefix: "t3-pr-template-test-", |
| ... | ... | @@ -59,7 +59,7 @@ const runWithTempDirectory = <A, E, R>( |
| 59 | 59 | yield* runGit(cwd, ["config", "user.name", "Test User"]); |
| 60 | 60 | return yield* test(cwd); |
| 61 | 61 | }), |
| 62 | ).pipe(Effect.provide(PrTemplateDetectionTestLayer)); | |
| 62 | ).pipe(Effect.provide(layerPrTemplateDetectionTest)); | |
| 63 | 63 | |
| 64 | 64 | const writeTemplate = (cwd: string, relativePath: string, contents: string) => |
| 65 | 65 | Effect.gen(function* () { |
apps/server/src/sourceControl/SourceControlDiscovery.test.ts+7-7| ... | ... | @@ -25,7 +25,7 @@ import * as SourceControlDiscovery from "./SourceControlDiscovery.ts"; |
| 25 | 25 | import * as SourceControlProviderRegistry from "./SourceControlProviderRegistry.ts"; |
| 26 | 26 | import { firstNonEmptyLine } from "./SourceControlProviderDiscovery.ts"; |
| 27 | 27 | |
| 28 | const sourceControlProviderRegistryTestLayer = (input: { | |
| 28 | const layerSourceControlProviderRegistryTest = (input: { | |
| 29 | 29 | readonly bitbucket: Partial<BitbucketApi.BitbucketApi["Service"]>; |
| 30 | 30 | readonly process: Partial<VcsProcess.VcsProcess["Service"]>; |
| 31 | 31 | }) => |
| ... | ... | @@ -530,7 +530,7 @@ it.effect("reports implemented tools separately from locally available executabl |
| 530 | 530 | ); |
| 531 | 531 | }, |
| 532 | 532 | } satisfies Partial<VcsProcess.VcsProcess["Service"]>; |
| 533 | const testLayer = SourceControlDiscovery.layer.pipe( | |
| 533 | const layerTest = SourceControlDiscovery.layer.pipe( | |
| 534 | 534 | Layer.provide( |
| 535 | 535 | ServerConfig.layerTest(process.cwd(), { |
| 536 | 536 | prefix: "t3-source-control-discovery-", |
| ... | ... | @@ -538,7 +538,7 @@ it.effect("reports implemented tools separately from locally available executabl |
| 538 | 538 | ), |
| 539 | 539 | Layer.provide(Layer.mock(VcsProcess.VcsProcess)(processMock)), |
| 540 | 540 | Layer.provide( |
| 541 | sourceControlProviderRegistryTestLayer({ | |
| 541 | layerSourceControlProviderRegistryTest({ | |
| 542 | 542 | process: processMock, |
| 543 | 543 | bitbucket: { |
| 544 | 544 | probeAuth: Effect.succeed({ |
| ... | ... | @@ -613,7 +613,7 @@ it.effect("reports implemented tools separately from locally available executabl |
| 613 | 613 | const bitbucket = result.sourceControlProviders.find((item) => item.kind === "bitbucket"); |
| 614 | 614 | assert.ok(bitbucket); |
| 615 | 615 | assert.strictEqual(bitbucket.executable, undefined); |
| 616 | }).pipe(Effect.provide(testLayer)); | |
| 616 | }).pipe(Effect.provide(layerTest)); | |
| 617 | 617 | }); |
| 618 | 618 | |
| 619 | 619 | it.effect("probes provider authentication without exposing token details", () => { |
| ... | ... | @@ -681,7 +681,7 @@ Logged in to gitlab.com as gitlab-user |
| 681 | 681 | ); |
| 682 | 682 | }, |
| 683 | 683 | } satisfies Partial<VcsProcess.VcsProcess["Service"]>; |
| 684 | const testLayer = SourceControlDiscovery.layer.pipe( | |
| 684 | const layerTest = SourceControlDiscovery.layer.pipe( | |
| 685 | 685 | Layer.provide( |
| 686 | 686 | ServerConfig.layerTest(process.cwd(), { |
| 687 | 687 | prefix: "t3-source-control-auth-discovery-", |
| ... | ... | @@ -689,7 +689,7 @@ Logged in to gitlab.com as gitlab-user |
| 689 | 689 | ), |
| 690 | 690 | Layer.provide(Layer.mock(VcsProcess.VcsProcess)(processMock)), |
| 691 | 691 | Layer.provide( |
| 692 | sourceControlProviderRegistryTestLayer({ | |
| 692 | layerSourceControlProviderRegistryTest({ | |
| 693 | 693 | process: processMock, |
| 694 | 694 | bitbucket: { |
| 695 | 695 | probeAuth: Effect.succeed({ |
| ... | ... | @@ -748,7 +748,7 @@ Logged in to gitlab.com as gitlab-user |
| 748 | 748 | }, |
| 749 | 749 | ], |
| 750 | 750 | ); |
| 751 | }).pipe(Effect.provide(testLayer)); | |
| 751 | }).pipe(Effect.provide(layerTest)); | |
| 752 | 752 | }); |
| 753 | 753 | |
| 754 | 754 | it.effect("discovers Forgejo accounts and retains the server port", () => |
apps/server/src/sourceControl/SourceControlProviderRegistry.test.ts+4-4| ... | ... | @@ -60,7 +60,7 @@ function makeRegistry(input: { |
| 60 | 60 | }), |
| 61 | 61 | } satisfies Partial<VcsDriver.VcsDriver["Service"]>; |
| 62 | 62 | |
| 63 | const registryLayer = Layer.mock(VcsDriverRegistry.VcsDriverRegistry)({ | |
| 63 | const layerRegistry = Layer.mock(VcsDriverRegistry.VcsDriverRegistry)({ | |
| 64 | 64 | get: () => Effect.succeed(driver as unknown as VcsDriver.VcsDriver["Service"]), |
| 65 | 65 | resolve: |
| 66 | 66 | input.resolve ?? |
| ... | ... | @@ -81,7 +81,7 @@ function makeRegistry(input: { |
| 81 | 81 | })), |
| 82 | 82 | }); |
| 83 | 83 | |
| 84 | const processLayer = Layer.mock(VcsProcess.VcsProcess)({ | |
| 84 | const layerProcess = Layer.mock(VcsProcess.VcsProcess)({ | |
| 85 | 85 | run: () => Effect.succeed(processOutput("")), |
| 86 | 86 | ...input.process, |
| 87 | 87 | }); |
| ... | ... | @@ -90,8 +90,8 @@ function makeRegistry(input: { |
| 90 | 90 | Effect.provide( |
| 91 | 91 | Layer.mergeAll( |
| 92 | 92 | NodeServices.layer, |
| 93 | registryLayer, | |
| 94 | processLayer, | |
| 93 | layerRegistry, | |
| 94 | layerProcess, | |
| 95 | 95 | Layer.mock(AzureDevOpsCli.AzureDevOpsCli)({}), |
| 96 | 96 | Layer.mock(BitbucketApi.BitbucketApi)({}), |
| 97 | 97 | Layer.mock(GitHubCli.GitHubCli)(input.github ?? {}), |
apps/server/src/sourceControl/SourceControlRepositoryService.test.ts+15-15| ... | ... | @@ -54,12 +54,12 @@ function processOutput(): GitVcsDriver.ExecuteGitResult { |
| 54 | 54 | }; |
| 55 | 55 | } |
| 56 | 56 | |
| 57 | function makeLayer(input: { | |
| 57 | function layer(input: { | |
| 58 | 58 | readonly provider?: SourceControlProvider.SourceControlProvider["Service"]; |
| 59 | 59 | readonly git?: Partial<GitVcsDriver.GitVcsDriver["Service"]>; |
| 60 | 60 | readonly fileSystem?: FileSystem.FileSystem; |
| 61 | 61 | }) { |
| 62 | const serviceLayer = SourceControlRepositoryService.layer.pipe( | |
| 62 | const layerService = SourceControlRepositoryService.layer.pipe( | |
| 63 | 63 | Layer.provide( |
| 64 | 64 | Layer.mock(SourceControlProviderRegistry.SourceControlProviderRegistry)({ |
| 65 | 65 | resolveLink: () => undefined, |
| ... | ... | @@ -89,11 +89,11 @@ function makeLayer(input: { |
| 89 | 89 | ); |
| 90 | 90 | |
| 91 | 91 | return input.fileSystem |
| 92 | ? serviceLayer.pipe( | |
| 92 | ? layerService.pipe( | |
| 93 | 93 | Layer.provide(Layer.succeed(FileSystem.FileSystem, input.fileSystem)), |
| 94 | 94 | Layer.provideMerge(NodePath.layer), |
| 95 | 95 | ) |
| 96 | : serviceLayer.pipe(Layer.provideMerge(NodeServices.layer)); | |
| 96 | : layerService.pipe(Layer.provideMerge(NodeServices.layer)); | |
| 97 | 97 | } |
| 98 | 98 | |
| 99 | 99 | it.effect("looks up repositories through the requested provider without search", () => { |
| ... | ... | @@ -116,7 +116,7 @@ it.effect("looks up repositories through the requested provider without search", |
| 116 | 116 | |
| 117 | 117 | assert.deepStrictEqual(result, { provider: "github", ...CLONE_URLS }); |
| 118 | 118 | assert.deepStrictEqual(calls, [{ cwd: "/workspace", repository: "octocat/t3code" }]); |
| 119 | }).pipe(Effect.provide(makeLayer({ provider }))); | |
| 119 | }).pipe(Effect.provide(layer({ provider }))); | |
| 120 | 120 | }); |
| 121 | 121 | |
| 122 | 122 | it.effect("preserves provider failures without deriving the repository message from them", () => { |
| ... | ... | @@ -149,7 +149,7 @@ it.effect("preserves provider failures without deriving the repository message f |
| 149 | 149 | "Source control repository operation lookupRepository failed for github: The source control operation could not be completed.", |
| 150 | 150 | ); |
| 151 | 151 | assert.strictEqual(error.cause, providerCause); |
| 152 | }).pipe(Effect.provide(makeLayer({ provider }))); | |
| 152 | }).pipe(Effect.provide(layer({ provider }))); | |
| 153 | 153 | }); |
| 154 | 154 | |
| 155 | 155 | it.effect("clones a looked-up repository into the requested destination", () => |
| ... | ... | @@ -184,7 +184,7 @@ it.effect("clones a looked-up repository into the requested destination", () => |
| 184 | 184 | ]); |
| 185 | 185 | }).pipe( |
| 186 | 186 | Effect.provide( |
| 187 | makeLayer({ | |
| 187 | layer({ | |
| 188 | 188 | git: { |
| 189 | 189 | execute: (input) => |
| 190 | 190 | Effect.sync(() => { |
| ... | ... | @@ -226,7 +226,7 @@ it.effect("reports clone progress from git's stderr and keeps its error text on |
| 226 | 226 | ); |
| 227 | 227 | }).pipe( |
| 228 | 228 | Effect.provide( |
| 229 | makeLayer({ | |
| 229 | layer({ | |
| 230 | 230 | git: { |
| 231 | 231 | execute: (input) => |
| 232 | 232 | Effect.gen(function* () { |
| ... | ... | @@ -274,7 +274,7 @@ it.effect("strips embedded credentials from the remote URL it reports", () => |
| 274 | 274 | }); |
| 275 | 275 | }).pipe( |
| 276 | 276 | Effect.provide( |
| 277 | makeLayer({ | |
| 277 | layer({ | |
| 278 | 278 | git: { |
| 279 | 279 | execute: (input) => |
| 280 | 280 | Effect.sync(() => { |
| ... | ... | @@ -316,7 +316,7 @@ it.effect("discards only a directory git wrote to", () => |
| 316 | 316 | assert.include(replacedError.detail, "could not be inspected"); |
| 317 | 317 | // A destination that never got created is nothing to discard. |
| 318 | 318 | yield* service.discardClone(path.join(parent, "missing")); |
| 319 | }).pipe(Effect.provide(makeLayer({}))); | |
| 319 | }).pipe(Effect.provide(layer({}))); | |
| 320 | 320 | |
| 321 | 321 | // The partial clone is emptied but its directory (the workspace root) stays. |
| 322 | 322 | assert.deepStrictEqual(yield* fs.readDirectory(partial), []); |
| ... | ... | @@ -342,7 +342,7 @@ it.effect("redacts query tokens and userinfo containing '@' from reported URLs", |
| 342 | 342 | destinationPath: path.join(parent, "b"), |
| 343 | 343 | }); |
| 344 | 344 | assert.equal(nested.remoteUrl, "https://github.com/octocat/t3code.git"); |
| 345 | }).pipe(Effect.provide(makeLayer({}))); | |
| 345 | }).pipe(Effect.provide(layer({}))); | |
| 346 | 346 | }).pipe(Effect.provide(NodeServices.layer)), |
| 347 | 347 | ); |
| 348 | 348 | |
| ... | ... | @@ -368,7 +368,7 @@ it.effect("preserves destination probe failures instead of treating them as miss |
| 368 | 368 | assert.strictEqual(error.cause, fileSystemCause); |
| 369 | 369 | }).pipe( |
| 370 | 370 | Effect.provide( |
| 371 | makeLayer({ | |
| 371 | layer({ | |
| 372 | 372 | fileSystem: FileSystem.makeNoop({ |
| 373 | 373 | exists: () => Effect.fail(fileSystemCause), |
| 374 | 374 | makeDirectory: () => Effect.void, |
| ... | ... | @@ -422,7 +422,7 @@ it.effect("publishes by creating the repository, adding a remote, and pushing up |
| 422 | 422 | assert.deepStrictEqual(pushCalls, [{ cwd: "/workspace", remoteName: "origin" }]); |
| 423 | 423 | }).pipe( |
| 424 | 424 | Effect.provide( |
| 425 | makeLayer({ | |
| 425 | layer({ | |
| 426 | 426 | provider, |
| 427 | 427 | git: { |
| 428 | 428 | ensureRemote: (input) => |
| ... | ... | @@ -464,7 +464,7 @@ it.effect("publishes to the remote name returned by ensureRemote", () => { |
| 464 | 464 | assert.deepStrictEqual(pushCalls, [{ cwd: "/workspace", remoteName: "origin-1" }]); |
| 465 | 465 | }).pipe( |
| 466 | 466 | Effect.provide( |
| 467 | makeLayer({ | |
| 467 | layer({ | |
| 468 | 468 | git: { |
| 469 | 469 | ensureRemote: () => Effect.succeed("origin-1"), |
| 470 | 470 | pushCurrentBranch: (cwd, _fallbackBranch, options) => |
| ... | ... | @@ -506,7 +506,7 @@ it.effect("publish succeeds with status remote_added when the local repo has no |
| 506 | 506 | assert.strictEqual(pushCalls, 0); |
| 507 | 507 | }).pipe( |
| 508 | 508 | Effect.provide( |
| 509 | makeLayer({ | |
| 509 | layer({ | |
| 510 | 510 | git: { |
| 511 | 511 | execute: (input) => |
| 512 | 512 | input.args[0] === "rev-parse" |
apps/server/src/telemetry/AnalyticsService.test.ts+20-20| ... | ... | @@ -61,7 +61,7 @@ const SentBatch = Schema.fromJsonString( |
| 61 | 61 | * before the response arrived. PostHog stores these batches, so the server |
| 62 | 62 | * must not send them forever. |
| 63 | 63 | */ |
| 64 | const acceptThenFailClient = (batches: Array<ReadonlyArray<{ readonly uuid: string }>>) => | |
| 64 | const layerAcceptThenFailClient = (batches: Array<ReadonlyArray<{ readonly uuid: string }>>) => | |
| 65 | 65 | Layer.succeed( |
| 66 | 66 | HttpClient.HttpClient, |
| 67 | 67 | HttpClient.make((request) => |
| ... | ... | @@ -90,7 +90,7 @@ it.layer(NodeServices.layer)("AnalyticsService test", (it) => { |
| 90 | 90 | it.effect("a batch that keeps failing is retried with backoff, then dropped", () => |
| 91 | 91 | Effect.gen(function* () { |
| 92 | 92 | const batches: Array<ReadonlyArray<{ readonly uuid: string }>> = []; |
| 93 | const runtimeLayer = AnalyticsService.layer.pipe( | |
| 93 | const layerRuntime = AnalyticsService.layer.pipe( | |
| 94 | 94 | Layer.provideMerge( |
| 95 | 95 | ServerConfig.ServerConfig.layerTest(process.cwd(), { prefix: "t3-telemetry-retry-" }), |
| 96 | 96 | ), |
| ... | ... | @@ -107,7 +107,7 @@ it.layer(NodeServices.layer)("AnalyticsService test", (it) => { |
| 107 | 107 | Layer.mergeAll( |
| 108 | 108 | Layer.succeed(HostProcessPlatform, "win32"), |
| 109 | 109 | Layer.succeed(HostProcessArchitecture, "x64"), |
| 110 | acceptThenFailClient(batches), | |
| 110 | layerAcceptThenFailClient(batches), | |
| 111 | 111 | ), |
| 112 | 112 | ), |
| 113 | 113 | ); |
| ... | ... | @@ -121,7 +121,7 @@ it.layer(NodeServices.layer)("AnalyticsService test", (it) => { |
| 121 | 121 | for (let second = 0; second < 600; second += 1) { |
| 122 | 122 | yield* TestClock.adjust("1 second"); |
| 123 | 123 | } |
| 124 | }).pipe(Effect.provide(runtimeLayer)); | |
| 124 | }).pipe(Effect.provide(layerRuntime)); | |
| 125 | 125 | |
| 126 | 126 | assert.equal(batches.length, 5); |
| 127 | 127 | const uuids = batches.map((batch) => batch.map((event) => event.uuid).join(",")); |
| ... | ... | @@ -133,12 +133,12 @@ it.layer(NodeServices.layer)("AnalyticsService test", (it) => { |
| 133 | 133 | it.effect("flush drains all buffered events across multiple batches", () => |
| 134 | 134 | Effect.gen(function* () { |
| 135 | 135 | const capturedRequests: Array<RecordedBatchRequest> = []; |
| 136 | const serverConfigLayer = ServerConfig.ServerConfig.layerTest(process.cwd(), { | |
| 136 | const layerServerConfig = ServerConfig.ServerConfig.layerTest(process.cwd(), { | |
| 137 | 137 | prefix: "t3-telemetry-base-", |
| 138 | 138 | }); |
| 139 | 139 | |
| 140 | const telemetryLayer = AnalyticsService.layer.pipe(Layer.provideMerge(serverConfigLayer)); | |
| 141 | const configLayer = ConfigProvider.layer( | |
| 140 | const layerTelemetry = AnalyticsService.layer.pipe(Layer.provideMerge(layerServerConfig)); | |
| 141 | const layerConfig = ConfigProvider.layer( | |
| 142 | 142 | ConfigProvider.fromUnknown({ |
| 143 | 143 | T3CODE_TELEMETRY_ENABLED: true, |
| 144 | 144 | T3CODE_POSTHOG_KEY: "phc_test_key", |
| ... | ... | @@ -146,7 +146,7 @@ it.layer(NodeServices.layer)("AnalyticsService test", (it) => { |
| 146 | 146 | T3CODE_TELEMETRY_FLUSH_BATCH_SIZE: 20, |
| 147 | 147 | }), |
| 148 | 148 | ); |
| 149 | const batchServerLayer = HttpServer.serve( | |
| 149 | const layerBatchServer = HttpServer.serve( | |
| 150 | 150 | Effect.gen(function* () { |
| 151 | 151 | const request = yield* HttpServerRequest.HttpServerRequest; |
| 152 | 152 | if (request.method !== "POST") { |
| ... | ... | @@ -163,8 +163,8 @@ it.layer(NodeServices.layer)("AnalyticsService test", (it) => { |
| 163 | 163 | return HttpServerResponse.jsonUnsafe({}); |
| 164 | 164 | }), |
| 165 | 165 | ); |
| 166 | const runtimeLayer = telemetryLayer.pipe( | |
| 167 | Layer.provide(configLayer), | |
| 166 | const layerRuntime = layerTelemetry.pipe( | |
| 167 | Layer.provide(layerConfig), | |
| 168 | 168 | Layer.provide( |
| 169 | 169 | Layer.mergeAll( |
| 170 | 170 | Layer.succeed(HostProcessPlatform, "linux"), |
| ... | ... | @@ -175,7 +175,7 @@ it.layer(NodeServices.layer)("AnalyticsService test", (it) => { |
| 175 | 175 | ); |
| 176 | 176 | |
| 177 | 177 | yield* Effect.gen(function* () { |
| 178 | yield* Layer.launch(batchServerLayer).pipe(Effect.forkScoped); | |
| 178 | yield* Layer.launch(layerBatchServer).pipe(Effect.forkScoped); | |
| 179 | 179 | const telemetryIdentifier = yield* getTelemetryIdentifier; |
| 180 | 180 | assert.equal(telemetryIdentifier !== null, true); |
| 181 | 181 | const analytics = yield* AnalyticsService.AnalyticsService; |
| ... | ... | @@ -185,7 +185,7 @@ it.layer(NodeServices.layer)("AnalyticsService test", (it) => { |
| 185 | 185 | } |
| 186 | 186 | |
| 187 | 187 | yield* analytics.flush; |
| 188 | }).pipe(Effect.provide(runtimeLayer)); | |
| 188 | }).pipe(Effect.provide(layerRuntime)); | |
| 189 | 189 | |
| 190 | 190 | const batchRequests = capturedRequests.filter( |
| 191 | 191 | (request): request is RecordedBatchRequest & { readonly body: RecordedBatchBody } => |
| ... | ... | @@ -235,26 +235,26 @@ it.layer(NodeServices.layer)("AnalyticsService test", (it) => { |
| 235 | 235 | it.effect("does not send batch requests when telemetry is disabled", () => |
| 236 | 236 | Effect.gen(function* () { |
| 237 | 237 | const capturedPaths: Array<string> = []; |
| 238 | const serverConfigLayer = ServerConfig.ServerConfig.layerTest(process.cwd(), { | |
| 238 | const layerServerConfig = ServerConfig.ServerConfig.layerTest(process.cwd(), { | |
| 239 | 239 | prefix: "t3-telemetry-disabled-", |
| 240 | 240 | }); |
| 241 | const telemetryLayer = AnalyticsService.layer.pipe(Layer.provideMerge(serverConfigLayer)); | |
| 242 | const configLayer = ConfigProvider.layer( | |
| 241 | const layerTelemetry = AnalyticsService.layer.pipe(Layer.provideMerge(layerServerConfig)); | |
| 242 | const layerConfig = ConfigProvider.layer( | |
| 243 | 243 | ConfigProvider.fromUnknown({ |
| 244 | 244 | T3CODE_TELEMETRY_ENABLED: false, |
| 245 | 245 | T3CODE_POSTHOG_KEY: "phc_test_key", |
| 246 | 246 | T3CODE_POSTHOG_HOST: "http://localhost", |
| 247 | 247 | }), |
| 248 | 248 | ); |
| 249 | const batchServerLayer = HttpServer.serve( | |
| 249 | const layerBatchServer = HttpServer.serve( | |
| 250 | 250 | Effect.gen(function* () { |
| 251 | 251 | const request = yield* HttpServerRequest.HttpServerRequest; |
| 252 | 252 | capturedPaths.push(request.url); |
| 253 | 253 | return HttpServerResponse.jsonUnsafe({}); |
| 254 | 254 | }), |
| 255 | 255 | ); |
| 256 | const runtimeLayer = telemetryLayer.pipe( | |
| 257 | Layer.provide(configLayer), | |
| 256 | const layerRuntime = layerTelemetry.pipe( | |
| 257 | Layer.provide(layerConfig), | |
| 258 | 258 | Layer.provide( |
| 259 | 259 | Layer.mergeAll( |
| 260 | 260 | Layer.succeed(HostProcessPlatform, "linux"), |
| ... | ... | @@ -265,11 +265,11 @@ it.layer(NodeServices.layer)("AnalyticsService test", (it) => { |
| 265 | 265 | ); |
| 266 | 266 | |
| 267 | 267 | yield* Effect.gen(function* () { |
| 268 | yield* Layer.launch(batchServerLayer).pipe(Effect.forkScoped); | |
| 268 | yield* Layer.launch(layerBatchServer).pipe(Effect.forkScoped); | |
| 269 | 269 | const analytics = yield* AnalyticsService.AnalyticsService; |
| 270 | 270 | yield* analytics.record("test.disabled", { index: 1 }); |
| 271 | 271 | yield* analytics.flush; |
| 272 | }).pipe(Effect.provide(runtimeLayer)); | |
| 272 | }).pipe(Effect.provide(layerRuntime)); | |
| 273 | 273 | |
| 274 | 274 | assert.deepEqual(capturedPaths, []); |
| 275 | 275 | }), |
apps/server/src/terminal/Manager.test.ts+15-12| ... | ... | @@ -37,7 +37,7 @@ import { expect } from "vite-plus/test"; |
| 37 | 37 | |
| 38 | 38 | import * as ServerSecretStore from "../auth/ServerSecretStore.ts"; |
| 39 | 39 | import * as ServerConfig from "../config.ts"; |
| 40 | import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts"; | |
| 40 | import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts"; | |
| 41 | 41 | import * as ProcessRunner from "../processRunner.ts"; |
| 42 | 42 | import * as ServerSettings from "../serverSettings.ts"; |
| 43 | 43 | import * as TerminalManager from "./Manager.ts"; |
| ... | ... | @@ -314,7 +314,7 @@ const createManager = ( |
| 314 | 314 | }), |
| 315 | 315 | ); |
| 316 | 316 | |
| 317 | const withHostPlatform = (platform: NodeJS.Platform) => | |
| 317 | const layerWithHostPlatform = (platform: NodeJS.Platform) => | |
| 318 | 318 | Layer.succeed(HostProcessPlatform, platform); |
| 319 | 319 | |
| 320 | 320 | // Apply the existing line policy, then find the longest code-point-aligned byte tail. |
| ... | ... | @@ -1153,7 +1153,7 @@ it.layer( |
| 1153 | 1153 | subprocessPollIntervalMs: 20, |
| 1154 | 1154 | }).pipe( |
| 1155 | 1155 | Effect.provideService(ProcessRunner.ProcessRunner, processRunner), |
| 1156 | Effect.provide(withHostPlatform("linux")), | |
| 1156 | Effect.provide(layerWithHostPlatform("linux")), | |
| 1157 | 1157 | ); |
| 1158 | 1158 | |
| 1159 | 1159 | yield* manager.open(openInput()); |
| ... | ... | @@ -1214,7 +1214,7 @@ it.layer( |
| 1214 | 1214 | subprocessPollIntervalMs: 20, |
| 1215 | 1215 | }).pipe( |
| 1216 | 1216 | Effect.provideService(ProcessRunner.ProcessRunner, processRunner), |
| 1217 | Effect.provide(withHostPlatform("linux")), | |
| 1217 | Effect.provide(layerWithHostPlatform("linux")), | |
| 1218 | 1218 | ); |
| 1219 | 1219 | |
| 1220 | 1220 | yield* manager.open(openInput()); |
| ... | ... | @@ -1259,7 +1259,7 @@ it.layer( |
| 1259 | 1259 | snapshotCalls += 1; |
| 1260 | 1260 | return [{ pid: 100, ppid: 9000, name: "ping.exe" }]; |
| 1261 | 1261 | }), |
| 1262 | }).pipe(Effect.provide(withHostPlatform("win32"))); | |
| 1262 | }).pipe(Effect.provide(layerWithHostPlatform("win32"))); | |
| 1263 | 1263 | |
| 1264 | 1264 | yield* manager.open(openInput()); |
| 1265 | 1265 | yield* waitFor( |
| ... | ... | @@ -1291,7 +1291,7 @@ it.layer( |
| 1291 | 1291 | { pid: 301, ppid: 300, name: "sleep" }, |
| 1292 | 1292 | { pid: 9003, ppid: 1, name: "zsh" }, |
| 1293 | 1293 | ]), |
| 1294 | }).pipe(Effect.provide(withHostPlatform("linux"))); | |
| 1294 | }).pipe(Effect.provide(layerWithHostPlatform("linux"))); | |
| 1295 | 1295 | yield* manager.open(openInput({ terminalId: "idle" })); |
| 1296 | 1296 | yield* manager.open(openInput({ terminalId: "dev-server" })); |
| 1297 | 1297 | yield* manager.open(openInput({ terminalId: "subshell" })); |
| ... | ... | @@ -1371,7 +1371,7 @@ it.layer( |
| 1371 | 1371 | ), |
| 1372 | 1372 | }).pipe( |
| 1373 | 1373 | Effect.provideService(ProcessRunner.ProcessRunner, processRunner), |
| 1374 | Effect.provide(withHostPlatform("linux")), | |
| 1374 | Effect.provide(layerWithHostPlatform("linux")), | |
| 1375 | 1375 | ); |
| 1376 | 1376 | |
| 1377 | 1377 | yield* manager.open(openInput()); |
| ... | ... | @@ -1869,7 +1869,7 @@ it.layer( |
| 1869 | 1869 | PATH: "C:\\Windows\\System32", |
| 1870 | 1870 | SystemRoot: "C:\\Windows", |
| 1871 | 1871 | }, |
| 1872 | }).pipe(Effect.provide(withHostPlatform("win32"))); | |
| 1872 | }).pipe(Effect.provide(layerWithHostPlatform("win32"))); | |
| 1873 | 1873 | |
| 1874 | 1874 | yield* manager.open(openInput()); |
| 1875 | 1875 | |
| ... | ... | @@ -1931,7 +1931,10 @@ it.layer( |
| 1931 | 1931 | }, |
| 1932 | 1932 | }).pipe( |
| 1933 | 1933 | Effect.provide( |
| 1934 | Layer.merge(withHostPlatform("win32"), Layer.succeed(HostProcessArchitecture, "x64")), | |
| 1934 | Layer.merge( | |
| 1935 | layerWithHostPlatform("win32"), | |
| 1936 | Layer.succeed(HostProcessArchitecture, "x64"), | |
| 1937 | ), | |
| 1935 | 1938 | ), |
| 1936 | 1939 | ); |
| 1937 | 1940 | |
| ... | ... | @@ -1954,7 +1957,7 @@ it.layer( |
| 1954 | 1957 | PATH: "C:\\Windows\\System32", |
| 1955 | 1958 | SystemRoot: "C:\\Windows", |
| 1956 | 1959 | }, |
| 1957 | }).pipe(Effect.provide(withHostPlatform("win32"))); | |
| 1960 | }).pipe(Effect.provide(layerWithHostPlatform("win32"))); | |
| 1958 | 1961 | ptyAdapter.spawnFailures.push( |
| 1959 | 1962 | new Error("spawn custom-shell.exe ENOENT"), |
| 1960 | 1963 | new Error("spawn pwsh.exe ENOENT"), |
| ... | ... | @@ -1988,7 +1991,7 @@ it.layer( |
| 1988 | 1991 | const { manager, ptyAdapter } = yield* createManager(5, { |
| 1989 | 1992 | shellResolver: () => "/bin/sh", |
| 1990 | 1993 | env, |
| 1991 | }).pipe(Effect.provide(withHostPlatform(platform))); | |
| 1994 | }).pipe(Effect.provide(layerWithHostPlatform(platform))); | |
| 1992 | 1995 | yield* manager.open( |
| 1993 | 1996 | openInput({ env: runtimeColor === undefined ? {} : { COLORTERM: runtimeColor } }), |
| 1994 | 1997 | ); |
| ... | ... | @@ -2447,7 +2450,7 @@ it.layer( |
| 2447 | 2450 | Effect.provide( |
| 2448 | 2451 | ServerSettings.layer.pipe( |
| 2449 | 2452 | Layer.provide(ServerSecretStore.layer), |
| 2450 | Layer.provide(SqlitePersistenceMemory), | |
| 2453 | Layer.provide(SqlitePersistence.layerMemory), | |
| 2451 | 2454 | Layer.provide( |
| 2452 | 2455 | ServerConfig.layerTest(process.cwd(), { prefix: "t3code-terminal-provider-restart-" }), |
| 2453 | 2456 | ), |
apps/server/src/terminal/NodePtyAdapter.test.ts+12-12| ... | ... | @@ -61,7 +61,7 @@ const spawnInput = { shell: "powershell.exe", cwd: ".", cols: 80, rows: 24, env: |
| 61 | 61 | |
| 62 | 62 | const fakeNodePty = { spawn } as unknown as typeof import("node-pty"); |
| 63 | 63 | |
| 64 | const makeTestLayer = (platform: NodeJS.Platform = "win32") => | |
| 64 | const layerTestFor = (platform: NodeJS.Platform = "win32") => | |
| 65 | 65 | NodePtyAdapter.layer.pipe( |
| 66 | 66 | Layer.provide( |
| 67 | 67 | Layer.mergeAll( |
| ... | ... | @@ -73,7 +73,7 @@ const makeTestLayer = (platform: NodeJS.Platform = "win32") => |
| 73 | 73 | ), |
| 74 | 74 | ); |
| 75 | 75 | |
| 76 | const testLayer = makeTestLayer(); | |
| 76 | const layerTest = layerTestFor(); | |
| 77 | 77 | |
| 78 | 78 | it.effect("waits for the Windows PID without requiring output", () => |
| 79 | 79 | Effect.gen(function* () { |
| ... | ... | @@ -107,7 +107,7 @@ it.effect("waits for the Windows PID without requiring output", () => |
| 107 | 107 | assert.deepEqual(exits, [{ exitCode: 0, signal: null }]); |
| 108 | 108 | stopData(); |
| 109 | 109 | stopExit(); |
| 110 | }).pipe(Effect.provide(testLayer)), | |
| 110 | }).pipe(Effect.provide(layerTest)), | |
| 111 | 111 | ); |
| 112 | 112 | |
| 113 | 113 | it.effect.each(["exit", "close", "error", "invalid-pid"] as const)( |
| ... | ... | @@ -129,7 +129,7 @@ it.effect.each(["exit", "close", "error", "invalid-pid"] as const)( |
| 129 | 129 | assert.equal(nativeProcess._socket.listenerCount("close"), 0); |
| 130 | 130 | assert.equal(nativeProcess.events.listenerCount("exit"), 0); |
| 131 | 131 | assert.equal(nativeProcess._agent.kill.mock.calls.length, 1); |
| 132 | }).pipe(Effect.provide(testLayer)), | |
| 132 | }).pipe(Effect.provide(layerTest)), | |
| 133 | 133 | ); |
| 134 | 134 | |
| 135 | 135 | it.effect("cancels the Windows connection without waiting for output", () => |
| ... | ... | @@ -143,7 +143,7 @@ it.effect("cancels the Windows connection without waiting for output", () => |
| 143 | 143 | assert.equal(nativeProcess.kill.mock.calls.length, 0); |
| 144 | 144 | assert.equal(nativeProcess._socket.listenerCount("ready_datapipe"), 0); |
| 145 | 145 | assert.equal(nativeProcess.events.listenerCount("exit"), 0); |
| 146 | }).pipe(Effect.provide(testLayer)), | |
| 146 | }).pipe(Effect.provide(layerTest)), | |
| 147 | 147 | ); |
| 148 | 148 | |
| 149 | 149 | it.effect("reports an incompatible Windows readiness API instead of hanging", () => |
| ... | ... | @@ -157,7 +157,7 @@ it.effect("reports an incompatible Windows readiness API instead of hanging", () |
| 157 | 157 | assert.instanceOf(error.cause, Error); |
| 158 | 158 | assert.equal(error.cause.message, "Windows PTY readiness socket is unavailable."); |
| 159 | 159 | assert.equal(nativeProcess._agent.kill.mock.calls.length, 1); |
| 160 | }).pipe(Effect.provide(testLayer)), | |
| 160 | }).pipe(Effect.provide(layerTest)), | |
| 161 | 161 | ); |
| 162 | 162 | |
| 163 | 163 | it.effect.each(["win32", "linux", "darwin"] as const)( |
| ... | ... | @@ -189,7 +189,7 @@ it.effect.each(["win32", "linux", "darwin"] as const)( |
| 189 | 189 | ? [[undefined], [undefined], [undefined]] |
| 190 | 190 | : [["SIGTERM"], ["SIGKILL"], [undefined]], |
| 191 | 191 | ); |
| 192 | }).pipe(Effect.provide(makeTestLayer(platform))), | |
| 192 | }).pipe(Effect.provide(layerTestFor(platform))), | |
| 193 | 193 | ); |
| 194 | 194 | |
| 195 | 195 | it.effect("spawns through the public adapter with the provided host references", () => |
| ... | ... | @@ -218,7 +218,7 @@ it.effect("spawns through the public adapter with the provided host references", |
| 218 | 218 | name: "xterm-256color", |
| 219 | 219 | }, |
| 220 | 220 | ]); |
| 221 | }).pipe(Effect.provide(testLayer)), | |
| 221 | }).pipe(Effect.provide(layerTest)), | |
| 222 | 222 | ); |
| 223 | 223 | |
| 224 | 224 | it.effect("preserves a caller-provided TERM in the spawn env on win32", () => |
| ... | ... | @@ -245,7 +245,7 @@ it.effect("preserves a caller-provided TERM in the spawn env on win32", () => |
| 245 | 245 | name: "xterm-256color", |
| 246 | 246 | }, |
| 247 | 247 | ]); |
| 248 | }).pipe(Effect.provide(testLayer)), | |
| 248 | }).pipe(Effect.provide(layerTest)), | |
| 249 | 249 | ); |
| 250 | 250 | |
| 251 | 251 | it.effect("reports native module load failures as structured startup defects", () => |
| ... | ... | @@ -300,7 +300,7 @@ it.effect.each([2048, 8])( |
| 300 | 300 | nativeProcess.events.emit("exit", { exitCode: 0 }); |
| 301 | 301 | yield* Fiber.join(fiber); |
| 302 | 302 | assert.equal(exits.length, 1); |
| 303 | }).pipe(Effect.provide(testLayer)), | |
| 303 | }).pipe(Effect.provide(layerTest)), | |
| 304 | 304 | ); |
| 305 | 305 | |
| 306 | 306 | it.effect("replays an exit to late subscribers and respects unsubscription", () => |
| ... | ... | @@ -317,7 +317,7 @@ it.effect("replays an exit to late subscribers and respects unsubscription", () |
| 317 | 317 | assert.equal(removed.mock.calls.length, 0); |
| 318 | 318 | assert.deepEqual(late.mock.calls, [[{ exitCode: 7, signal: 2 }]]); |
| 319 | 319 | assert.equal(nativeProcess.events.listenerCount("exit"), 0); |
| 320 | }).pipe(Effect.provide(testLayer)), | |
| 320 | }).pipe(Effect.provide(layerTest)), | |
| 321 | 321 | ); |
| 322 | 322 | |
| 323 | 323 | it.effect.each(["spawn", "interrupt"] as const)( |
| ... | ... | @@ -361,5 +361,5 @@ it.effect.each(["spawn", "interrupt"] as const)( |
| 361 | 361 | ]); |
| 362 | 362 | assert.equal(nativeProcess.events.listenerCount("exit"), 0); |
| 363 | 363 | assert.equal(nativeProcess._socket.listenerCount("ready_datapipe"), 0); |
| 364 | }).pipe(Effect.provide(testLayer)), | |
| 364 | }).pipe(Effect.provide(layerTest)), | |
| 365 | 365 | ); |
apps/server/src/textGeneration/ClaudeTextGeneration.test.ts+2-2| ... | ... | @@ -24,7 +24,7 @@ import { makeClaudeTextGeneration } from "./ClaudeTextGeneration.ts"; |
| 24 | 24 | import { writeFakeCli } from "../testUtils/fakeCli.ts"; |
| 25 | 25 | const decodeClaudeSettings = Schema.decodeSync(ClaudeSettings); |
| 26 | 26 | |
| 27 | const ClaudeTextGenerationTestLayer = ServerConfig.ServerConfig.layerTest(process.cwd(), { | |
| 27 | const layerClaudeTextGenerationTest = ServerConfig.ServerConfig.layerTest(process.cwd(), { | |
| 28 | 28 | prefix: "t3code-claude-text-generation-test-", |
| 29 | 29 | }).pipe(Layer.provideMerge(NodeServices.layer)); |
| 30 | 30 | |
| ... | ... | @@ -191,7 +191,7 @@ function withFakeClaudeEnv<A, E, R>( |
| 191 | 191 | }).pipe(Effect.scoped); |
| 192 | 192 | } |
| 193 | 193 | |
| 194 | it.layer(ClaudeTextGenerationTestLayer)("ClaudeTextGeneration", (it) => { | |
| 194 | it.layer(layerClaudeTextGenerationTest)("ClaudeTextGeneration", (it) => { | |
| 195 | 195 | it.effect("forwards Claude thinking settings without passing unsupported effort", () => |
| 196 | 196 | withFakeClaudeEnv( |
| 197 | 197 | { |
apps/server/src/textGeneration/CodexTextGeneration.test.ts+2-2| ... | ... | @@ -24,7 +24,7 @@ const DEFAULT_TEST_MODEL_SELECTION = createModelSelection( |
| 24 | 24 | "gpt-5.4-mini", |
| 25 | 25 | ); |
| 26 | 26 | |
| 27 | const CodexTextGenerationTestLayer = ServerConfig.ServerConfig.layerTest(process.cwd(), { | |
| 27 | const layerCodexTextGenerationTest = ServerConfig.ServerConfig.layerTest(process.cwd(), { | |
| 28 | 28 | prefix: "t3code-codex-text-generation-test-", |
| 29 | 29 | }).pipe(Layer.provideMerge(NodeServices.layer)); |
| 30 | 30 | |
| ... | ... | @@ -169,7 +169,7 @@ function withFakeCodexEnv<A, E, R>( |
| 169 | 169 | }).pipe(Effect.scoped); |
| 170 | 170 | } |
| 171 | 171 | |
| 172 | it.layer(CodexTextGenerationTestLayer)("CodexTextGeneration", (it) => { | |
| 172 | it.layer(layerCodexTextGenerationTest)("CodexTextGeneration", (it) => { | |
| 173 | 173 | it.effect.each(["gpt-5.6-luna", "openai.gpt-5.6-luna"])( |
| 174 | 174 | "dispatches the qualified live model for %s", |
| 175 | 175 | (selectedModel) => |
apps/server/src/textGeneration/CursorTextGeneration.test.ts+12-12| ... | ... | @@ -27,7 +27,7 @@ const cursorSdkMock = vi.hoisted(() => ({ |
| 27 | 27 | vi.mock("../provider/cursorSdk.ts", () => ({ Agent: { create: cursorSdkMock.create } })); |
| 28 | 28 | |
| 29 | 29 | let hasCustomPolicy = false; |
| 30 | const fsLayer = FileSystem.layerNoop({ | |
| 30 | const layerFs = FileSystem.layerNoop({ | |
| 31 | 31 | exists: () => Effect.succeed(hasCustomPolicy), |
| 32 | 32 | makeTempDirectoryScoped: () => Effect.succeed("/isolated-text-generation"), |
| 33 | 33 | }); |
| ... | ... | @@ -89,7 +89,7 @@ describe("CursorTextGeneration", () => { |
| 89 | 89 | expect(cursorSdkMock.create).toHaveBeenLastCalledWith( |
| 90 | 90 | expect.objectContaining({ apiKey: "second-browser-key" }), |
| 91 | 91 | ); |
| 92 | }).pipe(Effect.provide(fsLayer)), | |
| 92 | }).pipe(Effect.provide(layerFs)), | |
| 93 | 93 | ); |
| 94 | 94 | |
| 95 | 95 | it.effect("uses the Cursor SDK prompt API with model parameters and API key", () => |
| ... | ... | @@ -138,7 +138,7 @@ describe("CursorTextGeneration", () => { |
| 138 | 138 | enableAgentRetries: true, |
| 139 | 139 | }, |
| 140 | 140 | }); |
| 141 | }).pipe(Effect.provide(fsLayer)), | |
| 141 | }).pipe(Effect.provide(layerFs)), | |
| 142 | 142 | ); |
| 143 | 143 | |
| 144 | 144 | it.effect("continues in the temp directory when the SDK cannot sandbox", () => |
| ... | ... | @@ -174,7 +174,7 @@ describe("CursorTextGeneration", () => { |
| 174 | 174 | enableAgentRetries: true, |
| 175 | 175 | }, |
| 176 | 176 | }); |
| 177 | }).pipe(Effect.provide(fsLayer)), | |
| 177 | }).pipe(Effect.provide(layerFs)), | |
| 178 | 178 | ); |
| 179 | 179 | |
| 180 | 180 | it.effect("does not retry Agent.create for errors other than an unsupported sandbox", () => |
| ... | ... | @@ -198,7 +198,7 @@ describe("CursorTextGeneration", () => { |
| 198 | 198 | |
| 199 | 199 | expect(error.detail).toBe("Cursor SDK text generation failed."); |
| 200 | 200 | expect(cursorSdkMock.create).toHaveBeenCalledTimes(1); |
| 201 | }).pipe(Effect.provide(fsLayer)), | |
| 201 | }).pipe(Effect.provide(layerFs)), | |
| 202 | 202 | ); |
| 203 | 203 | |
| 204 | 204 | it.effect("accepts json objects with extra assistant text around them", () => |
| ... | ... | @@ -226,7 +226,7 @@ describe("CursorTextGeneration", () => { |
| 226 | 226 | |
| 227 | 227 | expect(generated.subject).toBe("Update README dummy comment with attribution and date"); |
| 228 | 228 | expect(generated.body).toBe(""); |
| 229 | }).pipe(Effect.provide(fsLayer)), | |
| 229 | }).pipe(Effect.provide(layerFs)), | |
| 230 | 230 | ); |
| 231 | 231 | |
| 232 | 232 | it.effect("generates thread titles through Cursor SDK text generation", () => |
| ... | ... | @@ -250,7 +250,7 @@ describe("CursorTextGeneration", () => { |
| 250 | 250 | }); |
| 251 | 251 | |
| 252 | 252 | expect(generated.title).toBe("Trim reconnect spinner status after resume."); |
| 253 | }).pipe(Effect.provide(fsLayer)), | |
| 253 | }).pipe(Effect.provide(layerFs)), | |
| 254 | 254 | ); |
| 255 | 255 | |
| 256 | 256 | it.effect.each(["error", "cancelled"] as const)( |
| ... | ... | @@ -284,7 +284,7 @@ describe("CursorTextGeneration", () => { |
| 284 | 284 | : "Cursor SDK request finished with an error.", |
| 285 | 285 | ); |
| 286 | 286 | expect(cursorSdkMock.close).toHaveBeenCalledOnce(); |
| 287 | }).pipe(Effect.provide(fsLayer)), | |
| 287 | }).pipe(Effect.provide(layerFs)), | |
| 288 | 288 | ); |
| 289 | 289 | |
| 290 | 290 | it.effect("fails closed when ambient sandbox policy can expand write access", () => |
| ... | ... | @@ -300,7 +300,7 @@ describe("CursorTextGeneration", () => { |
| 300 | 300 | ); |
| 301 | 301 | expect(failure.detail).toContain("custom ~/.cursor/sandbox.json"); |
| 302 | 302 | expect(cursorSdkMock.prompt).not.toHaveBeenCalled(); |
| 303 | }).pipe(Effect.provide(fsLayer)), | |
| 303 | }).pipe(Effect.provide(layerFs)), | |
| 304 | 304 | ); |
| 305 | 305 | |
| 306 | 306 | it.effect("cancels the native run when text generation times out", () => |
| ... | ... | @@ -326,7 +326,7 @@ describe("CursorTextGeneration", () => { |
| 326 | 326 | expect((yield* Fiber.join(result)).detail).toContain("timed out"); |
| 327 | 327 | expect(cursorSdkMock.cancel).toHaveBeenCalledOnce(); |
| 328 | 328 | expect(cursorSdkMock.close).toHaveBeenCalledOnce(); |
| 329 | }).pipe(Effect.provide(fsLayer), Effect.scoped), | |
| 329 | }).pipe(Effect.provide(layerFs), Effect.scoped), | |
| 330 | 330 | ); |
| 331 | 331 | |
| 332 | 332 | it.effect.each(["create", "send"] as const)( |
| ... | ... | @@ -388,7 +388,7 @@ describe("CursorTextGeneration", () => { |
| 388 | 388 | expect(cursorSdkMock.close).toHaveBeenCalledOnce(); |
| 389 | 389 | expect(wait).not.toHaveBeenCalled(); |
| 390 | 390 | if (phase === "send") expect(cursorSdkMock.cancel).toHaveBeenCalledOnce(); |
| 391 | }).pipe(Effect.provide(fsLayer), Effect.scoped), | |
| 391 | }).pipe(Effect.provide(layerFs), Effect.scoped), | |
| 392 | 392 | ); |
| 393 | 393 | |
| 394 | 394 | it.effect("requires CURSOR_API_KEY before calling the SDK", () => |
| ... | ... | @@ -410,6 +410,6 @@ describe("CursorTextGeneration", () => { |
| 410 | 410 | |
| 411 | 411 | expect(error.detail).toBe("Sign in with Cursor or add CURSOR_API_KEY in provider settings."); |
| 412 | 412 | expect(cursorSdkMock.prompt).not.toHaveBeenCalled(); |
| 413 | }).pipe(Effect.provide(fsLayer)), | |
| 413 | }).pipe(Effect.provide(layerFs)), | |
| 414 | 414 | ); |
| 415 | 415 | }); |
apps/server/src/textGeneration/GrokTextGeneration.test.ts+2-2| ... | ... | @@ -22,7 +22,7 @@ const decodeGrokSettings = Schema.decodeSync(GrokSettings); |
| 22 | 22 | const __dirname = NodePath.dirname(NodeURL.fileURLToPath(import.meta.url)); |
| 23 | 23 | const mockAgentPath = NodePath.join(__dirname, "../provider/testFixtures/grok-text-mock-agent.mjs"); |
| 24 | 24 | |
| 25 | const GrokTextGenerationTestLayer = ServerConfig.ServerConfig.layerTest(process.cwd(), { | |
| 25 | const layerGrokTextGenerationTest = ServerConfig.ServerConfig.layerTest(process.cwd(), { | |
| 26 | 26 | prefix: "t3code-grok-text-generation-test-", |
| 27 | 27 | }).pipe(Layer.provideMerge(NodeServices.layer)); |
| 28 | 28 | |
| ... | ... | @@ -66,7 +66,7 @@ function readJsonRpcRequests( |
| 66 | 66 | .map((line) => JSON.parse(line) as { method?: string; params?: Record<string, unknown> }); |
| 67 | 67 | } |
| 68 | 68 | |
| 69 | it.layer(GrokTextGenerationTestLayer)("GrokTextGeneration", (it) => { | |
| 69 | it.layer(layerGrokTextGenerationTest)("GrokTextGeneration", (it) => { | |
| 70 | 70 | it.effect("uses ACP with disabled tool capabilities and forwards the requested model id", () => { |
| 71 | 71 | const requestLogDir = NodeFS.mkdtempSync( |
| 72 | 72 | NodePath.join(NodeOS.tmpdir(), "t3code-grok-text-log-"), |
apps/server/src/textGeneration/OpenCodeTextGeneration.test.ts+4-4| ... | ... | @@ -162,7 +162,7 @@ const DEFAULT_COMMIT_MESSAGE_INPUT = { |
| 162 | 162 | |
| 163 | 163 | const OPENCODE_TEXT_GENERATION_IDLE_TTL_MS = 30_000; |
| 164 | 164 | |
| 165 | const OpenCodeTextGenerationTestLayer = Layer.succeed( | |
| 165 | const layerOpenCodeTextGenerationTest = Layer.succeed( | |
| 166 | 166 | OpenCodeRuntime.OpenCodeRuntime, |
| 167 | 167 | OpenCodeRuntimeTestDouble, |
| 168 | 168 | ).pipe( |
| ... | ... | @@ -175,7 +175,7 @@ const OpenCodeTextGenerationTestLayer = Layer.succeed( |
| 175 | 175 | Layer.provideMerge(NodeServices.layer), |
| 176 | 176 | ); |
| 177 | 177 | |
| 178 | const OpenCodeTextGenerationExistingServerTestLayer = Layer.succeed( | |
| 178 | const layerOpenCodeTextGenerationExistingServerTest = Layer.succeed( | |
| 179 | 179 | OpenCodeRuntime.OpenCodeRuntime, |
| 180 | 180 | OpenCodeRuntimeTestDouble, |
| 181 | 181 | ).pipe( |
| ... | ... | @@ -234,7 +234,7 @@ const advanceIdleClock = Effect.gen(function* () { |
| 234 | 234 | yield* Effect.yieldNow; |
| 235 | 235 | }); |
| 236 | 236 | |
| 237 | it.layer(OpenCodeTextGenerationTestLayer)("OpenCodeTextGeneration", (it) => { | |
| 237 | it.layer(layerOpenCodeTextGenerationTest)("OpenCodeTextGeneration", (it) => { | |
| 238 | 238 | it.effect("excludes generic files from thread title generation", () => |
| 239 | 239 | withOpenCodeTextGeneration(DEFAULT_OPENCODE_SETTINGS, (textGeneration) => |
| 240 | 240 | Effect.gen(function* () { |
| ... | ... | @@ -545,7 +545,7 @@ it.layer(OpenCodeTextGenerationTestLayer)("OpenCodeTextGeneration", (it) => { |
| 545 | 545 | ); |
| 546 | 546 | }); |
| 547 | 547 | |
| 548 | it.layer(OpenCodeTextGenerationExistingServerTestLayer)( | |
| 548 | it.layer(layerOpenCodeTextGenerationExistingServerTest)( | |
| 549 | 549 | "OpenCodeTextGeneration with configured server URL", |
| 550 | 550 | (it) => { |
| 551 | 551 | it.effect("does not send a local environment password to a configured server", () => |
apps/server/src/textGeneration/ThreadTitleLinks.test.ts+4-4| ... | ... | @@ -9,7 +9,7 @@ import { SourceControlProviderError } from "@t3tools/contracts"; |
| 9 | 9 | import { resolveThreadTitleLinks } from "./ThreadTitleLinks.ts"; |
| 10 | 10 | import * as SourceControlProviderRegistry from "../sourceControl/SourceControlProviderRegistry.ts"; |
| 11 | 11 | |
| 12 | const registry = Layer.mock(SourceControlProviderRegistry.SourceControlProviderRegistry); | |
| 12 | const layerRegistry = Layer.mock(SourceControlProviderRegistry.SourceControlProviderRegistry); | |
| 13 | 13 | const encodeSubject = Schema.encodeSync( |
| 14 | 14 | Schema.fromJsonString(Schema.Struct({ title: Schema.String, body: Schema.String })), |
| 15 | 15 | ); |
| ... | ... | @@ -26,7 +26,7 @@ it.effect( |
| 26 | 26 | "https://docs.test/guide [https://forge.test/change/1] https://forge.test/change/1#discussion https://forge.test/change/1?view=full `https://forge.test/change/2` https://forge.test/change/2. https://forge.test/change/3", |
| 27 | 27 | }).pipe( |
| 28 | 28 | Effect.provide( |
| 29 | registry({ | |
| 29 | layerRegistry({ | |
| 30 | 30 | resolveLink: ({ url, cwd }) => |
| 31 | 31 | url.host === "forge.test" |
| 32 | 32 | ? Effect.sync(() => { |
| ... | ... | @@ -58,7 +58,7 @@ it.effect("returns unavailable when a lookup times out while retaining successfu |
| 58 | 58 | message: "https://forge.test/change/1 https://forge.test/change/2", |
| 59 | 59 | }).pipe( |
| 60 | 60 | Effect.provide( |
| 61 | registry({ | |
| 61 | layerRegistry({ | |
| 62 | 62 | resolveLink: ({ url }) => |
| 63 | 63 | url.pathname.endsWith("1") |
| 64 | 64 | ? Deferred.succeed(started, undefined).pipe(Effect.andThen(Effect.never)) |
| ... | ... | @@ -83,7 +83,7 @@ it.effect("keeps lookup failure out of generation and skips unlinked messages", |
| 83 | 83 | ).toContain("unavailable"); |
| 84 | 84 | }).pipe( |
| 85 | 85 | Effect.provide( |
| 86 | registry({ | |
| 86 | layerRegistry({ | |
| 87 | 87 | resolveLink: () => |
| 88 | 88 | Effect.fail( |
| 89 | 89 | new SourceControlProviderError({ |
apps/server/src/usage/UsageService.test.ts+24-30| ... | ... | @@ -78,7 +78,7 @@ const setup = Effect.gen(function* () { |
| 78 | 78 | }; |
| 79 | 79 | }); |
| 80 | 80 | |
| 81 | const serviceLayers = (input: { | |
| 81 | const layerService = (input: { | |
| 82 | 82 | readonly prefix: string; |
| 83 | 83 | readonly home: string; |
| 84 | 84 | readonly settings: Parameters<typeof ServerSettings.layerTest>[0]; |
| ... | ... | @@ -197,7 +197,7 @@ describe("UsageService", () => { |
| 197 | 197 | return yield* service.readSummary(WINDOW); |
| 198 | 198 | }).pipe( |
| 199 | 199 | Effect.provide( |
| 200 | serviceLayers({ | |
| 200 | layerService({ | |
| 201 | 201 | prefix: "usage-managed-accounts", |
| 202 | 202 | home, |
| 203 | 203 | settings: { |
| ... | ... | @@ -254,7 +254,7 @@ describe("UsageService", () => { |
| 254 | 254 | for (const platform of ["linux", "win32", "darwin"] as const) { |
| 255 | 255 | const service = yield* UsageService.make.pipe( |
| 256 | 256 | Effect.provide( |
| 257 | serviceLayers({ | |
| 257 | layerService({ | |
| 258 | 258 | prefix: `usage-service-cursor-no-login-${platform}`, |
| 259 | 259 | home, |
| 260 | 260 | settings, |
| ... | ... | @@ -279,7 +279,7 @@ describe("UsageService", () => { |
| 279 | 279 | }); |
| 280 | 280 | const service = yield* UsageService.make.pipe( |
| 281 | 281 | Effect.provide( |
| 282 | serviceLayers({ prefix: "usage-service-cursor-invalid-login", home, settings }), | |
| 282 | layerService({ prefix: "usage-service-cursor-invalid-login", home, settings }), | |
| 283 | 283 | ), |
| 284 | 284 | ); |
| 285 | 285 | const summary = yield* service.readSummary(WINDOW); |
| ... | ... | @@ -293,7 +293,7 @@ describe("UsageService", () => { |
| 293 | 293 | const { settings, home } = yield* setup; |
| 294 | 294 | const service = yield* UsageService.make.pipe( |
| 295 | 295 | Effect.provide( |
| 296 | serviceLayers({ | |
| 296 | layerService({ | |
| 297 | 297 | prefix: "usage-service-cursor-keychain-disabled", |
| 298 | 298 | home, |
| 299 | 299 | settings, |
| ... | ... | @@ -339,7 +339,7 @@ describe("UsageService", () => { |
| 339 | 339 | }); |
| 340 | 340 | const service = yield* UsageService.make.pipe( |
| 341 | 341 | Effect.provide( |
| 342 | serviceLayers({ | |
| 342 | layerService({ | |
| 343 | 343 | prefix: `usage-service-cursor-store-${index}`, |
| 344 | 344 | home, |
| 345 | 345 | settings, |
| ... | ... | @@ -395,7 +395,7 @@ describe("UsageService", () => { |
| 395 | 395 | } |
| 396 | 396 | }); |
| 397 | 397 | const service = yield* UsageService.make.pipe( |
| 398 | Effect.provide(serviceLayers({ prefix: "usage-service-opencode", home, settings })), | |
| 398 | Effect.provide(layerService({ prefix: "usage-service-opencode", home, settings })), | |
| 399 | 399 | ); |
| 400 | 400 | const summary = yield* service.readSummary(WINDOW); |
| 401 | 401 | assert.strictEqual(summary.buckets[0]?.provider, "opencode"); |
| ... | ... | @@ -441,7 +441,7 @@ describe("UsageService", () => { |
| 441 | 441 | }); |
| 442 | 442 | const service = yield* UsageService.make.pipe( |
| 443 | 443 | Effect.provide( |
| 444 | serviceLayers({ | |
| 444 | layerService({ | |
| 445 | 445 | prefix: "usage-service-aliased-roots-test", |
| 446 | 446 | home, |
| 447 | 447 | settings, |
| ... | ... | @@ -521,7 +521,7 @@ describe("UsageService", () => { |
| 521 | 521 | }); |
| 522 | 522 | const service = yield* UsageService.make.pipe( |
| 523 | 523 | Effect.provide( |
| 524 | serviceLayers({ | |
| 524 | layerService({ | |
| 525 | 525 | prefix: "usage-service-accounts-test", |
| 526 | 526 | home, |
| 527 | 527 | settings: { |
| ... | ... | @@ -643,7 +643,7 @@ describe("UsageService", () => { |
| 643 | 643 | ); |
| 644 | 644 | }).pipe( |
| 645 | 645 | Effect.provide( |
| 646 | serviceLayers({ | |
| 646 | layerService({ | |
| 647 | 647 | prefix: "usage-service-home-refresh-test", |
| 648 | 648 | home, |
| 649 | 649 | environment: { CLAUDE_CONFIG_DIR: NodePath.join(home, "host-ignored") }, |
| ... | ... | @@ -673,7 +673,7 @@ describe("UsageService", () => { |
| 673 | 673 | yield* Effect.promise(() => NodeFSP.writeFile(transcript, claudeLine(1, 5))); |
| 674 | 674 | const service = yield* UsageService.make.pipe( |
| 675 | 675 | Effect.provide( |
| 676 | serviceLayers({ | |
| 676 | layerService({ | |
| 677 | 677 | prefix: "usage-service-inherited-homes-test", |
| 678 | 678 | home, |
| 679 | 679 | environment: { |
| ... | ... | @@ -748,7 +748,7 @@ describe("UsageService", () => { |
| 748 | 748 | assert.deepStrictEqual(restored.buckets, original.buckets); |
| 749 | 749 | }).pipe( |
| 750 | 750 | Effect.provide( |
| 751 | serviceLayers({ prefix: "usage-service-price-overrides-test", home, settings }), | |
| 751 | layerService({ prefix: "usage-service-price-overrides-test", home, settings }), | |
| 752 | 752 | ), |
| 753 | 753 | ); |
| 754 | 754 | }).pipe(Effect.scoped), |
| ... | ... | @@ -760,7 +760,7 @@ describe("UsageService", () => { |
| 760 | 760 | yield* Effect.promise(() => NodeFSP.writeFile(transcript, claudeLine(1, 5))); |
| 761 | 761 | |
| 762 | 762 | const service = yield* UsageService.make.pipe( |
| 763 | Effect.provide(serviceLayers({ prefix: "usage-service-grow-test", home, settings })), | |
| 763 | Effect.provide(layerService({ prefix: "usage-service-grow-test", home, settings })), | |
| 764 | 764 | ); |
| 765 | 765 | |
| 766 | 766 | const first = yield* service.readSummary(WINDOW); |
| ... | ... | @@ -812,7 +812,7 @@ describe("UsageService", () => { |
| 812 | 812 | ); |
| 813 | 813 | }).pipe( |
| 814 | 814 | Effect.provide( |
| 815 | serviceLayers({ | |
| 815 | layerService({ | |
| 816 | 816 | prefix: "usage-service-large-record-test", |
| 817 | 817 | home, |
| 818 | 818 | settings, |
| ... | ... | @@ -899,7 +899,7 @@ describe("UsageService", () => { |
| 899 | 899 | ); |
| 900 | 900 | }).pipe( |
| 901 | 901 | Effect.provide( |
| 902 | serviceLayers({ | |
| 902 | layerService({ | |
| 903 | 903 | prefix: "usage-service-v4-upgrade-test", |
| 904 | 904 | home, |
| 905 | 905 | settings, |
| ... | ... | @@ -995,7 +995,7 @@ describe("UsageService", () => { |
| 995 | 995 | assert.strictEqual(outsideWindow.sources[0]?.distinctSessions, 0); |
| 996 | 996 | }).pipe( |
| 997 | 997 | Effect.provide( |
| 998 | serviceLayers({ | |
| 998 | layerService({ | |
| 999 | 999 | prefix: "usage-service-cleanup-test", |
| 1000 | 1000 | home, |
| 1001 | 1001 | settings: { providers: { ...settings.providers, claudeAgent: { homePath: alias } } }, |
| ... | ... | @@ -1042,7 +1042,7 @@ describe("UsageService", () => { |
| 1042 | 1042 | const restored = yield* (yield* UsageService.make).readSummary(WINDOW); |
| 1043 | 1043 | assert.deepStrictEqual(restored.buckets, live.buckets); |
| 1044 | 1044 | }).pipe( |
| 1045 | Effect.provide(serviceLayers({ prefix: "usage-service-copy-order-test", home, settings })), | |
| 1045 | Effect.provide(layerService({ prefix: "usage-service-copy-order-test", home, settings })), | |
| 1046 | 1046 | ); |
| 1047 | 1047 | }).pipe(Effect.scoped), |
| 1048 | 1048 | ); |
| ... | ... | @@ -1073,9 +1073,7 @@ describe("UsageService", () => { |
| 1073 | 1073 | ); |
| 1074 | 1074 | assert.strictEqual(totalOutputTokens(yield* service.readSummary(WINDOW)), 12); |
| 1075 | 1075 | }).pipe( |
| 1076 | Effect.provide( | |
| 1077 | serviceLayers({ prefix: "usage-service-stale-read-test", home, settings }), | |
| 1078 | ), | |
| 1076 | Effect.provide(layerService({ prefix: "usage-service-stale-read-test", home, settings })), | |
| 1079 | 1077 | ); |
| 1080 | 1078 | }).pipe(Effect.scoped), |
| 1081 | 1079 | ); |
| ... | ... | @@ -1119,7 +1117,7 @@ describe("UsageService", () => { |
| 1119 | 1117 | ); |
| 1120 | 1118 | assert.strictEqual(totalOutputTokens(yield* service.readSummary(WINDOW)), 12); |
| 1121 | 1119 | }).pipe( |
| 1122 | Effect.provide(serviceLayers({ prefix: "usage-service-late-read-test", home, settings })), | |
| 1120 | Effect.provide(layerService({ prefix: "usage-service-late-read-test", home, settings })), | |
| 1123 | 1121 | ); |
| 1124 | 1122 | }).pipe(Effect.scoped), |
| 1125 | 1123 | ); |
| ... | ... | @@ -1133,9 +1131,7 @@ describe("UsageService", () => { |
| 1133 | 1131 | await NodeFSP.utimes(transcript, lastWrite, lastWrite); |
| 1134 | 1132 | }); |
| 1135 | 1133 | const service = yield* UsageService.make.pipe( |
| 1136 | Effect.provide( | |
| 1137 | serviceLayers({ prefix: "usage-service-saved-window-test", home, settings }), | |
| 1138 | ), | |
| 1134 | Effect.provide(layerService({ prefix: "usage-service-saved-window-test", home, settings })), | |
| 1139 | 1135 | ); |
| 1140 | 1136 | const first = yield* service.readSummary(WINDOW); |
| 1141 | 1137 | yield* Effect.promise(() => |
| ... | ... | @@ -1215,7 +1211,7 @@ describe("UsageService", () => { |
| 1215 | 1211 | assert.strictEqual(original.buckets[0]?.costUsd, 0); |
| 1216 | 1212 | assert.closeTo(updated.buckets[0]?.costUsd ?? -1, 0.00006, 1e-12); |
| 1217 | 1213 | }).pipe( |
| 1218 | Effect.provide(serviceLayers({ prefix: "usage-service-price-race-test", home, settings })), | |
| 1214 | Effect.provide(layerService({ prefix: "usage-service-price-race-test", home, settings })), | |
| 1219 | 1215 | ); |
| 1220 | 1216 | }).pipe(Effect.scoped), |
| 1221 | 1217 | ); |
| ... | ... | @@ -1228,7 +1224,7 @@ describe("UsageService", () => { |
| 1228 | 1224 | let ratesFetches = 0; |
| 1229 | 1225 | const service = yield* UsageService.make.pipe( |
| 1230 | 1226 | Effect.provide( |
| 1231 | serviceLayers({ | |
| 1227 | layerService({ | |
| 1232 | 1228 | prefix: "usage-service-flight-test", |
| 1233 | 1229 | home, |
| 1234 | 1230 | settings, |
| ... | ... | @@ -1260,7 +1256,7 @@ describe("UsageService", () => { |
| 1260 | 1256 | let ratesFetches = 0; |
| 1261 | 1257 | const service = yield* UsageService.make.pipe( |
| 1262 | 1258 | Effect.provide( |
| 1263 | serviceLayers({ | |
| 1259 | layerService({ | |
| 1264 | 1260 | prefix: "usage-service-rates-refresh-test", |
| 1265 | 1261 | home, |
| 1266 | 1262 | settings, |
| ... | ... | @@ -1298,9 +1294,7 @@ describe("UsageService", () => { |
| 1298 | 1294 | Effect.gen(function* () { |
| 1299 | 1295 | const { settings, home } = yield* setup; |
| 1300 | 1296 | const service = yield* UsageService.make.pipe( |
| 1301 | Effect.provide( | |
| 1302 | serviceLayers({ prefix: "usage-service-interruption-test", home, settings }), | |
| 1303 | ), | |
| 1297 | Effect.provide(layerService({ prefix: "usage-service-interruption-test", home, settings })), | |
| 1304 | 1298 | ); |
| 1305 | 1299 | |
| 1306 | 1300 | let orphanedAt: number | undefined; |
apps/server/src/vcs/GitVcsDriver.test.ts+24-24| ... | ... | @@ -24,16 +24,16 @@ import * as VcsDriverRegistry from "./VcsDriverRegistry.ts"; |
| 24 | 24 | import * as VcsProcess from "./VcsProcess.ts"; |
| 25 | 25 | import { runVcsDriverContractSuite } from "./testing/VcsDriverContractHarness.ts"; |
| 26 | 26 | |
| 27 | const ServerConfigLayer = ServerConfig.layerTest(process.cwd(), { | |
| 27 | const layerServerConfig = ServerConfig.layerTest(process.cwd(), { | |
| 28 | 28 | prefix: "t3-git-vcs-contract-", |
| 29 | 29 | }); |
| 30 | const GitContractLayer = Layer.mergeAll(GitVcsDriver.vcsLayer, GitVcsDriver.layer).pipe( | |
| 31 | Layer.provide(ServerConfigLayer), | |
| 30 | const layerGitContract = Layer.mergeAll(GitVcsDriver.layerVcs, GitVcsDriver.layer).pipe( | |
| 31 | Layer.provide(layerServerConfig), | |
| 32 | 32 | Layer.provideMerge(VcsProcess.layer), |
| 33 | 33 | Layer.provideMerge(NodeServices.layer), |
| 34 | 34 | ); |
| 35 | const GitCaptureContractLayer = Layer.merge( | |
| 36 | GitContractLayer, | |
| 35 | const layerGitCaptureContract = Layer.merge( | |
| 36 | layerGitContract, | |
| 37 | 37 | ProcessRunner.layer.pipe(Layer.provide(NodeServices.layer)), |
| 38 | 38 | ); |
| 39 | 39 | |
| ... | ... | @@ -69,7 +69,7 @@ type GitContractError = GitCommandError | PlatformError.PlatformError; |
| 69 | 69 | runVcsDriverContractSuite<GitVcsDriver.GitVcsDriver, GitContractError>({ |
| 70 | 70 | name: "Git", |
| 71 | 71 | kind: "git", |
| 72 | layer: GitContractLayer, | |
| 72 | layer: layerGitContract, | |
| 73 | 73 | fixture: { |
| 74 | 74 | createRepo: (cwd) => |
| 75 | 75 | Effect.gen(function* () { |
| ... | ... | @@ -160,7 +160,7 @@ it.effect("checkpoint capture skips untracked nested repositories without a comm |
| 160 | 160 | yield* fileSystem.readFileString(path.join(cwd, nested, "private.txt")), |
| 161 | 161 | "nested\n", |
| 162 | 162 | ); |
| 163 | }).pipe(Effect.scoped, Effect.provide(GitContractLayer)), | |
| 163 | }).pipe(Effect.scoped, Effect.provide(layerGitContract)), | |
| 164 | 164 | ); |
| 165 | 165 | |
| 166 | 166 | it.effect("checkpoint recovery discovers nested HEAD independently of inherited GIT_DIR", () => |
| ... | ... | @@ -188,7 +188,7 @@ it.effect("checkpoint recovery discovers nested HEAD independently of inherited |
| 188 | 188 | assert.strictEqual((yield* git(["show", `${checkpointRef}:file.txt`])).stdout, "unstaged\n"); |
| 189 | 189 | assert.strictEqual((yield* git(["ls-tree", "-r", checkpointRef, "--", "empty"])).stdout, ""); |
| 190 | 190 | assert.deepEqual(yield* fs.readFile(path.join(cwd, ".git", "index")), originalIndex); |
| 191 | }).pipe(Effect.scoped, Effect.provide(GitContractLayer)), | |
| 191 | }).pipe(Effect.scoped, Effect.provide(layerGitContract)), | |
| 192 | 192 | ); |
| 193 | 193 | |
| 194 | 194 | it.effect("checkpoint capture still fails when a clean filter rejects a file", () => |
| ... | ... | @@ -212,7 +212,7 @@ it.effect("checkpoint capture still fails when a clean filter rejects a file", ( |
| 212 | 212 | assert.strictEqual(result._tag, "Failure"); |
| 213 | 213 | assert.deepEqual(yield* fileSystem.readFile(path.join(cwd, ".git", "index")), originalIndex); |
| 214 | 214 | assert.isFalse(yield* driver.checkpoints.hasCheckpointRef({ cwd, checkpointRef })); |
| 215 | }).pipe(Effect.scoped, Effect.provide(GitContractLayer)), | |
| 215 | }).pipe(Effect.scoped, Effect.provide(layerGitContract)), | |
| 216 | 216 | ); |
| 217 | 217 | |
| 218 | 218 | it.effect("checkpoint capture refuses a truncated nested repository listing", () => |
| ... | ... | @@ -242,7 +242,7 @@ it.effect("checkpoint capture refuses a truncated nested repository listing", () |
| 242 | 242 | |
| 243 | 243 | assert.strictEqual(result._tag, "Failure"); |
| 244 | 244 | assert.isFalse(yield* driver.checkpoints.hasCheckpointRef({ cwd, checkpointRef })); |
| 245 | }).pipe(Effect.scoped, Effect.provide(GitContractLayer)), | |
| 245 | }).pipe(Effect.scoped, Effect.provide(layerGitContract)), | |
| 246 | 246 | ); |
| 247 | 247 | |
| 248 | 248 | it.effect("checkpoint recovery refuses excessive candidates before probing", () => |
| ... | ... | @@ -283,7 +283,7 @@ it.effect("checkpoint recovery refuses excessive candidates before probing", () |
| 283 | 283 | if (result._tag === "Failure") assert.strictEqual(result.failure, stageError); |
| 284 | 284 | assert.isFalse(yield* driver.checkpoints.hasCheckpointRef({ cwd, checkpointRef })); |
| 285 | 285 | assert.deepEqual(yield* fs.readFile(path.join(cwd, ".git", "index")), originalIndex); |
| 286 | }).pipe(Effect.scoped, Effect.provide(GitContractLayer)), | |
| 286 | }).pipe(Effect.scoped, Effect.provide(layerGitContract)), | |
| 287 | 287 | ); |
| 288 | 288 | |
| 289 | 289 | it.effect.each([ |
| ... | ... | @@ -389,7 +389,7 @@ it.effect.each([ |
| 389 | 389 | assert.isFalse(yield* fs.exists(`${index}.lock`)); |
| 390 | 390 | } |
| 391 | 391 | assert.deepEqual(yield* fs.readFile(path.join(cwd, ".git", "index")), originalIndex); |
| 392 | }).pipe(Effect.scoped, Effect.provide(GitCaptureContractLayer)), | |
| 392 | }).pipe(Effect.scoped, Effect.provide(layerGitCaptureContract)), | |
| 393 | 393 | ); |
| 394 | 394 | |
| 395 | 395 | it.effect.each(["discovery", "probe", "retry"] as const)( |
| ... | ... | @@ -478,7 +478,7 @@ it.effect.each(["discovery", "probe", "retry"] as const)( |
| 478 | 478 | assert.isFalse(yield* fs.exists(`${privateIndex!}.lock`)); |
| 479 | 479 | assert.isFalse(yield* driver.checkpoints.hasCheckpointRef({ cwd, checkpointRef })); |
| 480 | 480 | assert.deepEqual(yield* fs.readFile(path.join(cwd, ".git", "index")), originalIndex); |
| 481 | }).pipe(Effect.scoped, Effect.provide(GitContractLayer)), | |
| 481 | }).pipe(Effect.scoped, Effect.provide(layerGitContract)), | |
| 482 | 482 | ); |
| 483 | 483 | |
| 484 | 484 | it.effect("checkpoint recovery preserves interruption and removes the private index", () => |
| ... | ... | @@ -512,7 +512,7 @@ it.effect("checkpoint recovery preserves interruption and removes the private in |
| 512 | 512 | assert.isDefined(privateIndex); |
| 513 | 513 | assert.isFalse(yield* fs.exists(privateIndex!)); |
| 514 | 514 | assert.isFalse(yield* driver.checkpoints.hasCheckpointRef({ cwd, checkpointRef })); |
| 515 | }).pipe(Effect.scoped, Effect.provide(GitContractLayer)), | |
| 515 | }).pipe(Effect.scoped, Effect.provide(layerGitContract)), | |
| 516 | 516 | ); |
| 517 | 517 | |
| 518 | 518 | it.effect("checkpoint capture does not rerun clean filters for unchanged indexed files", () => |
| ... | ... | @@ -545,7 +545,7 @@ it.effect("checkpoint capture does not rerun clean filters for unchanged indexed |
| 545 | 545 | assert.strictEqual((yield* git(["show", `${checkpointRef}:file.txt`])).stdout, "changed\n"); |
| 546 | 546 | assert.strictEqual((yield* git(["show", `${checkpointRef}:stable.txt`])).stdout, "unchanged\n"); |
| 547 | 547 | assert.deepEqual(yield* fileSystem.readFile(path.join(cwd, ".git", "index")), originalIndex); |
| 548 | }).pipe(Effect.scoped, Effect.provide(GitContractLayer)), | |
| 548 | }).pipe(Effect.scoped, Effect.provide(layerGitContract)), | |
| 549 | 549 | ); |
| 550 | 550 | |
| 551 | 551 | it.effect.each( |
| ... | ... | @@ -643,7 +643,7 @@ it.effect.each( |
| 643 | 643 | "working outside\n", |
| 644 | 644 | ); |
| 645 | 645 | } |
| 646 | }).pipe(Effect.scoped, Effect.provide(GitContractLayer)), | |
| 646 | }).pipe(Effect.scoped, Effect.provide(layerGitContract)), | |
| 647 | 647 | ); |
| 648 | 648 | |
| 649 | 649 | it.effect("checkpoint capture keeps the legacy path when Git lacks add --sparse", () => |
| ... | ... | @@ -683,7 +683,7 @@ it.effect("checkpoint capture keeps the legacy path when Git lacks add --sparse" |
| 683 | 683 | yield* captureDriver.checkpoints.captureCheckpoint({ cwd, checkpointRef }); |
| 684 | 684 | assert.strictEqual((yield* git(["show", `${checkpointRef}:file.txt`])).stdout, "unstaged\n"); |
| 685 | 685 | assert.deepEqual(yield* fs.readFile(path.join(cwd, ".git/index")), originalIndex); |
| 686 | }).pipe(Effect.scoped, Effect.provide(GitContractLayer)), | |
| 686 | }).pipe(Effect.scoped, Effect.provide(layerGitContract)), | |
| 687 | 687 | ); |
| 688 | 688 | |
| 689 | 689 | it.effect.each(["normal", "flags", "sparse"] as const)( |
| ... | ... | @@ -737,7 +737,7 @@ it.effect.each(["normal", "flags", "sparse"] as const)( |
| 737 | 737 | if (indexMode !== "flags") |
| 738 | 738 | assert.strictEqual(yield* fs.readFileString(path.join(cwd, ".git/reads")), ""); |
| 739 | 739 | assert.deepEqual(yield* fs.readFile(path.join(cwd, ".git/index")), originalIndex); |
| 740 | }).pipe(Effect.scoped, Effect.provide(GitContractLayer)), | |
| 740 | }).pipe(Effect.scoped, Effect.provide(layerGitContract)), | |
| 741 | 741 | ); |
| 742 | 742 | |
| 743 | 743 | it.effect.each([1_700_000_000, 1_700_000_000.9999])( |
| ... | ... | @@ -767,7 +767,7 @@ it.effect.each([1_700_000_000, 1_700_000_000.9999])( |
| 767 | 767 | assert.strictEqual((yield* git(["show", `${checkpointRef}:file.txt`])).stdout, "after!\n"); |
| 768 | 768 | assert.deepEqual(yield* fileSystem.readFile(indexPath), originalIndex); |
| 769 | 769 | assert.deepEqual((yield* fileSystem.stat(indexPath)).mtime, originalIndexMtime); |
| 770 | }).pipe(Effect.scoped, Effect.provide(GitContractLayer)), | |
| 770 | }).pipe(Effect.scoped, Effect.provide(layerGitContract)), | |
| 771 | 771 | ); |
| 772 | 772 | it.effect.each( |
| 773 | 773 | [false, true].flatMap((nested) => |
| ... | ... | @@ -864,7 +864,7 @@ it.effect.each( |
| 864 | 864 | "working outside\n", |
| 865 | 865 | ); |
| 866 | 866 | } |
| 867 | }).pipe(Effect.scoped, Effect.provide(GitContractLayer)), | |
| 867 | }).pipe(Effect.scoped, Effect.provide(layerGitContract)), | |
| 868 | 868 | ); |
| 869 | 869 | |
| 870 | 870 | it.effect("checkpoint capture preserves racy edits made after resetting the index", () => |
| ... | ... | @@ -907,7 +907,7 @@ it.effect("checkpoint capture preserves racy edits made after resetting the inde |
| 907 | 907 | assert.strictEqual((yield* git(["show", `${checkpointRef}:file.txt`])).stdout, "staged\n"); |
| 908 | 908 | assert.deepEqual(yield* fileSystem.readFile(indexPath), originalIndex); |
| 909 | 909 | assert.deepEqual((yield* fileSystem.stat(indexPath)).mtime, originalIndexMtime); |
| 910 | }).pipe(Effect.scoped, Effect.provide(GitContractLayer)), | |
| 910 | }).pipe(Effect.scoped, Effect.provide(layerGitContract)), | |
| 911 | 911 | ); |
| 912 | 912 | |
| 913 | 913 | it.effect.each( |
| ... | ... | @@ -985,7 +985,7 @@ it.effect.each( |
| 985 | 985 | "added in second turn\n", |
| 986 | 986 | ); |
| 987 | 987 | assert.deepEqual(yield* fileSystem.readFile(path.join(cwd, ".git", "index")), originalIndex); |
| 988 | }).pipe(Effect.scoped, Effect.provide(GitContractLayer)), | |
| 988 | }).pipe(Effect.scoped, Effect.provide(layerGitContract)), | |
| 989 | 989 | ); |
| 990 | 990 | |
| 991 | 991 | it.effect.each(["missing", "invalid"] as const)( |
| ... | ... | @@ -1012,7 +1012,7 @@ it.effect.each(["missing", "invalid"] as const)( |
| 1012 | 1012 | } else { |
| 1013 | 1013 | assert.strictEqual(yield* fileSystem.readFileString(indexPath), "invalid index"); |
| 1014 | 1014 | } |
| 1015 | }).pipe(Effect.scoped, Effect.provide(GitContractLayer)), | |
| 1015 | }).pipe(Effect.scoped, Effect.provide(layerGitContract)), | |
| 1016 | 1016 | ); |
| 1017 | 1017 | |
| 1018 | 1018 | it.effect("restores empty checkpoints without changing paths outside the workspace", () => |
| ... | ... | @@ -1076,7 +1076,7 @@ it.effect("restores empty checkpoints without changing paths outside the workspa |
| 1076 | 1076 | assert.strictEqual(staged.stdout.trim(), "outside.txt"); |
| 1077 | 1077 | } |
| 1078 | 1078 | } |
| 1079 | }).pipe(Effect.scoped, Effect.provide(GitContractLayer)), | |
| 1079 | }).pipe(Effect.scoped, Effect.provide(layerGitContract)), | |
| 1080 | 1080 | ); |
| 1081 | 1081 | |
| 1082 | 1082 | it.effect("GitVcsDriver forwards execute env to the VCS process", () => { |
apps/server/src/vcs/GitVcsDriver.ts+1-1| ... | ... | @@ -1257,5 +1257,5 @@ export const make = Effect.gen(function* () { |
| 1257 | 1257 | return GitVcsDriver.of(git); |
| 1258 | 1258 | }); |
| 1259 | 1259 | |
| 1260 | export const vcsLayer = Layer.effect(VcsDriver.VcsDriver, makeVcsDriver); | |
| 1260 | export const layerVcs = Layer.effect(VcsDriver.VcsDriver, makeVcsDriver); | |
| 1261 | 1261 | export const layer = Layer.effect(GitVcsDriver, make); |
apps/server/src/vcs/GitVcsDriverCore.test.ts+28-28| ... | ... | @@ -40,11 +40,11 @@ import * as GitVcsDriver from "./GitVcsDriver.ts"; |
| 40 | 40 | |
| 41 | 41 | const encodeGitCommandError = Schema.encodeEffect(Schema.fromJsonString(GitCommandError)); |
| 42 | 42 | |
| 43 | const ServerConfigLayer = ServerConfig.layerTest(process.cwd(), { | |
| 43 | const layerServerConfig = ServerConfig.layerTest(process.cwd(), { | |
| 44 | 44 | prefix: "t3-git-vcs-driver-test-", |
| 45 | 45 | }); |
| 46 | const TestLayer = GitVcsDriver.layer.pipe( | |
| 47 | Layer.provide(ServerConfigLayer), | |
| 46 | const layerTest = GitVcsDriver.layer.pipe( | |
| 47 | Layer.provide(layerServerConfig), | |
| 48 | 48 | Layer.provideMerge(NodeServices.layer), |
| 49 | 49 | ); |
| 50 | 50 | |
| ... | ... | @@ -201,7 +201,7 @@ it.effect("bounds Git bursts across drivers without timing out queued commands", |
| 201 | 201 | ); |
| 202 | 202 | assert.equal(duration.count, 16); |
| 203 | 203 | assert.equal(duration.sum, 16_000); |
| 204 | }).pipe(Effect.provide(ServerConfigLayer.pipe(Layer.provideMerge(NodeServices.layer)))), | |
| 204 | }).pipe(Effect.provide(layerServerConfig.pipe(Layer.provideMerge(NodeServices.layer)))), | |
| 205 | 205 | ); |
| 206 | 206 | |
| 207 | 207 | it.effect.each([{ timeoutMs: null }, { timeoutMs: 30_001 }])( |
| ... | ... | @@ -252,7 +252,7 @@ it.effect.each([{ timeoutMs: null }, { timeoutMs: 30_001 }])( |
| 252 | 252 | yield* Deferred.succeed(slowGate, undefined); |
| 253 | 253 | assert.equal((yield* Fiber.join(slow)).stdout, "ok"); |
| 254 | 254 | assert.equal(active, 0); |
| 255 | }).pipe(Effect.provide(ServerConfigLayer.pipe(Layer.provideMerge(NodeServices.layer)))), | |
| 255 | }).pipe(Effect.provide(layerServerConfig.pipe(Layer.provideMerge(NodeServices.layer)))), | |
| 256 | 256 | ); |
| 257 | 257 | |
| 258 | 258 | it.effect.each(["root", "nested", "worktree"] as const)( |
| ... | ... | @@ -295,7 +295,7 @@ it.effect.each(["root", "nested", "worktree"] as const)( |
| 295 | 295 | const status = yield* driver.statusDetailsLocal(statusCwd); |
| 296 | 296 | assert.isFalse(status.hasWorkingTreeChanges); |
| 297 | 297 | assert.include(yield* fs.readFileString(runsPath), "clean"); |
| 298 | }).pipe(Effect.provide(TestLayer)), | |
| 298 | }).pipe(Effect.provide(layerTest)), | |
| 299 | 299 | ); |
| 300 | 300 | |
| 301 | 301 | it.effect("uses stable diagnostics for every parsed non-repository command", () => { |
| ... | ... | @@ -312,13 +312,13 @@ it.effect("uses stable diagnostics for every parsed non-repository command", () |
| 312 | 312 | return makeNonRepositoryHandle(); |
| 313 | 313 | }), |
| 314 | 314 | ); |
| 315 | const nodeServicesLayer = Layer.merge( | |
| 315 | const layerNodeServices = Layer.merge( | |
| 316 | 316 | NodeServices.layer, |
| 317 | 317 | Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner), |
| 318 | 318 | ); |
| 319 | 319 | const layer = GitVcsDriver.layer.pipe( |
| 320 | Layer.provide(ServerConfigLayer), | |
| 321 | Layer.provideMerge(nodeServicesLayer), | |
| 320 | Layer.provide(layerServerConfig), | |
| 321 | Layer.provideMerge(layerNodeServices), | |
| 322 | 322 | ); |
| 323 | 323 | |
| 324 | 324 | return Effect.gen(function* () { |
| ... | ... | @@ -353,7 +353,7 @@ it.effect("invalidates origin remote cache when a driver mutation adds origin", |
| 353 | 353 | |
| 354 | 354 | const after = yield* driver.statusDetailsLocal(cwd); |
| 355 | 355 | assert.equal(after.hasOriginRemote, true); |
| 356 | }).pipe(Effect.provide(TestLayer)), | |
| 356 | }).pipe(Effect.provide(layerTest)), | |
| 357 | 357 | ); |
| 358 | 358 | |
| 359 | 359 | it.effect("re-reads origin remote status after cache TTL expiry and bypassed invalidation", () => |
| ... | ... | @@ -380,7 +380,7 @@ it.effect("re-reads origin remote status after cache TTL expiry and bypassed inv |
| 380 | 380 | // After expiry, the next call re-executes and picks up the remote |
| 381 | 381 | const afterExpiry = yield* driver.statusDetailsLocal(cwd); |
| 382 | 382 | assert.equal(afterExpiry.hasOriginRemote, true); |
| 383 | }).pipe(Effect.provide(TestLayer)), | |
| 383 | }).pipe(Effect.provide(layerTest)), | |
| 384 | 384 | ); |
| 385 | 385 | |
| 386 | 386 | it.effect("coalesces concurrent ref pages into one repository snapshot", () => |
| ... | ... | @@ -492,7 +492,7 @@ it.effect("coalesces concurrent ref pages into one repository snapshot", () => |
| 492 | 492 | 2, |
| 493 | 493 | ); |
| 494 | 494 | }), |
| 495 | ).pipe(Effect.provide(ServerConfigLayer.pipe(Layer.provideMerge(NodeServices.layer)))), | |
| 495 | ).pipe(Effect.provide(layerServerConfig.pipe(Layer.provideMerge(NodeServices.layer)))), | |
| 496 | 496 | ); |
| 497 | 497 | |
| 498 | 498 | it.effect("retries an in-flight ref snapshot invalidated by a mutation", () => |
| ... | ... | @@ -551,7 +551,7 @@ it.effect("retries an in-flight ref snapshot invalidated by a mutation", () => |
| 551 | 551 | assert.isTrue(refs.refs.some((ref) => ref.name === "feature/during-refresh")); |
| 552 | 552 | assert.equal(yield* Ref.get(refScans), 2); |
| 553 | 553 | }), |
| 554 | ).pipe(Effect.provide(ServerConfigLayer.pipe(Layer.provideMerge(NodeServices.layer)))), | |
| 554 | ).pipe(Effect.provide(layerServerConfig.pipe(Layer.provideMerge(NodeServices.layer)))), | |
| 555 | 555 | ); |
| 556 | 556 | |
| 557 | 557 | it.effect("invalidates a ref snapshot when a mutation fails after changing Git", () => |
| ... | ... | @@ -583,7 +583,7 @@ it.effect("invalidates a ref snapshot when a mutation fails after changing Git", |
| 583 | 583 | const refs = yield* driver.listRefs({ cwd }); |
| 584 | 584 | assert.isTrue(refs.refs.some((ref) => ref.name === "feature/partial-failure")); |
| 585 | 585 | }), |
| 586 | ).pipe(Effect.provide(ServerConfigLayer.pipe(Layer.provideMerge(NodeServices.layer)))), | |
| 586 | ).pipe(Effect.provide(layerServerConfig.pipe(Layer.provideMerge(NodeServices.layer)))), | |
| 587 | 587 | ); |
| 588 | 588 | |
| 589 | 589 | it.effect("fails a ref snapshot when for-each-ref exits unsuccessfully", () => |
| ... | ... | @@ -619,7 +619,7 @@ it.effect("fails a ref snapshot when for-each-ref exits unsuccessfully", () => |
| 619 | 619 | }); |
| 620 | 620 | assert.equal(yield* Ref.get(snapshotAttempts), 1); |
| 621 | 621 | }), |
| 622 | ).pipe(Effect.provide(ServerConfigLayer.pipe(Layer.provideMerge(NodeServices.layer)))), | |
| 622 | ).pipe(Effect.provide(layerServerConfig.pipe(Layer.provideMerge(NodeServices.layer)))), | |
| 623 | 623 | ); |
| 624 | 624 | |
| 625 | 625 | it.effect("marks the current branch when worktree metadata is unavailable", () => |
| ... | ... | @@ -654,7 +654,7 @@ it.effect("marks the current branch when worktree metadata is unavailable", () = |
| 654 | 654 | assert.isTrue(refs.isRepo); |
| 655 | 655 | assert.isTrue(refs.refs.find((ref) => ref.name === initialBranch)?.current); |
| 656 | 656 | }), |
| 657 | ).pipe(Effect.provide(ServerConfigLayer.pipe(Layer.provideMerge(NodeServices.layer)))), | |
| 657 | ).pipe(Effect.provide(layerServerConfig.pipe(Layer.provideMerge(NodeServices.layer)))), | |
| 658 | 658 | ); |
| 659 | 659 | |
| 660 | 660 | it.effect("ignores worktree metadata for directories that no longer exist", () => |
| ... | ... | @@ -690,7 +690,7 @@ it.effect("ignores worktree metadata for directories that no longer exist", () = |
| 690 | 690 | |
| 691 | 691 | assert.equal(refs.refs.find((ref) => ref.name === "stale-worktree")?.worktreePath, null); |
| 692 | 692 | }), |
| 693 | ).pipe(Effect.provide(ServerConfigLayer.pipe(Layer.provideMerge(NodeServices.layer)))), | |
| 693 | ).pipe(Effect.provide(layerServerConfig.pipe(Layer.provideMerge(NodeServices.layer)))), | |
| 694 | 694 | ); |
| 695 | 695 | |
| 696 | 696 | it.effect("refreshes the current branch after an external checkout", () => |
| ... | ... | @@ -718,7 +718,7 @@ it.effect("refreshes the current branch after an external checkout", () => |
| 718 | 718 | assert.isTrue(refreshedRefs.refs.find((ref) => ref.name === "external-checkout")?.current); |
| 719 | 719 | assert.isFalse(refreshedRefs.refs.find((ref) => ref.name === initialBranch)?.current); |
| 720 | 720 | }), |
| 721 | ).pipe(Effect.provide(TestLayer)), | |
| 721 | ).pipe(Effect.provide(layerTest)), | |
| 722 | 722 | ); |
| 723 | 723 | |
| 724 | 724 | it.effect("backs off and logs failed fetch attempts across linked worktrees", () => |
| ... | ... | @@ -818,7 +818,7 @@ it.effect("backs off and logs failed fetch attempts across linked worktrees", () |
| 818 | 818 | assert.equal(yield* Ref.get(fetchAttempts), 3); |
| 819 | 819 | assert.lengthOf(warnings, 3); |
| 820 | 820 | }), |
| 821 | ).pipe(Effect.provide(ServerConfigLayer.pipe(Layer.provideMerge(NodeServices.layer)))), | |
| 821 | ).pipe(Effect.provide(layerServerConfig.pipe(Layer.provideMerge(NodeServices.layer)))), | |
| 822 | 822 | ); |
| 823 | 823 | |
| 824 | 824 | it.effect.each([ |
| ... | ... | @@ -927,10 +927,10 @@ it.effect.each([ |
| 927 | 927 | assert.notProperty(error, "stderr"); |
| 928 | 928 | assert.notProperty(error, "args"); |
| 929 | 929 | assert.equal(yield* Ref.get(attempts), 1); |
| 930 | }).pipe(Effect.provide(ServerConfigLayer.pipe(Layer.provideMerge(NodeServices.layer)))), | |
| 930 | }).pipe(Effect.provide(layerServerConfig.pipe(Layer.provideMerge(NodeServices.layer)))), | |
| 931 | 931 | ); |
| 932 | 932 | |
| 933 | it.layer(TestLayer)("GitVcsDriver core integration", (it) => { | |
| 933 | it.layer(layerTest)("GitVcsDriver core integration", (it) => { | |
| 934 | 934 | describe("process environment", () => { |
| 935 | 935 | it.effect("preserves the caller locale for general Git subprocesses", () => |
| 936 | 936 | Effect.gen(function* () { |
| ... | ... | @@ -1424,7 +1424,7 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => { |
| 1424 | 1424 | }); |
| 1425 | 1425 | const driver = yield* makeGitVcsDriverCore().pipe( |
| 1426 | 1426 | Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner), |
| 1427 | Effect.provide(ServerConfigLayer), | |
| 1427 | Effect.provide(layerServerConfig), | |
| 1428 | 1428 | ); |
| 1429 | 1429 | const branch = yield* driver.getReviewDiffPreview({ |
| 1430 | 1430 | cwd, |
| ... | ... | @@ -1463,7 +1463,7 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => { |
| 1463 | 1463 | ); |
| 1464 | 1464 | const driver = yield* makeGitVcsDriverCore().pipe( |
| 1465 | 1465 | Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner), |
| 1466 | Effect.provide(ServerConfigLayer), | |
| 1466 | Effect.provide(layerServerConfig), | |
| 1467 | 1467 | ); |
| 1468 | 1468 | const result = yield* driver.getReviewDiffPreview({ cwd }).pipe(Effect.result); |
| 1469 | 1469 | assert.isTrue(Result.isFailure(result)); |
| ... | ... | @@ -1667,7 +1667,7 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => { |
| 1667 | 1667 | }); |
| 1668 | 1668 | const driver = yield* makeGitVcsDriverCore().pipe( |
| 1669 | 1669 | Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, failingLsFilesSpawner), |
| 1670 | Effect.provide(ServerConfigLayer), | |
| 1670 | Effect.provide(layerServerConfig), | |
| 1671 | 1671 | ); |
| 1672 | 1672 | const cwd = yield* makeTmpDir(); |
| 1673 | 1673 | yield* initRepoWithCommit(cwd).pipe( |
| ... | ... | @@ -2312,7 +2312,7 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => { |
| 2312 | 2312 | ); |
| 2313 | 2313 | const driver = yield* makeGitVcsDriverCore().pipe( |
| 2314 | 2314 | Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner), |
| 2315 | Effect.provide(ServerConfigLayer), | |
| 2315 | Effect.provide(layerServerConfig), | |
| 2316 | 2316 | ); |
| 2317 | 2317 | |
| 2318 | 2318 | for (const hasUpstream of [false, true]) { |
| ... | ... | @@ -3214,7 +3214,7 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => { |
| 3214 | 3214 | ); |
| 3215 | 3215 | const driver = yield* makeGitVcsDriverCore().pipe( |
| 3216 | 3216 | Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, delayedRemovalSpawner), |
| 3217 | Effect.provide(ServerConfigLayer), | |
| 3217 | Effect.provide(layerServerConfig), | |
| 3218 | 3218 | ); |
| 3219 | 3219 | const cwd = yield* makeTmpDir(); |
| 3220 | 3220 | const { initialBranch } = yield* initRepoWithCommit(cwd); |
| ... | ... | @@ -3523,7 +3523,7 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => { |
| 3523 | 3523 | ); |
| 3524 | 3524 | const driver = yield* makeGitVcsDriverCore().pipe( |
| 3525 | 3525 | Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner), |
| 3526 | Effect.provide(ServerConfigLayer), | |
| 3526 | Effect.provide(layerServerConfig), | |
| 3527 | 3527 | ); |
| 3528 | 3528 | const fetching = yield* driver |
| 3529 | 3529 | .fetchRemote({ cwd, remoteName: "origin", refName: "main" }) |
| ... | ... | @@ -3703,7 +3703,7 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => { |
| 3703 | 3703 | ); |
| 3704 | 3704 | const driver = yield* makeGitVcsDriverCore().pipe( |
| 3705 | 3705 | Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, delayedPushSpawner), |
| 3706 | Effect.provide(ServerConfigLayer), | |
| 3706 | Effect.provide(layerServerConfig), | |
| 3707 | 3707 | ); |
| 3708 | 3708 | const cwd = yield* makeTmpDir(); |
| 3709 | 3709 | const remote = yield* makeTmpDir("git-remote-"); |
apps/server/src/vcs/VcsProcess.test.ts+2-2| ... | ... | @@ -30,10 +30,10 @@ const run = (input: VcsProcess.VcsProcessInput) => |
| 30 | 30 | return yield* process.run(input); |
| 31 | 31 | }); |
| 32 | 32 | |
| 33 | const liveLayer = VcsProcess.layer.pipe(Layer.provide(NodeServices.layer)); | |
| 33 | const layerLive = VcsProcess.layer.pipe(Layer.provide(NodeServices.layer)); | |
| 34 | 34 | |
| 35 | 35 | const provideLive = <A, E, R>(effect: Effect.Effect<A, E, R | VcsProcess.VcsProcess>) => |
| 36 | effect.pipe(Effect.provide(liveLayer)); | |
| 36 | effect.pipe(Effect.provide(layerLive)); | |
| 37 | 37 | |
| 38 | 38 | const baseInput = { |
| 39 | 39 | operation: "test.process-boundary", |
apps/server/src/vcs/VcsProjectConfig.test.ts+8-8| ... | ... | @@ -8,13 +8,13 @@ import * as Path from "effect/Path"; |
| 8 | 8 | |
| 9 | 9 | import * as VcsProjectConfig from "./VcsProjectConfig.ts"; |
| 10 | 10 | |
| 11 | const TestLayer = VcsProjectConfig.layer.pipe( | |
| 11 | const layerTest = VcsProjectConfig.layer.pipe( | |
| 12 | 12 | Layer.provide(NodeServices.layer), |
| 13 | 13 | Layer.provideMerge(NodeServices.layer), |
| 14 | 14 | ); |
| 15 | 15 | |
| 16 | 16 | describe("VcsProjectConfig", () => { |
| 17 | it.layer(TestLayer)("uses an explicit requested VCS kind before config", (it) => { | |
| 17 | it.layer(layerTest)("uses an explicit requested VCS kind before config", (it) => { | |
| 18 | 18 | it.effect("returns the requested kind", () => |
| 19 | 19 | Effect.gen(function* () { |
| 20 | 20 | const config = yield* VcsProjectConfig.VcsProjectConfig; |
| ... | ... | @@ -28,7 +28,7 @@ describe("VcsProjectConfig", () => { |
| 28 | 28 | ); |
| 29 | 29 | }); |
| 30 | 30 | |
| 31 | it.layer(TestLayer)("discovers .t3code/vcs.json from nested workspaces", (it) => { | |
| 31 | it.layer(layerTest)("discovers .t3code/vcs.json from nested workspaces", (it) => { | |
| 32 | 32 | it.effect("returns the configured kind", () => |
| 33 | 33 | Effect.gen(function* () { |
| 34 | 34 | const fileSystem = yield* FileSystem.FileSystem; |
| ... | ... | @@ -54,7 +54,7 @@ describe("VcsProjectConfig", () => { |
| 54 | 54 | ); |
| 55 | 55 | }); |
| 56 | 56 | |
| 57 | it.layer(TestLayer)("continues to parent configs after a candidate inspect failure", (it) => { | |
| 57 | it.layer(layerTest)("continues to parent configs after a candidate inspect failure", (it) => { | |
| 58 | 58 | it.effect("logs the failed candidate and returns the parent config", () => { |
| 59 | 59 | const messages: unknown[] = []; |
| 60 | 60 | const logger = Logger.make<unknown, void>(({ message }) => { |
| ... | ... | @@ -97,7 +97,7 @@ describe("VcsProjectConfig", () => { |
| 97 | 97 | }); |
| 98 | 98 | }); |
| 99 | 99 | |
| 100 | it.layer(TestLayer)("falls back to auto when no config exists", (it) => { | |
| 100 | it.layer(layerTest)("falls back to auto when no config exists", (it) => { | |
| 101 | 101 | it.effect("returns auto", () => |
| 102 | 102 | Effect.gen(function* () { |
| 103 | 103 | const fileSystem = yield* FileSystem.FileSystem; |
| ... | ... | @@ -112,7 +112,7 @@ describe("VcsProjectConfig", () => { |
| 112 | 112 | ); |
| 113 | 113 | }); |
| 114 | 114 | |
| 115 | it.layer(TestLayer)("falls back to auto when config JSON is malformed", (it) => { | |
| 115 | it.layer(layerTest)("falls back to auto when config JSON is malformed", (it) => { | |
| 116 | 116 | it.effect("returns auto and logs the failed operation and path", () => { |
| 117 | 117 | const messages: unknown[] = []; |
| 118 | 118 | const logger = Logger.make<unknown, void>(({ message }) => { |
| ... | ... | @@ -150,7 +150,7 @@ describe("VcsProjectConfig", () => { |
| 150 | 150 | }); |
| 151 | 151 | }); |
| 152 | 152 | |
| 153 | it.layer(TestLayer)("falls back to auto when the config path cannot be read", (it) => { | |
| 153 | it.layer(layerTest)("falls back to auto when the config path cannot be read", (it) => { | |
| 154 | 154 | it.effect("retains the read failure context", () => { |
| 155 | 155 | const messages: unknown[] = []; |
| 156 | 156 | const logger = Logger.make<unknown, void>(({ message }) => { |
| ... | ... | @@ -184,7 +184,7 @@ describe("VcsProjectConfig", () => { |
| 184 | 184 | }); |
| 185 | 185 | }); |
| 186 | 186 | |
| 187 | it.layer(TestLayer)("falls back to auto when config kind is invalid", (it) => { | |
| 187 | it.layer(layerTest)("falls back to auto when config kind is invalid", (it) => { | |
| 188 | 188 | it.effect("returns auto", () => |
| 189 | 189 | Effect.gen(function* () { |
| 190 | 190 | const fileSystem = yield* FileSystem.FileSystem; |
apps/server/src/vcs/VcsProvisioningService.test.ts+4-4| ... | ... | @@ -61,7 +61,7 @@ function makeDriver(calls: string[]): VcsDriver.VcsDriver["Service"] { |
| 61 | 61 | it.effect("routes repository initialization through an explicit VCS driver kind", () => { |
| 62 | 62 | const calls: string[] = []; |
| 63 | 63 | const driver = makeDriver(calls); |
| 64 | const testLayer = VcsProvisioningService.layer.pipe( | |
| 64 | const layerTest = VcsProvisioningService.layer.pipe( | |
| 65 | 65 | Layer.provide( |
| 66 | 66 | Layer.mock(VcsDriverRegistry.VcsDriverRegistry)({ |
| 67 | 67 | get: (kind) => (kind === "git" ? Effect.succeed(driver) : Effect.die("unexpected kind")), |
| ... | ... | @@ -74,13 +74,13 @@ it.effect("routes repository initialization through an explicit VCS driver kind" |
| 74 | 74 | yield* provisioning.initRepository({ cwd: "/repo", kind: "git" }); |
| 75 | 75 | |
| 76 | 76 | assert.deepStrictEqual(calls, ["git:/repo"]); |
| 77 | }).pipe(Effect.provide(testLayer)); | |
| 77 | }).pipe(Effect.provide(layerTest)); | |
| 78 | 78 | }); |
| 79 | 79 | |
| 80 | 80 | it.effect("defaults repository initialization to Git until callers choose a VCS kind", () => { |
| 81 | 81 | const calls: string[] = []; |
| 82 | 82 | const driver = makeDriver(calls); |
| 83 | const testLayer = VcsProvisioningService.layer.pipe( | |
| 83 | const layerTest = VcsProvisioningService.layer.pipe( | |
| 84 | 84 | Layer.provide( |
| 85 | 85 | Layer.mock(VcsDriverRegistry.VcsDriverRegistry)({ |
| 86 | 86 | get: (kind) => (kind === "git" ? Effect.succeed(driver) : Effect.die("unexpected kind")), |
| ... | ... | @@ -93,5 +93,5 @@ it.effect("defaults repository initialization to Git until callers choose a VCS |
| 93 | 93 | yield* provisioning.initRepository({ cwd: "/repo" }); |
| 94 | 94 | |
| 95 | 95 | assert.deepStrictEqual(calls, ["default:/repo"]); |
| 96 | }).pipe(Effect.provide(testLayer)); | |
| 96 | }).pipe(Effect.provide(layerTest)); | |
| 97 | 97 | }); |
apps/server/src/vcs/VcsStatusBroadcaster.test.ts+34-34| ... | ... | @@ -69,7 +69,7 @@ const baseStatus: VcsStatusResult = { |
| 69 | 69 | ...baseRemoteStatus, |
| 70 | 70 | }; |
| 71 | 71 | |
| 72 | function makeTestLayer(state: { | |
| 72 | function layerTestFor(state: { | |
| 73 | 73 | currentLocalStatus: VcsStatusLocalResult; |
| 74 | 74 | currentRemoteStatus: VcsStatusRemoteResult | null; |
| 75 | 75 | localStatusCalls: number; |
| ... | ... | @@ -83,7 +83,7 @@ function makeTestLayer(state: { |
| 83 | 83 | }) { |
| 84 | 84 | return VcsStatusBroadcaster.layer.pipe( |
| 85 | 85 | Layer.provideMerge(NodeServices.layer), |
| 86 | Layer.provide(makeBackgroundPolicyLayer(() => state.backgroundWorkEnabled !== false)), | |
| 86 | Layer.provide(layerBackgroundPolicy(() => state.backgroundWorkEnabled !== false)), | |
| 87 | 87 | Layer.provide( |
| 88 | 88 | Layer.mock(GitWorkflowService.GitWorkflowService)({ |
| 89 | 89 | localStatus: () => |
| ... | ... | @@ -119,7 +119,7 @@ function makeTestLayer(state: { |
| 119 | 119 | ); |
| 120 | 120 | } |
| 121 | 121 | |
| 122 | function makeBackgroundPolicyLayer(shouldRunScopeWork: (scope: BackgroundScope) => boolean) { | |
| 122 | function layerBackgroundPolicy(shouldRunScopeWork: (scope: BackgroundScope) => boolean) { | |
| 123 | 123 | return Layer.mock(BackgroundPolicy.BackgroundPolicy)({ |
| 124 | 124 | reportClientActivity: () => Effect.void, |
| 125 | 125 | removeRpcClient: () => Effect.void, |
| ... | ... | @@ -162,9 +162,9 @@ describe("VcsStatusBroadcaster", () => { |
| 162 | 162 | isDefaultRef: true, |
| 163 | 163 | refName: "main", |
| 164 | 164 | }; |
| 165 | const testLayer = VcsStatusBroadcaster.layer.pipe( | |
| 165 | const layerTest = VcsStatusBroadcaster.layer.pipe( | |
| 166 | 166 | Layer.provideMerge(NodeServices.layer), |
| 167 | Layer.provide(makeBackgroundPolicyLayer(() => true)), | |
| 167 | Layer.provide(layerBackgroundPolicy(() => true)), | |
| 168 | 168 | Layer.provide( |
| 169 | 169 | Layer.succeed(VcsStatusBroadcaster.VcsAutoPullPolicy, { |
| 170 | 170 | isEnabled: (cwd) => Effect.succeed(cwd === configuredWorkspaceRoot), |
| ... | ... | @@ -208,7 +208,7 @@ describe("VcsStatusBroadcaster", () => { |
| 208 | 208 | |
| 209 | 209 | assert.equal(pullCalls, 1); |
| 210 | 210 | assert.equal(status.behindCount, 0); |
| 211 | }).pipe(Effect.provide(testLayer)); | |
| 211 | }).pipe(Effect.provide(layerTest)); | |
| 212 | 212 | }, |
| 213 | 213 | ); |
| 214 | 214 | |
| ... | ... | @@ -234,7 +234,7 @@ describe("VcsStatusBroadcaster", () => { |
| 234 | 234 | assert.equal(state.remoteStatusCalls, 1); |
| 235 | 235 | assert.equal(state.localInvalidationCalls, 0); |
| 236 | 236 | assert.equal(state.remoteInvalidationCalls, 0); |
| 237 | }).pipe(Effect.provide(makeTestLayer(state))); | |
| 237 | }).pipe(Effect.provide(layerTestFor(state))); | |
| 238 | 238 | }); |
| 239 | 239 | |
| 240 | 240 | it.effect("refreshes a loaded cwd without reusing a previous branch's PR", () => { |
| ... | ... | @@ -271,7 +271,7 @@ describe("VcsStatusBroadcaster", () => { |
| 271 | 271 | const refreshedBranch = yield* broadcaster.refreshPullRequestStatus("/repo"); |
| 272 | 272 | assert.deepStrictEqual(refreshedBranch, baseRemoteStatus); |
| 273 | 273 | assert.equal(state.remoteStatusCalls, 3); |
| 274 | }).pipe(Effect.provide(makeTestLayer(state))); | |
| 274 | }).pipe(Effect.provide(layerTestFor(state))); | |
| 275 | 275 | }); |
| 276 | 276 | |
| 277 | 277 | it.effect("a poll that started before the turn-end refresh cannot overwrite its PR", () => { |
| ... | ... | @@ -280,7 +280,7 @@ describe("VcsStatusBroadcaster", () => { |
| 280 | 280 | let remoteReads = 0; |
| 281 | 281 | const layer = VcsStatusBroadcaster.layer.pipe( |
| 282 | 282 | Layer.provideMerge(NodeServices.layer), |
| 283 | Layer.provide(makeBackgroundPolicyLayer(() => true)), | |
| 283 | Layer.provide(layerBackgroundPolicy(() => true)), | |
| 284 | 284 | Layer.provide( |
| 285 | 285 | Layer.mock(GitWorkflowService.GitWorkflowService)({ |
| 286 | 286 | localStatus: () => Effect.succeed(baseLocalStatus), |
| ... | ... | @@ -326,7 +326,7 @@ describe("VcsStatusBroadcaster", () => { |
| 326 | 326 | const layer = VcsStatusBroadcaster.layer.pipe( |
| 327 | 327 | Layer.provide(FileSystem.layerNoop({ realPath: (path) => Effect.succeed(path) })), |
| 328 | 328 | Layer.provideMerge(NodeServices.layer), |
| 329 | Layer.provide(makeBackgroundPolicyLayer(() => true)), | |
| 329 | Layer.provide(layerBackgroundPolicy(() => true)), | |
| 330 | 330 | Layer.provide( |
| 331 | 331 | Layer.mock(GitWorkflowService.GitWorkflowService)({ |
| 332 | 332 | localStatus: () => Effect.succeed(baseLocalStatus), |
| ... | ... | @@ -377,7 +377,7 @@ describe("VcsStatusBroadcaster", () => { |
| 377 | 377 | yield* broadcaster.refreshPullRequestStatus("/repo"); |
| 378 | 378 | assert.equal(state.remoteStatusCalls, 1); |
| 379 | 379 | assert.equal(state.remoteInvalidationCalls, 0); |
| 380 | }).pipe(Effect.provide(makeTestLayer(state))); | |
| 380 | }).pipe(Effect.provide(layerTestFor(state))); | |
| 381 | 381 | }); |
| 382 | 382 | |
| 383 | 383 | it.effect("refreshes the cached snapshot after explicit invalidation", () => { |
| ... | ... | @@ -418,7 +418,7 @@ describe("VcsStatusBroadcaster", () => { |
| 418 | 418 | assert.equal(state.remoteStatusCalls, 2); |
| 419 | 419 | assert.equal(state.localInvalidationCalls, 1); |
| 420 | 420 | assert.equal(state.remoteInvalidationCalls, 1); |
| 421 | }).pipe(Effect.provide(makeTestLayer(state))); | |
| 421 | }).pipe(Effect.provide(layerTestFor(state))); | |
| 422 | 422 | }); |
| 423 | 423 | |
| 424 | 424 | it.effect("keeps the cached snapshot unchanged when a refresh branch fails", () => { |
| ... | ... | @@ -431,9 +431,9 @@ describe("VcsStatusBroadcaster", () => { |
| 431 | 431 | remoteInvalidationCalls: 0, |
| 432 | 432 | failRemoteStatus: false, |
| 433 | 433 | }; |
| 434 | const testLayer = VcsStatusBroadcaster.layer.pipe( | |
| 434 | const layerTest = VcsStatusBroadcaster.layer.pipe( | |
| 435 | 435 | Layer.provideMerge(NodeServices.layer), |
| 436 | Layer.provide(makeBackgroundPolicyLayer(() => true)), | |
| 436 | Layer.provide(layerBackgroundPolicy(() => true)), | |
| 437 | 437 | Layer.provide( |
| 438 | 438 | Layer.mock(GitWorkflowService.GitWorkflowService)({ |
| 439 | 439 | localStatus: () => |
| ... | ... | @@ -490,7 +490,7 @@ describe("VcsStatusBroadcaster", () => { |
| 490 | 490 | |
| 491 | 491 | assert.isTrue(Exit.isFailure(refreshExit)); |
| 492 | 492 | assert.deepStrictEqual(cached, baseStatus); |
| 493 | }).pipe(Effect.provide(testLayer)); | |
| 493 | }).pipe(Effect.provide(layerTest)); | |
| 494 | 494 | }); |
| 495 | 495 | |
| 496 | 496 | it.effect("refreshes only the cached local snapshot when requested", () => { |
| ... | ... | @@ -526,7 +526,7 @@ describe("VcsStatusBroadcaster", () => { |
| 526 | 526 | assert.equal(state.remoteStatusCalls, 1); |
| 527 | 527 | assert.equal(state.localInvalidationCalls, 1); |
| 528 | 528 | assert.equal(state.remoteInvalidationCalls, 0); |
| 529 | }).pipe(Effect.provide(makeTestLayer(state))); | |
| 529 | }).pipe(Effect.provide(layerTestFor(state))); | |
| 530 | 530 | }); |
| 531 | 531 | |
| 532 | 532 | it.effect.skipIf(!symlinksSupported)( |
| ... | ... | @@ -541,9 +541,9 @@ describe("VcsStatusBroadcaster", () => { |
| 541 | 541 | localInvalidationCalls: 0, |
| 542 | 542 | remoteInvalidationCalls: 0, |
| 543 | 543 | }; |
| 544 | const testLayer = VcsStatusBroadcaster.layer.pipe( | |
| 544 | const layerTest = VcsStatusBroadcaster.layer.pipe( | |
| 545 | 545 | Layer.provideMerge(NodeServices.layer), |
| 546 | Layer.provide(makeBackgroundPolicyLayer(() => true)), | |
| 546 | Layer.provide(layerBackgroundPolicy(() => true)), | |
| 547 | 547 | Layer.provide( |
| 548 | 548 | Layer.mock(GitWorkflowService.GitWorkflowService)({ |
| 549 | 549 | localStatus: (input) => |
| ... | ... | @@ -590,7 +590,7 @@ describe("VcsStatusBroadcaster", () => { |
| 590 | 590 | assert.deepStrictEqual(seenCwds, [realPath, realPath]); |
| 591 | 591 | assert.equal(state.localStatusCalls, 1); |
| 592 | 592 | assert.equal(state.remoteStatusCalls, 1); |
| 593 | }).pipe(Effect.provide(testLayer)); | |
| 593 | }).pipe(Effect.provide(layerTest)); | |
| 594 | 594 | }, |
| 595 | 595 | ); |
| 596 | 596 | |
| ... | ... | @@ -631,7 +631,7 @@ describe("VcsStatusBroadcaster", () => { |
| 631 | 631 | _tag: "remoteUpdated", |
| 632 | 632 | remote: baseRemoteStatus, |
| 633 | 633 | } satisfies VcsStatusStreamEvent); |
| 634 | }).pipe(Effect.provide(makeTestLayer(state))); | |
| 634 | }).pipe(Effect.provide(layerTestFor(state))); | |
| 635 | 635 | }); |
| 636 | 636 | |
| 637 | 637 | it.effect("loads remote status once when periodic refreshes are disabled", () => { |
| ... | ... | @@ -687,7 +687,7 @@ describe("VcsStatusBroadcaster", () => { |
| 687 | 687 | assert.equal(state.remoteInvalidationCalls, 0); |
| 688 | 688 | |
| 689 | 689 | yield* Scope.close(scope, Exit.void); |
| 690 | }).pipe(Effect.provide(Layer.merge(makeTestLayer(state), TestClock.layer()))); | |
| 690 | }).pipe(Effect.provide(Layer.merge(layerTestFor(state), TestClock.layer()))); | |
| 691 | 691 | }); |
| 692 | 692 | |
| 693 | 693 | it.effect("retries the initial remote load when periodic refreshes are disabled", () => { |
| ... | ... | @@ -706,9 +706,9 @@ describe("VcsStatusBroadcaster", () => { |
| 706 | 706 | messages.push(message as ReadonlyArray<unknown>); |
| 707 | 707 | }); |
| 708 | 708 | let firstRemoteAttemptDeferred: Deferred.Deferred<void> | null = null; |
| 709 | const testLayer = VcsStatusBroadcaster.layer.pipe( | |
| 709 | const layerTest = VcsStatusBroadcaster.layer.pipe( | |
| 710 | 710 | Layer.provideMerge(NodeServices.layer), |
| 711 | Layer.provide(makeBackgroundPolicyLayer(() => true)), | |
| 711 | Layer.provide(layerBackgroundPolicy(() => true)), | |
| 712 | 712 | Layer.provide( |
| 713 | 713 | Layer.mock(GitWorkflowService.GitWorkflowService)({ |
| 714 | 714 | localStatus: () => |
| ... | ... | @@ -803,7 +803,7 @@ describe("VcsStatusBroadcaster", () => { |
| 803 | 803 | }).pipe( |
| 804 | 804 | Effect.provide( |
| 805 | 805 | Layer.mergeAll( |
| 806 | testLayer, | |
| 806 | layerTest, | |
| 807 | 807 | TestClock.layer(), |
| 808 | 808 | Logger.layer([logger], { mergeWithExisting: false }), |
| 809 | 809 | ), |
| ... | ... | @@ -850,7 +850,7 @@ describe("VcsStatusBroadcaster", () => { |
| 850 | 850 | assert.equal(state.remoteInvalidationCalls, 1); |
| 851 | 851 | |
| 852 | 852 | yield* Scope.close(scope, Exit.void); |
| 853 | }).pipe(Effect.provide(Layer.merge(makeTestLayer(state), TestClock.layer()))); | |
| 853 | }).pipe(Effect.provide(Layer.merge(layerTestFor(state), TestClock.layer()))); | |
| 854 | 854 | }); |
| 855 | 855 | |
| 856 | 856 | // A push from a terminal moves ahead; a PR merged on the host moves ahead-of-default. |
| ... | ... | @@ -901,11 +901,11 @@ describe("VcsStatusBroadcaster", () => { |
| 901 | 901 | local: pushedLocal, |
| 902 | 902 | } satisfies VcsStatusStreamEvent); |
| 903 | 903 | yield* Scope.close(scope, Exit.void); |
| 904 | }).pipe(Effect.provide(Layer.merge(makeTestLayer(state), TestClock.layer()))); | |
| 904 | }).pipe(Effect.provide(Layer.merge(layerTestFor(state), TestClock.layer()))); | |
| 905 | 905 | }); |
| 906 | 906 | |
| 907 | 907 | it.effect("an explicit refresh reads local totals after the fetch", () => { |
| 908 | const state: Parameters<typeof makeTestLayer>[0] = { | |
| 908 | const state: Parameters<typeof layerTestFor>[0] = { | |
| 909 | 909 | currentLocalStatus: baseLocalStatus, |
| 910 | 910 | currentRemoteStatus: baseRemoteStatus, |
| 911 | 911 | localStatusCalls: 0, |
| ... | ... | @@ -934,7 +934,7 @@ describe("VcsStatusBroadcaster", () => { |
| 934 | 934 | |
| 935 | 935 | const status = yield* Fiber.join(refresh); |
| 936 | 936 | assert.deepStrictEqual(status.branchChanges, fetchedLocal.branchChanges); |
| 937 | }).pipe(Effect.provide(makeTestLayer(state))); | |
| 937 | }).pipe(Effect.provide(layerTestFor(state))); | |
| 938 | 938 | }); |
| 939 | 939 | |
| 940 | 940 | it("backs off remote refresh failures exponentially and honors larger configured intervals", () => { |
| ... | ... | @@ -990,9 +990,9 @@ describe("VcsStatusBroadcaster", () => { |
| 990 | 990 | localInvalidationCalls: 0, |
| 991 | 991 | remoteInvalidationCalls: 0, |
| 992 | 992 | }; |
| 993 | const testLayer = VcsStatusBroadcaster.layer.pipe( | |
| 993 | const layerTest = VcsStatusBroadcaster.layer.pipe( | |
| 994 | 994 | Layer.provideMerge(NodeServices.layer), |
| 995 | Layer.provide(makeBackgroundPolicyLayer(() => false)), | |
| 995 | Layer.provide(layerBackgroundPolicy(() => false)), | |
| 996 | 996 | Layer.provide( |
| 997 | 997 | Layer.mock(GitWorkflowService.GitWorkflowService)({ |
| 998 | 998 | localStatus: () => |
| ... | ... | @@ -1029,7 +1029,7 @@ describe("VcsStatusBroadcaster", () => { |
| 1029 | 1029 | assert.isTrue(Option.isSome(snapshot)); |
| 1030 | 1030 | assert.equal(state.remoteStatusCalls, 0); |
| 1031 | 1031 | assert.equal(state.remoteInvalidationCalls, 0); |
| 1032 | }).pipe(Effect.provide(testLayer)); | |
| 1032 | }).pipe(Effect.provide(layerTest)); | |
| 1033 | 1033 | }); |
| 1034 | 1034 | |
| 1035 | 1035 | it.effect("stops the remote poller after the last stream subscriber disconnects", () => { |
| ... | ... | @@ -1043,9 +1043,9 @@ describe("VcsStatusBroadcaster", () => { |
| 1043 | 1043 | }; |
| 1044 | 1044 | let remoteInterruptedDeferred: Deferred.Deferred<void, never> | null = null; |
| 1045 | 1045 | let remoteStartedDeferred: Deferred.Deferred<void, never> | null = null; |
| 1046 | const testLayer = VcsStatusBroadcaster.layer.pipe( | |
| 1046 | const layerTest = VcsStatusBroadcaster.layer.pipe( | |
| 1047 | 1047 | Layer.provideMerge(NodeServices.layer), |
| 1048 | Layer.provide(makeBackgroundPolicyLayer(() => true)), | |
| 1048 | Layer.provide(layerBackgroundPolicy(() => true)), | |
| 1049 | 1049 | Layer.provide( |
| 1050 | 1050 | Layer.mock(GitWorkflowService.GitWorkflowService)({ |
| 1051 | 1051 | localStatus: () => |
| ... | ... | @@ -1115,6 +1115,6 @@ describe("VcsStatusBroadcaster", () => { |
| 1115 | 1115 | yield* Scope.close(secondScope, Exit.void).pipe(Effect.forkScoped); |
| 1116 | 1116 | yield* Deferred.await(remoteInterrupted); |
| 1117 | 1117 | assert.isTrue(Option.isSome(yield* Deferred.poll(remoteInterrupted))); |
| 1118 | }).pipe(Effect.provide(testLayer)); | |
| 1118 | }).pipe(Effect.provide(layerTest)); | |
| 1119 | 1119 | }); |
| 1120 | 1120 | }); |
apps/server/src/vcs/VcsStatusBroadcaster.ts+1-1| ... | ... | @@ -149,7 +149,7 @@ export class VcsAutoPullPolicy extends Context.Reference<{ |
| 149 | 149 | defaultValue: () => ({ isEnabled: () => Effect.succeed(false) }), |
| 150 | 150 | }) {} |
| 151 | 151 | |
| 152 | export const autoPullPolicyLayer = Layer.effect( | |
| 152 | export const layerAutoPullPolicy = Layer.effect( | |
| 153 | 153 | VcsAutoPullPolicy, |
| 154 | 154 | Effect.gen(function* () { |
| 155 | 155 | const projects = yield* ProjectStore.ProjectStoreV2; |
apps/server/src/workspace/WorkspaceEntries.test.ts+2-2| ... | ... | @@ -21,7 +21,7 @@ vi.mock("node:fs/promises", async (importOriginal) => { |
| 21 | 21 | return { ...actual, readdir: vi.fn(actual.readdir) }; |
| 22 | 22 | }); |
| 23 | 23 | |
| 24 | const TestLayer = Layer.empty.pipe( | |
| 24 | const layerTest = Layer.empty.pipe( | |
| 25 | 25 | Layer.provideMerge(WorkspaceEntries.layer.pipe(Layer.provide(WorkspacePaths.layer))), |
| 26 | 26 | Layer.provideMerge(WorkspacePaths.layer), |
| 27 | 27 | Layer.provideMerge(VcsProcess.layer), |
| ... | ... | @@ -90,7 +90,7 @@ const appendSeparator = (input: string) => |
| 90 | 90 | : `${input}${platform === "win32" ? "\\" : "/"}`, |
| 91 | 91 | ); |
| 92 | 92 | |
| 93 | it.layer(TestLayer, { excludeTestServices: true })("WorkspaceEntries", (it) => { | |
| 93 | it.layer(layerTest, { excludeTestServices: true })("WorkspaceEntries", (it) => { | |
| 94 | 94 | afterEach(() => { |
| 95 | 95 | vi.restoreAllMocks(); |
| 96 | 96 | }); |
apps/server/src/workspace/WorkspaceFileSystem.test.ts+4-4| ... | ... | @@ -17,13 +17,13 @@ import * as WorkspacePaths from "./WorkspacePaths.ts"; |
| 17 | 17 | import { HostProcessPlatform } from "@t3tools/shared/hostProcess"; |
| 18 | 18 | import { symlinksSupported } from "@t3tools/shared/testing/symlinks"; |
| 19 | 19 | |
| 20 | const ProjectLayer = WorkspaceFileSystem.layer.pipe( | |
| 20 | const layerProject = WorkspaceFileSystem.layer.pipe( | |
| 21 | 21 | Layer.provide(WorkspacePaths.layer), |
| 22 | 22 | Layer.provide(WorkspaceEntries.layer.pipe(Layer.provide(WorkspacePaths.layer))), |
| 23 | 23 | ); |
| 24 | 24 | |
| 25 | const TestLayer = Layer.empty.pipe( | |
| 26 | Layer.provideMerge(ProjectLayer), | |
| 25 | const layerTest = Layer.empty.pipe( | |
| 26 | Layer.provideMerge(layerProject), | |
| 27 | 27 | Layer.provideMerge(WorkspaceEntries.layer.pipe(Layer.provide(WorkspacePaths.layer))), |
| 28 | 28 | Layer.provideMerge(WorkspacePaths.layer), |
| 29 | 29 | Layer.provideMerge(VcsDriverRegistry.layer.pipe(Layer.provide(VcsProcess.layer))), |
| ... | ... | @@ -56,7 +56,7 @@ const writeTextFile = Effect.fn("writeTextFile")(function* ( |
| 56 | 56 | yield* fileSystem.writeFileString(absolutePath, contents).pipe(Effect.orDie); |
| 57 | 57 | }); |
| 58 | 58 | |
| 59 | it.layer(TestLayer, { excludeTestServices: true })("WorkspaceFileSystemLive", (it) => { | |
| 59 | it.layer(layerTest, { excludeTestServices: true })("WorkspaceFileSystemLive", (it) => { | |
| 60 | 60 | describe("readFile", () => { |
| 61 | 61 | it.effect("reads UTF-8 files relative to the workspace root", () => |
| 62 | 62 | Effect.gen(function* () { |
apps/server/src/workspace/WorkspacePaths.test.ts+2-2| ... | ... | @@ -8,7 +8,7 @@ import * as PlatformError from "effect/PlatformError"; |
| 8 | 8 | |
| 9 | 9 | import * as WorkspacePaths from "./WorkspacePaths.ts"; |
| 10 | 10 | |
| 11 | const TestLayer = Layer.empty.pipe( | |
| 11 | const layerTest = Layer.empty.pipe( | |
| 12 | 12 | Layer.provideMerge(WorkspacePaths.layer), |
| 13 | 13 | Layer.provideMerge(NodeServices.layer), |
| 14 | 14 | ); |
| ... | ... | @@ -34,7 +34,7 @@ const writeTextFile = Effect.fn("writeTextFile")(function* ( |
| 34 | 34 | yield* fileSystem.writeFileString(absolutePath, contents).pipe(Effect.orDie); |
| 35 | 35 | }); |
| 36 | 36 | |
| 37 | it.layer(TestLayer)("WorkspacePathsLive", (it) => { | |
| 37 | it.layer(layerTest)("WorkspacePathsLive", (it) => { | |
| 38 | 38 | describe("normalizeWorkspaceRoot", () => { |
| 39 | 39 | it.effect("resolves an existing directory", () => |
| 40 | 40 | Effect.gen(function* () { |
apps/server/src/ws.ts+6-14| ... | ... | @@ -212,11 +212,8 @@ import * as DirectEndpoints from "./environment/DirectEndpoints.ts"; |
| 212 | 212 | import * as RemoteOpenTargets from "./environment/RemoteOpenTargets.ts"; |
| 213 | 213 | import * as BackgroundPolicy from "./background/BackgroundPolicy.ts"; |
| 214 | 214 | import * as EnvironmentAuth from "./auth/EnvironmentAuth.ts"; |
| 215 | import { | |
| 216 | requiredScopeForDeviceList, | |
| 217 | rpcAuthorizationError, | |
| 218 | rpcScopeAuthorizationLayer, | |
| 219 | } from "./auth/RpcAuthorization.ts"; | |
| 215 | import { requiredScopeForDeviceList, rpcAuthorizationError } from "./auth/RpcAuthorization.ts"; | |
| 216 | import * as RpcAuthorization from "./auth/RpcAuthorization.ts"; | |
| 220 | 217 | import * as ProcessDiagnostics from "./diagnostics/ProcessDiagnostics.ts"; |
| 221 | 218 | import * as ProcessResourceMonitor from "./diagnostics/ProcessResourceMonitor.ts"; |
| 222 | 219 | import * as ResourceTelemetry from "./resourceTelemetry/ResourceTelemetry.ts"; |
| ... | ... | @@ -1181,7 +1178,7 @@ export const subscribeOrchestrationV2Shell = Effect.fn("ws.orchestrationV2.subsc |
| 1181 | 1178 | }, |
| 1182 | 1179 | ); |
| 1183 | 1180 | |
| 1184 | const makeWsRpcLayer = ( | |
| 1181 | const layerWsRpc = ( | |
| 1185 | 1182 | currentSession: EnvironmentAuth.AuthenticatedSession, |
| 1186 | 1183 | clientOrigin: OrchestrationClientOrigin, |
| 1187 | 1184 | clientAnalyticsProps: Readonly<Record<string, unknown>>, |
| ... | ... | @@ -3796,7 +3793,7 @@ const makeWsRpcLayer = ( |
| 3796 | 3793 | }), |
| 3797 | 3794 | ); |
| 3798 | 3795 | |
| 3799 | export const websocketRpcRouteLayer = Layer.unwrap( | |
| 3796 | export const layer = Layer.unwrap( | |
| 3800 | 3797 | Effect.gen(function* () { |
| 3801 | 3798 | const previewAutomationBroker = yield* PreviewAutomationBroker.PreviewAutomationBroker; |
| 3802 | 3799 | const serverSelfUpdate = yield* ServerSelfUpdate.ServerSelfUpdate; |
| ... | ... | @@ -3840,19 +3837,14 @@ export const websocketRpcRouteLayer = Layer.unwrap( |
| 3840 | 3837 | const { protocol, httpEffect } = yield* RpcServer.makeProtocolWithHttpEffectWebsocket; |
| 3841 | 3838 | yield* RpcServer.make(ServerWsRpcGroup, { disableTracing: true }).pipe( |
| 3842 | 3839 | Effect.provideService(RpcServer.Protocol, withTerminalOutputWindow(protocol)), |
| 3843 | Effect.provide(rpcScopeAuthorizationLayer(session.scopes)), | |
| 3840 | Effect.provide(RpcAuthorization.layer(session.scopes)), | |
| 3844 | 3841 | Effect.forkScoped, |
| 3845 | 3842 | ); |
| 3846 | 3843 | // @effect-diagnostics-next-line returnEffectInGen:off |
| 3847 | 3844 | return httpEffect; |
| 3848 | 3845 | }).pipe( |
| 3849 | 3846 | Effect.provide( |
| 3850 | makeWsRpcLayer( | |
| 3851 | session, | |
| 3852 | clientOrigin, | |
| 3853 | clientAnalyticsProps, | |
| 3854 | previewAutomationBroker, | |
| 3855 | ).pipe( | |
| 3847 | layerWsRpc(session, clientOrigin, clientAnalyticsProps, previewAutomationBroker).pipe( | |
| 3856 | 3848 | Layer.provideMerge(RpcSerialization.layerJson), |
| 3857 | 3849 | Layer.provide(Layer.succeed(SqlClient.SqlClient, sql)), |
| 3858 | 3850 | Layer.provide(AgentSessionScanner.layer), |
apps/web/src/cloud/dpop.test.ts+3-2| ... | ... | @@ -4,7 +4,8 @@ import * as Effect from "effect/Effect"; |
| 4 | 4 | import { decodeJwt } from "jose"; |
| 5 | 5 | import { vi } from "vite-plus/test"; |
| 6 | 6 | |
| 7 | import { browserCryptoLayer, createBrowserDpopProof, generateBrowserDpopKey } from "./dpop"; | |
| 7 | import { createBrowserDpopProof, generateBrowserDpopKey } from "./dpop"; | |
| 8 | import * as Dpop from "./dpop"; | |
| 8 | 9 | |
| 9 | 10 | describe("browser DPoP proofs", () => { |
| 10 | 11 | it.effect("signs relay resource proofs with an access-token hash", () => |
| ... | ... | @@ -16,7 +17,7 @@ describe("browser DPoP proofs", () => { |
| 16 | 17 | url: "https://relay.example.test/v1/environments/env-1/connect?ignored=true", |
| 17 | 18 | accessToken: "relay-access-token", |
| 18 | 19 | proofKey, |
| 19 | }).pipe(Effect.provide(browserCryptoLayer)); | |
| 20 | }).pipe(Effect.provide(Dpop.layer)); | |
| 20 | 21 | const issuedAt = decodeJwt(proof.proof).iat; |
| 21 | 22 | expect(issuedAt).toBeTypeOf("number"); |
| 22 | 23 |
apps/web/src/cloud/dpop.ts+1-1| ... | ... | @@ -27,7 +27,7 @@ const DPOP_KEY_STORE_NAME = "keys"; |
| 27 | 27 | const DPOP_KEY_ID = "relay-dpop-proof-key"; |
| 28 | 28 | const decodeDpopPublicJwk = Schema.decodeUnknownEffect(DpopPublicJwk); |
| 29 | 29 | |
| 30 | export const browserCryptoLayer = Layer.succeed( | |
| 30 | export const layer = Layer.succeed( | |
| 31 | 31 | Crypto.Crypto, |
| 32 | 32 | Crypto.make({ |
| 33 | 33 | randomBytes: (size) => globalThis.crypto.getRandomValues(new Uint8Array(size)), |
apps/web/src/cloud/linkEnvironment.test.ts+11-11| ... | ... | @@ -22,7 +22,7 @@ import { |
| 22 | 22 | import { type RpcSession } from "@t3tools/client-runtime/rpc"; |
| 23 | 23 | import { EnvironmentRegistry } from "@t3tools/client-runtime/connection"; |
| 24 | 24 | import { ManagedRelay } from "@t3tools/client-runtime/relay"; |
| 25 | import { remoteHttpClientLayer } from "@t3tools/client-runtime/rpc"; | |
| 25 | import { layerRemoteHttpClient } from "@t3tools/client-runtime/rpc"; | |
| 26 | 26 | import { __resetDesktopPrimaryAuthForTests } from "../environments/primary/desktopAuth"; |
| 27 | 27 | |
| 28 | 28 | import { |
| ... | ... | @@ -53,7 +53,7 @@ vi.mock("./relayClientInstallDialog", () => ({ |
| 53 | 53 | })); |
| 54 | 54 | |
| 55 | 55 | const createProof = vi.fn(() => Effect.succeed("dpop-proof")); |
| 56 | const dpopSignerLayer = Layer.succeed( | |
| 56 | const layerDpopSigner = Layer.succeed( | |
| 57 | 57 | ManagedRelay.ManagedRelayDpopSigner, |
| 58 | 58 | ManagedRelay.ManagedRelayDpopSigner.of({ |
| 59 | 59 | thumbprint: Effect.succeed("thumbprint"), |
| ... | ... | @@ -61,18 +61,18 @@ const dpopSignerLayer = Layer.succeed( |
| 61 | 61 | }), |
| 62 | 62 | ); |
| 63 | 63 | |
| 64 | function relayLayer() { | |
| 65 | const http = remoteHttpClientLayer(globalThis.fetch); | |
| 64 | function layerRelay() { | |
| 65 | const layerHttp = layerRemoteHttpClient(globalThis.fetch); | |
| 66 | 66 | return Layer.mergeAll( |
| 67 | http, | |
| 67 | layerHttp, | |
| 68 | 68 | ManagedRelay.layer({ |
| 69 | 69 | relayUrl: "https://relay.example.test", |
| 70 | 70 | clientId: RelayWebClientId, |
| 71 | }).pipe(Layer.provideMerge(dpopSignerLayer), Layer.provide(http)), | |
| 71 | }).pipe(Layer.provideMerge(layerDpopSigner), Layer.provide(layerHttp)), | |
| 72 | 72 | ); |
| 73 | 73 | } |
| 74 | 74 | |
| 75 | function registryLayer(options?: { | |
| 75 | function layerRegistry(options?: { | |
| 76 | 76 | readonly status?: { readonly status: "available"; readonly version: string }; |
| 77 | 77 | readonly installEvents?: ReadonlyArray<RelayClientInstallProgressEvent>; |
| 78 | 78 | }) { |
| ... | ... | @@ -119,8 +119,8 @@ function registryLayer(options?: { |
| 119 | 119 | ); |
| 120 | 120 | } |
| 121 | 121 | |
| 122 | function services(options?: Parameters<typeof registryLayer>[0]) { | |
| 123 | return Layer.mergeAll(relayLayer(), registryLayer(options)); | |
| 122 | function layerServices(options?: Parameters<typeof layerRegistry>[0]) { | |
| 123 | return Layer.mergeAll(layerRelay(), layerRegistry(options)); | |
| 124 | 124 | } |
| 125 | 125 | |
| 126 | 126 | function withServices<A, E>( |
| ... | ... | @@ -131,9 +131,9 @@ function withServices<A, E>( |
| 131 | 131 | | ManagedRelay.ManagedRelayClient |
| 132 | 132 | | EnvironmentRegistry.EnvironmentRegistry |
| 133 | 133 | >, |
| 134 | options?: Parameters<typeof registryLayer>[0], | |
| 134 | options?: Parameters<typeof layerRegistry>[0], | |
| 135 | 135 | ) { |
| 136 | return effect.pipe(Effect.provide(services(options))); | |
| 136 | return effect.pipe(Effect.provide(layerServices(options))); | |
| 137 | 137 | } |
| 138 | 138 | |
| 139 | 139 | function bodyText(body: BodyInit | null | undefined): string { |
apps/web/src/cloud/linkEnvironment.ts+5-5| ... | ... | @@ -24,7 +24,7 @@ import { request, runStream } from "@t3tools/client-runtime/rpc"; |
| 24 | 24 | import { makeEnvironmentHttpApiClient } from "@t3tools/client-runtime/rpc"; |
| 25 | 25 | import { ManagedRelay, relayProtectedErrorMessage } from "@t3tools/client-runtime/relay"; |
| 26 | 26 | |
| 27 | import { primaryEnvironmentHttpLayer } from "../environments/primary/httpLayer"; | |
| 27 | import * as PrimaryEnvironmentHttpLayer from "../environments/primary/httpLayer"; | |
| 28 | 28 | import { resolveCloudPublicConfig } from "./publicConfig"; |
| 29 | 29 | import { |
| 30 | 30 | finishRelayClientInstall, |
| ... | ... | @@ -193,7 +193,7 @@ export function readPrimaryCloudLinkState(input: { |
| 193 | 193 | return yield* client.connect |
| 194 | 194 | .linkState({ headers: {} }) |
| 195 | 195 | .pipe(Effect.mapError(environmentApiError("Could not read environment cloud link state."))); |
| 196 | }).pipe(Effect.provide(primaryEnvironmentHttpLayer)); | |
| 196 | }).pipe(Effect.provide(PrimaryEnvironmentHttpLayer.layer)); | |
| 197 | 197 | } |
| 198 | 198 | |
| 199 | 199 | export function updatePrimaryCloudPreferences(input: { |
| ... | ... | @@ -212,7 +212,7 @@ export function updatePrimaryCloudPreferences(input: { |
| 212 | 212 | .pipe( |
| 213 | 213 | Effect.mapError(environmentApiError("Could not update environment cloud preferences.")), |
| 214 | 214 | ); |
| 215 | }).pipe(Effect.provide(primaryEnvironmentHttpLayer)); | |
| 215 | }).pipe(Effect.provide(PrimaryEnvironmentHttpLayer.layer)); | |
| 216 | 216 | } |
| 217 | 217 | |
| 218 | 218 | export function unlinkPrimaryEnvironmentFromCloud(input: { |
| ... | ... | @@ -245,7 +245,7 @@ export function unlinkPrimaryEnvironmentFromCloud(input: { |
| 245 | 245 | ), |
| 246 | 246 | ); |
| 247 | 247 | } |
| 248 | }).pipe(Effect.provide(primaryEnvironmentHttpLayer)); | |
| 248 | }).pipe(Effect.provide(PrimaryEnvironmentHttpLayer.layer)); | |
| 249 | 249 | } |
| 250 | 250 | |
| 251 | 251 | // "publish_only" links the environment to the relay for agent-activity |
| ... | ... | @@ -346,5 +346,5 @@ export function linkPrimaryEnvironmentToCloud(input: { |
| 346 | 346 | }, |
| 347 | 347 | }) |
| 348 | 348 | .pipe(Effect.mapError(environmentApiError("Could not configure environment relay access."))); |
| 349 | }).pipe(Effect.provide(primaryEnvironmentHttpLayer)); | |
| 349 | }).pipe(Effect.provide(PrimaryEnvironmentHttpLayer.layer)); | |
| 350 | 350 | } |
apps/web/src/cloud/managedRelayLayer.ts+3-3| ... | ... | @@ -13,7 +13,7 @@ import { |
| 13 | 13 | type BrowserDpopKey, |
| 14 | 14 | } from "./dpop"; |
| 15 | 15 | |
| 16 | const relayDpopSignerLayer = Layer.effect( | |
| 16 | const layerRelayDpopSigner = Layer.effect( | |
| 17 | 17 | ManagedRelay.ManagedRelayDpopSigner, |
| 18 | 18 | Effect.gen(function* () { |
| 19 | 19 | const crypto = yield* Crypto.Crypto; |
| ... | ... | @@ -76,7 +76,7 @@ const relayDpopSignerLayer = Layer.effect( |
| 76 | 76 | }), |
| 77 | 77 | ); |
| 78 | 78 | |
| 79 | export const managedRelayClientLayer = (relayUrl: string) => | |
| 79 | export const layer = (relayUrl: string) => | |
| 80 | 80 | ManagedRelay.layer({ relayUrl, clientId: RelayWebClientId }).pipe( |
| 81 | Layer.provideMerge(relayDpopSignerLayer), | |
| 81 | Layer.provideMerge(layerRelayDpopSigner), | |
| 82 | 82 | ); |
apps/web/src/connection/platform.ts+28-25| ... | ... | @@ -41,7 +41,7 @@ import { FetchHttpClient } from "effect/http"; |
| 41 | 41 | |
| 42 | 42 | import { APP_VERSION } from "../branding"; |
| 43 | 43 | import { readDesktopPrimaryBearerToken } from "../environments/primary/desktopAuth"; |
| 44 | import { primaryEnvironmentHttpLayer } from "../environments/primary/httpLayer"; | |
| 44 | import * as PrimaryEnvironmentHttpLayer from "../environments/primary/httpLayer"; | |
| 45 | 45 | import { |
| 46 | 46 | readPrimaryEnvironmentTarget, |
| 47 | 47 | type PrimaryEnvironmentTarget, |
| ... | ... | @@ -56,7 +56,7 @@ import { |
| 56 | 56 | readDesktopSecondaryBootstrapsResult, |
| 57 | 57 | type DesktopSecondaryBootstrapsRead, |
| 58 | 58 | } from "./desktopLocal"; |
| 59 | import { connectionStorageLayer } from "./storage"; | |
| 59 | import * as ConnectionStorage from "./storage"; | |
| 60 | 60 | import { clientPresentationMetadata } from "./clientMetadata"; |
| 61 | 61 | |
| 62 | 62 | let nextObservedRpcRequestId = 0; |
| ... | ... | @@ -68,7 +68,7 @@ function currentNetworkStatus(): "unknown" | "offline" | "online" { |
| 68 | 68 | return navigator.onLine ? "online" : "offline"; |
| 69 | 69 | } |
| 70 | 70 | |
| 71 | const connectivityLayer = Connectivity.layer({ | |
| 71 | const layerConnectivity = Connectivity.layer({ | |
| 72 | 72 | status: Effect.sync(currentNetworkStatus), |
| 73 | 73 | changes: Stream.callback((queue) => |
| 74 | 74 | Effect.acquireRelease( |
| ... | ... | @@ -124,7 +124,7 @@ const networkPathChanges = Stream.callback<"network-changed">((queue) => |
| 124 | 124 | ).pipe(Effect.asVoid), |
| 125 | 125 | ); |
| 126 | 126 | |
| 127 | const wakeupsLayer = Wakeups.layer({ | |
| 127 | const layerWakeups = Wakeups.layer({ | |
| 128 | 128 | changes: Stream.mergeAll( |
| 129 | 129 | [ |
| 130 | 130 | Stream.callback<"application-active">((queue) => |
| ... | ... | @@ -223,7 +223,7 @@ export const provisionDesktopSshEnvironment = Effect.fn( |
| 223 | 223 | }; |
| 224 | 224 | }); |
| 225 | 225 | |
| 226 | const capabilitiesLayer = Layer.effectContext( | |
| 226 | const layerCapabilities = Layer.effectContext( | |
| 227 | 227 | Effect.sync(() => { |
| 228 | 228 | const presentation = ClientCapabilities.ClientPresentation.of({ |
| 229 | 229 | metadata: clientMetadata(), |
| ... | ... | @@ -346,7 +346,10 @@ const loadPrimaryConnectionRegistration = Effect.fn( |
| 346 | 346 | )(function* (resolved: PrimaryEnvironmentTarget) { |
| 347 | 347 | const descriptor = yield* fetchRemoteEnvironmentDescriptor({ |
| 348 | 348 | httpBaseUrl: resolved.target.httpBaseUrl, |
| 349 | }).pipe(Effect.provide(primaryEnvironmentHttpLayer), Effect.mapError(mapRemoteEnvironmentError)); | |
| 349 | }).pipe( | |
| 350 | Effect.provide(PrimaryEnvironmentHttpLayer.layer), | |
| 351 | Effect.mapError(mapRemoteEnvironmentError), | |
| 352 | ); | |
| 350 | 353 | return new PrimaryConnectionRegistration({ |
| 351 | 354 | target: new PrimaryConnectionTarget({ |
| 352 | 355 | environmentId: descriptor.environmentId, |
| ... | ... | @@ -511,7 +514,7 @@ export function secondaryRegistrationsToRetainAfterTopologyRead( |
| 511 | 514 | ); |
| 512 | 515 | } |
| 513 | 516 | |
| 514 | const platformConnectionSourceLayer = Layer.effect( | |
| 517 | const layerPlatformConnectionSource = Layer.effect( | |
| 515 | 518 | PlatformConnectionSource.PlatformConnectionSource, |
| 516 | 519 | Effect.gen(function* () { |
| 517 | 520 | if (isHostedStaticApp() || isLocalEnvironmentDisabled()) { |
| ... | ... | @@ -631,7 +634,7 @@ const platformConnectionSourceLayer = Layer.effect( |
| 631 | 634 | }), |
| 632 | 635 | ); |
| 633 | 636 | |
| 634 | const environmentOwnedDataCleanupLayer = Layer.succeed( | |
| 637 | const layerEnvironmentOwnedDataCleanup = Layer.succeed( | |
| 635 | 638 | Persistence.EnvironmentOwnedDataCleanup, |
| 636 | 639 | Persistence.EnvironmentOwnedDataCleanup.of({ |
| 637 | 640 | clear: (environmentId) => |
| ... | ... | @@ -641,7 +644,7 @@ const environmentOwnedDataCleanupLayer = Layer.succeed( |
| 641 | 644 | }), |
| 642 | 645 | ); |
| 643 | 646 | |
| 644 | const rpcRequestObserverLayer = Layer.succeed( | |
| 647 | const layerRpcRequestObserver = Layer.succeed( | |
| 645 | 648 | EnvironmentRpcRequestObserver, |
| 646 | 649 | EnvironmentRpcRequestObserver.of({ |
| 647 | 650 | observe: ({ environmentId, method }) => |
| ... | ... | @@ -657,24 +660,24 @@ const rpcRequestObserverLayer = Layer.succeed( |
| 657 | 660 | ); |
| 658 | 661 | |
| 659 | 662 | type ConnectionPlatformLayerSource = |
| 660 | | typeof connectionStorageLayer | |
| 661 | | typeof connectivityLayer | |
| 662 | | typeof wakeupsLayer | |
| 663 | | typeof capabilitiesLayer | |
| 664 | | typeof platformConnectionSourceLayer | |
| 665 | | typeof environmentOwnedDataCleanupLayer | |
| 666 | | typeof rpcRequestObserverLayer; | |
| 667 | ||
| 668 | export const connectionPlatformLayer: Layer.Layer< | |
| 663 | | typeof ConnectionStorage.layer | |
| 664 | | typeof layerConnectivity | |
| 665 | | typeof layerWakeups | |
| 666 | | typeof layerCapabilities | |
| 667 | | typeof layerPlatformConnectionSource | |
| 668 | | typeof layerEnvironmentOwnedDataCleanup | |
| 669 | | typeof layerRpcRequestObserver; | |
| 670 | ||
| 671 | export const layer: Layer.Layer< | |
| 669 | 672 | Layer.Success<ConnectionPlatformLayerSource>, |
| 670 | 673 | Layer.Error<ConnectionPlatformLayerSource>, |
| 671 | 674 | Layer.Services<ConnectionPlatformLayerSource> |
| 672 | 675 | > = Layer.mergeAll( |
| 673 | connectionStorageLayer, | |
| 674 | connectivityLayer, | |
| 675 | wakeupsLayer, | |
| 676 | capabilitiesLayer, | |
| 677 | platformConnectionSourceLayer, | |
| 678 | environmentOwnedDataCleanupLayer, | |
| 679 | rpcRequestObserverLayer, | |
| 676 | ConnectionStorage.layer, | |
| 677 | layerConnectivity, | |
| 678 | layerWakeups, | |
| 679 | layerCapabilities, | |
| 680 | layerPlatformConnectionSource, | |
| 681 | layerEnvironmentOwnedDataCleanup, | |
| 682 | layerRpcRequestObserver, | |
| 680 | 683 | ); |
apps/web/src/connection/runtime.ts+19-24| ... | ... | @@ -1,26 +1,21 @@ |
| 1 | 1 | import { Connection } from "@t3tools/client-runtime/connection"; |
| 2 | 2 | import { ShellSnapshotLoader } from "@t3tools/client-runtime/state/shell"; |
| 3 | 3 | import { |
| 4 | boundedThreadSnapshotLoaderLayer, | |
| 4 | BoundedThreadSnapshotLoader, | |
| 5 | 5 | ThreadHistoryController, |
| 6 | 6 | } from "@t3tools/client-runtime/state/threads"; |
| 7 | 7 | import { PullRequestDiffLoader } from "@t3tools/client-runtime/state/pull-requests"; |
| 8 | 8 | import * as Layer from "effect/Layer"; |
| 9 | 9 | import { Atom } from "effect/reactivity"; |
| 10 | 10 | |
| 11 | import { runtimeContextLayer } from "../lib/runtime"; | |
| 12 | import { | |
| 13 | backgroundActivityObserverLayer, | |
| 14 | backgroundActivityReporterLayer, | |
| 15 | } from "../lib/backgroundActivityReporter"; | |
| 16 | import { connectionPlatformLayer } from "./platform"; | |
| 11 | import * as Runtime from "../lib/runtime"; | |
| 12 | import * as BackgroundActivityReporter from "../lib/backgroundActivityReporter"; | |
| 13 | import * as ConnectionPlatform from "./platform"; | |
| 17 | 14 | |
| 18 | const providedConnectionPlatformLayer = connectionPlatformLayer.pipe( | |
| 19 | Layer.provide(runtimeContextLayer), | |
| 20 | ); | |
| 15 | const layerProvidedConnectionPlatform = ConnectionPlatform.layer.pipe(Layer.provide(Runtime.layer)); | |
| 21 | 16 | |
| 22 | const snapshotLoaderLayer = Layer.mergeAll( | |
| 23 | boundedThreadSnapshotLoaderLayer, | |
| 17 | const layerSnapshotLoader = Layer.mergeAll( | |
| 18 | BoundedThreadSnapshotLoader.layer, | |
| 24 | 19 | ShellSnapshotLoader.layer, |
| 25 | 20 | ThreadHistoryController.layer, |
| 26 | 21 | PullRequestDiffLoader.layer, |
| ... | ... | @@ -28,13 +23,13 @@ const snapshotLoaderLayer = Layer.mergeAll( |
| 28 | 23 | |
| 29 | 24 | type ConnectionLayerSource = |
| 30 | 25 | | typeof Connection.layer |
| 31 | | typeof snapshotLoaderLayer | |
| 32 | | typeof runtimeContextLayer | |
| 33 | | typeof connectionPlatformLayer | |
| 34 | | typeof backgroundActivityObserverLayer | |
| 35 | | typeof backgroundActivityReporterLayer; | |
| 26 | | typeof layerSnapshotLoader | |
| 27 | | typeof Runtime.layer | |
| 28 | | typeof ConnectionPlatform.layer | |
| 29 | | typeof BackgroundActivityReporter.layerObserver | |
| 30 | | typeof BackgroundActivityReporter.layer; | |
| 36 | 31 | |
| 37 | const providedClientConnectionLayer = snapshotLoaderLayer.pipe( | |
| 32 | const layerProvidedClientConnection = layerSnapshotLoader.pipe( | |
| 38 | 33 | Layer.provideMerge( |
| 39 | 34 | Connection.layerWithOptions({ |
| 40 | 35 | environmentThemes: true, |
| ... | ... | @@ -44,18 +39,18 @@ const providedClientConnectionLayer = snapshotLoaderLayer.pipe( |
| 44 | 39 | ), |
| 45 | 40 | Layer.provideMerge( |
| 46 | 41 | Layer.mergeAll( |
| 47 | runtimeContextLayer, | |
| 48 | providedConnectionPlatformLayer, | |
| 49 | backgroundActivityObserverLayer, | |
| 42 | Runtime.layer, | |
| 43 | layerProvidedConnectionPlatform, | |
| 44 | BackgroundActivityReporter.layerObserver, | |
| 50 | 45 | ), |
| 51 | 46 | ), |
| 52 | 47 | ); |
| 53 | 48 | |
| 54 | const connectionLayer = backgroundActivityReporterLayer.pipe( | |
| 55 | Layer.provideMerge(providedClientConnectionLayer), | |
| 49 | const layerConnection = BackgroundActivityReporter.layer.pipe( | |
| 50 | Layer.provideMerge(layerProvidedClientConnection), | |
| 56 | 51 | ); |
| 57 | 52 | |
| 58 | 53 | export const connectionAtomRuntime: Atom.AtomRuntime< |
| 59 | 54 | Layer.Success<ConnectionLayerSource>, |
| 60 | 55 | Layer.Error<ConnectionLayerSource> |
| 61 | > = Atom.runtime(connectionLayer); | |
| 56 | > = Atom.runtime(layerConnection); |
apps/web/src/connection/storage.ts+1-1| ... | ... | @@ -457,7 +457,7 @@ export function makeBrowserGitHubRoutingPermissions( |
| 457 | 457 | }); |
| 458 | 458 | } |
| 459 | 459 | |
| 460 | export const connectionStorageLayer = Layer.effectContext( | |
| 460 | export const layer = Layer.effectContext( | |
| 461 | 461 | Effect.gen(function* () { |
| 462 | 462 | const database = yield* Effect.acquireRelease(openDatabase(), (database) => |
| 463 | 463 | Effect.sync(() => database.close()), |
apps/web/src/environments/primary/httpLayer.test.ts+3-3| ... | ... | @@ -4,7 +4,7 @@ import * as Effect from "effect/Effect"; |
| 4 | 4 | import { HttpClient } from "effect/http"; |
| 5 | 5 | |
| 6 | 6 | import { __resetDesktopPrimaryAuthForTests } from "./desktopAuth"; |
| 7 | import { makePrimaryEnvironmentHttpLayer } from "./httpLayer"; | |
| 7 | import * as PrimaryEnvironmentHttpLayer from "./httpLayer"; | |
| 8 | 8 | |
| 9 | 9 | describe("primary environment HTTP layer", { concurrent: false }, () => { |
| 10 | 10 | afterEach(() => { |
| ... | ... | @@ -32,7 +32,7 @@ describe("primary environment HTTP layer", { concurrent: false }, () => { |
| 32 | 32 | const request = new Request(fetchMock.mock.calls[0]?.[0], fetchMock.mock.calls[0]?.[1]); |
| 33 | 33 | expect(request.credentials).toBe("include"); |
| 34 | 34 | expect(request.headers.get("authorization")).toBeNull(); |
| 35 | }).pipe(Effect.provide(makePrimaryEnvironmentHttpLayer())); | |
| 35 | }).pipe(Effect.provide(PrimaryEnvironmentHttpLayer.layerForCurrentOrigin())); | |
| 36 | 36 | }); |
| 37 | 37 | |
| 38 | 38 | it.effect("uses bearer auth without cookies for desktop-managed primaries", () => { |
| ... | ... | @@ -60,6 +60,6 @@ describe("primary environment HTTP layer", { concurrent: false }, () => { |
| 60 | 60 | const request = new Request(fetchMock.mock.calls[0]?.[0], fetchMock.mock.calls[0]?.[1]); |
| 61 | 61 | expect(request.credentials).not.toBe("include"); |
| 62 | 62 | expect(request.headers.get("authorization")).toBe("Bearer desktop-bearer-token"); |
| 63 | }).pipe(Effect.provide(makePrimaryEnvironmentHttpLayer())); | |
| 63 | }).pipe(Effect.provide(PrimaryEnvironmentHttpLayer.layerForCurrentOrigin())); | |
| 64 | 64 | }); |
| 65 | 65 | }); |
apps/web/src/environments/primary/httpLayer.ts+8-8| ... | ... | @@ -1,4 +1,4 @@ |
| 1 | import { remoteHttpClientLayer } from "@t3tools/client-runtime/rpc"; | |
| 1 | import { layerRemoteHttpClient } from "@t3tools/client-runtime/rpc"; | |
| 2 | 2 | import * as Effect from "effect/Effect"; |
| 3 | 3 | import * as Layer from "effect/Layer"; |
| 4 | 4 | import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/http"; |
| ... | ... | @@ -30,28 +30,28 @@ function withPrimaryBearerToken(client: HttpClient.HttpClient): HttpClient.HttpC |
| 30 | 30 | ); |
| 31 | 31 | } |
| 32 | 32 | |
| 33 | export function makePrimaryEnvironmentHttpLayer() { | |
| 33 | export function layerForCurrentOrigin() { | |
| 34 | 34 | return Layer.unwrap( |
| 35 | 35 | Effect.sync(() => { |
| 36 | const baseLayer = remoteHttpClientLayer(globalThis.fetch); | |
| 36 | const layerBase = layerRemoteHttpClient(globalThis.fetch); | |
| 37 | 37 | if (isSameOriginBrowserPrimary()) { |
| 38 | 38 | return Layer.merge( |
| 39 | baseLayer, | |
| 39 | layerBase, | |
| 40 | 40 | Layer.succeed(FetchHttpClient.RequestInit, { credentials: "include" }), |
| 41 | 41 | ); |
| 42 | 42 | } |
| 43 | 43 | |
| 44 | const bearerClientLayer = Layer.effect( | |
| 44 | const layerBearerClient = Layer.effect( | |
| 45 | 45 | HttpClient.HttpClient, |
| 46 | 46 | Effect.map(HttpClient.HttpClient, withPrimaryBearerToken), |
| 47 | ).pipe(Layer.provide(baseLayer)); | |
| 47 | ).pipe(Layer.provide(layerBase)); | |
| 48 | 48 | |
| 49 | 49 | return Layer.merge( |
| 50 | bearerClientLayer, | |
| 50 | layerBearerClient, | |
| 51 | 51 | Layer.succeed(FetchHttpClient.RequestInit, { credentials: "omit" }), |
| 52 | 52 | ); |
| 53 | 53 | }), |
| 54 | 54 | ); |
| 55 | 55 | } |
| 56 | 56 | |
| 57 | export const primaryEnvironmentHttpLayer = makePrimaryEnvironmentHttpLayer(); | |
| 57 | export const layer = layerForCurrentOrigin(); |
apps/web/src/lib/backgroundActivityReporter.ts+2-2| ... | ... | @@ -164,14 +164,14 @@ export function retainedBackgroundScopes( |
| 164 | 164 | ).filter((scope): scope is BackgroundScope => scope !== null); |
| 165 | 165 | } |
| 166 | 166 | |
| 167 | export const backgroundActivityObserverLayer = Layer.succeed( | |
| 167 | export const layerObserver = Layer.succeed( | |
| 168 | 168 | EnvironmentRpcSubscriptionObserver, |
| 169 | 169 | EnvironmentRpcSubscriptionObserver.of({ |
| 170 | 170 | observe: observeBackgroundActivitySubscription, |
| 171 | 171 | }), |
| 172 | 172 | ); |
| 173 | 173 | |
| 174 | export const backgroundActivityReporterLayer = Layer.effectDiscard( | |
| 174 | export const layer = Layer.effectDiscard( | |
| 175 | 175 | Effect.gen(function* () { |
| 176 | 176 | if (typeof window === "undefined" || typeof document === "undefined") { |
| 177 | 177 | return; |
apps/web/src/lib/runtime.ts+21-21| ... | ... | @@ -3,13 +3,13 @@ import type * as Effect from "effect/Effect"; |
| 3 | 3 | import * as Layer from "effect/Layer"; |
| 4 | 4 | import * as Socket from "effect/socket/Socket"; |
| 5 | 5 | |
| 6 | import { remoteHttpClientLayer } from "@t3tools/client-runtime/rpc"; | |
| 7 | import { makeRelayClientTracingLayer } from "@t3tools/shared/relayTracing"; | |
| 6 | import { layerRemoteHttpClient } from "@t3tools/client-runtime/rpc"; | |
| 7 | import * as RelayTracing from "@t3tools/shared/relayTracing"; | |
| 8 | 8 | import * as PrimaryEnvironmentHttpClient from "../environments/primary/httpClient"; |
| 9 | import { primaryEnvironmentHttpLayer } from "../environments/primary/httpLayer"; | |
| 9 | import * as PrimaryEnvironmentHttpLayer from "../environments/primary/httpLayer"; | |
| 10 | 10 | |
| 11 | import { browserCryptoLayer } from "../cloud/dpop"; | |
| 12 | import { managedRelayClientLayer } from "../cloud/managedRelayLayer"; | |
| 11 | import * as Dpop from "../cloud/dpop"; | |
| 12 | import * as ManagedRelayLayer from "../cloud/managedRelayLayer"; | |
| 13 | 13 | import { resolveCloudPublicConfig, resolveRelayTracingConfig } from "../cloud/publicConfig"; |
| 14 | 14 | import * as ClientTracer from "../observability/clientTracer"; |
| 15 | 15 | |
| ... | ... | @@ -17,24 +17,24 @@ function configuredRelayUrl(): string { |
| 17 | 17 | return resolveCloudPublicConfig().relayUrl ?? "http://relay.invalid"; |
| 18 | 18 | } |
| 19 | 19 | |
| 20 | const httpClientLayer = remoteHttpClientLayer((input, init) => globalThis.fetch(input, init)); | |
| 21 | const relayTracingLayer = makeRelayClientTracingLayer(resolveRelayTracingConfig(), { | |
| 20 | const layerHttpClient = layerRemoteHttpClient((input, init) => globalThis.fetch(input, init)); | |
| 21 | const layerRelayTracing = RelayTracing.layer(resolveRelayTracingConfig(), { | |
| 22 | 22 | serviceName: "t3code-web", |
| 23 | 23 | serviceVersion: import.meta.env.APP_VERSION, |
| 24 | 24 | runtime: "browser", |
| 25 | 25 | client: typeof window !== "undefined" && window.desktopBridge ? "desktop" : "web", |
| 26 | }).pipe(Layer.provide(httpClientLayer)); | |
| 26 | }).pipe(Layer.provide(layerHttpClient)); | |
| 27 | 27 | |
| 28 | 28 | type RuntimeLayerSource = |
| 29 | | typeof httpClientLayer | |
| 30 | | typeof browserCryptoLayer | |
| 29 | | typeof layerHttpClient | |
| 30 | | typeof Dpop.layer | |
| 31 | 31 | | typeof Socket.layerWebSocketConstructorGlobal |
| 32 | | typeof relayTracingLayer | |
| 32 | | typeof layerRelayTracing | |
| 33 | 33 | | typeof ClientTracer.layer |
| 34 | | ReturnType<typeof managedRelayClientLayer>; | |
| 34 | | ReturnType<typeof ManagedRelayLayer.layer>; | |
| 35 | 35 | |
| 36 | 36 | const primaryHttpRuntime = ManagedRuntime.make( |
| 37 | PrimaryEnvironmentHttpClient.layer.pipe(Layer.provide(primaryEnvironmentHttpLayer)), | |
| 37 | PrimaryEnvironmentHttpClient.layer.pipe(Layer.provide(PrimaryEnvironmentHttpLayer.layer)), | |
| 38 | 38 | ); |
| 39 | 39 | |
| 40 | 40 | export type PrimaryHttpEffectRunner = <A, E>( |
| ... | ... | @@ -54,23 +54,23 @@ export function __setPrimaryHttpRunnerForTests(runner?: PrimaryHttpEffectRunner) |
| 54 | 54 | primaryHttpRunner = runner ?? livePrimaryHttpRunner; |
| 55 | 55 | } |
| 56 | 56 | |
| 57 | const runtimeLayer = Layer.mergeAll( | |
| 58 | httpClientLayer, | |
| 59 | browserCryptoLayer, | |
| 57 | const layerRuntime = Layer.mergeAll( | |
| 58 | layerHttpClient, | |
| 59 | Dpop.layer, | |
| 60 | 60 | Socket.layerWebSocketConstructorGlobal, |
| 61 | 61 | ClientTracer.layer, |
| 62 | relayTracingLayer, | |
| 63 | managedRelayClientLayer(configuredRelayUrl()).pipe( | |
| 64 | Layer.provide(Layer.mergeAll(httpClientLayer, browserCryptoLayer)), | |
| 62 | layerRelayTracing, | |
| 63 | ManagedRelayLayer.layer(configuredRelayUrl()).pipe( | |
| 64 | Layer.provide(Layer.mergeAll(layerHttpClient, Dpop.layer)), | |
| 65 | 65 | ), |
| 66 | 66 | ); |
| 67 | 67 | |
| 68 | 68 | export const runtime: ManagedRuntime.ManagedRuntime< |
| 69 | 69 | Layer.Success<RuntimeLayerSource>, |
| 70 | 70 | Layer.Error<RuntimeLayerSource> |
| 71 | > = ManagedRuntime.make(runtimeLayer); | |
| 71 | > = ManagedRuntime.make(layerRuntime); | |
| 72 | 72 | |
| 73 | export const runtimeContextLayer: Layer.Layer< | |
| 73 | export const layer: Layer.Layer< | |
| 74 | 74 | Layer.Success<RuntimeLayerSource>, |
| 75 | 75 | Layer.Error<RuntimeLayerSource> |
| 76 | 76 | > = Layer.effectContext(runtime.contextEffect); |
apps/web/src/observability/clientTracing.ts+4-4| ... | ... | @@ -9,7 +9,7 @@ import { settleAsyncResult, squashAtomCommandFailure } from "@t3tools/client-run |
| 9 | 9 | import { safeErrorLogAttributes } from "@t3tools/client-runtime/errors"; |
| 10 | 10 | import { resolvePrimaryEnvironmentHttpUrl } from "../environments/primary"; |
| 11 | 11 | import * as ClientTracer from "./clientTracer"; |
| 12 | import { primaryEnvironmentHttpLayer } from "../environments/primary/httpLayer"; | |
| 12 | import * as PrimaryEnvironmentHttpLayer from "../environments/primary/httpLayer"; | |
| 13 | 13 | import { isElectron } from "../env"; |
| 14 | 14 | import { APP_VERSION } from "~/branding"; |
| 15 | 15 | |
| ... | ... | @@ -24,8 +24,8 @@ const CLIENT_TRACING_RESOURCE = { |
| 24 | 24 | }, |
| 25 | 25 | } as const; |
| 26 | 26 | |
| 27 | const delegateRuntimeLayer = Layer.mergeAll( | |
| 28 | primaryEnvironmentHttpLayer, | |
| 27 | const layerDelegateRuntime = Layer.mergeAll( | |
| 28 | PrimaryEnvironmentHttpLayer.layer, | |
| 29 | 29 | OtlpExporter.layerFlusher, |
| 30 | 30 | OtlpSerialization.layerJson, |
| 31 | 31 | Layer.succeed(HttpClient.TracerDisabledWhen, () => true), |
| ... | ... | @@ -70,7 +70,7 @@ async function applyClientTracingConfig(config: ClientTracingConfig): Promise<vo |
| 70 | 70 | |
| 71 | 71 | await disposeTracerRuntime(previousRuntime, previousScope); |
| 72 | 72 | |
| 73 | const runtime = ManagedRuntime.make(delegateRuntimeLayer); | |
| 73 | const runtime = ManagedRuntime.make(layerDelegateRuntime); | |
| 74 | 74 | const scope = runtime.runSync(Scope.make()); |
| 75 | 75 | |
| 76 | 76 | const delegateResult = await settleAsyncResult(() => |
infra/relay/alchemy.run.ts+3-3| ... | ... | @@ -12,7 +12,7 @@ import { PublishClientConfig, tokenDigest } from "./src/clientConfig.ts"; |
| 12 | 12 | import * as RelayDb from "./src/db.ts"; |
| 13 | 13 | import { RelayObservability } from "./src/observability.ts"; |
| 14 | 14 | import { ManagedEndpointZone, RelayApiZone } from "./src/zone.ts"; |
| 15 | import ApiLive, { Api } from "./src/worker.ts"; | |
| 15 | import * as RelayWorker from "./src/worker.ts"; | |
| 16 | 16 | |
| 17 | 17 | export default Alchemy.Stack( |
| 18 | 18 | "T3CodeRelay", |
| ... | ... | @@ -31,7 +31,7 @@ export default Alchemy.Stack( |
| 31 | 31 | const managedEndpointZone = yield* ManagedEndpointZone.pipe(Effect.orDie); |
| 32 | 32 | const relayApiZone = yield* RelayApiZone.pipe(Effect.orDie); |
| 33 | 33 | const observability = yield* RelayObservability; |
| 34 | const api = yield* Api; | |
| 34 | const api = yield* RelayWorker.Api; | |
| 35 | 35 | yield* PublishClientConfig({ |
| 36 | 36 | url: api.url, |
| 37 | 37 | mobileTracingUrl: observability.traces.otelTracesEndpoint, |
| ... | ... | @@ -61,5 +61,5 @@ export default Alchemy.Stack( |
| 61 | 61 | clientTracingDataset: observability.traces.name, |
| 62 | 62 | clientTracingToken: observability.clientIngestToken.token, |
| 63 | 63 | }; |
| 64 | }).pipe(Effect.provide(ApiLive)), | |
| 64 | }).pipe(Effect.provide(RelayWorker.layer)), | |
| 65 | 65 | ); |
infra/relay/scripts/android-push-watch.ts+7-5| ... | ... | @@ -86,7 +86,7 @@ const main = Effect.gen(function* () { |
| 86 | 86 | const connection = yield* readFile(connectionPath).pipe( |
| 87 | 87 | Effect.flatMap(Schema.decodeUnknownEffect(Schema.fromJsonString(Connection))), |
| 88 | 88 | ); |
| 89 | const socketConstructor = Layer.succeed( | |
| 89 | const layerSocketConstructor = Layer.succeed( | |
| 90 | 90 | Socket.WebSocketConstructor, |
| 91 | 91 | // Socket.makeWebSocket only ever passes its `protocols` option here. |
| 92 | 92 | (url, protocols) => |
| ... | ... | @@ -94,11 +94,13 @@ const main = Effect.gen(function* () { |
| 94 | 94 | headers: { authorization: `Bearer ${connection.bearerToken}` }, |
| 95 | 95 | }) as unknown as globalThis.WebSocket, |
| 96 | 96 | ); |
| 97 | const protocol = RpcClient.layerProtocolSocket().pipe( | |
| 98 | Layer.provide(Socket.layerWebSocket(connection.wsUrl).pipe(Layer.provide(socketConstructor))), | |
| 97 | const layerProtocol = RpcClient.layerProtocolSocket().pipe( | |
| 98 | Layer.provide( | |
| 99 | Socket.layerWebSocket(connection.wsUrl).pipe(Layer.provide(layerSocketConstructor)), | |
| 100 | ), | |
| 99 | 101 | Layer.provide(RpcSerialization.layerJson), |
| 100 | 102 | ); |
| 101 | const fcm = FcmClient.layer.pipe( | |
| 103 | const layerFcm = FcmClient.layer.pipe( | |
| 102 | 104 | Layer.provide( |
| 103 | 105 | FcmAssertionSigner.layer.pipe( |
| 104 | 106 | Layer.provide(Layer.succeed(WebCrypto.WebCrypto, { subtle: globalThis.crypto.subtle })), |
| ... | ... | @@ -206,7 +208,7 @@ const main = Effect.gen(function* () { |
| 206 | 208 | }), |
| 207 | 209 | ), |
| 208 | 210 | ); |
| 209 | }).pipe(Effect.provide(Layer.mergeAll(protocol, fcm))); | |
| 211 | }).pipe(Effect.provide(Layer.mergeAll(layerProtocol, layerFcm))); | |
| 210 | 212 | }); |
| 211 | 213 | |
| 212 | 214 | NodeRuntime.runMain( |
infra/relay/src/agentActivity/AgentActivityPublisher.test.ts+6-6| ... | ... | @@ -10,7 +10,7 @@ import * as AgentActivityPublisher from "./AgentActivityPublisher.ts"; |
| 10 | 10 | import * as FcmDeliveries from "./FcmDeliveries.ts"; |
| 11 | 11 | import * as ApnsDeliveries from "./ApnsDeliveries.ts"; |
| 12 | 12 | |
| 13 | const publisherLayer = AgentActivityPublisher.layer.pipe( | |
| 13 | const layerPublisher = AgentActivityPublisher.layer.pipe( | |
| 14 | 14 | Layer.provide( |
| 15 | 15 | Layer.succeed(FcmDeliveries.FcmDeliveries, { |
| 16 | 16 | enqueue: () => Effect.succeed(null), |
| ... | ... | @@ -225,7 +225,7 @@ describe("AgentActivityPublisher", () => { |
| 225 | 225 | }); |
| 226 | 226 | }).pipe( |
| 227 | 227 | Effect.provide( |
| 228 | publisherLayer.pipe( | |
| 228 | layerPublisher.pipe( | |
| 229 | 229 | Layer.provide( |
| 230 | 230 | Layer.mergeAll( |
| 231 | 231 | Layer.succeed(AgentActivityRows.AgentActivityRows, makeAgentActivityRows()), |
| ... | ... | @@ -298,7 +298,7 @@ describe("AgentActivityPublisher", () => { |
| 298 | 298 | }); |
| 299 | 299 | }).pipe( |
| 300 | 300 | Effect.provide( |
| 301 | publisherLayer.pipe( | |
| 301 | layerPublisher.pipe( | |
| 302 | 302 | Layer.provide( |
| 303 | 303 | Layer.mergeAll( |
| 304 | 304 | Layer.succeed( |
| ... | ... | @@ -392,7 +392,7 @@ describe("AgentActivityPublisher", () => { |
| 392 | 392 | }); |
| 393 | 393 | }).pipe( |
| 394 | 394 | Effect.provide( |
| 395 | publisherLayer.pipe( | |
| 395 | layerPublisher.pipe( | |
| 396 | 396 | Layer.provide( |
| 397 | 397 | Layer.mergeAll( |
| 398 | 398 | Layer.succeed( |
| ... | ... | @@ -498,7 +498,7 @@ describe("AgentActivityPublisher", () => { |
| 498 | 498 | }); |
| 499 | 499 | }).pipe( |
| 500 | 500 | Effect.provide( |
| 501 | publisherLayer.pipe( | |
| 501 | layerPublisher.pipe( | |
| 502 | 502 | Layer.provide( |
| 503 | 503 | Layer.mergeAll( |
| 504 | 504 | Layer.succeed( |
| ... | ... | @@ -610,7 +610,7 @@ describe("AgentActivityPublisher", () => { |
| 610 | 610 | }); |
| 611 | 611 | }).pipe( |
| 612 | 612 | Effect.provide( |
| 613 | publisherLayer.pipe( | |
| 613 | layerPublisher.pipe( | |
| 614 | 614 | Layer.provide( |
| 615 | 615 | Layer.mergeAll( |
| 616 | 616 | Layer.succeed( |
infra/relay/src/agentActivity/ApnsClient.test.ts+7-7| ... | ... | @@ -22,7 +22,7 @@ import * as ApnsProviderTokens from "./ApnsProviderTokens.ts"; |
| 22 | 22 | const isApnsJwtSigningError = Schema.is(ApnsClient.ApnsJwtSigningError); |
| 23 | 23 | const isApnsHttpRequestError = Schema.is(ApnsClient.ApnsHttpRequestError); |
| 24 | 24 | |
| 25 | const TestLayer = ApnsClient.layer.pipe( | |
| 25 | const layerTest = ApnsClient.layer.pipe( | |
| 26 | 26 | Layer.provide(ApnsProviderTokens.layer), |
| 27 | 27 | Layer.provide( |
| 28 | 28 | Layer.succeed( |
| ... | ... | @@ -76,7 +76,7 @@ describe("ApnsClient", () => { |
| 76 | 76 | }, |
| 77 | 77 | }, |
| 78 | 78 | }); |
| 79 | }).pipe(Effect.provide(TestLayer)), | |
| 79 | }).pipe(Effect.provide(layerTest)), | |
| 80 | 80 | ); |
| 81 | 81 | |
| 82 | 82 | it.effect("builds a low-priority update payload", () => |
| ... | ... | @@ -99,7 +99,7 @@ describe("ApnsClient", () => { |
| 99 | 99 | }, |
| 100 | 100 | }, |
| 101 | 101 | }); |
| 102 | }).pipe(Effect.provide(TestLayer)), | |
| 102 | }).pipe(Effect.provide(layerTest)), | |
| 103 | 103 | ); |
| 104 | 104 | |
| 105 | 105 | it.effect("builds a high-priority alerting update payload when an alert is attached", () => |
| ... | ... | @@ -125,7 +125,7 @@ describe("ApnsClient", () => { |
| 125 | 125 | }, |
| 126 | 126 | }, |
| 127 | 127 | }); |
| 128 | }).pipe(Effect.provide(TestLayer)), | |
| 128 | }).pipe(Effect.provide(layerTest)), | |
| 129 | 129 | ); |
| 130 | 130 | |
| 131 | 131 | it.effect("builds an end payload with a dismissal date", () => |
| ... | ... | @@ -162,7 +162,7 @@ describe("ApnsClient", () => { |
| 162 | 162 | "dismissal-date": 15, |
| 163 | 163 | }, |
| 164 | 164 | }); |
| 165 | }).pipe(Effect.provide(TestLayer)), | |
| 165 | }).pipe(Effect.provide(layerTest)), | |
| 166 | 166 | ); |
| 167 | 167 | |
| 168 | 168 | it.effect("builds a standard APNs alert payload with routing metadata", () => |
| ... | ... | @@ -193,7 +193,7 @@ describe("ApnsClient", () => { |
| 193 | 193 | threadId: "thread", |
| 194 | 194 | deepLink: "/threads/env/thread", |
| 195 | 195 | }); |
| 196 | }).pipe(Effect.provide(TestLayer)), | |
| 196 | }).pipe(Effect.provide(layerTest)), | |
| 197 | 197 | ); |
| 198 | 198 | |
| 199 | 199 | it.effect("preserves JWT signing context and the crypto cause", () => |
| ... | ... | @@ -237,7 +237,7 @@ describe("ApnsClient", () => { |
| 237 | 237 | cause: expect.any(Error), |
| 238 | 238 | message: "Failed to sign APNs JWT for key key-1.", |
| 239 | 239 | }); |
| 240 | }).pipe(Effect.provide(TestLayer)), | |
| 240 | }).pipe(Effect.provide(layerTest)), | |
| 241 | 241 | ); |
| 242 | 242 | |
| 243 | 243 | it.effect("preserves APNs request context and the HTTP cause", () => { |
infra/relay/src/agentActivity/ApnsDeliveries.test.ts+37-37| ... | ... | @@ -141,7 +141,7 @@ const target: LiveActivities.TargetRow = { |
| 141 | 141 | last_live_activity_delivery_at: null, |
| 142 | 142 | }; |
| 143 | 143 | |
| 144 | function makeLayer(input: { | |
| 144 | function layerFor(input: { | |
| 145 | 145 | readonly attempts: Array<DeliveryAttempts.DeliveryAttemptInput>; |
| 146 | 146 | readonly sourceJobClaims?: ReadonlyMap<string, DeliveryAttempts.DeliverySourceJobClaimResult>; |
| 147 | 147 | readonly queuedJobs?: Array<SignedApnsDeliveryJob>; |
| ... | ... | @@ -284,7 +284,7 @@ describe("ApnsDeliveries", () => { |
| 284 | 284 | expect(attempts).toHaveLength(0); |
| 285 | 285 | }).pipe( |
| 286 | 286 | Effect.provide( |
| 287 | makeLayer({ | |
| 287 | layerFor({ | |
| 288 | 288 | attempts, |
| 289 | 289 | queuedJobs, |
| 290 | 290 | config: { ...config, apns: null }, |
| ... | ... | @@ -322,7 +322,7 @@ describe("ApnsDeliveries", () => { |
| 322 | 322 | expect(queuedJobs).toEqual([]); |
| 323 | 323 | expect(queuedStarts).toEqual([]); |
| 324 | 324 | expect(attempts).toEqual([]); |
| 325 | }).pipe(Effect.provide(makeLayer({ attempts, queuedJobs, queuedStarts }))); | |
| 325 | }).pipe(Effect.provide(layerFor({ attempts, queuedJobs, queuedStarts }))); | |
| 326 | 326 | }); |
| 327 | 327 | |
| 328 | 328 | it.effect("ends the armed card when nothing remains to show", () => { |
| ... | ... | @@ -362,7 +362,7 @@ describe("ApnsDeliveries", () => { |
| 362 | 362 | }, |
| 363 | 363 | ]); |
| 364 | 364 | expect(attempts).toEqual([]); |
| 365 | }).pipe(Effect.provide(makeLayer({ attempts, queuedJobs }))); | |
| 365 | }).pipe(Effect.provide(layerFor({ attempts, queuedJobs }))); | |
| 366 | 366 | }); |
| 367 | 367 | |
| 368 | 368 | it.effect("does not queue a remote start when Live Activities are disabled", () => { |
| ... | ... | @@ -386,7 +386,7 @@ describe("ApnsDeliveries", () => { |
| 386 | 386 | expect(result).toBeNull(); |
| 387 | 387 | expect(queuedJobs).toEqual([]); |
| 388 | 388 | expect(attempts).toEqual([]); |
| 389 | }).pipe(Effect.provide(makeLayer({ attempts, queuedJobs }))); | |
| 389 | }).pipe(Effect.provide(layerFor({ attempts, queuedJobs }))); | |
| 390 | 390 | }); |
| 391 | 391 | |
| 392 | 392 | it.effect("does not queue a duplicate remote start while a start is already queued", () => { |
| ... | ... | @@ -410,7 +410,7 @@ describe("ApnsDeliveries", () => { |
| 410 | 410 | expect(result).toBeNull(); |
| 411 | 411 | expect(queuedJobs).toEqual([]); |
| 412 | 412 | expect(attempts).toEqual([]); |
| 413 | }).pipe(Effect.provide(makeLayer({ attempts, queuedJobs }))); | |
| 413 | }).pipe(Effect.provide(layerFor({ attempts, queuedJobs }))); | |
| 414 | 414 | }); |
| 415 | 415 | |
| 416 | 416 | it.effect("queues bounded Live Activity aggregate payloads", () => { |
| ... | ... | @@ -449,7 +449,7 @@ describe("ApnsDeliveries", () => { |
| 449 | 449 | expect(payloadAggregate?.activities[0]?.status.length).toBeLessThanOrEqual(40); |
| 450 | 450 | expect(payloadAggregate?.activities[0]?.deepLink).toBe("/"); |
| 451 | 451 | expect(attempts).toEqual([]); |
| 452 | }).pipe(Effect.provide(makeLayer({ attempts, queuedJobs }))); | |
| 452 | }).pipe(Effect.provide(layerFor({ attempts, queuedJobs }))); | |
| 453 | 453 | }); |
| 454 | 454 | |
| 455 | 455 | it.effect("queues Live Activity jobs with the device's APNs routing", () => { |
| ... | ... | @@ -481,7 +481,7 @@ describe("ApnsDeliveries", () => { |
| 481 | 481 | }, |
| 482 | 482 | }, |
| 483 | 483 | ]); |
| 484 | }).pipe(Effect.provide(makeLayer({ attempts, queuedJobs }))); | |
| 484 | }).pipe(Effect.provide(layerFor({ attempts, queuedJobs }))); | |
| 485 | 485 | }); |
| 486 | 486 | |
| 487 | 487 | it.effect("sends signed jobs to the device's APNs environment and bundle topic", () => { |
| ... | ... | @@ -521,7 +521,7 @@ describe("ApnsDeliveries", () => { |
| 521 | 521 | ); |
| 522 | 522 | }).pipe( |
| 523 | 523 | Effect.provide( |
| 524 | makeLayer({ | |
| 524 | layerFor({ | |
| 525 | 525 | attempts, |
| 526 | 526 | currentTargets: [ |
| 527 | 527 | { ...target, bundle_id: "com.t3tools.t3code.preview", aps_environment: "sandbox" }, |
| ... | ... | @@ -568,7 +568,7 @@ describe("ApnsDeliveries", () => { |
| 568 | 568 | expect(result).toBeNull(); |
| 569 | 569 | expect(queuedJobs).toEqual([]); |
| 570 | 570 | expect(attempts).toEqual([]); |
| 571 | }).pipe(Effect.provide(makeLayer({ attempts, queuedJobs }))); | |
| 571 | }).pipe(Effect.provide(layerFor({ attempts, queuedJobs }))); | |
| 572 | 572 | }, |
| 573 | 573 | ); |
| 574 | 574 | |
| ... | ... | @@ -612,7 +612,7 @@ describe("ApnsDeliveries", () => { |
| 612 | 612 | }, |
| 613 | 613 | }, |
| 614 | 614 | ]); |
| 615 | }).pipe(Effect.provide(makeLayer({ attempts, queuedJobs }))); | |
| 615 | }).pipe(Effect.provide(layerFor({ attempts, queuedJobs }))); | |
| 616 | 616 | }, |
| 617 | 617 | ); |
| 618 | 618 | |
| ... | ... | @@ -641,7 +641,7 @@ describe("ApnsDeliveries", () => { |
| 641 | 641 | |
| 642 | 642 | expect(result).toBeNull(); |
| 643 | 643 | expect(queuedJobs).toEqual([]); |
| 644 | }).pipe(Effect.provide(makeLayer({ attempts, queuedJobs }))); | |
| 644 | }).pipe(Effect.provide(layerFor({ attempts, queuedJobs }))); | |
| 645 | 645 | }, |
| 646 | 646 | ); |
| 647 | 647 | |
| ... | ... | @@ -672,7 +672,7 @@ describe("ApnsDeliveries", () => { |
| 672 | 672 | }, |
| 673 | 673 | ]); |
| 674 | 674 | expect(attempts).toEqual([]); |
| 675 | }).pipe(Effect.provide(makeLayer({ attempts, queuedJobs }))); | |
| 675 | }).pipe(Effect.provide(layerFor({ attempts, queuedJobs }))); | |
| 676 | 676 | }); |
| 677 | 677 | |
| 678 | 678 | it.effect( |
| ... | ... | @@ -732,7 +732,7 @@ describe("ApnsDeliveries", () => { |
| 732 | 732 | }, |
| 733 | 733 | ]); |
| 734 | 734 | expect(attempts).toEqual([]); |
| 735 | }).pipe(Effect.provide(makeLayer({ attempts, queuedJobs }))); | |
| 735 | }).pipe(Effect.provide(layerFor({ attempts, queuedJobs }))); | |
| 736 | 736 | }, |
| 737 | 737 | ); |
| 738 | 738 | |
| ... | ... | @@ -774,7 +774,7 @@ describe("ApnsDeliveries", () => { |
| 774 | 774 | }, |
| 775 | 775 | ]); |
| 776 | 776 | expect(attempts).toEqual([]); |
| 777 | }).pipe(Effect.provide(makeLayer({ attempts, queuedJobs }))); | |
| 777 | }).pipe(Effect.provide(layerFor({ attempts, queuedJobs }))); | |
| 778 | 778 | }); |
| 779 | 779 | |
| 780 | 780 | it.effect( |
| ... | ... | @@ -827,7 +827,7 @@ describe("ApnsDeliveries", () => { |
| 827 | 827 | }, |
| 828 | 828 | ]); |
| 829 | 829 | expect(attempts).toEqual([]); |
| 830 | }).pipe(Effect.provide(makeLayer({ attempts, queuedJobs }))); | |
| 830 | }).pipe(Effect.provide(layerFor({ attempts, queuedJobs }))); | |
| 831 | 831 | }, |
| 832 | 832 | ); |
| 833 | 833 | |
| ... | ... | @@ -852,7 +852,7 @@ describe("ApnsDeliveries", () => { |
| 852 | 852 | expect(result).toBeNull(); |
| 853 | 853 | expect(queuedJobs).toEqual([]); |
| 854 | 854 | expect(attempts).toEqual([]); |
| 855 | }).pipe(Effect.provide(makeLayer({ attempts, queuedJobs }))); | |
| 855 | }).pipe(Effect.provide(layerFor({ attempts, queuedJobs }))); | |
| 856 | 856 | }); |
| 857 | 857 | |
| 858 | 858 | it.effect("queues bounded alert notification payloads", () => { |
| ... | ... | @@ -892,7 +892,7 @@ describe("ApnsDeliveries", () => { |
| 892 | 892 | expect(notification?.body.length).toBeLessThanOrEqual(120); |
| 893 | 893 | expect(notification?.deepLink).toBe("/"); |
| 894 | 894 | expect(attempts).toEqual([]); |
| 895 | }).pipe(Effect.provide(makeLayer({ attempts, queuedJobs }))); | |
| 895 | }).pipe(Effect.provide(layerFor({ attempts, queuedJobs }))); | |
| 896 | 896 | }); |
| 897 | 897 | |
| 898 | 898 | it.effect("preserves the schema cause for invalid queue payloads", () => { |
| ... | ... | @@ -908,7 +908,7 @@ describe("ApnsDeliveries", () => { |
| 908 | 908 | message: "Invalid APNs delivery queue job with object payload.", |
| 909 | 909 | }); |
| 910 | 910 | expect(error.cause).toMatchObject({ _tag: "SchemaError" }); |
| 911 | }).pipe(Effect.provide(makeLayer({ attempts }))); | |
| 911 | }).pipe(Effect.provide(layerFor({ attempts }))); | |
| 912 | 912 | }); |
| 913 | 913 | |
| 914 | 914 | it.effect("skips a queued start when the user no longer has live work", () => { |
| ... | ... | @@ -950,7 +950,7 @@ describe("ApnsDeliveries", () => { |
| 950 | 950 | }, |
| 951 | 951 | ]); |
| 952 | 952 | expect(clearedStarts).toMatchObject([{ userId: target.user_id, deviceId: target.device_id }]); |
| 953 | }).pipe(Effect.provide(makeLayer({ attempts, clearedStarts, activityStates: [] }))); | |
| 953 | }).pipe(Effect.provide(layerFor({ attempts, clearedStarts, activityStates: [] }))); | |
| 954 | 954 | }); |
| 955 | 955 | |
| 956 | 956 | it.effect("processes signed jobs through APNs and records attempts", () => { |
| ... | ... | @@ -1012,7 +1012,7 @@ describe("ApnsDeliveries", () => { |
| 1012 | 1012 | }).pipe( |
| 1013 | 1013 | Effect.provide( |
| 1014 | 1014 | Layer.mergeAll( |
| 1015 | makeLayer({ attempts }), | |
| 1015 | layerFor({ attempts }), | |
| 1016 | 1016 | Logger.layer([logger], { mergeWithExisting: false }), |
| 1017 | 1017 | ), |
| 1018 | 1018 | ), |
| ... | ... | @@ -1047,7 +1047,7 @@ describe("ApnsDeliveries", () => { |
| 1047 | 1047 | ); |
| 1048 | 1048 | const started = yield* Deferred.make<void>(); |
| 1049 | 1049 | const results: Array<{ deviceId: string; ok: boolean }> = []; |
| 1050 | const layer = makeLayer({ | |
| 1050 | const layer = layerFor({ | |
| 1051 | 1051 | attempts, |
| 1052 | 1052 | markedDeliveries, |
| 1053 | 1053 | currentTargets: [target, secondTarget], |
| ... | ... | @@ -1138,7 +1138,7 @@ describe("ApnsDeliveries", () => { |
| 1138 | 1138 | ]); |
| 1139 | 1139 | }).pipe( |
| 1140 | 1140 | Effect.provide( |
| 1141 | makeLayer({ | |
| 1141 | layerFor({ | |
| 1142 | 1142 | attempts, |
| 1143 | 1143 | currentTargets: [ |
| 1144 | 1144 | { |
| ... | ... | @@ -1197,7 +1197,7 @@ describe("ApnsDeliveries", () => { |
| 1197 | 1197 | expect(attempts).toEqual([]); |
| 1198 | 1198 | }).pipe( |
| 1199 | 1199 | Effect.provide( |
| 1200 | makeLayer({ | |
| 1200 | layerFor({ | |
| 1201 | 1201 | attempts, |
| 1202 | 1202 | sourceJobClaims: new Map([["job-push-duplicate", "completed"]]), |
| 1203 | 1203 | config: signingConfig, |
| ... | ... | @@ -1251,7 +1251,7 @@ describe("ApnsDeliveries", () => { |
| 1251 | 1251 | ]); |
| 1252 | 1252 | }).pipe( |
| 1253 | 1253 | Effect.provide( |
| 1254 | makeLayer({ | |
| 1254 | layerFor({ | |
| 1255 | 1255 | attempts, |
| 1256 | 1256 | config: signingConfig, |
| 1257 | 1257 | execute, |
| ... | ... | @@ -1311,7 +1311,7 @@ describe("ApnsDeliveries", () => { |
| 1311 | 1311 | ]); |
| 1312 | 1312 | }).pipe( |
| 1313 | 1313 | Effect.provide( |
| 1314 | makeLayer({ | |
| 1314 | layerFor({ | |
| 1315 | 1315 | attempts, |
| 1316 | 1316 | currentTargets: [ |
| 1317 | 1317 | { |
| ... | ... | @@ -1383,7 +1383,7 @@ describe("ApnsDeliveries", () => { |
| 1383 | 1383 | ]); |
| 1384 | 1384 | }).pipe( |
| 1385 | 1385 | Effect.provide( |
| 1386 | makeLayer({ | |
| 1386 | layerFor({ | |
| 1387 | 1387 | attempts, |
| 1388 | 1388 | activityStates: [ |
| 1389 | 1389 | { |
| ... | ... | @@ -1452,7 +1452,7 @@ describe("ApnsDeliveries", () => { |
| 1452 | 1452 | ]); |
| 1453 | 1453 | }).pipe( |
| 1454 | 1454 | Effect.provide( |
| 1455 | makeLayer({ | |
| 1455 | layerFor({ | |
| 1456 | 1456 | attempts, |
| 1457 | 1457 | currentTargets: [{ ...target, push_token: "apns-device-token" }], |
| 1458 | 1458 | currentActivityStates: [ |
| ... | ... | @@ -1512,7 +1512,7 @@ describe("ApnsDeliveries", () => { |
| 1512 | 1512 | expect(attempts).toEqual([]); |
| 1513 | 1513 | }).pipe( |
| 1514 | 1514 | Effect.provide( |
| 1515 | makeLayer({ | |
| 1515 | layerFor({ | |
| 1516 | 1516 | attempts, |
| 1517 | 1517 | sourceJobClaims: new Map([["job-push-in-flight", "in_flight"]]), |
| 1518 | 1518 | config: signingConfig, |
| ... | ... | @@ -1573,7 +1573,7 @@ describe("ApnsDeliveries", () => { |
| 1573 | 1573 | ]); |
| 1574 | 1574 | }).pipe( |
| 1575 | 1575 | Effect.provide( |
| 1576 | makeLayer({ | |
| 1576 | layerFor({ | |
| 1577 | 1577 | attempts, |
| 1578 | 1578 | invalidatedTokens, |
| 1579 | 1579 | currentTargets: [ |
| ... | ... | @@ -1623,7 +1623,7 @@ describe("ApnsDeliveries", () => { |
| 1623 | 1623 | ]); |
| 1624 | 1624 | }).pipe( |
| 1625 | 1625 | Effect.provide( |
| 1626 | makeLayer({ | |
| 1626 | layerFor({ | |
| 1627 | 1627 | attempts, |
| 1628 | 1628 | clearedStarts, |
| 1629 | 1629 | activityStates: [{ ...state, updatedAt: "9999-01-01T00:00:00.000Z" }], |
| ... | ... | @@ -1676,7 +1676,7 @@ describe("ApnsDeliveries", () => { |
| 1676 | 1676 | ]); |
| 1677 | 1677 | }).pipe( |
| 1678 | 1678 | Effect.provide( |
| 1679 | makeLayer({ | |
| 1679 | layerFor({ | |
| 1680 | 1680 | attempts, |
| 1681 | 1681 | invalidatedTokens, |
| 1682 | 1682 | activityStates: [{ ...state, updatedAt: "9999-01-01T00:00:00.000Z" }], |
| ... | ... | @@ -1730,7 +1730,7 @@ describe("ApnsDeliveries", () => { |
| 1730 | 1730 | }, |
| 1731 | 1731 | ]); |
| 1732 | 1732 | }).pipe( |
| 1733 | Effect.provide(makeLayer({ attempts, invalidatedTokens, config: signingConfig, execute })), | |
| 1733 | Effect.provide(layerFor({ attempts, invalidatedTokens, config: signingConfig, execute })), | |
| 1734 | 1734 | ); |
| 1735 | 1735 | }); |
| 1736 | 1736 | }); |
| ... | ... | @@ -1938,7 +1938,7 @@ describe("queued iOS alert policy", () => { |
| 1938 | 1938 | expect(sent).toBe(scenario === "enabled" ? 1 : 0); |
| 1939 | 1939 | }).pipe( |
| 1940 | 1940 | Effect.provide( |
| 1941 | makeLayer({ | |
| 1941 | layerFor({ | |
| 1942 | 1942 | attempts: [], |
| 1943 | 1943 | config: signingConfig, |
| 1944 | 1944 | currentTargets: [ |
| ... | ... | @@ -1984,7 +1984,7 @@ describe("fast completion delivery", () => { |
| 1984 | 1984 | expect( |
| 1985 | 1985 | queuedJobs.some((x) => x.payload.alert !== null && x.payload.alert !== undefined), |
| 1986 | 1986 | ).toBe(true); |
| 1987 | }).pipe(Effect.provide(makeLayer({ attempts: [], queuedJobs, currentTargets: [device] }))); | |
| 1987 | }).pipe(Effect.provide(layerFor({ attempts: [], queuedJobs, currentTargets: [device] }))); | |
| 1988 | 1988 | }); |
| 1989 | 1989 | it.effect("replays a newly visible completion without alerting", () => { |
| 1990 | 1990 | const queuedJobs: SignedApnsDeliveryJob[] = []; |
| ... | ... | @@ -2010,7 +2010,7 @@ describe("fast completion delivery", () => { |
| 2010 | 2010 | }); |
| 2011 | 2011 | expect(queuedJobs).toHaveLength(1); |
| 2012 | 2012 | expect(queuedJobs[0]?.payload.alert).toBeUndefined(); |
| 2013 | }).pipe(Effect.provide(makeLayer({ attempts: [], queuedJobs }))); | |
| 2013 | }).pipe(Effect.provide(layerFor({ attempts: [], queuedJobs }))); | |
| 2014 | 2014 | }); |
| 2015 | 2015 | }); |
| 2016 | 2016 | |
| ... | ... | @@ -2063,7 +2063,7 @@ describe("signed APNs registration metadata", () => { |
| 2063 | 2063 | ); |
| 2064 | 2064 | }).pipe( |
| 2065 | 2065 | Effect.provide( |
| 2066 | makeLayer({ | |
| 2066 | layerFor({ | |
| 2067 | 2067 | attempts, |
| 2068 | 2068 | config: signingConfig, |
| 2069 | 2069 | currentTargets: [ |
infra/relay/src/agentActivity/FcmClient.test.ts+4-4| ... | ... | @@ -55,7 +55,7 @@ const input = { |
| 55 | 55 | alert: false, |
| 56 | 56 | }; |
| 57 | 57 | |
| 58 | function testLayer(requests: HttpClientRequest.HttpClientRequest[], responses: Response[]) { | |
| 58 | function layerTest(requests: HttpClientRequest.HttpClientRequest[], responses: Response[]) { | |
| 59 | 59 | const http = HttpClient.make((request) => { |
| 60 | 60 | requests.push(request); |
| 61 | 61 | const response = responses.shift(); |
| ... | ... | @@ -198,7 +198,7 @@ describe("FCM delivery", () => { |
| 198 | 198 | }); |
| 199 | 199 | }).pipe( |
| 200 | 200 | Effect.provide( |
| 201 | testLayer(requests, [ | |
| 201 | layerTest(requests, [ | |
| 202 | 202 | Response.json({ access_token: "access-token" }), |
| 203 | 203 | Response.json({ name: "one" }), |
| 204 | 204 | Response.json({ name: "two" }), |
| ... | ... | @@ -219,7 +219,7 @@ describe("FCM delivery", () => { |
| 219 | 219 | expect(requests[3]!.headers.authorization).toBe("Bearer fresh-token"); |
| 220 | 220 | }).pipe( |
| 221 | 221 | Effect.provide( |
| 222 | testLayer(requests, [ | |
| 222 | layerTest(requests, [ | |
| 223 | 223 | Response.json({ access_token: "old-token" }), |
| 224 | 224 | Response.json({}, { status: 401 }), |
| 225 | 225 | Response.json({ access_token: "fresh-token" }), |
| ... | ... | @@ -249,6 +249,6 @@ describe("FCM delivery", () => { |
| 249 | 249 | .pipe(Effect.flip); |
| 250 | 250 | expect(error.operation).toBe("send"); |
| 251 | 251 | expect(requests).toHaveLength(0); |
| 252 | }).pipe(Effect.provide(testLayer(requests, []))); | |
| 252 | }).pipe(Effect.provide(layerTest(requests, []))); | |
| 253 | 253 | }); |
| 254 | 254 | }); |
infra/relay/src/agentActivity/FcmDeliveries.test.ts+2-2| ... | ... | @@ -98,7 +98,7 @@ function harness() { |
| 98 | 98 | linked: true, |
| 99 | 99 | deliveryFailure: null as FcmClient.FcmClientError | null, |
| 100 | 100 | }; |
| 101 | const services = Layer.mergeAll( | |
| 101 | const layerServices = Layer.mergeAll( | |
| 102 | 102 | NodeCryptoLayer.layer, |
| 103 | 103 | Layer.succeed(RelayConfiguration.RelayConfiguration, config), |
| 104 | 104 | Layer.succeed(FcmDeliveryQueueSender.FcmDeliveryQueueSender, { |
| ... | ... | @@ -198,7 +198,7 @@ function harness() { |
| 198 | 198 | queued, |
| 199 | 199 | marked, |
| 200 | 200 | current, |
| 201 | layer: FcmDeliveries.layer.pipe(Layer.provide(services)), | |
| 201 | layer: FcmDeliveries.layer.pipe(Layer.provide(layerServices)), | |
| 202 | 202 | job: { |
| 203 | 203 | userId: "user", |
| 204 | 204 | deviceId: "phone", |
infra/relay/src/agentActivity/MobileRegistrations.test.ts+4-4| ... | ... | @@ -20,7 +20,7 @@ import * as RelayConfiguration from "../Config.ts"; |
| 20 | 20 | import * as AgentActivityPublisher from "./AgentActivityPublisher.ts"; |
| 21 | 21 | import * as FcmDeliveries from "./FcmDeliveries.ts"; |
| 22 | 22 | |
| 23 | const publisherLayer = AgentActivityPublisher.layer.pipe( | |
| 23 | const layerPublisher = AgentActivityPublisher.layer.pipe( | |
| 24 | 24 | Layer.provide( |
| 25 | 25 | Layer.succeed(FcmDeliveries.FcmDeliveries, { |
| 26 | 26 | enqueue: () => Effect.succeed(null), |
| ... | ... | @@ -153,13 +153,13 @@ const config = RelayConfiguration.RelayConfiguration.of({ |
| 153 | 153 | managedEndpointNamespace: undefined, |
| 154 | 154 | }); |
| 155 | 155 | |
| 156 | function makeRegistrationReplayLayer(input: { | |
| 156 | function layerRegistrationReplay(input: { | |
| 157 | 157 | readonly devices: Devices.Devices["Service"]; |
| 158 | 158 | readonly liveActivities: LiveActivities.LiveActivities["Service"]; |
| 159 | 159 | readonly queuedJobs: Array<SignedApnsDeliveryJob>; |
| 160 | 160 | }) { |
| 161 | 161 | return MobileRegistrations.layer.pipe( |
| 162 | Layer.provide(publisherLayer), | |
| 162 | Layer.provide(layerPublisher), | |
| 163 | 163 | Layer.provide( |
| 164 | 164 | ApnsDeliveries.layer.pipe( |
| 165 | 165 | Layer.provide(ApnsClient.layer.pipe(Layer.provide(ApnsProviderTokens.layer))), |
| ... | ... | @@ -490,7 +490,7 @@ describe("MobileRegistrations", () => { |
| 490 | 490 | expect(registeredDevices).toHaveLength(1); |
| 491 | 491 | expect(queuedStarts).toEqual([]); |
| 492 | 492 | expect(queuedJobs).toEqual([]); |
| 493 | }).pipe(Effect.provide(makeRegistrationReplayLayer({ devices, liveActivities, queuedJobs }))); | |
| 493 | }).pipe(Effect.provide(layerRegistrationReplay({ devices, liveActivities, queuedJobs }))); | |
| 494 | 494 | }, |
| 495 | 495 | ); |
| 496 | 496 | }); |
infra/relay/src/environments/EnvironmentConnector.test.ts+16-16| ... | ... | @@ -156,7 +156,7 @@ function signHealthResponse( |
| 156 | 156 | }; |
| 157 | 157 | } |
| 158 | 158 | |
| 159 | function connectorTestLayer( | |
| 159 | function layerConnectorTest( | |
| 160 | 160 | execute: ( |
| 161 | 161 | request: HttpClientRequest.HttpClientRequest, |
| 162 | 162 | ) => Effect.Effect<HttpClientResponse.HttpClientResponse>, |
| ... | ... | @@ -267,7 +267,7 @@ describe("EnvironmentConnector", () => { |
| 267 | 267 | }); |
| 268 | 268 | }).pipe( |
| 269 | 269 | Effect.provide( |
| 270 | connectorTestLayer(execute, { | |
| 270 | layerConnectorTest(execute, { | |
| 271 | 271 | links: { |
| 272 | 272 | ...links, |
| 273 | 273 | getForUser: (input) => waitForPeer.pipe(Effect.andThen(links.getForUser(input))), |
| ... | ... | @@ -326,7 +326,7 @@ describe("EnvironmentConnector", () => { |
| 326 | 326 | label: "Connector Test Environment", |
| 327 | 327 | }, |
| 328 | 328 | }); |
| 329 | }).pipe(Effect.provide(connectorTestLayer(execute))); | |
| 329 | }).pipe(Effect.provide(layerConnectorTest(execute))); | |
| 330 | 330 | }); |
| 331 | 331 | |
| 332 | 332 | it.effect("rejects manual endpoints before sending a health request", () => { |
| ... | ... | @@ -359,7 +359,7 @@ describe("EnvironmentConnector", () => { |
| 359 | 359 | expect(requestCount).toBe(0); |
| 360 | 360 | }).pipe( |
| 361 | 361 | Effect.provide( |
| 362 | connectorTestLayer(execute, { | |
| 362 | layerConnectorTest(execute, { | |
| 363 | 363 | links: makeLinks({ |
| 364 | 364 | endpoint: { |
| 365 | 365 | httpBaseUrl: "https://127.0.0.1/", |
| ... | ... | @@ -424,7 +424,7 @@ describe("EnvironmentConnector", () => { |
| 424 | 424 | expect(requestCount).toBe(0); |
| 425 | 425 | }).pipe( |
| 426 | 426 | Effect.provide( |
| 427 | connectorTestLayer(execute, { | |
| 427 | layerConnectorTest(execute, { | |
| 428 | 428 | links: makeLinks({ |
| 429 | 429 | endpoint: { |
| 430 | 430 | httpBaseUrl: "https://attacker.example.test/", |
| ... | ... | @@ -468,7 +468,7 @@ describe("EnvironmentConnector", () => { |
| 468 | 468 | expect(requestCount).toBe(0); |
| 469 | 469 | }).pipe( |
| 470 | 470 | Effect.provide( |
| 471 | connectorTestLayer(execute, { | |
| 471 | layerConnectorTest(execute, { | |
| 472 | 472 | allocations: makeAllocations({ |
| 473 | 473 | userId: "user_123", |
| 474 | 474 | environmentId: "env-connector-test", |
| ... | ... | @@ -519,7 +519,7 @@ describe("EnvironmentConnector", () => { |
| 519 | 519 | if (result._tag === "Failure") { |
| 520 | 520 | expect(result.cause.toString()).toContain("EnvironmentMintResponseInvalid"); |
| 521 | 521 | } |
| 522 | }).pipe(Effect.provide(connectorTestLayer(execute))); | |
| 522 | }).pipe(Effect.provide(layerConnectorTest(execute))); | |
| 523 | 523 | }); |
| 524 | 524 | |
| 525 | 525 | it.effect("reports offline status when the managed endpoint health request fails", () => { |
| ... | ... | @@ -550,7 +550,7 @@ describe("EnvironmentConnector", () => { |
| 550 | 550 | error: "Managed endpoint health request failed: Environment is unavailable.", |
| 551 | 551 | traceId: expect.any(String), |
| 552 | 552 | }); |
| 553 | }).pipe(Effect.provide(connectorTestLayer(execute))); | |
| 553 | }).pipe(Effect.provide(layerConnectorTest(execute))); | |
| 554 | 554 | }); |
| 555 | 555 | |
| 556 | 556 | it.effect("rejects health responses with a mismatched top-level environment id", () => { |
| ... | ... | @@ -581,7 +581,7 @@ describe("EnvironmentConnector", () => { |
| 581 | 581 | if (result._tag === "Failure") { |
| 582 | 582 | expect(result.cause.toString()).toContain("EnvironmentMintResponseInvalid"); |
| 583 | 583 | } |
| 584 | }).pipe(Effect.provide(connectorTestLayer(execute))); | |
| 584 | }).pipe(Effect.provide(layerConnectorTest(execute))); | |
| 585 | 585 | }); |
| 586 | 586 | |
| 587 | 587 | it.effect("rejects health responses with an unsigned top-level descriptor mutation", () => { |
| ... | ... | @@ -617,7 +617,7 @@ describe("EnvironmentConnector", () => { |
| 617 | 617 | if (result._tag === "Failure") { |
| 618 | 618 | expect(result.cause.toString()).toContain("EnvironmentMintResponseInvalid"); |
| 619 | 619 | } |
| 620 | }).pipe(Effect.provide(connectorTestLayer(execute))); | |
| 620 | }).pipe(Effect.provide(layerConnectorTest(execute))); | |
| 621 | 621 | }); |
| 622 | 622 | |
| 623 | 623 | it.effect("rejects health responses when the linked environment public key is malformed", () => { |
| ... | ... | @@ -645,7 +645,7 @@ describe("EnvironmentConnector", () => { |
| 645 | 645 | } |
| 646 | 646 | }).pipe( |
| 647 | 647 | Effect.provide( |
| 648 | connectorTestLayer(execute, { | |
| 648 | layerConnectorTest(execute, { | |
| 649 | 649 | links: makeLinks({ |
| 650 | 650 | environmentPublicKey: "not a pem public key", |
| 651 | 651 | }), |
| ... | ... | @@ -696,7 +696,7 @@ describe("EnvironmentConnector", () => { |
| 696 | 696 | wsBaseUrl: "wss://env.example.test/ws", |
| 697 | 697 | }, |
| 698 | 698 | }); |
| 699 | }).pipe(Effect.provide(connectorTestLayer(execute))); | |
| 699 | }).pipe(Effect.provide(layerConnectorTest(execute))); | |
| 700 | 700 | }); |
| 701 | 701 | |
| 702 | 702 | it.effect("only accepts mint responses signed by the user's linked environment key", () => { |
| ... | ... | @@ -725,7 +725,7 @@ describe("EnvironmentConnector", () => { |
| 725 | 725 | if (result._tag === "Failure") { |
| 726 | 726 | expect(result.cause.toString()).toContain("EnvironmentMintResponseInvalid"); |
| 727 | 727 | } |
| 728 | }).pipe(Effect.provide(connectorTestLayer(execute))); | |
| 728 | }).pipe(Effect.provide(layerConnectorTest(execute))); | |
| 729 | 729 | }); |
| 730 | 730 | |
| 731 | 731 | it.effect("rejects mint responses when the linked environment public key is malformed", () => { |
| ... | ... | @@ -754,7 +754,7 @@ describe("EnvironmentConnector", () => { |
| 754 | 754 | } |
| 755 | 755 | }).pipe( |
| 756 | 756 | Effect.provide( |
| 757 | connectorTestLayer(execute, { | |
| 757 | layerConnectorTest(execute, { | |
| 758 | 758 | links: makeLinks({ |
| 759 | 759 | environmentPublicKey: "not a pem public key", |
| 760 | 760 | }), |
| ... | ... | @@ -790,7 +790,7 @@ describe("EnvironmentConnector", () => { |
| 790 | 790 | if (result._tag === "Failure") { |
| 791 | 791 | expect(result.cause.toString()).toContain("EnvironmentMintResponseInvalid"); |
| 792 | 792 | } |
| 793 | }).pipe(Effect.provide(connectorTestLayer(execute))); | |
| 793 | }).pipe(Effect.provide(layerConnectorTest(execute))); | |
| 794 | 794 | }); |
| 795 | 795 | |
| 796 | 796 | it.effect("times out hung managed endpoint mint requests", () => { |
| ... | ... | @@ -827,6 +827,6 @@ describe("EnvironmentConnector", () => { |
| 827 | 827 | timeoutMs: EnvironmentConnector.ENVIRONMENT_MINT_REQUEST_TIMEOUT_MS, |
| 828 | 828 | }); |
| 829 | 829 | } |
| 830 | }).pipe(Effect.provide(Layer.merge(TestClock.layer(), connectorTestLayer(execute)))); | |
| 830 | }).pipe(Effect.provide(Layer.merge(TestClock.layer(), layerConnectorTest(execute)))); | |
| 831 | 831 | }); |
| 832 | 832 | }); |
infra/relay/src/environments/EnvironmentLinker.test.ts+5-5| ... | ... | @@ -106,7 +106,7 @@ const makeRequest = Effect.gen(function* () { |
| 106 | 106 | }; |
| 107 | 107 | }); |
| 108 | 108 | |
| 109 | function testLayer(input?: { | |
| 109 | function layerTest(input?: { | |
| 110 | 110 | readonly upsert?: EnvironmentLinks.EnvironmentLinks["Service"]["upsert"]; |
| 111 | 111 | readonly consume?: DpopProofs.DpopProofReplay["Service"]["consume"]; |
| 112 | 112 | readonly deprovision?: ManagedEndpointProvider.ManagedEndpointProvider["Service"]["deprovision"]; |
| ... | ... | @@ -167,7 +167,7 @@ describe("EnvironmentLinker", () => { |
| 167 | 167 | expect(persistedEnvironmentId).toBe(payload.environmentId); |
| 168 | 168 | }).pipe( |
| 169 | 169 | Effect.provide( |
| 170 | testLayer({ | |
| 170 | layerTest({ | |
| 171 | 171 | upsert: (input) => |
| 172 | 172 | Effect.sync(() => { |
| 173 | 173 | persistedEnvironmentId = input.proof.environmentId; |
| ... | ... | @@ -236,7 +236,7 @@ describe("EnvironmentLinker", () => { |
| 236 | 236 | expect(deprovisionedEnvironmentId).toBe("env-link-test"); |
| 237 | 237 | }).pipe( |
| 238 | 238 | Effect.provide( |
| 239 | testLayer({ | |
| 239 | layerTest({ | |
| 240 | 240 | upsert: (input) => |
| 241 | 241 | Effect.sync(() => { |
| 242 | 242 | persistedEndpoint = input.endpoint.httpBaseUrl; |
| ... | ... | @@ -277,7 +277,7 @@ describe("EnvironmentLinker", () => { |
| 277 | 277 | expect(persisted).toBe(false); |
| 278 | 278 | }).pipe( |
| 279 | 279 | Effect.provide( |
| 280 | testLayer({ | |
| 280 | layerTest({ | |
| 281 | 281 | upsert: () => |
| 282 | 282 | Effect.sync(() => { |
| 283 | 283 | persisted = true; |
| ... | ... | @@ -304,6 +304,6 @@ describe("EnvironmentLinker", () => { |
| 304 | 304 | }); |
| 305 | 305 | } |
| 306 | 306 | } |
| 307 | }).pipe(Effect.provide(testLayer({ consume: () => Effect.succeed(false) }))), | |
| 307 | }).pipe(Effect.provide(layerTest({ consume: () => Effect.succeed(false) }))), | |
| 308 | 308 | ); |
| 309 | 309 | }); |
infra/relay/src/environments/ManagedEndpointProvider.test.ts+41-41| ... | ... | @@ -346,7 +346,7 @@ function makeTunnelLimits( |
| 346 | 346 | }); |
| 347 | 347 | } |
| 348 | 348 | |
| 349 | function providerLayer( | |
| 349 | function layerProvider( | |
| 350 | 350 | tunnelClient = makeTunnelClient(), |
| 351 | 351 | dnsClient = makeDnsClient(), |
| 352 | 352 | allocations = makeAllocations(), |
| ... | ... | @@ -492,7 +492,7 @@ describe("ManagedEndpointProvider", () => { |
| 492 | 492 | ]); |
| 493 | 493 | }).pipe( |
| 494 | 494 | Effect.provide( |
| 495 | providerLayer( | |
| 495 | layerProvider( | |
| 496 | 496 | makeTunnelClient(tunnelCalls), |
| 497 | 497 | makeDnsClient(dnsCalls), |
| 498 | 498 | makeAllocations(allocationCalls), |
| ... | ... | @@ -515,7 +515,7 @@ describe("ManagedEndpointProvider", () => { |
| 515 | 515 | expect(limitCalls).toEqual([{ userId: "user_ABC", environmentId: "env_ABC" }]); |
| 516 | 516 | }).pipe( |
| 517 | 517 | Effect.provide( |
| 518 | providerLayer( | |
| 518 | layerProvider( | |
| 519 | 519 | makeTunnelClient(), |
| 520 | 520 | makeDnsClient(), |
| 521 | 521 | makeAllocations(), |
| ... | ... | @@ -552,7 +552,7 @@ describe("ManagedEndpointProvider", () => { |
| 552 | 552 | expect(allocationCalls).toEqual([]); |
| 553 | 553 | }).pipe( |
| 554 | 554 | Effect.provide( |
| 555 | providerLayer( | |
| 555 | layerProvider( | |
| 556 | 556 | makeTunnelClient(tunnelCalls), |
| 557 | 557 | makeDnsClient(dnsCalls), |
| 558 | 558 | makeAllocations(allocationCalls), |
| ... | ... | @@ -607,7 +607,7 @@ describe("ManagedEndpointProvider", () => { |
| 607 | 607 | name: requestedName, |
| 608 | 608 | configSrc: "cloudflare", |
| 609 | 609 | }); |
| 610 | }).pipe(Effect.provide(providerLayer(makeTunnelClient(tunnelCalls)))); | |
| 610 | }).pipe(Effect.provide(layerProvider(makeTunnelClient(tunnelCalls)))); | |
| 611 | 611 | }); |
| 612 | 612 | |
| 613 | 613 | it.effect("formats IPv6 loopback origins as valid Cloudflare ingress service URLs", () => { |
| ... | ... | @@ -633,7 +633,7 @@ describe("ManagedEndpointProvider", () => { |
| 633 | 633 | ], |
| 634 | 634 | }, |
| 635 | 635 | }); |
| 636 | }).pipe(Effect.provide(providerLayer(makeTunnelClient(tunnelCalls)))); | |
| 636 | }).pipe(Effect.provide(layerProvider(makeTunnelClient(tunnelCalls)))); | |
| 637 | 637 | }); |
| 638 | 638 | |
| 639 | 639 | it.effect("rejects non-loopback managed endpoint origins before calling Cloudflare", () => { |
| ... | ... | @@ -660,7 +660,7 @@ describe("ManagedEndpointProvider", () => { |
| 660 | 660 | port: 3773, |
| 661 | 661 | }); |
| 662 | 662 | } |
| 663 | }).pipe(Effect.provide(providerLayer(makeTunnelClient(), makeDnsClient(dnsCalls)))); | |
| 663 | }).pipe(Effect.provide(layerProvider(makeTunnelClient(), makeDnsClient(dnsCalls)))); | |
| 664 | 664 | }); |
| 665 | 665 | |
| 666 | 666 | it.effect("rejects invalid managed endpoint origin ports before calling Cloudflare", () => { |
| ... | ... | @@ -681,7 +681,7 @@ describe("ManagedEndpointProvider", () => { |
| 681 | 681 | if (result._tag === "Failure") { |
| 682 | 682 | expect(result.failure._tag).toBe("ManagedEndpointOriginNotAllowed"); |
| 683 | 683 | } |
| 684 | }).pipe(Effect.provide(providerLayer(makeTunnelClient(), makeDnsClient(dnsCalls)))); | |
| 684 | }).pipe(Effect.provide(layerProvider(makeTunnelClient(), makeDnsClient(dnsCalls)))); | |
| 685 | 685 | }); |
| 686 | 686 | |
| 687 | 687 | it.effect("reconciles an existing same-host DNS record through the DNS client", () => { |
| ... | ... | @@ -698,7 +698,7 @@ describe("ManagedEndpointProvider", () => { |
| 698 | 698 | expect(dnsCalls[1]?.input).toMatchObject({ dnsRecordId: "existing-record-id" }); |
| 699 | 699 | }).pipe( |
| 700 | 700 | Effect.provide( |
| 701 | providerLayer(makeTunnelClient(), makeDnsClient(dnsCalls, [{ id: "existing-record-id" }])), | |
| 701 | layerProvider(makeTunnelClient(), makeDnsClient(dnsCalls, [{ id: "existing-record-id" }])), | |
| 702 | 702 | ), |
| 703 | 703 | ); |
| 704 | 704 | }); |
| ... | ... | @@ -707,7 +707,7 @@ describe("ManagedEndpointProvider", () => { |
| 707 | 707 | const tunnelCalls: TunnelCall[] = []; |
| 708 | 708 | const dnsCalls: DnsCall[] = []; |
| 709 | 709 | const allocationCalls: AllocationCall[] = []; |
| 710 | const layer = providerLayer( | |
| 710 | const layer = layerProvider( | |
| 711 | 711 | makePersistentTunnelClient(tunnelCalls), |
| 712 | 712 | makeDnsClient(dnsCalls), |
| 713 | 713 | makeAllocations(allocationCalls), |
| ... | ... | @@ -754,7 +754,7 @@ describe("ManagedEndpointProvider", () => { |
| 754 | 754 | const dnsCalls: DnsCall[] = []; |
| 755 | 755 | const allocationCalls: AllocationCall[] = []; |
| 756 | 756 | const dnsClient = makeDnsClient(dnsCalls); |
| 757 | const layer = providerLayer( | |
| 757 | const layer = layerProvider( | |
| 758 | 758 | makePersistentTunnelClient(), |
| 759 | 759 | dnsClient, |
| 760 | 760 | makeAllocations(allocationCalls), |
| ... | ... | @@ -810,7 +810,7 @@ describe("ManagedEndpointProvider", () => { |
| 810 | 810 | deleteRecord: () => Effect.void, |
| 811 | 811 | }); |
| 812 | 812 | const allocations = makeAllocations(); |
| 813 | const layer = providerLayer(makePersistentTunnelClient(), dnsClient, allocations); | |
| 813 | const layer = layerProvider(makePersistentTunnelClient(), dnsClient, allocations); | |
| 814 | 814 | |
| 815 | 815 | return Effect.gen(function* () { |
| 816 | 816 | const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider; |
| ... | ... | @@ -846,7 +846,7 @@ describe("ManagedEndpointProvider", () => { |
| 846 | 846 | const tunnelCalls: TunnelCall[] = []; |
| 847 | 847 | const dnsCalls: DnsCall[] = []; |
| 848 | 848 | const allocationCalls: AllocationCall[] = []; |
| 849 | const layer = providerLayer( | |
| 849 | const layer = layerProvider( | |
| 850 | 850 | makePersistentTunnelClient(tunnelCalls), |
| 851 | 851 | makeDnsClient(dnsCalls), |
| 852 | 852 | makeAllocations(allocationCalls), |
| ... | ... | @@ -890,7 +890,7 @@ describe("ManagedEndpointProvider", () => { |
| 890 | 890 | const tunnelCalls: TunnelCall[] = []; |
| 891 | 891 | const dnsCalls: DnsCall[] = []; |
| 892 | 892 | const allocationCalls: AllocationCall[] = []; |
| 893 | const layer = providerLayer( | |
| 893 | const layer = layerProvider( | |
| 894 | 894 | makePersistentTunnelClient(tunnelCalls), |
| 895 | 895 | makeDnsClient(dnsCalls), |
| 896 | 896 | makeAllocations(allocationCalls), |
| ... | ... | @@ -931,7 +931,7 @@ describe("ManagedEndpointProvider", () => { |
| 931 | 931 | const tunnelCalls: TunnelCall[] = []; |
| 932 | 932 | const dnsCalls: DnsCall[] = []; |
| 933 | 933 | const allocationCalls: AllocationCall[] = []; |
| 934 | const layer = providerLayer( | |
| 934 | const layer = layerProvider( | |
| 935 | 935 | makePersistentTunnelClient(tunnelCalls), |
| 936 | 936 | makeDnsClient(dnsCalls), |
| 937 | 937 | makeAllocations(allocationCalls), |
| ... | ... | @@ -974,7 +974,7 @@ describe("ManagedEndpointProvider", () => { |
| 974 | 974 | it.effect("treats an environment without a recorded tunnel as already released", () => { |
| 975 | 975 | const tunnelCalls: TunnelCall[] = []; |
| 976 | 976 | const dnsCalls: DnsCall[] = []; |
| 977 | const layer = providerLayer( | |
| 977 | const layer = layerProvider( | |
| 978 | 978 | makePersistentTunnelClient(tunnelCalls), |
| 979 | 979 | makeDnsClient(dnsCalls), |
| 980 | 980 | makeAllocations(), |
| ... | ... | @@ -999,7 +999,7 @@ describe("ManagedEndpointProvider", () => { |
| 999 | 999 | ...allocations, |
| 1000 | 1000 | claimRelease: () => Effect.succeed(null), |
| 1001 | 1001 | }); |
| 1002 | const layer = providerLayer(makePersistentTunnelClient(tunnelCalls), makeDnsClient(), outdated); | |
| 1002 | const layer = layerProvider(makePersistentTunnelClient(tunnelCalls), makeDnsClient(), outdated); | |
| 1003 | 1003 | |
| 1004 | 1004 | return Effect.gen(function* () { |
| 1005 | 1005 | const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider; |
| ... | ... | @@ -1024,7 +1024,7 @@ describe("ManagedEndpointProvider", () => { |
| 1024 | 1024 | |
| 1025 | 1025 | it.effect("does not release a tunnel when the requested tunnel id is outdated", () => { |
| 1026 | 1026 | const tunnelCalls: TunnelCall[] = []; |
| 1027 | const layer = providerLayer( | |
| 1027 | const layer = layerProvider( | |
| 1028 | 1028 | makePersistentTunnelClient(tunnelCalls), |
| 1029 | 1029 | makeDnsClient(), |
| 1030 | 1030 | makeAllocations(), |
| ... | ... | @@ -1065,7 +1065,7 @@ describe("ManagedEndpointProvider", () => { |
| 1065 | 1065 | }), |
| 1066 | 1066 | ), |
| 1067 | 1067 | }); |
| 1068 | const layer = providerLayer(makePersistentTunnelClient(tunnelCalls), makeDnsClient(), replaced); | |
| 1068 | const layer = layerProvider(makePersistentTunnelClient(tunnelCalls), makeDnsClient(), replaced); | |
| 1069 | 1069 | |
| 1070 | 1070 | return Effect.gen(function* () { |
| 1071 | 1071 | const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider; |
| ... | ... | @@ -1082,7 +1082,7 @@ describe("ManagedEndpointProvider", () => { |
| 1082 | 1082 | |
| 1083 | 1083 | it.effect("does no Cloudflare work when the registered origin is unchanged", () => { |
| 1084 | 1084 | const tunnelCalls: TunnelCall[] = []; |
| 1085 | const layer = providerLayer(makePersistentTunnelClient(tunnelCalls)); | |
| 1085 | const layer = layerProvider(makePersistentTunnelClient(tunnelCalls)); | |
| 1086 | 1086 | |
| 1087 | 1087 | return Effect.gen(function* () { |
| 1088 | 1088 | const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider; |
| ... | ... | @@ -1105,7 +1105,7 @@ describe("ManagedEndpointProvider", () => { |
| 1105 | 1105 | |
| 1106 | 1106 | it.effect("updates Cloudflare ingress once when the registered port changes", () => { |
| 1107 | 1107 | const tunnelCalls: TunnelCall[] = []; |
| 1108 | const layer = providerLayer(makePersistentTunnelClient(tunnelCalls)); | |
| 1108 | const layer = layerProvider(makePersistentTunnelClient(tunnelCalls)); | |
| 1109 | 1109 | |
| 1110 | 1110 | return Effect.gen(function* () { |
| 1111 | 1111 | const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider; |
| ... | ... | @@ -1161,7 +1161,7 @@ describe("ManagedEndpointProvider", () => { |
| 1161 | 1161 | ) |
| 1162 | 1162 | : baseTunnelClient.putConfiguration(tunnelId, tunnelConfig), |
| 1163 | 1163 | }); |
| 1164 | const layer = providerLayer(tunnelClient); | |
| 1164 | const layer = layerProvider(tunnelClient); | |
| 1165 | 1165 | |
| 1166 | 1166 | return Effect.gen(function* () { |
| 1167 | 1167 | const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider; |
| ... | ... | @@ -1191,7 +1191,7 @@ describe("ManagedEndpointProvider", () => { |
| 1191 | 1191 | withClaimedTunnel: (input, effect) => |
| 1192 | 1192 | loseClaim ? Effect.succeedNone : allocations.withClaimedTunnel(input, effect), |
| 1193 | 1193 | }); |
| 1194 | const layer = providerLayer(makePersistentTunnelClient(), makeDnsClient(), changed); | |
| 1194 | const layer = layerProvider(makePersistentTunnelClient(), makeDnsClient(), changed); | |
| 1195 | 1195 | |
| 1196 | 1196 | return Effect.gen(function* () { |
| 1197 | 1197 | const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider; |
| ... | ... | @@ -1218,7 +1218,7 @@ describe("ManagedEndpointProvider", () => { |
| 1218 | 1218 | }); |
| 1219 | 1219 | |
| 1220 | 1220 | it.effect("rejects an active endpoint that does not match the allocation hostname", () => { |
| 1221 | const layer = providerLayer(makePersistentTunnelClient()); | |
| 1221 | const layer = layerProvider(makePersistentTunnelClient()); | |
| 1222 | 1222 | |
| 1223 | 1223 | return Effect.gen(function* () { |
| 1224 | 1224 | const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider; |
| ... | ... | @@ -1263,7 +1263,7 @@ describe("ManagedEndpointProvider", () => { |
| 1263 | 1263 | return yield* allocations.recordTunnel(input); |
| 1264 | 1264 | }), |
| 1265 | 1265 | }); |
| 1266 | const layer = providerLayer( | |
| 1266 | const layer = layerProvider( | |
| 1267 | 1267 | makePersistentTunnelClient(tunnelCalls), |
| 1268 | 1268 | makeDnsClient(), |
| 1269 | 1269 | changed, |
| ... | ... | @@ -1297,7 +1297,7 @@ describe("ManagedEndpointProvider", () => { |
| 1297 | 1297 | ...allocations, |
| 1298 | 1298 | recordDns: () => Effect.succeed(null), |
| 1299 | 1299 | }); |
| 1300 | const layer = providerLayer(makePersistentTunnelClient(), makeDnsClient(), changed); | |
| 1300 | const layer = layerProvider(makePersistentTunnelClient(), makeDnsClient(), changed); | |
| 1301 | 1301 | |
| 1302 | 1302 | return Effect.gen(function* () { |
| 1303 | 1303 | const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider; |
| ... | ... | @@ -1323,7 +1323,7 @@ describe("ManagedEndpointProvider", () => { |
| 1323 | 1323 | ...allocations, |
| 1324 | 1324 | withClaimedTunnel: () => Effect.succeedNone, |
| 1325 | 1325 | }); |
| 1326 | const layer = providerLayer(makePersistentTunnelClient(tunnelCalls), makeDnsClient(), changed); | |
| 1326 | const layer = layerProvider(makePersistentTunnelClient(tunnelCalls), makeDnsClient(), changed); | |
| 1327 | 1327 | |
| 1328 | 1328 | return Effect.gen(function* () { |
| 1329 | 1329 | const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider; |
| ... | ... | @@ -1353,7 +1353,7 @@ describe("ManagedEndpointProvider", () => { |
| 1353 | 1353 | withClaimedTunnel: (input, effect) => |
| 1354 | 1354 | ++lockCount === 1 ? allocations.withClaimedTunnel(input, effect) : Effect.succeedNone, |
| 1355 | 1355 | }); |
| 1356 | const layer = providerLayer(makePersistentTunnelClient(), makeDnsClient(dnsCalls), changed); | |
| 1356 | const layer = layerProvider(makePersistentTunnelClient(), makeDnsClient(dnsCalls), changed); | |
| 1357 | 1357 | |
| 1358 | 1358 | return Effect.gen(function* () { |
| 1359 | 1359 | const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider; |
| ... | ... | @@ -1379,7 +1379,7 @@ describe("ManagedEndpointProvider", () => { |
| 1379 | 1379 | ...allocations, |
| 1380 | 1380 | markReady: () => Effect.succeed(false), |
| 1381 | 1381 | }); |
| 1382 | const layer = providerLayer(makePersistentTunnelClient(), makeDnsClient(), changed); | |
| 1382 | const layer = layerProvider(makePersistentTunnelClient(), makeDnsClient(), changed); | |
| 1383 | 1383 | |
| 1384 | 1384 | return Effect.gen(function* () { |
| 1385 | 1385 | const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider; |
| ... | ... | @@ -1411,7 +1411,7 @@ describe("ManagedEndpointProvider", () => { |
| 1411 | 1411 | connsInactiveAt: null, |
| 1412 | 1412 | }), |
| 1413 | 1413 | }); |
| 1414 | const layer = providerLayer(tunnelClient, makeDnsClient(), makeAllocations()); | |
| 1414 | const layer = layerProvider(tunnelClient, makeDnsClient(), makeAllocations()); | |
| 1415 | 1415 | |
| 1416 | 1416 | return Effect.gen(function* () { |
| 1417 | 1417 | const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider; |
| ... | ... | @@ -1454,7 +1454,7 @@ describe("ManagedEndpointProvider", () => { |
| 1454 | 1454 | ), |
| 1455 | 1455 | ), |
| 1456 | 1456 | }); |
| 1457 | const layer = providerLayer(makePersistentTunnelClient(tunnelCalls), makeDnsClient(), replaced); | |
| 1457 | const layer = layerProvider(makePersistentTunnelClient(tunnelCalls), makeDnsClient(), replaced); | |
| 1458 | 1458 | |
| 1459 | 1459 | return Effect.gen(function* () { |
| 1460 | 1460 | const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider; |
| ... | ... | @@ -1489,7 +1489,7 @@ describe("ManagedEndpointProvider", () => { |
| 1489 | 1489 | }), |
| 1490 | 1490 | ), |
| 1491 | 1491 | }); |
| 1492 | const layer = providerLayer(tunnelClient, makeDnsClient(), makeAllocations()); | |
| 1492 | const layer = layerProvider(tunnelClient, makeDnsClient(), makeAllocations()); | |
| 1493 | 1493 | |
| 1494 | 1494 | return Effect.gen(function* () { |
| 1495 | 1495 | const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider; |
| ... | ... | @@ -1512,7 +1512,7 @@ describe("ManagedEndpointProvider", () => { |
| 1512 | 1512 | ...makeTunnelClient(), |
| 1513 | 1513 | delete: () => Effect.fail(failure), |
| 1514 | 1514 | }); |
| 1515 | const layer = providerLayer(tunnelClient, makeDnsClient(), makeAllocations()); | |
| 1515 | const layer = layerProvider(tunnelClient, makeDnsClient(), makeAllocations()); | |
| 1516 | 1516 | |
| 1517 | 1517 | return Effect.gen(function* () { |
| 1518 | 1518 | const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider; |
| ... | ... | @@ -1551,7 +1551,7 @@ describe("ManagedEndpointProvider", () => { |
| 1551 | 1551 | }), |
| 1552 | 1552 | ), |
| 1553 | 1553 | }); |
| 1554 | const layer = providerLayer(tunnelClient, makeDnsClient(), makeAllocations()); | |
| 1554 | const layer = layerProvider(tunnelClient, makeDnsClient(), makeAllocations()); | |
| 1555 | 1555 | |
| 1556 | 1556 | return Effect.gen(function* () { |
| 1557 | 1557 | const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider; |
| ... | ... | @@ -1568,7 +1568,7 @@ describe("ManagedEndpointProvider", () => { |
| 1568 | 1568 | const tunnelCalls: TunnelCall[] = []; |
| 1569 | 1569 | const dnsCalls: DnsCall[] = []; |
| 1570 | 1570 | const allocationCalls: AllocationCall[] = []; |
| 1571 | const layer = providerLayer( | |
| 1571 | const layer = layerProvider( | |
| 1572 | 1572 | makePersistentTunnelClient(tunnelCalls), |
| 1573 | 1573 | makeDnsClient(dnsCalls), |
| 1574 | 1574 | makeAllocations(allocationCalls), |
| ... | ... | @@ -1606,7 +1606,7 @@ describe("ManagedEndpointProvider", () => { |
| 1606 | 1606 | } |
| 1607 | 1607 | }), |
| 1608 | 1608 | }); |
| 1609 | const layer = providerLayer(tunnelClient, makeDnsClient(), makeAllocations(allocationCalls)); | |
| 1609 | const layer = layerProvider(tunnelClient, makeDnsClient(), makeAllocations(allocationCalls)); | |
| 1610 | 1610 | |
| 1611 | 1611 | return Effect.gen(function* () { |
| 1612 | 1612 | const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider; |
| ... | ... | @@ -1668,7 +1668,7 @@ describe("ManagedEndpointProvider", () => { |
| 1668 | 1668 | }), |
| 1669 | 1669 | ), |
| 1670 | 1670 | }); |
| 1671 | const layer = providerLayer(tunnelClient, dnsClient, makeAllocations(allocationCalls)); | |
| 1671 | const layer = layerProvider(tunnelClient, dnsClient, makeAllocations(allocationCalls)); | |
| 1672 | 1672 | |
| 1673 | 1673 | return Effect.gen(function* () { |
| 1674 | 1674 | const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider; |
| ... | ... | @@ -1705,7 +1705,7 @@ describe("ManagedEndpointProvider", () => { |
| 1705 | 1705 | { name: expectedManagedTunnelName("env_shared", "user_ABC"), isDeleted: false }, |
| 1706 | 1706 | { name: expectedManagedTunnelName("env_shared", "user_DEF"), isDeleted: false }, |
| 1707 | 1707 | ]); |
| 1708 | }).pipe(Effect.provide(providerLayer(makeTunnelClient(tunnelCalls)))); | |
| 1708 | }).pipe(Effect.provide(layerProvider(makeTunnelClient(tunnelCalls)))); | |
| 1709 | 1709 | }); |
| 1710 | 1710 | |
| 1711 | 1711 | it.effect("recovers when DNS creation reports failure after the record became visible", () => { |
| ... | ... | @@ -1752,7 +1752,7 @@ describe("ManagedEndpointProvider", () => { |
| 1752 | 1752 | "listRecords", |
| 1753 | 1753 | "updateRecord", |
| 1754 | 1754 | ]); |
| 1755 | }).pipe(Effect.provide(providerLayer(makeTunnelClient(), dnsClient))); | |
| 1755 | }).pipe(Effect.provide(layerProvider(makeTunnelClient(), dnsClient))); | |
| 1756 | 1756 | }); |
| 1757 | 1757 | |
| 1758 | 1758 | it.effect("reports mismatched tunnel responses without manufacturing a cause", () => { |
| ... | ... | @@ -1786,7 +1786,7 @@ describe("ManagedEndpointProvider", () => { |
| 1786 | 1786 | expect(error.cause).toBeUndefined(); |
| 1787 | 1787 | } |
| 1788 | 1788 | expect(dnsCalls).toHaveLength(0); |
| 1789 | }).pipe(Effect.provide(providerLayer(tunnelClient, makeDnsClient(dnsCalls)))); | |
| 1789 | }).pipe(Effect.provide(layerProvider(tunnelClient, makeDnsClient(dnsCalls)))); | |
| 1790 | 1790 | }); |
| 1791 | 1791 | |
| 1792 | 1792 | it.effect("fails provisioning when the DNS client fails", () => { |
| ... | ... | @@ -1824,6 +1824,6 @@ describe("ManagedEndpointProvider", () => { |
| 1824 | 1824 | if (error._tag === "ManagedEndpointProvisioningFailed") { |
| 1825 | 1825 | expect(error.cause).toBe(failure); |
| 1826 | 1826 | } |
| 1827 | }).pipe(Effect.provide(providerLayer(makeTunnelClient(), dnsClient))); | |
| 1827 | }).pipe(Effect.provide(layerProvider(makeTunnelClient(), dnsClient))); | |
| 1828 | 1828 | }); |
| 1829 | 1829 | }); |
infra/relay/src/hooks/HeldHooks.test.ts+2-2| ... | ... | @@ -66,7 +66,7 @@ const withService = <A, E>( |
| 66 | 66 | allocationFor("user_1", ownKey, options.ownReady ?? true), |
| 67 | 67 | allocationFor("user_2", otherKey, true), |
| 68 | 68 | ]; |
| 69 | const dependencies = Layer.mergeAll( | |
| 69 | const layerDependencies = Layer.mergeAll( | |
| 70 | 70 | Layer.succeed(RelayConfiguration.RelayConfiguration, settings), |
| 71 | 71 | Layer.mock(EnvironmentLinks.EnvironmentLinks, { |
| 72 | 72 | findActiveManagedForEnvironment: (input) => |
| ... | ... | @@ -103,7 +103,7 @@ const withService = <A, E>( |
| 103 | 103 | return Effect.gen(function* () { |
| 104 | 104 | const heldHooks = yield* HeldHooks.HeldHooks; |
| 105 | 105 | return yield* body({ heldHooks, cleared, woken }); |
| 106 | }).pipe(Effect.provide(HeldHooks.layer.pipe(Layer.provide(dependencies)))); | |
| 106 | }).pipe(Effect.provide(HeldHooks.layer.pipe(Layer.provide(layerDependencies)))); | |
| 107 | 107 | }; |
| 108 | 108 | |
| 109 | 109 | describe("HeldHooks", () => { |
infra/relay/src/hooks/HookForwarder.test.ts+6-10| ... | ... | @@ -29,12 +29,8 @@ import * as HttpServerResponse from "effect/http/HttpServerResponse"; |
| 29 | 29 | import * as RelayConfiguration from "../Config.ts"; |
| 30 | 30 | import * as EnvironmentLinks from "../environments/EnvironmentLinks.ts"; |
| 31 | 31 | import * as ManagedEndpointAllocations from "../environments/ManagedEndpointAllocations.ts"; |
| 32 | import { | |
| 33 | RELAY_HTTP_ROUTER_CONFIG, | |
| 34 | relayCors, | |
| 35 | relayNotFoundRoute, | |
| 36 | traceRelayHttpRequestWith, | |
| 37 | } from "../http/Api.ts"; | |
| 32 | import { RELAY_HTTP_ROUTER_CONFIG, traceRelayHttpRequestWith } from "../http/Api.ts"; | |
| 33 | import * as RelayHttpApi from "../http/Api.ts"; | |
| 38 | 34 | import * as HookForwarder from "./HookForwarder.ts"; |
| 39 | 35 | import { RELAY_HOOK_DELIVERY_TYP, verifyRelayJwt } from "@t3tools/shared/relayJwt"; |
| 40 | 36 | import * as HeldHooks from "./HeldHooks.ts"; |
| ... | ... | @@ -114,7 +110,7 @@ function makeHarness(options: Harness = {}) { |
| 114 | 110 | new Response("ok", { status: 200, headers: { "content-type": "text/plain" } }), |
| 115 | 111 | ), |
| 116 | 112 | )); |
| 117 | const forwarderLayer = HookForwarder.layer.pipe( | |
| 113 | const layerForwarder = HookForwarder.layer.pipe( | |
| 118 | 114 | Layer.provideMerge(HeldHooks.layer), |
| 119 | 115 | Layer.provide( |
| 120 | 116 | Layer.mergeAll( |
| ... | ... | @@ -167,11 +163,11 @@ function makeHarness(options: Harness = {}) { |
| 167 | 163 | const httpEffect = HttpRouter.toHttpEffect( |
| 168 | 164 | Layer.mergeAll( |
| 169 | 165 | HttpApiBuilder.layer(HttpApi.make("RelayApi").add(RelayApi.groups.hooks)).pipe( |
| 170 | Layer.provide(HookForwarder.hooksApi.pipe(Layer.provide(forwarderLayer))), | |
| 166 | Layer.provide(HookForwarder.layerApi.pipe(Layer.provide(layerForwarder))), | |
| 171 | 167 | Layer.provide([NodeServices.layer, NodeHttpPlatform.layer, Etag.layerWeak]), |
| 172 | 168 | ), |
| 173 | relayNotFoundRoute, | |
| 174 | relayCors, | |
| 169 | RelayHttpApi.layerNotFoundRoute, | |
| 170 | RelayHttpApi.layerCors, | |
| 175 | 171 | ), |
| 176 | 172 | ).pipe(Effect.provideService(HttpRouter.RouterConfig, RELAY_HTTP_ROUTER_CONFIG)); |
| 177 | 173 | const send = (request: Request) => |
infra/relay/src/hooks/HookForwarder.ts+1-1| ... | ... | @@ -446,7 +446,7 @@ export const layer = Layer.effect(HookForwarder, make); |
| 446 | 446 | * re-reads the encoded path segments from the request so the token and hook |
| 447 | 447 | * id reach the environment byte for byte, and streams the body itself. |
| 448 | 448 | */ |
| 449 | export const hooksApi = HttpApiBuilder.group( | |
| 449 | export const layerApi = HttpApiBuilder.group( | |
| 450 | 450 | RelayApi, |
| 451 | 451 | "hooks", |
| 452 | 452 | Effect.fnUntraced(function* (handlers) { |
infra/relay/src/hooks/HookInboxObject.ts+3-3| ... | ... | @@ -91,19 +91,19 @@ const deliver = (baseUrl: string, hook: HookInboxStore.HeldHook) => |
| 91 | 91 | Effect.provide(FetchHttpClient.layer), |
| 92 | 92 | ); |
| 93 | 93 | |
| 94 | export const HookInboxObjectLive = HookInboxObject.make( | |
| 94 | export const layer = HookInboxObject.make( | |
| 95 | 95 | Effect.gen(function* () { |
| 96 | 96 | const state = yield* Cloudflare.DurableObjectState; |
| 97 | 97 | // The init phase returns the per-instance Effect, which alchemy runs once |
| 98 | 98 | // per object; only that inner Effect may touch storage. |
| 99 | 99 | // @effect-diagnostics-next-line returnEffectInGen:off |
| 100 | 100 | return Effect.gen(function* () { |
| 101 | const sql = SqliteClient.layer({ storage: state.raw.storage }); | |
| 101 | const layerSql = SqliteClient.layer({ storage: state.raw.storage }); | |
| 102 | 102 | // Inboxes are opened by endpoint key, so spans line up with the |
| 103 | 103 | // forward spans that held their requests. |
| 104 | 104 | const inboxId = state.raw.id.name ?? state.raw.id.toString(); |
| 105 | 105 | const run = <A, E, R>(effect: Effect.Effect<A, E, R>) => |
| 106 | effect.pipe(Effect.provide(sql), Effect.orDie); | |
| 106 | effect.pipe(Effect.provide(layerSql), Effect.orDie); | |
| 107 | 107 | yield* run(HookInboxStore.migrate); |
| 108 | 108 | |
| 109 | 109 | /** Moves the alarm to `at`, unless one is already due sooner. */ |
infra/relay/src/http/Api.test.ts+44-40| ... | ... | @@ -42,22 +42,17 @@ import { RELAY_MANAGED_TUNNEL_RECOVERY_TYP, signRelayJwt } from "@t3tools/shared |
| 42 | 42 | import { |
| 43 | 43 | RELAY_HTTP_ROUTER_CONFIG, |
| 44 | 44 | RELAY_REQUEST_DEADLINE_MS, |
| 45 | clientApi, | |
| 46 | relayCors, | |
| 47 | relayDocsRedirectRoute, | |
| 48 | relayEnvironmentAuthLayer, | |
| 49 | relayNotFoundRoute, | |
| 50 | 45 | recoverEnvironmentTunnelRecord, |
| 51 | 46 | registerEnvironmentTunnelRecovery, |
| 52 | 47 | relayDpopFailureReason, |
| 53 | 48 | revokeEnvironmentLinkRecord, |
| 54 | serverApi, | |
| 55 | 49 | traceRelayHttpRequestWith, |
| 56 | 50 | unlinkEnvironmentRecord, |
| 57 | 51 | verifyRelayClientBearerToken, |
| 58 | 52 | verifyEnvironmentTunnelRecoveryProof, |
| 59 | 53 | withoutCapturedParentSpan, |
| 60 | 54 | } from "./Api.ts"; |
| 55 | import * as RelayHttpApi from "./Api.ts"; | |
| 61 | 56 | import * as RelayConfiguration from "../Config.ts"; |
| 62 | 57 | import * as RelayDb from "../db.ts"; |
| 63 | 58 | import * as EnvironmentCredentials from "../environments/EnvironmentCredentials.ts"; |
| ... | ... | @@ -119,7 +114,7 @@ describe("device listing compatibility", () => { |
| 119 | 114 | iosMajorVersion: null, |
| 120 | 115 | androidApiLevel: 36, |
| 121 | 116 | }; |
| 122 | const handlers = clientApi.pipe( | |
| 117 | const layerHandlers = RelayHttpApi.layerClientApi.pipe( | |
| 123 | 118 | HttpRouter.provideRequest( |
| 124 | 119 | Layer.mergeAll( |
| 125 | 120 | Layer.mock(EnvironmentCredentials.EnvironmentCredentials, {}), |
| ... | ... | @@ -160,7 +155,7 @@ describe("device listing compatibility", () => { |
| 160 | 155 | Effect.sync(() => |
| 161 | 156 | HttpRouter.toWebHandler( |
| 162 | 157 | HttpApiBuilder.layer(HttpApi.make("RelayApi").add(RelayApi.groups.client)).pipe( |
| 163 | Layer.provide(handlers), | |
| 158 | Layer.provide(layerHandlers), | |
| 164 | 159 | Layer.provide(HttpServer.layerServices), |
| 165 | 160 | ), |
| 166 | 161 | { disableLogger: true }, |
| ... | ... | @@ -301,7 +296,7 @@ describe("relay environment authentication", () => { |
| 301 | 296 | route: {} as never, |
| 302 | 297 | }), |
| 303 | 298 | Effect.provide( |
| 304 | relayEnvironmentAuthLayer.pipe( | |
| 299 | RelayHttpApi.layerEnvironmentAuth.pipe( | |
| 305 | 300 | Layer.provide(Layer.succeed(EnvironmentCredentials.EnvironmentCredentials, credentials)), |
| 306 | 301 | ), |
| 307 | 302 | ), |
| ... | ... | @@ -310,7 +305,7 @@ describe("relay environment authentication", () => { |
| 310 | 305 | }); |
| 311 | 306 | }); |
| 312 | 307 | |
| 313 | function relayUnlinkTestLayer(input?: { | |
| 308 | function layerRelayUnlinkTest(input?: { | |
| 314 | 309 | readonly withTransaction?: RelayDb.RelayTransactions["Service"]["withTransaction"]; |
| 315 | 310 | readonly getForUser?: EnvironmentLinks.EnvironmentLinks["Service"]["getForUser"]; |
| 316 | 311 | readonly revokeForUser?: EnvironmentLinks.EnvironmentLinks["Service"]["revokeForUser"]; |
| ... | ... | @@ -512,7 +507,7 @@ describe("relay managed tunnel recovery", () => { |
| 512 | 507 | }).pipe( |
| 513 | 508 | Effect.provide( |
| 514 | 509 | Layer.merge( |
| 515 | relayUnlinkTestLayer({ | |
| 510 | layerRelayUnlinkTest({ | |
| 516 | 511 | getForUser: () => Effect.succeed(linkedEnvironmentRecord), |
| 517 | 512 | provision: () => Effect.die("registration must not provision a tunnel"), |
| 518 | 513 | }), |
| ... | ... | @@ -545,7 +540,7 @@ describe("relay managed tunnel recovery", () => { |
| 545 | 540 | }).pipe( |
| 546 | 541 | Effect.provide( |
| 547 | 542 | Layer.merge( |
| 548 | relayUnlinkTestLayer({ | |
| 543 | layerRelayUnlinkTest({ | |
| 549 | 544 | getForUser: () => Effect.succeed(linkedEnvironmentRecord), |
| 550 | 545 | reconcileOrigin: () => Effect.succeed("recovery_required"), |
| 551 | 546 | }), |
| ... | ... | @@ -580,7 +575,7 @@ describe("relay managed tunnel recovery", () => { |
| 580 | 575 | }).pipe( |
| 581 | 576 | Effect.provide( |
| 582 | 577 | Layer.merge( |
| 583 | relayUnlinkTestLayer({ | |
| 578 | layerRelayUnlinkTest({ | |
| 584 | 579 | getForUser: () => Effect.succeed(linkedEnvironmentRecord), |
| 585 | 580 | }), |
| 586 | 581 | Layer.mock(ManagedEndpointAllocations.ManagedEndpointAllocations)({ |
| ... | ... | @@ -611,7 +606,7 @@ describe("relay managed tunnel recovery", () => { |
| 611 | 606 | }).pipe( |
| 612 | 607 | Effect.provide( |
| 613 | 608 | Layer.merge( |
| 614 | relayUnlinkTestLayer({ | |
| 609 | layerRelayUnlinkTest({ | |
| 615 | 610 | getForUser: () => Effect.succeed(linkedEnvironmentRecord), |
| 616 | 611 | }), |
| 617 | 612 | Layer.mock(ManagedEndpointAllocations.ManagedEndpointAllocations)({ |
| ... | ... | @@ -657,7 +652,7 @@ describe("relay managed tunnel recovery", () => { |
| 657 | 652 | }).pipe( |
| 658 | 653 | Effect.provide( |
| 659 | 654 | Layer.merge( |
| 660 | relayUnlinkTestLayer({ | |
| 655 | layerRelayUnlinkTest({ | |
| 661 | 656 | getForUser: () => Effect.succeed(linkedEnvironmentRecord), |
| 662 | 657 | provision: () => |
| 663 | 658 | Effect.succeed({ |
| ... | ... | @@ -694,7 +689,7 @@ describe("relay managed tunnel recovery", () => { |
| 694 | 689 | }).pipe( |
| 695 | 690 | Effect.provide( |
| 696 | 691 | Layer.merge( |
| 697 | relayUnlinkTestLayer({ | |
| 692 | layerRelayUnlinkTest({ | |
| 698 | 693 | getForUser: () => Effect.succeed(linkedEnvironmentRecord), |
| 699 | 694 | provision: () => |
| 700 | 695 | Effect.sync(() => { |
| ... | ... | @@ -727,7 +722,7 @@ describe("relay managed tunnel recovery", () => { |
| 727 | 722 | }).pipe( |
| 728 | 723 | Effect.provide( |
| 729 | 724 | Layer.merge( |
| 730 | relayUnlinkTestLayer({ | |
| 725 | layerRelayUnlinkTest({ | |
| 731 | 726 | getForUser: () => |
| 732 | 727 | Effect.succeed({ |
| 733 | 728 | ...linkedEnvironmentRecord, |
| ... | ... | @@ -764,7 +759,7 @@ describe("relay managed tunnel recovery", () => { |
| 764 | 759 | }).pipe( |
| 765 | 760 | Effect.provide( |
| 766 | 761 | Layer.merge( |
| 767 | relayUnlinkTestLayer({ | |
| 762 | layerRelayUnlinkTest({ | |
| 768 | 763 | getForUser: () => Effect.succeed(linkedEnvironmentRecord), |
| 769 | 764 | provision: () => |
| 770 | 765 | Effect.succeed({ |
| ... | ... | @@ -843,7 +838,7 @@ describe("relay managed tunnel recovery", () => { |
| 843 | 838 | }).pipe( |
| 844 | 839 | Effect.provide( |
| 845 | 840 | Layer.merge( |
| 846 | relayUnlinkTestLayer({ | |
| 841 | layerRelayUnlinkTest({ | |
| 847 | 842 | getForUser: () => |
| 848 | 843 | Effect.sync(() => (++lookups === 1 ? linkedEnvironmentRecord : currentLink)), |
| 849 | 844 | provision: () => |
| ... | ... | @@ -887,7 +882,7 @@ describe("relay environment unlink", () => { |
| 887 | 882 | expect(calls).toEqual(["transaction", "link", "credential"]); |
| 888 | 883 | }).pipe( |
| 889 | 884 | Effect.provide( |
| 890 | relayUnlinkTestLayer({ | |
| 885 | layerRelayUnlinkTest({ | |
| 891 | 886 | withTransaction: (effect) => { |
| 892 | 887 | calls.push("transaction"); |
| 893 | 888 | return effect; |
| ... | ... | @@ -917,7 +912,7 @@ describe("relay environment unlink", () => { |
| 917 | 912 | managedEndpointNamespace: "dev", |
| 918 | 913 | }).pipe( |
| 919 | 914 | Effect.provide( |
| 920 | relayUnlinkTestLayer({ | |
| 915 | layerRelayUnlinkTest({ | |
| 921 | 916 | getForUser: () => Effect.succeed(linkedEnvironmentRecord), |
| 922 | 917 | revokeForUser: () => Effect.succeed(true), |
| 923 | 918 | prepareDeprovision: () => |
| ... | ... | @@ -986,7 +981,7 @@ describe("relay environment unlink", () => { |
| 986 | 981 | ]); |
| 987 | 982 | }).pipe( |
| 988 | 983 | Effect.provide( |
| 989 | relayUnlinkTestLayer({ | |
| 984 | layerRelayUnlinkTest({ | |
| 990 | 985 | withTransaction: (effect) => { |
| 991 | 986 | calls.push("transaction"); |
| 992 | 987 | return effect; |
| ... | ... | @@ -1041,7 +1036,7 @@ describe("relay environment unlink", () => { |
| 1041 | 1036 | expect(calls).toEqual(["prepare", "transaction", "link", "credential"]); |
| 1042 | 1037 | }).pipe( |
| 1043 | 1038 | Effect.provide( |
| 1044 | relayUnlinkTestLayer({ | |
| 1039 | layerRelayUnlinkTest({ | |
| 1045 | 1040 | withTransaction: (effect) => { |
| 1046 | 1041 | calls.push("transaction"); |
| 1047 | 1042 | return effect; |
| ... | ... | @@ -1083,7 +1078,7 @@ describe("relay environment unlink", () => { |
| 1083 | 1078 | expect(calls).toEqual(["prepare", "deprovision"]); |
| 1084 | 1079 | }).pipe( |
| 1085 | 1080 | Effect.provide( |
| 1086 | relayUnlinkTestLayer({ | |
| 1081 | layerRelayUnlinkTest({ | |
| 1087 | 1082 | prepareDeprovision: () => |
| 1088 | 1083 | Effect.sync(() => { |
| 1089 | 1084 | calls.push("prepare"); |
| ... | ... | @@ -1125,7 +1120,7 @@ describe("relay environment unlink", () => { |
| 1125 | 1120 | expect(targets).toEqual([target, target]); |
| 1126 | 1121 | }).pipe( |
| 1127 | 1122 | Effect.provide( |
| 1128 | relayUnlinkTestLayer({ | |
| 1123 | layerRelayUnlinkTest({ | |
| 1129 | 1124 | getForUser: () => Effect.sync(() => (++lookups === 1 ? linkedEnvironmentRecord : null)), |
| 1130 | 1125 | revokeForUser: () => Effect.succeed(true), |
| 1131 | 1126 | prepareDeprovision: () => Effect.succeed(target), |
| ... | ... | @@ -1234,7 +1229,7 @@ describe("relay routing fallback", () => { |
| 1234 | 1229 | EnvironmentPublishSignatures.EnvironmentPublishSignatures["Service"]["verify"] |
| 1235 | 1230 | >[0] |
| 1236 | 1231 | > = []; |
| 1237 | const publisher = Layer.succeed(AgentActivityPublisher.AgentActivityPublisher, { | |
| 1232 | const layerPublisher = Layer.succeed(AgentActivityPublisher.AgentActivityPublisher, { | |
| 1238 | 1233 | publish: (input) => |
| 1239 | 1234 | Effect.sync(() => { |
| 1240 | 1235 | published.push(input); |
| ... | ... | @@ -1242,13 +1237,16 @@ describe("relay routing fallback", () => { |
| 1242 | 1237 | }), |
| 1243 | 1238 | replayForLiveActivityRegistration: () => Effect.succeed(null), |
| 1244 | 1239 | }); |
| 1245 | const signatures = Layer.succeed(EnvironmentPublishSignatures.EnvironmentPublishSignatures, { | |
| 1246 | verify: (input) => | |
| 1247 | Effect.sync(() => { | |
| 1248 | verified.push(input); | |
| 1249 | }), | |
| 1250 | }); | |
| 1251 | const auth = Layer.succeed(RelayEnvironmentAuth, { | |
| 1240 | const layerSignatures = Layer.succeed( | |
| 1241 | EnvironmentPublishSignatures.EnvironmentPublishSignatures, | |
| 1242 | { | |
| 1243 | verify: (input) => | |
| 1244 | Effect.sync(() => { | |
| 1245 | verified.push(input); | |
| 1246 | }), | |
| 1247 | }, | |
| 1248 | ); | |
| 1249 | const layerAuth = Layer.succeed(RelayEnvironmentAuth, { | |
| 1252 | 1250 | environmentBearer: (effect) => |
| 1253 | 1251 | effect.pipe( |
| 1254 | 1252 | Effect.provideService(RelayEnvironmentPrincipal, { |
| ... | ... | @@ -1257,11 +1255,11 @@ describe("relay routing fallback", () => { |
| 1257 | 1255 | }), |
| 1258 | 1256 | ), |
| 1259 | 1257 | }); |
| 1260 | const routes = HttpApiBuilder.layer( | |
| 1258 | const layerRoutes = HttpApiBuilder.layer( | |
| 1261 | 1259 | HttpApi.make("RelayApi").add(RelayApi.groups.server), |
| 1262 | 1260 | ).pipe( |
| 1263 | 1261 | Layer.provide( |
| 1264 | serverApi.pipe( | |
| 1262 | RelayHttpApi.layerServerApi.pipe( | |
| 1265 | 1263 | HttpRouter.provideRequest( |
| 1266 | 1264 | Layer.mergeAll( |
| 1267 | 1265 | Layer.succeed(RelayConfiguration.RelayConfiguration, relaySettings), |
| ... | ... | @@ -1270,14 +1268,14 @@ describe("relay routing fallback", () => { |
| 1270 | 1268 | Layer.mock(ManagedEndpointProvider.ManagedEndpointProvider, {}), |
| 1271 | 1269 | ), |
| 1272 | 1270 | ), |
| 1273 | Layer.provide([publisher, signatures, Layer.mock(HeldHooks.HeldHooks, {})]), | |
| 1271 | Layer.provide([layerPublisher, layerSignatures, Layer.mock(HeldHooks.HeldHooks, {})]), | |
| 1274 | 1272 | ), |
| 1275 | 1273 | ), |
| 1276 | Layer.provide(auth), | |
| 1274 | Layer.provide(layerAuth), | |
| 1277 | 1275 | Layer.provide([NodeServices.layer, NodeHttpPlatform.layer, Etag.layerWeak]), |
| 1278 | 1276 | ); |
| 1279 | 1277 | const httpEffect = yield* HttpRouter.toHttpEffect( |
| 1280 | Layer.mergeAll(routes, relayNotFoundRoute, relayCors), | |
| 1278 | Layer.mergeAll(layerRoutes, RelayHttpApi.layerNotFoundRoute, RelayHttpApi.layerCors), | |
| 1281 | 1279 | ).pipe(Effect.provideService(HttpRouter.RouterConfig, RELAY_HTTP_ROUTER_CONFIG)); |
| 1282 | 1280 | const threadIds = [ |
| 1283 | 1281 | "b7c8c522-d244-43dc-875f-7224fce79912", |
| ... | ... | @@ -1319,7 +1317,11 @@ describe("relay routing fallback", () => { |
| 1319 | 1317 | Effect.gen(function* () { |
| 1320 | 1318 | const request = HttpServerRequest.fromWeb(new Request("https://relay.test/")); |
| 1321 | 1319 | const httpEffect = yield* HttpRouter.toHttpEffect( |
| 1322 | Layer.mergeAll(relayDocsRedirectRoute, relayNotFoundRoute, relayCors), | |
| 1320 | Layer.mergeAll( | |
| 1321 | RelayHttpApi.layerDocsRedirectRoute, | |
| 1322 | RelayHttpApi.layerNotFoundRoute, | |
| 1323 | RelayHttpApi.layerCors, | |
| 1324 | ), | |
| 1323 | 1325 | ); |
| 1324 | 1326 | const response = yield* httpEffect.pipe( |
| 1325 | 1327 | Effect.provideService(HttpServerRequest.HttpServerRequest, request), |
| ... | ... | @@ -1336,7 +1338,9 @@ describe("relay routing fallback", () => { |
| 1336 | 1338 | const request = HttpServerRequest.fromWeb( |
| 1337 | 1339 | new Request("https://relay.test/v1/environmentsd", { method: "GET" }), |
| 1338 | 1340 | ); |
| 1339 | const httpEffect = yield* HttpRouter.toHttpEffect(Layer.merge(relayNotFoundRoute, relayCors)); | |
| 1341 | const httpEffect = yield* HttpRouter.toHttpEffect( | |
| 1342 | Layer.merge(RelayHttpApi.layerNotFoundRoute, RelayHttpApi.layerCors), | |
| 1343 | ); | |
| 1340 | 1344 | const response = yield* httpEffect.pipe( |
| 1341 | 1345 | Effect.provideService(HttpServerRequest.HttpServerRequest, request), |
| 1342 | 1346 | ); |
infra/relay/src/http/Api.ts+16-16| ... | ... | @@ -21,7 +21,7 @@ import * as HttpTraceContext from "effect/http/HttpTraceContext"; |
| 21 | 21 | import * as HttpApiBuilder from "effect/http-api/HttpApiBuilder"; |
| 22 | 22 | import * as HttpApiError from "effect/http-api/HttpApiError"; |
| 23 | 23 | import { encodeOAuthScope } from "@t3tools/shared/oauthScope"; |
| 24 | import { httpHeaderRedactionLayer } from "@t3tools/shared/httpObservability"; | |
| 24 | import * as HttpObservability from "@t3tools/shared/httpObservability"; | |
| 25 | 25 | |
| 26 | 26 | import { |
| 27 | 27 | RelayApi, |
| ... | ... | @@ -146,7 +146,7 @@ const appendRelayTraceContextResponseHeader = Effect.gen(function* () { |
| 146 | 146 | ); |
| 147 | 147 | }).pipe(Effect.ignore); |
| 148 | 148 | |
| 149 | export const relayCors = HttpRouter.middleware( | |
| 149 | export const layerCors = HttpRouter.middleware( | |
| 150 | 150 | Effect.fnUntraced(function* <E, R>( |
| 151 | 151 | httpEffect: Effect.Effect< |
| 152 | 152 | HttpServerResponse.HttpServerResponse, |
| ... | ... | @@ -171,13 +171,13 @@ export const relayCors = HttpRouter.middleware( |
| 171 | 171 | { global: true }, |
| 172 | 172 | ); |
| 173 | 173 | |
| 174 | export const relayNotFoundRoute = HttpRouter.add( | |
| 174 | export const layerNotFoundRoute = HttpRouter.add( | |
| 175 | 175 | "*", |
| 176 | 176 | "/*", |
| 177 | 177 | HttpServerResponse.empty({ status: 404 }), |
| 178 | 178 | ); |
| 179 | 179 | |
| 180 | export const relayDocsRedirectRoute = HttpRouter.add( | |
| 180 | export const layerDocsRedirectRoute = HttpRouter.add( | |
| 181 | 181 | "GET", |
| 182 | 182 | "/", |
| 183 | 183 | HttpServerResponse.redirect("/docs"), |
| ... | ... | @@ -280,7 +280,7 @@ export const traceRelayHttpRequest = <E, R>( |
| 280 | 280 | |
| 281 | 281 | // Webhook senders put shared secrets and signatures in headers such as |
| 282 | 282 | // x-hub-signature-256, stripe-signature, x-gitlab-token and x-webhook-key. |
| 283 | const webhookHeaderRedactionLayer = Layer.effect( | |
| 283 | const layerWebhookHeaderRedaction = Layer.effect( | |
| 284 | 284 | Headers.CurrentRedactedNames, |
| 285 | 285 | Effect.map(Headers.CurrentRedactedNames, (names) => [ |
| 286 | 286 | ...names, |
| ... | ... | @@ -304,7 +304,7 @@ export const traceRelayHttpRequestWith = <E, R, LayerError, LayerRequirements>( |
| 304 | 304 | Effect.provide( |
| 305 | 305 | Layer.merge( |
| 306 | 306 | tracerLayer, |
| 307 | webhookHeaderRedactionLayer.pipe(Layer.provide(httpHeaderRedactionLayer)), | |
| 307 | layerWebhookHeaderRedaction.pipe(Layer.provide(HttpObservability.layer)), | |
| 308 | 308 | ), |
| 309 | 309 | ), |
| 310 | 310 | ); |
| ... | ... | @@ -319,7 +319,7 @@ export const withoutCapturedParentSpan = <A, E, R>( |
| 319 | 319 | return effect.pipe(Effect.ensuring(Effect.sync(() => fiber.setContext(context)))); |
| 320 | 320 | }); |
| 321 | 321 | |
| 322 | export const relayClientAuthLayer = Layer.effect( | |
| 322 | export const layerClientAuth = Layer.effect( | |
| 323 | 323 | RelayClientAuth, |
| 324 | 324 | Effect.gen(function* () { |
| 325 | 325 | const config = yield* RelayConfiguration.RelayConfiguration; |
| ... | ... | @@ -358,7 +358,7 @@ export const relayClientAuthLayer = Layer.effect( |
| 358 | 358 | }), |
| 359 | 359 | ); |
| 360 | 360 | |
| 361 | export const relayEnvironmentAuthLayer = Layer.effect( | |
| 361 | export const layerEnvironmentAuth = Layer.effect( | |
| 362 | 362 | RelayEnvironmentAuth, |
| 363 | 363 | Effect.gen(function* () { |
| 364 | 364 | const credentials = yield* EnvironmentCredentials.EnvironmentCredentials; |
| ... | ... | @@ -391,7 +391,7 @@ export const relayEnvironmentAuthLayer = Layer.effect( |
| 391 | 391 | }), |
| 392 | 392 | ); |
| 393 | 393 | |
| 394 | export const relayDpopClientAuthLayer = Layer.effect( | |
| 394 | export const layerDpopClientAuth = Layer.effect( | |
| 395 | 395 | RelayDpopClientAuth, |
| 396 | 396 | Effect.gen(function* () { |
| 397 | 397 | const relayTokens = yield* RelayTokens.RelayTokens; |
| ... | ... | @@ -438,7 +438,7 @@ function readHttpAuthorizationCredential(credential: Redacted.Redacted<string>): |
| 438 | 438 | return Redacted.value(credential).trimStart(); |
| 439 | 439 | } |
| 440 | 440 | |
| 441 | export const metadataApi = HttpApiBuilder.group( | |
| 441 | export const layerMetadataApi = HttpApiBuilder.group( | |
| 442 | 442 | RelayApi, |
| 443 | 443 | "metadata", |
| 444 | 444 | Effect.fnUntraced(function* (handlers) { |
| ... | ... | @@ -472,7 +472,7 @@ export const metadataApi = HttpApiBuilder.group( |
| 472 | 472 | }), |
| 473 | 473 | ); |
| 474 | 474 | |
| 475 | export const healthApi = HttpApiBuilder.group( | |
| 475 | export const layerHealthApi = HttpApiBuilder.group( | |
| 476 | 476 | RelayApi, |
| 477 | 477 | "health", |
| 478 | 478 | Effect.fnUntraced(function* (handlers) { |
| ... | ... | @@ -768,7 +768,7 @@ export const recoverEnvironmentTunnelRecord = Effect.fn( |
| 768 | 768 | }; |
| 769 | 769 | }); |
| 770 | 770 | |
| 771 | export const mobileApi = HttpApiBuilder.group( | |
| 771 | export const layerMobileApi = HttpApiBuilder.group( | |
| 772 | 772 | RelayApi, |
| 773 | 773 | "mobile", |
| 774 | 774 | Effect.fnUntraced(function* (handlers) { |
| ... | ... | @@ -825,7 +825,7 @@ export const mobileApi = HttpApiBuilder.group( |
| 825 | 825 | }), |
| 826 | 826 | ); |
| 827 | 827 | |
| 828 | export const clientApi = HttpApiBuilder.group( | |
| 828 | export const layerClientApi = HttpApiBuilder.group( | |
| 829 | 829 | RelayApi, |
| 830 | 830 | "client", |
| 831 | 831 | Effect.fnUntraced(function* (handlers) { |
| ... | ... | @@ -1006,7 +1006,7 @@ export const clientApi = HttpApiBuilder.group( |
| 1006 | 1006 | }), |
| 1007 | 1007 | ); |
| 1008 | 1008 | |
| 1009 | export const tokenApi = HttpApiBuilder.group( | |
| 1009 | export const layerTokenApi = HttpApiBuilder.group( | |
| 1010 | 1010 | RelayApi, |
| 1011 | 1011 | "token", |
| 1012 | 1012 | Effect.fnUntraced(function* (handlers) { |
| ... | ... | @@ -1068,7 +1068,7 @@ export const tokenApi = HttpApiBuilder.group( |
| 1068 | 1068 | }), |
| 1069 | 1069 | ); |
| 1070 | 1070 | |
| 1071 | export const dpopClientApi = HttpApiBuilder.group( | |
| 1071 | export const layerDpopClientApi = HttpApiBuilder.group( | |
| 1072 | 1072 | RelayApi, |
| 1073 | 1073 | "dpopClient", |
| 1074 | 1074 | Effect.fnUntraced(function* (handlers) { |
| ... | ... | @@ -1171,7 +1171,7 @@ export const dpopClientApi = HttpApiBuilder.group( |
| 1171 | 1171 | }), |
| 1172 | 1172 | ); |
| 1173 | 1173 | |
| 1174 | export const serverApi = HttpApiBuilder.group( | |
| 1174 | export const layerServerApi = HttpApiBuilder.group( | |
| 1175 | 1175 | RelayApi, |
| 1176 | 1176 | "server", |
| 1177 | 1177 | Effect.fnUntraced(function* (handlers) { |
infra/relay/src/observability.test.ts+2-2| ... | ... | @@ -10,7 +10,7 @@ import * as HttpServerResponse from "effect/http/HttpServerResponse"; |
| 10 | 10 | import type { OtlpTracer } from "effect/observability"; |
| 11 | 11 | |
| 12 | 12 | import * as EnvironmentConnector from "./environments/EnvironmentConnector.ts"; |
| 13 | import { makeRelayTraceLayer } from "./observability.ts"; | |
| 13 | import * as Observability from "./observability.ts"; | |
| 14 | 14 | |
| 15 | 15 | interface ExportedRequest { |
| 16 | 16 | readonly authorization: string | undefined; |
| ... | ... | @@ -52,7 +52,7 @@ it.effect("exports schema error fields as span attributes", () => |
| 52 | 52 | Effect.withSpan("relay.test.schema_error"), |
| 53 | 53 | Effect.exit, |
| 54 | 54 | Effect.provide( |
| 55 | makeRelayTraceLayer({ | |
| 55 | Observability.layer({ | |
| 56 | 56 | tracesEndpoint: "/v1/traces", |
| 57 | 57 | tracesDatasetName: "relay-test-traces", |
| 58 | 58 | ingestToken: Redacted.make("test-token"), |
infra/relay/src/observability.ts+1-1| ... | ... | @@ -212,7 +212,7 @@ const withSchemaErrorAttributes = (delegate: Tracer.Tracer): Tracer.Tracer => |
| 212 | 212 | ...(delegate.context ? { context: delegate.context } : {}), |
| 213 | 213 | }); |
| 214 | 214 | |
| 215 | export const makeRelayTraceLayer = (input: { | |
| 215 | export const layer = (input: { | |
| 216 | 216 | readonly tracesEndpoint: string; |
| 217 | 217 | readonly tracesDatasetName: string; |
| 218 | 218 | readonly ingestToken: Redacted.Redacted<string>; |
infra/relay/src/worker.ts+43-51| ... | ... | @@ -21,25 +21,14 @@ import * as HttpApiScalar from "effect/http-api/HttpApiScalar"; |
| 21 | 21 | import { RelayApi } from "@t3tools/contracts/relay"; |
| 22 | 22 | |
| 23 | 23 | import { |
| 24 | clientApi, | |
| 25 | dpopClientApi, | |
| 26 | healthApi, | |
| 27 | metadataApi, | |
| 28 | mobileApi, | |
| 29 | 24 | RELAY_HTTP_ROUTER_CONFIG, |
| 30 | relayClientAuthLayer, | |
| 31 | relayDpopClientAuthLayer, | |
| 32 | relayCors, | |
| 33 | relayDocsRedirectRoute, | |
| 34 | relayEnvironmentAuthLayer, | |
| 35 | relayNotFoundRoute, | |
| 36 | serverApi, | |
| 37 | 25 | traceRelayHttpRequestWith, |
| 38 | tokenApi, | |
| 39 | 26 | withoutCapturedParentSpan, |
| 40 | 27 | } from "./http/Api.ts"; |
| 28 | import * as RelayHttpApi from "./http/Api.ts"; | |
| 41 | 29 | import { ManagedEndpointZone, RelayApiZone, RelayDeploymentConfig } from "./zone.ts"; |
| 42 | import { makeRelayTraceLayer, RelayObservability } from "./observability.ts"; | |
| 30 | import { RelayObservability } from "./observability.ts"; | |
| 31 | import * as Observability from "./observability.ts"; | |
| 43 | 32 | import * as DeliveryAttempts from "./agentActivity/DeliveryAttempts.ts"; |
| 44 | 33 | import * as AgentActivityRows from "./agentActivity/AgentActivityRows.ts"; |
| 45 | 34 | import * as Devices from "./agentActivity/Devices.ts"; |
| ... | ... | @@ -78,9 +67,10 @@ import * as MobileRegistrations from "./agentActivity/MobileRegistrations.ts"; |
| 78 | 67 | import * as HookForwarder from "./hooks/HookForwarder.ts"; |
| 79 | 68 | import * as HeldHooks from "./hooks/HeldHooks.ts"; |
| 80 | 69 | import * as HookInbox from "./hooks/HookInbox.ts"; |
| 81 | import { HookInboxObject, HookInboxObjectLive } from "./hooks/HookInboxObject.ts"; | |
| 70 | import { HookInboxObject } from "./hooks/HookInboxObject.ts"; | |
| 71 | import * as HookInboxObjectLayer from "./hooks/HookInboxObject.ts"; | |
| 82 | 72 | |
| 83 | const webcryptoLayer = Layer.succeed( | |
| 73 | const layerWebcrypto = Layer.succeed( | |
| 84 | 74 | Crypto.Crypto, |
| 85 | 75 | Crypto.make({ |
| 86 | 76 | randomBytes: (size) => globalThis.crypto.getRandomValues(new Uint8Array(size)), |
| ... | ... | @@ -93,7 +83,7 @@ const webcryptoLayer = Layer.succeed( |
| 93 | 83 | }), |
| 94 | 84 | ); |
| 95 | 85 | |
| 96 | const httpPlatformNotSupportedLayer = Layer.succeed(HttpPlatform.HttpPlatform, { | |
| 86 | const layerHttpPlatformNotSupported = Layer.succeed(HttpPlatform.HttpPlatform, { | |
| 97 | 87 | platform: "web", |
| 98 | 88 | compression: { |
| 99 | 89 | algorithms: new Set<HttpPlatform.CompressionAlgorithm>(), |
| ... | ... | @@ -103,14 +93,14 @@ const httpPlatformNotSupportedLayer = Layer.succeed(HttpPlatform.HttpPlatform, { |
| 103 | 93 | fileWebResponse: () => Effect.die("Relay API does not serve file responses"), |
| 104 | 94 | }); |
| 105 | 95 | |
| 106 | const relayApiLayer = Layer.mergeAll( | |
| 107 | healthApi, | |
| 108 | metadataApi, | |
| 109 | mobileApi, | |
| 110 | clientApi, | |
| 111 | tokenApi, | |
| 112 | dpopClientApi, | |
| 113 | serverApi, | |
| 96 | const layerRelayApi = Layer.mergeAll( | |
| 97 | RelayHttpApi.layerHealthApi, | |
| 98 | RelayHttpApi.layerMetadataApi, | |
| 99 | RelayHttpApi.layerMobileApi, | |
| 100 | RelayHttpApi.layerClientApi, | |
| 101 | RelayHttpApi.layerTokenApi, | |
| 102 | RelayHttpApi.layerDpopClientApi, | |
| 103 | RelayHttpApi.layerServerApi, | |
| 114 | 104 | ); |
| 115 | 105 | |
| 116 | 106 | const CloudMintKeyPair = Alchemy.KeyPair("CloudMintKeyPair"); |
| ... | ... | @@ -120,7 +110,7 @@ const ApnsDeliveryJobSigningSecret = Alchemy.makeRandom("ApnsDeliveryJobSigningS |
| 120 | 110 | |
| 121 | 111 | export class Api extends Cloudflare.Worker<Api, {}>()("Api") {} |
| 122 | 112 | |
| 123 | export const ApiLive = Api.make( | |
| 113 | export const layer = Api.make( | |
| 124 | 114 | RelayDeploymentConfig.pipe( |
| 125 | 115 | Effect.map(({ relayPublicDomain }) => ({ |
| 126 | 116 | main: import.meta.filename, |
| ... | ... | @@ -235,12 +225,12 @@ export const ApiLive = Api.make( |
| 235 | 225 | }); |
| 236 | 226 | }); |
| 237 | 227 | |
| 238 | const relayTraceLayer = Layer.unwrap( | |
| 228 | const layerRelayTrace = Layer.unwrap( | |
| 239 | 229 | Effect.all({ |
| 240 | 230 | tracesDatasetName: axiomDatasetName, |
| 241 | 231 | tracesEndpoint: axiomTracesEndpoint, |
| 242 | 232 | ingestToken: axiomIngestToken, |
| 243 | }).pipe(Effect.map(makeRelayTraceLayer)), | |
| 233 | }).pipe(Effect.map(Observability.layer)), | |
| 244 | 234 | ); |
| 245 | 235 | |
| 246 | 236 | // Each managed endpoint's held webhook requests live in its own Durable Object. |
| ... | ... | @@ -259,7 +249,7 @@ export const ApiLive = Api.make( |
| 259 | 249 | ), |
| 260 | 250 | ), |
| 261 | 251 | ); |
| 262 | const hookInboxLayer = Layer.succeed(HookInbox.HookInbox, { | |
| 252 | const layerHookInbox = Layer.succeed(HookInbox.HookInbox, { | |
| 263 | 253 | hold: ({ endpointKey, baseUrl, hook }) => |
| 264 | 254 | hookInboxes.getByName(endpointKey).hold(hook, baseUrl).pipe(inboxCall("hold", endpointKey)), |
| 265 | 255 | wake: ({ endpointKey, baseUrl }) => |
| ... | ... | @@ -268,7 +258,7 @@ export const ApiLive = Api.make( |
| 268 | 258 | hookInboxes.getByName(endpointKey).clear().pipe(inboxCall("clear", endpointKey)), |
| 269 | 259 | }); |
| 270 | 260 | |
| 271 | const runtimeLayer = Layer.empty.pipe( | |
| 261 | const layerRuntime = Layer.empty.pipe( | |
| 272 | 262 | Layer.provideMerge(MobileRegistrations.layer), |
| 273 | 263 | Layer.provideMerge(AgentActivityPublisher.layer), |
| 274 | 264 | Layer.provideMerge(EnvironmentConnector.layer), |
| ... | ... | @@ -313,7 +303,7 @@ export const ApiLive = Api.make( |
| 313 | 303 | Layer.provideMerge( |
| 314 | 304 | ApnsDeliveryQueue.layerCloudflareQueues(apnsDeliveryQueueSender, alchemyRuntimeContext), |
| 315 | 305 | ), |
| 316 | Layer.provideMerge(Layer.mergeAll(AgentActivityRows.layer, Devices.layer, hookInboxLayer)), | |
| 306 | Layer.provideMerge(Layer.mergeAll(AgentActivityRows.layer, Devices.layer, layerHookInbox)), | |
| 317 | 307 | Layer.provideMerge(EnvironmentCredentials.layer), |
| 318 | 308 | Layer.provideMerge( |
| 319 | 309 | Layer.mergeAll( |
| ... | ... | @@ -331,7 +321,7 @@ export const ApiLive = Api.make( |
| 331 | 321 | ), |
| 332 | 322 | ), |
| 333 | 323 | Layer.provideMerge(Layer.effect(RelayConfiguration.RelayConfiguration, loadSettings)), |
| 334 | Layer.provideMerge(webcryptoLayer), | |
| 324 | Layer.provideMerge(layerWebcrypto), | |
| 335 | 325 | ); |
| 336 | 326 | |
| 337 | 327 | // Fails open: a limiter outage must not drop webhooks the environment would accept. |
| ... | ... | @@ -351,22 +341,22 @@ export const ApiLive = Api.make( |
| 351 | 341 | ), |
| 352 | 342 | ), |
| 353 | 343 | ); |
| 354 | const hookRateLimiterLayer = Layer.succeed(HookForwarder.HookRateLimiter, { | |
| 344 | const layerHookRateLimiter = Layer.succeed(HookForwarder.HookRateLimiter, { | |
| 355 | 345 | allowHook: allowWith(hookRateLimit), |
| 356 | 346 | allowEndpoint: allowWith(hookEndpointRateLimit), |
| 357 | 347 | }); |
| 358 | 348 | |
| 359 | const appLayer = Layer.merge( | |
| 360 | relayApiLayer, | |
| 361 | HookForwarder.hooksApi.pipe( | |
| 349 | const layerApp = Layer.merge( | |
| 350 | layerRelayApi, | |
| 351 | HookForwarder.layerApi.pipe( | |
| 362 | 352 | Layer.provide(HookForwarder.layer), |
| 363 | Layer.provide(hookRateLimiterLayer), | |
| 353 | Layer.provide(layerHookRateLimiter), | |
| 364 | 354 | ), |
| 365 | 355 | ).pipe( |
| 366 | Layer.provideMerge(relayClientAuthLayer), | |
| 367 | Layer.provideMerge(relayDpopClientAuthLayer), | |
| 368 | Layer.provideMerge(relayEnvironmentAuthLayer), | |
| 369 | Layer.provide(runtimeLayer), | |
| 356 | Layer.provideMerge(RelayHttpApi.layerClientAuth), | |
| 357 | Layer.provideMerge(RelayHttpApi.layerDpopClientAuth), | |
| 358 | Layer.provideMerge(RelayHttpApi.layerEnvironmentAuth), | |
| 359 | Layer.provide(layerRuntime), | |
| 370 | 360 | ); |
| 371 | 361 | |
| 372 | 362 | yield* Cloudflare.Queues.consumeQueueMessages<unknown>( |
| ... | ... | @@ -387,7 +377,7 @@ export const ApiLive = Api.make( |
| 387 | 377 | Effect.withSpan("relay.apn_delivery_queue.process_message"), |
| 388 | 378 | ), |
| 389 | 379 | ), |
| 390 | Effect.provide(runtimeLayer), | |
| 380 | Effect.provide(layerRuntime), | |
| 391 | 381 | ), |
| 392 | 382 | ); |
| 393 | 383 | |
| ... | ... | @@ -404,7 +394,7 @@ export const ApiLive = Api.make( |
| 404 | 394 | stream.pipe( |
| 405 | 395 | Stream.withSpan("relay.fcm_delivery_queue.process_batch"), |
| 406 | 396 | Stream.runForEach(FcmDeliveryQueueConsumer.processMessage), |
| 407 | Effect.provide(runtimeLayer), | |
| 397 | Effect.provide(layerRuntime), | |
| 408 | 398 | ), |
| 409 | 399 | ); |
| 410 | 400 | |
| ... | ... | @@ -447,24 +437,26 @@ export const ApiLive = Api.make( |
| 447 | 437 | ).pipe( |
| 448 | 438 | Effect.withSpan("relay.cron.prune_expired_state"), |
| 449 | 439 | // Export cron spans to Axiom like HTTP spans; the scope flushes them before the run ends. |
| 450 | Effect.provide(Layer.merge(runtimeLayer, relayTraceLayer)), | |
| 440 | Effect.provide(Layer.merge(layerRuntime, layerRelayTrace)), | |
| 451 | 441 | ), |
| 452 | 442 | ); |
| 453 | 443 | |
| 454 | 444 | const fetch = Layer.merge( |
| 455 | 445 | Layer.mergeAll( |
| 456 | 446 | HttpApiBuilder.layer(RelayApi, { openapiPath: "/openapi.json" }).pipe( |
| 457 | Layer.provide(appLayer), | |
| 447 | Layer.provide(layerApp), | |
| 458 | 448 | ), |
| 459 | 449 | HttpApiScalar.layer(RelayApi, { path: "/docs" }), |
| 460 | relayDocsRedirectRoute, | |
| 461 | ).pipe(Layer.provide([Etag.layerWeak, httpPlatformNotSupportedLayer, relayCors])), | |
| 462 | relayNotFoundRoute, | |
| 450 | RelayHttpApi.layerDocsRedirectRoute, | |
| 451 | ).pipe( | |
| 452 | Layer.provide([Etag.layerWeak, layerHttpPlatformNotSupported, RelayHttpApi.layerCors]), | |
| 453 | ), | |
| 454 | RelayHttpApi.layerNotFoundRoute, | |
| 463 | 455 | ).pipe( |
| 464 | 456 | HttpRouter.toHttpEffect, |
| 465 | 457 | Effect.provideService(HttpRouter.RouterConfig, RELAY_HTTP_ROUTER_CONFIG), |
| 466 | 458 | withoutCapturedParentSpan, |
| 467 | Effect.flatMap((httpEffect) => traceRelayHttpRequestWith(httpEffect, relayTraceLayer)), | |
| 459 | Effect.flatMap((httpEffect) => traceRelayHttpRequestWith(httpEffect, layerRelayTrace)), | |
| 468 | 460 | ); |
| 469 | 461 | |
| 470 | 462 | return { fetch }; |
| ... | ... | @@ -478,7 +470,7 @@ export const ApiLive = Api.make( |
| 478 | 470 | Layer.provideMerge(Cloudflare.Tunnel.ReadWriteTunnelBinding), |
| 479 | 471 | Layer.provideMerge(Cloudflare.DNS.ReadWriteDnsHttp), |
| 480 | 472 | Layer.provideMerge(Cloudflare.Workers.RateLimitBinding), |
| 481 | Layer.provideMerge(HookInboxObjectLive), | |
| 473 | Layer.provideMerge(HookInboxObjectLayer.layer), | |
| 482 | 474 | // The worker runtime opens its own HTTP span around ours. For webhook |
| 483 | 475 | // paths it would record the raw URL, token included, and adopt the |
| 484 | 476 | // sender's traceparent, so only our redacted span covers those. |
| ... | ... | @@ -509,4 +501,4 @@ export const ApiLive = Api.make( |
| 509 | 501 | ), |
| 510 | 502 | ); |
| 511 | 503 | |
| 512 | export default ApiLive; | |
| 504 | export default layer; |
packages/client-runtime/src/authorization/layer.test.ts+2-2| ... | ... | @@ -16,7 +16,7 @@ import * as TestClock from "effect/testing/TestClock"; |
| 16 | 16 | |
| 17 | 17 | import { DPOP_UNKNOWN_HINT } from "../relay/errorPresentation.ts"; |
| 18 | 18 | import * as ManagedRelay from "../relay/managedRelay.ts"; |
| 19 | import { remoteHttpClientLayer } from "../rpc/http.ts"; | |
| 19 | import * as RpcHttp from "../rpc/http.ts"; | |
| 20 | 20 | import * as ClientCapabilities from "../platform/capabilities.ts"; |
| 21 | 21 | import * as RemoteEnvironmentAuthorization from "./service.ts"; |
| 22 | 22 | import * as TokenStore from "./tokenStore.ts"; |
| ... | ... | @@ -191,7 +191,7 @@ const makeHarness = Effect.fn("TestRemoteAuthorization.makeHarness")(function* ( |
| 191 | 191 | const layer = RemoteEnvironmentAuthorization.layer.pipe( |
| 192 | 192 | Layer.provide( |
| 193 | 193 | Layer.mergeAll( |
| 194 | remoteHttpClientLayer(fetch.fetchFn), | |
| 194 | RpcHttp.layerRemoteHttpClient(fetch.fetchFn), | |
| 195 | 195 | Layer.succeed(ManagedRelay.ManagedRelayDpopSigner, signer), |
| 196 | 196 | Layer.succeed(ManagedRelay.ManagedRelayClient, relay), |
| 197 | 197 | Layer.succeed(ClientCapabilities.CloudSession, { |
packages/client-runtime/src/authorization/remote.test.ts+3-2| ... | ... | @@ -18,7 +18,7 @@ import { |
| 18 | 18 | resolveRemoteWebSocketConnectionUrl, |
| 19 | 19 | } from "./remote.ts"; |
| 20 | 20 | import { fetchRemoteEnvironmentDescriptor } from "../environment/descriptor.ts"; |
| 21 | import { remoteHttpClientLayer } from "../rpc/http.ts"; | |
| 21 | import * as RpcHttp from "../rpc/http.ts"; | |
| 22 | 22 | |
| 23 | 23 | const isEnvironmentAuthInvalidError = Schema.is(EnvironmentAuthInvalidError); |
| 24 | 24 | |
| ... | ... | @@ -49,7 +49,8 @@ const hangingFetch = () => { |
| 49 | 49 | return { fetchFn, calls }; |
| 50 | 50 | }; |
| 51 | 51 | |
| 52 | const provideRemoteHttp = (fetchFn: typeof fetch) => Effect.provide(remoteHttpClientLayer(fetchFn)); | |
| 52 | const provideRemoteHttp = (fetchFn: typeof fetch) => | |
| 53 | Effect.provide(RpcHttp.layerRemoteHttpClient(fetchFn)); | |
| 53 | 54 | |
| 54 | 55 | const expectFetchCall = ( |
| 55 | 56 | calls: ReadonlyArray<FetchCall>, |
packages/client-runtime/src/connection/layer.ts+9-9| ... | ... | @@ -73,19 +73,19 @@ export const watchDiscoveredCompatibility = Effect.fn("connection.watchDiscovere |
| 73 | 73 | ); |
| 74 | 74 | |
| 75 | 75 | export function layerWithOptions(options: RpcSession.RpcSessionOptions) { |
| 76 | const driverLayer = ConnectionDriver.layer.pipe( | |
| 76 | const layerDriver = ConnectionDriver.layer.pipe( | |
| 77 | 77 | Layer.provide(Layer.mergeAll(ConnectionResolver.layer, RpcSession.layer(options))), |
| 78 | 78 | ); |
| 79 | const registryLayer = EnvironmentRegistry.layer.pipe(Layer.provide(driverLayer)); | |
| 80 | const onboardingLayer = ConnectionOnboarding.layer.pipe(Layer.provide(registryLayer)); | |
| 81 | const connectionServicesLayer = Layer.mergeAll( | |
| 82 | registryLayer, | |
| 79 | const layerRegistry = EnvironmentRegistry.layer.pipe(Layer.provide(layerDriver)); | |
| 80 | const layerOnboarding = ConnectionOnboarding.layer.pipe(Layer.provide(layerRegistry)); | |
| 81 | const layerConnectionServices = Layer.mergeAll( | |
| 82 | layerRegistry, | |
| 83 | 83 | RelayEnvironmentDiscovery.layer, |
| 84 | onboardingLayer, | |
| 84 | layerOnboarding, | |
| 85 | 85 | // Exposed for updating hosts too old to connect through the driver. |
| 86 | 86 | ConnectionResolver.layer, |
| 87 | 87 | ); |
| 88 | const connectionStartupLayer = Layer.effectDiscard( | |
| 88 | const layerConnectionStartup = Layer.effectDiscard( | |
| 89 | 89 | Effect.gen(function* () { |
| 90 | 90 | const registry = yield* EnvironmentRegistry.EnvironmentRegistry; |
| 91 | 91 | const platformSource = yield* PlatformConnectionSource.PlatformConnectionSource; |
| ... | ... | @@ -97,8 +97,8 @@ export function layerWithOptions(options: RpcSession.RpcSessionOptions) { |
| 97 | 97 | ); |
| 98 | 98 | }).pipe(Effect.withSpan("clientRuntime.connection.application.start")), |
| 99 | 99 | ); |
| 100 | return connectionStartupLayer.pipe( | |
| 101 | Layer.provideMerge(connectionServicesLayer), | |
| 100 | return layerConnectionStartup.pipe( | |
| 101 | Layer.provideMerge(layerConnectionServices), | |
| 102 | 102 | Layer.provideMerge(RemoteEnvironmentAuthorization.layer), |
| 103 | 103 | ); |
| 104 | 104 | } |
packages/client-runtime/src/connection/onboarding.test.ts+15-15| ... | ... | @@ -8,7 +8,7 @@ import * as Effect from "effect/Effect"; |
| 8 | 8 | import * as Layer from "effect/Layer"; |
| 9 | 9 | import * as Option from "effect/Option"; |
| 10 | 10 | |
| 11 | import { remoteHttpClientLayer } from "../rpc/http.ts"; | |
| 11 | import * as RpcHttp from "../rpc/http.ts"; | |
| 12 | 12 | import * as ClientCapabilities from "../platform/capabilities.ts"; |
| 13 | 13 | import { BearerConnectionCredential, BearerConnectionProfile } from "./catalog.ts"; |
| 14 | 14 | import { BearerConnectionTarget } from "./model.ts"; |
| ... | ... | @@ -18,7 +18,7 @@ import { |
| 18 | 18 | prepareSshRegistration, |
| 19 | 19 | } from "./onboarding.ts"; |
| 20 | 20 | |
| 21 | const CLIENT_PRESENTATION_LAYER = Layer.succeed( | |
| 21 | const layerClientPresentation = Layer.succeed( | |
| 22 | 22 | ClientCapabilities.ClientPresentation, |
| 23 | 23 | ClientCapabilities.ClientPresentation.of({ |
| 24 | 24 | metadata: { |
| ... | ... | @@ -30,7 +30,7 @@ const CLIENT_PRESENTATION_LAYER = Layer.succeed( |
| 30 | 30 | }), |
| 31 | 31 | ); |
| 32 | 32 | |
| 33 | function pairingHttpLayer( | |
| 33 | function layerPairingHttp( | |
| 34 | 34 | calls: Array<{ readonly url: string; readonly init: RequestInit }>, |
| 35 | 35 | options?: { |
| 36 | 36 | readonly failDescriptor?: boolean; |
| ... | ... | @@ -81,7 +81,7 @@ function pairingHttpLayer( |
| 81 | 81 | return Promise.reject(new Error(`Unexpected request: ${url}`)); |
| 82 | 82 | }) satisfies typeof fetch; |
| 83 | 83 | |
| 84 | return remoteHttpClientLayer(fetchFn); | |
| 84 | return RpcHttp.layerRemoteHttpClient(fetchFn); | |
| 85 | 85 | } |
| 86 | 86 | |
| 87 | 87 | describe("connection onboarding", () => { |
| ... | ... | @@ -91,7 +91,7 @@ describe("connection onboarding", () => { |
| 91 | 91 | const registration = yield* preparePairingRegistration({ |
| 92 | 92 | host: "remote.example.test", |
| 93 | 93 | pairingCode: "pairing-token", |
| 94 | }).pipe(Effect.provide(Layer.mergeAll(CLIENT_PRESENTATION_LAYER, pairingHttpLayer(calls)))); | |
| 94 | }).pipe(Effect.provide(Layer.mergeAll(layerClientPresentation, layerPairingHttp(calls)))); | |
| 95 | 95 | |
| 96 | 96 | expect(registration).toMatchObject({ |
| 97 | 97 | _tag: "BearerConnectionRegistration", |
| ... | ... | @@ -137,8 +137,8 @@ describe("connection onboarding", () => { |
| 137 | 137 | }).pipe( |
| 138 | 138 | Effect.provide( |
| 139 | 139 | Layer.mergeAll( |
| 140 | CLIENT_PRESENTATION_LAYER, | |
| 141 | pairingHttpLayer(calls, { protocolVersion: ORCHESTRATION_PROTOCOL_VERSION + 1 }), | |
| 140 | layerClientPresentation, | |
| 141 | layerPairingHttp(calls, { protocolVersion: ORCHESTRATION_PROTOCOL_VERSION + 1 }), | |
| 142 | 142 | ), |
| 143 | 143 | ), |
| 144 | 144 | Effect.flip, |
| ... | ... | @@ -158,7 +158,7 @@ describe("connection onboarding", () => { |
| 158 | 158 | pairingCode: "pairing-token", |
| 159 | 159 | expectedEnvironmentId: EnvironmentId.make("some-other-machine"), |
| 160 | 160 | }).pipe( |
| 161 | Effect.provide(Layer.mergeAll(CLIENT_PRESENTATION_LAYER, pairingHttpLayer(calls))), | |
| 161 | Effect.provide(Layer.mergeAll(layerClientPresentation, layerPairingHttp(calls))), | |
| 162 | 162 | Effect.flip, |
| 163 | 163 | ); |
| 164 | 164 | expect(error).toMatchObject({ reason: "configuration" }); |
| ... | ... | @@ -178,8 +178,8 @@ describe("connection onboarding", () => { |
| 178 | 178 | }).pipe( |
| 179 | 179 | Effect.provide( |
| 180 | 180 | Layer.mergeAll( |
| 181 | CLIENT_PRESENTATION_LAYER, | |
| 182 | pairingHttpLayer(calls, { | |
| 181 | layerClientPresentation, | |
| 182 | layerPairingHttp(calls, { | |
| 183 | 183 | protocolVersion: ORCHESTRATION_PROTOCOL_VERSION - 1, |
| 184 | 184 | selfUpdate: true, |
| 185 | 185 | }), |
| ... | ... | @@ -200,8 +200,8 @@ describe("connection onboarding", () => { |
| 200 | 200 | }).pipe( |
| 201 | 201 | Effect.provide( |
| 202 | 202 | Layer.mergeAll( |
| 203 | CLIENT_PRESENTATION_LAYER, | |
| 204 | pairingHttpLayer(calls, { protocolVersion: ORCHESTRATION_PROTOCOL_VERSION - 1 }), | |
| 203 | layerClientPresentation, | |
| 204 | layerPairingHttp(calls, { protocolVersion: ORCHESTRATION_PROTOCOL_VERSION - 1 }), | |
| 205 | 205 | ), |
| 206 | 206 | ), |
| 207 | 207 | Effect.flip, |
| ... | ... | @@ -224,8 +224,8 @@ describe("connection onboarding", () => { |
| 224 | 224 | }).pipe( |
| 225 | 225 | Effect.provide( |
| 226 | 226 | Layer.mergeAll( |
| 227 | CLIENT_PRESENTATION_LAYER, | |
| 228 | pairingHttpLayer(calls, { failDescriptor: true }), | |
| 227 | layerClientPresentation, | |
| 228 | layerPairingHttp(calls, { failDescriptor: true }), | |
| 229 | 229 | ), |
| 230 | 230 | ), |
| 231 | 231 | Effect.flip, |
| ... | ... | @@ -244,7 +244,7 @@ describe("connection onboarding", () => { |
| 244 | 244 | host: "", |
| 245 | 245 | pairingCode: "", |
| 246 | 246 | }).pipe( |
| 247 | Effect.provide(Layer.mergeAll(CLIENT_PRESENTATION_LAYER, pairingHttpLayer(calls))), | |
| 247 | Effect.provide(Layer.mergeAll(layerClientPresentation, layerPairingHttp(calls))), | |
| 248 | 248 | Effect.flip, |
| 249 | 249 | ); |
| 250 | 250 |
packages/client-runtime/src/connection/registry.test.ts+2-2| ... | ... | @@ -420,7 +420,7 @@ const makeHarness = Effect.fn("TestEnvironmentRegistry.makeHarness")(function* ( |
| 420 | 420 | checkRoute(route).pipe(Effect.map((check) => check === "answered")), |
| 421 | 421 | }); |
| 422 | 422 | |
| 423 | const cacheLayer = Layer.succeed(Persistence.EnvironmentCacheStore, cacheStore); | |
| 423 | const layerCache = Layer.succeed(Persistence.EnvironmentCacheStore, cacheStore); | |
| 424 | 424 | const layer = EnvironmentRegistry.layer.pipe( |
| 425 | 425 | Layer.provide( |
| 426 | 426 | Layer.mergeAll( |
| ... | ... | @@ -436,7 +436,7 @@ const makeHarness = Effect.fn("TestEnvironmentRegistry.makeHarness")(function* ( |
| 436 | 436 | ConnectionWakeups.ConnectionWakeups.of({ changes: Stream.never }), |
| 437 | 437 | ), |
| 438 | 438 | Layer.succeed(ConnectionDriver.ConnectionDriver, driver), |
| 439 | cacheLayer, | |
| 439 | layerCache, | |
| 440 | 440 | Layer.succeed(Persistence.EnvironmentOwnedDataCleanup, ownedDataCleanup), |
| 441 | 441 | ), |
| 442 | 442 | ), |
packages/client-runtime/src/connection/resolver.test.ts+24-24| ... | ... | @@ -32,7 +32,7 @@ import { |
| 32 | 32 | type ConnectionTarget, |
| 33 | 33 | } from "./model.ts"; |
| 34 | 34 | import * as ConnectionProfileStore from "./profileStore.ts"; |
| 35 | import { remoteHttpClientLayer } from "../rpc/http.ts"; | |
| 35 | import * as RpcHttp from "../rpc/http.ts"; | |
| 36 | 36 | import { |
| 37 | 37 | GitHubRoutingPermissions, |
| 38 | 38 | gitHubRoutingConnectionKey, |
| ... | ... | @@ -145,8 +145,8 @@ const makeDependencies = Effect.fn("TestConnectionResolver.makeDependencies")((o |
| 145 | 145 | disconnect: () => Effect.void, |
| 146 | 146 | }); |
| 147 | 147 | |
| 148 | const dependencies = Layer.mergeAll( | |
| 149 | remoteHttpClientLayer((() => | |
| 148 | const layerDependencies = Layer.mergeAll( | |
| 149 | RpcHttp.layerRemoteHttpClient((() => | |
| 150 | 150 | Promise.resolve( |
| 151 | 151 | Response.json({ |
| 152 | 152 | environmentId: ENVIRONMENT_ID, |
| ... | ... | @@ -183,14 +183,14 @@ const makeDependencies = Effect.fn("TestConnectionResolver.makeDependencies")((o |
| 183 | 183 | Layer.succeed(ClientCapabilities.SshEnvironmentGateway, ssh), |
| 184 | 184 | ); |
| 185 | 185 | |
| 186 | return Effect.succeed(ConnectionResolver.layer.pipe(Layer.provide(dependencies))); | |
| 186 | return Effect.succeed(ConnectionResolver.layer.pipe(Layer.provide(layerDependencies))); | |
| 187 | 187 | }); |
| 188 | 188 | |
| 189 | 189 | describe("ConnectionResolver", () => { |
| 190 | 190 | it.effect("blocks an old host during discovery before opening orchestration RPC", () => |
| 191 | 191 | Effect.gen(function* () { |
| 192 | const brokerLayer = yield* makeDependencies({ descriptorProtocolVersion: null }); | |
| 193 | const broker = yield* ConnectionResolver.ConnectionResolver.pipe(Effect.provide(brokerLayer)); | |
| 192 | const layerBroker = yield* makeDependencies({ descriptorProtocolVersion: null }); | |
| 193 | const broker = yield* ConnectionResolver.ConnectionResolver.pipe(Effect.provide(layerBroker)); | |
| 194 | 194 | const target = new PrimaryConnectionTarget({ |
| 195 | 195 | environmentId: ENVIRONMENT_ID, |
| 196 | 196 | label: "Primary", |
| ... | ... | @@ -207,10 +207,10 @@ describe("ConnectionResolver", () => { |
| 207 | 207 | |
| 208 | 208 | it.effect("blocks an incompatible host during discovery before opening orchestration RPC", () => |
| 209 | 209 | Effect.gen(function* () { |
| 210 | const brokerLayer = yield* makeDependencies({ | |
| 210 | const layerBroker = yield* makeDependencies({ | |
| 211 | 211 | descriptorProtocolVersion: ORCHESTRATION_PROTOCOL_VERSION + 1, |
| 212 | 212 | }); |
| 213 | const broker = yield* ConnectionResolver.ConnectionResolver.pipe(Effect.provide(brokerLayer)); | |
| 213 | const broker = yield* ConnectionResolver.ConnectionResolver.pipe(Effect.provide(layerBroker)); | |
| 214 | 214 | const target = new PrimaryConnectionTarget({ |
| 215 | 215 | environmentId: ENVIRONMENT_ID, |
| 216 | 216 | label: "Primary", |
| ... | ... | @@ -227,8 +227,8 @@ describe("ConnectionResolver", () => { |
| 227 | 227 | |
| 228 | 228 | it.effect("prepares a primary environment without remote capabilities", () => |
| 229 | 229 | Effect.gen(function* () { |
| 230 | const brokerLayer = yield* makeDependencies(); | |
| 231 | const broker = yield* ConnectionResolver.ConnectionResolver.pipe(Effect.provide(brokerLayer)); | |
| 230 | const layerBroker = yield* makeDependencies(); | |
| 231 | const broker = yield* ConnectionResolver.ConnectionResolver.pipe(Effect.provide(layerBroker)); | |
| 232 | 232 | const target = new PrimaryConnectionTarget({ |
| 233 | 233 | environmentId: ENVIRONMENT_ID, |
| 234 | 234 | label: "Primary", |
| ... | ... | @@ -251,7 +251,7 @@ describe("ConnectionResolver", () => { |
| 251 | 251 | it.effect("authorizes a desktop primary environment with its platform bearer token", () => |
| 252 | 252 | Effect.gen(function* () { |
| 253 | 253 | const bearerInputs = yield* Ref.make<ReadonlyArray<{ token: string; method: string }>>([]); |
| 254 | const brokerLayer = yield* makeDependencies({ | |
| 254 | const layerBroker = yield* makeDependencies({ | |
| 255 | 255 | primaryBearerToken: "desktop-bearer", |
| 256 | 256 | authorizeBearer: (input) => |
| 257 | 257 | Ref.update(bearerInputs, (values) => [ |
| ... | ... | @@ -270,7 +270,7 @@ describe("ConnectionResolver", () => { |
| 270 | 270 | }), |
| 271 | 271 | ), |
| 272 | 272 | }); |
| 273 | const broker = yield* ConnectionResolver.ConnectionResolver.pipe(Effect.provide(brokerLayer)); | |
| 273 | const broker = yield* ConnectionResolver.ConnectionResolver.pipe(Effect.provide(layerBroker)); | |
| 274 | 274 | const target = new PrimaryConnectionTarget({ |
| 275 | 275 | environmentId: ENVIRONMENT_ID, |
| 276 | 276 | label: "Primary", |
| ... | ... | @@ -302,7 +302,7 @@ describe("ConnectionResolver", () => { |
| 302 | 302 | httpBaseUrl: ENDPOINT.httpBaseUrl, |
| 303 | 303 | wsBaseUrl: ENDPOINT.wsBaseUrl, |
| 304 | 304 | }); |
| 305 | const brokerLayer = yield* makeDependencies({ | |
| 305 | const layerBroker = yield* makeDependencies({ | |
| 306 | 306 | credentials: [["saved-1", new BearerConnectionCredential({ token: "secret-bearer" })]], |
| 307 | 307 | authorizeBearer: (input) => |
| 308 | 308 | Ref.update(bearerInputs, (values) => [ |
| ... | ... | @@ -321,7 +321,7 @@ describe("ConnectionResolver", () => { |
| 321 | 321 | }), |
| 322 | 322 | ), |
| 323 | 323 | }); |
| 324 | const broker = yield* ConnectionResolver.ConnectionResolver.pipe(Effect.provide(brokerLayer)); | |
| 324 | const broker = yield* ConnectionResolver.ConnectionResolver.pipe(Effect.provide(layerBroker)); | |
| 325 | 325 | |
| 326 | 326 | expect( |
| 327 | 327 | (yield* broker.prepare(catalogEntry(target, Option.some(profile)))).socketUrl, |
| ... | ... | @@ -336,8 +336,8 @@ describe("ConnectionResolver", () => { |
| 336 | 336 | environmentId: ENVIRONMENT_ID, |
| 337 | 337 | label: "Cloud", |
| 338 | 338 | }); |
| 339 | const brokerLayer = yield* makeDependencies(); | |
| 340 | const broker = yield* ConnectionResolver.ConnectionResolver.pipe(Effect.provide(brokerLayer)); | |
| 339 | const layerBroker = yield* makeDependencies(); | |
| 340 | const broker = yield* ConnectionResolver.ConnectionResolver.pipe(Effect.provide(layerBroker)); | |
| 341 | 341 | |
| 342 | 342 | expect(yield* broker.prepare(catalogEntry(target))).toEqual({ |
| 343 | 343 | environmentId: ENVIRONMENT_ID, |
| ... | ... | @@ -362,7 +362,7 @@ describe("ConnectionResolver", () => { |
| 362 | 362 | environmentId: ENVIRONMENT_ID, |
| 363 | 363 | label: "Cloud", |
| 364 | 364 | }); |
| 365 | const brokerLayer = yield* makeDependencies({ | |
| 365 | const layerBroker = yield* makeDependencies({ | |
| 366 | 366 | authorizeDpop: (input) => |
| 367 | 367 | Effect.succeed({ |
| 368 | 368 | environmentId: input.expectedEnvironmentId, |
| ... | ... | @@ -376,7 +376,7 @@ describe("ConnectionResolver", () => { |
| 376 | 376 | }, |
| 377 | 377 | }).pipe(Effect.withSpan("test.remote.authorizeDpop")), |
| 378 | 378 | }); |
| 379 | const broker = yield* ConnectionResolver.ConnectionResolver.pipe(Effect.provide(brokerLayer)); | |
| 379 | const broker = yield* ConnectionResolver.ConnectionResolver.pipe(Effect.provide(layerBroker)); | |
| 380 | 380 | |
| 381 | 381 | yield* broker |
| 382 | 382 | .prepare(catalogEntry(target)) |
| ... | ... | @@ -407,7 +407,7 @@ describe("ConnectionResolver", () => { |
| 407 | 407 | label: "SSH", |
| 408 | 408 | target: SSH_TARGET, |
| 409 | 409 | }); |
| 410 | const brokerLayer = yield* makeDependencies({ | |
| 410 | const layerBroker = yield* makeDependencies({ | |
| 411 | 411 | prepareSsh: (input) => |
| 412 | 412 | Ref.update(preparedTargets, (values) => [...values, input.target]).pipe( |
| 413 | 413 | Effect.as({ |
| ... | ... | @@ -434,7 +434,7 @@ describe("ConnectionResolver", () => { |
| 434 | 434 | }), |
| 435 | 435 | ), |
| 436 | 436 | }); |
| 437 | const broker = yield* ConnectionResolver.ConnectionResolver.pipe(Effect.provide(brokerLayer)); | |
| 437 | const broker = yield* ConnectionResolver.ConnectionResolver.pipe(Effect.provide(layerBroker)); | |
| 438 | 438 | |
| 439 | 439 | expect( |
| 440 | 440 | (yield* broker.prepare(catalogEntry(target, Option.some(profile)))).socketUrl, |
| ... | ... | @@ -485,7 +485,7 @@ describe("ConnectionResolver", () => { |
| 485 | 485 | ), |
| 486 | 486 | }); |
| 487 | 487 | let savedProfile: ConnectionProfile = profile; |
| 488 | const brokerLayer = yield* makeDependencies({ | |
| 488 | const layerBroker = yield* makeDependencies({ | |
| 489 | 489 | profileStore: { |
| 490 | 490 | get: () => Effect.sync(() => Option.some(savedProfile)), |
| 491 | 491 | put: (value) => |
| ... | ... | @@ -518,7 +518,7 @@ describe("ConnectionResolver", () => { |
| 518 | 518 | }), |
| 519 | 519 | }); |
| 520 | 520 | const broker = yield* ConnectionResolver.ConnectionResolver.pipe( |
| 521 | Effect.provide(brokerLayer), | |
| 521 | Effect.provide(layerBroker), | |
| 522 | 522 | ); |
| 523 | 523 | const prepare = broker |
| 524 | 524 | .prepare(entry) |
| ... | ... | @@ -551,10 +551,10 @@ describe("ConnectionResolver", () => { |
| 551 | 551 | detail: "Relay environment connection timed out.", |
| 552 | 552 | traceId: "relay-trace", |
| 553 | 553 | }); |
| 554 | const brokerLayer = yield* makeDependencies({ | |
| 554 | const layerBroker = yield* makeDependencies({ | |
| 555 | 555 | authorizeDpop: () => Effect.fail(authorizationError), |
| 556 | 556 | }); |
| 557 | const broker = yield* ConnectionResolver.ConnectionResolver.pipe(Effect.provide(brokerLayer)); | |
| 557 | const broker = yield* ConnectionResolver.ConnectionResolver.pipe(Effect.provide(layerBroker)); | |
| 558 | 558 | const error = yield* Effect.flip(broker.prepare(catalogEntry(target))); |
| 559 | 559 | |
| 560 | 560 | expect(error).toBe(authorizationError); |
packages/client-runtime/src/connection/supervisor.test.ts+6-6| ... | ... | @@ -16,7 +16,7 @@ import * as RemoteEnvironmentAuthorization from "../authorization/service.ts"; |
| 16 | 16 | import * as TokenStore from "../authorization/tokenStore.ts"; |
| 17 | 17 | import * as ClientCapabilities from "../platform/capabilities.ts"; |
| 18 | 18 | import * as ManagedRelay from "../relay/managedRelay.ts"; |
| 19 | import { remoteHttpClientLayer } from "../rpc/http.ts"; | |
| 19 | import * as RpcHttp from "../rpc/http.ts"; | |
| 20 | 20 | import type { WsRpcProtocolClient } from "../rpc/protocol.ts"; |
| 21 | 21 | import { fetchEnvironmentSessionState } from "../state/session.ts"; |
| 22 | 22 | import type { ConnectionCatalogEntry, ConnectionRoute } from "./catalog.ts"; |
| ... | ... | @@ -233,7 +233,7 @@ const makeHarness = Effect.fn("TestConnectionHarness.make")(function* (options?: |
| 233 | 233 | ); |
| 234 | 234 | }); |
| 235 | 235 | |
| 236 | const dependencies = Layer.mergeAll( | |
| 236 | const layerDependencies = Layer.mergeAll( | |
| 237 | 237 | // Jitter at its maximum, so each retry waits exactly its ceiling: 2s, 4s, 8s... |
| 238 | 238 | Layer.succeed(Random.Random, { |
| 239 | 239 | nextDoubleUnsafe: () => 1 - Number.EPSILON, |
| ... | ... | @@ -261,7 +261,7 @@ const makeHarness = Effect.fn("TestConnectionHarness.make")(function* (options?: |
| 261 | 261 | ); |
| 262 | 262 | |
| 263 | 263 | return { |
| 264 | dependencies, | |
| 264 | dependencies: layerDependencies, | |
| 265 | 265 | prepareCount, |
| 266 | 266 | sessionCount, |
| 267 | 267 | releaseCount, |
| ... | ... | @@ -1551,11 +1551,11 @@ describe("EnvironmentSupervisor", () => { |
| 1551 | 1551 | getAgentActivitySnapshot: unused, |
| 1552 | 1552 | resetTokenCache: Effect.void, |
| 1553 | 1553 | }); |
| 1554 | const httpLayer = remoteHttpClientLayer(fetchFn); | |
| 1554 | const layerHttp = RpcHttp.layerRemoteHttpClient(fetchFn); | |
| 1555 | 1555 | const remoteAuthorization = yield* RemoteEnvironmentAuthorization.make.pipe( |
| 1556 | 1556 | Effect.provide( |
| 1557 | 1557 | Layer.mergeAll( |
| 1558 | httpLayer, | |
| 1558 | layerHttp, | |
| 1559 | 1559 | Layer.succeed(ManagedRelay.ManagedRelayDpopSigner, signer), |
| 1560 | 1560 | Layer.succeed(ManagedRelay.ManagedRelayClient, relay), |
| 1561 | 1561 | Layer.succeed(ClientCapabilities.CloudSession, { |
| ... | ... | @@ -1593,7 +1593,7 @@ describe("EnvironmentSupervisor", () => { |
| 1593 | 1593 | prepared, |
| 1594 | 1594 | signer: Option.some(signer), |
| 1595 | 1595 | remoteAuthorization: Option.some(remoteAuthorization), |
| 1596 | }).pipe(Effect.provide(httpLayer)); | |
| 1596 | }).pipe(Effect.provide(layerHttp)); | |
| 1597 | 1597 | |
| 1598 | 1598 | yield* TestClock.adjust("2 hours"); |
| 1599 | 1599 | expect((yield* readSession).authenticated).toBe(true); |
packages/client-runtime/src/operations/commands.performance.test.ts+2-2| ... | ... | @@ -146,7 +146,7 @@ const TARGET = new PrimaryConnectionTarget({ |
| 146 | 146 | wsBaseUrl: "wss://environment.example.test", |
| 147 | 147 | }); |
| 148 | 148 | |
| 149 | const TEST_CRYPTO_LAYER = Layer.succeed( | |
| 149 | const layerTestCrypto = Layer.succeed( | |
| 150 | 150 | Crypto.Crypto, |
| 151 | 151 | Crypto.make({ |
| 152 | 152 | randomBytes: (size) => new Uint8Array(size), |
| ... | ... | @@ -280,6 +280,6 @@ describe("routine command transport budget", () => { |
| 280 | 280 | }); |
| 281 | 281 | expect(currentRpcJsonBytes).toBeLessThanOrEqual(MAX_CURRENT_SEND_RPC_JSON_BYTES); |
| 282 | 282 | expect(currentRpcJsonBytes / formerRpcJsonBytes).toBeLessThanOrEqual(0.000_06); |
| 283 | }).pipe(Effect.provide(TEST_CRYPTO_LAYER)), | |
| 283 | }).pipe(Effect.provide(layerTestCrypto)), | |
| 284 | 284 | ); |
| 285 | 285 | }); |
packages/client-runtime/src/operations/commands.test.ts+22-22| ... | ... | @@ -56,7 +56,7 @@ import { |
| 56 | 56 | updateThreadMetadata, |
| 57 | 57 | } from "./commands.ts"; |
| 58 | 58 | |
| 59 | const TEST_CRYPTO_LAYER = Layer.succeed( | |
| 59 | const layerTestCrypto = Layer.succeed( | |
| 60 | 60 | Crypto.Crypto, |
| 61 | 61 | Crypto.make({ |
| 62 | 62 | randomBytes: (size) => new Uint8Array(size), |
| ... | ... | @@ -170,7 +170,7 @@ describe("V2 environment commands", () => { |
| 170 | 170 | createWorkspaceRootIfMissing: true, |
| 171 | 171 | }, |
| 172 | 172 | ]); |
| 173 | }).pipe(Effect.provide(TEST_CRYPTO_LAYER)), | |
| 173 | }).pipe(Effect.provide(layerTestCrypto)), | |
| 174 | 174 | ); |
| 175 | 175 | |
| 176 | 176 | it.effect("persists and clears project presentation and environment settings", () => |
| ... | ... | @@ -214,7 +214,7 @@ describe("V2 environment commands", () => { |
| 214 | 214 | defaultThreadEnvMode: null, |
| 215 | 215 | }, |
| 216 | 216 | ]); |
| 217 | }).pipe(Effect.provide(TEST_CRYPTO_LAYER)), | |
| 217 | }).pipe(Effect.provide(layerTestCrypto)), | |
| 218 | 218 | ); |
| 219 | 219 | |
| 220 | 220 | it.effect("preserves caller command ids for idempotent V2 commands", () => |
| ... | ... | @@ -230,7 +230,7 @@ describe("V2 environment commands", () => { |
| 230 | 230 | expect(commands).toEqual([ |
| 231 | 231 | { type: "thread.archive", commandId: "queued-command", threadId: "thread-1" }, |
| 232 | 232 | ]); |
| 233 | }).pipe(Effect.provide(TEST_CRYPTO_LAYER)), | |
| 233 | }).pipe(Effect.provide(layerTestCrypto)), | |
| 234 | 234 | ); |
| 235 | 235 | |
| 236 | 236 | it.effect("resolves run ordinal zero to the persisted thread-start checkpoint", () => |
| ... | ... | @@ -274,7 +274,7 @@ describe("V2 environment commands", () => { |
| 274 | 274 | checkpointId, |
| 275 | 275 | }, |
| 276 | 276 | ]); |
| 277 | }).pipe(Effect.provide(TEST_CRYPTO_LAYER)), | |
| 277 | }).pipe(Effect.provide(layerTestCrypto)), | |
| 278 | 278 | ); |
| 279 | 279 | |
| 280 | 280 | it.effect("preserves plan implementation provenance on V2 runs", () => |
| ... | ... | @@ -314,7 +314,7 @@ describe("V2 environment commands", () => { |
| 314 | 314 | deliveryIntent: "auto", |
| 315 | 315 | dispatchMode: { type: "start_immediately" }, |
| 316 | 316 | }); |
| 317 | }).pipe(Effect.provide(TEST_CRYPTO_LAYER)), | |
| 317 | }).pipe(Effect.provide(layerTestCrypto)), | |
| 318 | 318 | ); |
| 319 | 319 | |
| 320 | 320 | it.effect("preserves an existing worktree and branch during first-message launch", () => |
| ... | ... | @@ -358,7 +358,7 @@ describe("V2 environment commands", () => { |
| 358 | 358 | branch: "feature", |
| 359 | 359 | }, |
| 360 | 360 | }); |
| 361 | }).pipe(Effect.provide(TEST_CRYPTO_LAYER)), | |
| 361 | }).pipe(Effect.provide(layerTestCrypto)), | |
| 362 | 362 | ); |
| 363 | 363 | |
| 364 | 364 | it.effect("provisions an origin-based worktree for an existing empty thread", () => |
| ... | ... | @@ -398,7 +398,7 @@ describe("V2 environment commands", () => { |
| 398 | 398 | startFromOrigin: true, |
| 399 | 399 | }, |
| 400 | 400 | }); |
| 401 | }).pipe(Effect.provide(TEST_CRYPTO_LAYER)), | |
| 401 | }).pipe(Effect.provide(layerTestCrypto)), | |
| 402 | 402 | ); |
| 403 | 403 | |
| 404 | 404 | it.effect("uses server-resolved delivery intent without fetching the full projection", () => |
| ... | ... | @@ -434,7 +434,7 @@ describe("V2 environment commands", () => { |
| 434 | 434 | }); |
| 435 | 435 | } |
| 436 | 436 | expect(projectionRequests).toEqual([]); |
| 437 | }).pipe(Effect.provide(TEST_CRYPTO_LAYER)), | |
| 437 | }).pipe(Effect.provide(layerTestCrypto)), | |
| 438 | 438 | ); |
| 439 | 439 | |
| 440 | 440 | it.effect("retains projection-shaped delivery for servers without command context support", () => |
| ... | ... | @@ -499,7 +499,7 @@ describe("V2 environment commands", () => { |
| 499 | 499 | expect(commands.at(-1)).not.toHaveProperty("deliveryIntent"); |
| 500 | 500 | } |
| 501 | 501 | expect(projectionRequests).toEqual([v2ThreadId, v2ThreadId, v2ThreadId, v2ThreadId]); |
| 502 | }).pipe(Effect.provide(TEST_CRYPTO_LAYER)), | |
| 502 | }).pipe(Effect.provide(layerTestCrypto)), | |
| 503 | 503 | ); |
| 504 | 504 | |
| 505 | 505 | it.effect("Stop with no run left ends the thread's pull request watches", () => |
| ... | ... | @@ -553,7 +553,7 @@ describe("V2 environment commands", () => { |
| 553 | 553 | watching: false, |
| 554 | 554 | }, |
| 555 | 555 | ]); |
| 556 | }).pipe(Effect.provide(TEST_CRYPTO_LAYER)), | |
| 556 | }).pipe(Effect.provide(layerTestCrypto)), | |
| 557 | 557 | ); |
| 558 | 558 | |
| 559 | 559 | it.effect.each([ |
| ... | ... | @@ -629,7 +629,7 @@ describe("V2 environment commands", () => { |
| 629 | 629 | }, |
| 630 | 630 | ], |
| 631 | 631 | ); |
| 632 | }).pipe(Effect.provide(TEST_CRYPTO_LAYER)), | |
| 632 | }).pipe(Effect.provide(layerTestCrypto)), | |
| 633 | 633 | ); |
| 634 | 634 | |
| 635 | 635 | it.effect( |
| ... | ... | @@ -717,7 +717,7 @@ describe("V2 environment commands", () => { |
| 717 | 717 | ]); |
| 718 | 718 | // A text-only edit must not send an attachments replacement list. |
| 719 | 719 | expect(commands[5]).not.toHaveProperty("attachments"); |
| 720 | }).pipe(Effect.provide(TEST_CRYPTO_LAYER)), | |
| 720 | }).pipe(Effect.provide(layerTestCrypto)), | |
| 721 | 721 | ); |
| 722 | 722 | |
| 723 | 723 | it.effect("delegates model selection to the server without fetching the full projection", () => |
| ... | ... | @@ -759,7 +759,7 @@ describe("V2 environment commands", () => { |
| 759 | 759 | }, |
| 760 | 760 | ]); |
| 761 | 761 | expect(projectionRequests).toEqual([]); |
| 762 | }).pipe(Effect.provide(TEST_CRYPTO_LAYER)), | |
| 762 | }).pipe(Effect.provide(layerTestCrypto)), | |
| 763 | 763 | ); |
| 764 | 764 | |
| 765 | 765 | it.effect("retains provider-switch shaping for servers without command context support", () => |
| ... | ... | @@ -791,7 +791,7 @@ describe("V2 environment commands", () => { |
| 791 | 791 | modelSelection: { instanceId: "claude", model: "claude-sonnet-4-6" }, |
| 792 | 792 | }, |
| 793 | 793 | ]); |
| 794 | }).pipe(Effect.provide(TEST_CRYPTO_LAYER)), | |
| 794 | }).pipe(Effect.provide(layerTestCrypto)), | |
| 795 | 795 | ); |
| 796 | 796 | |
| 797 | 797 | it.effect.each([true, false])( |
| ... | ... | @@ -821,7 +821,7 @@ describe("V2 environment commands", () => { |
| 821 | 821 | restoreFiles, |
| 822 | 822 | }, |
| 823 | 823 | ]); |
| 824 | }).pipe(Effect.provide(TEST_CRYPTO_LAYER)), | |
| 824 | }).pipe(Effect.provide(layerTestCrypto)), | |
| 825 | 825 | ); |
| 826 | 826 | |
| 827 | 827 | it.effect("validates identified checkpoints locally for older servers", () => |
| ... | ... | @@ -884,7 +884,7 @@ describe("V2 environment commands", () => { |
| 884 | 884 | } |
| 885 | 885 | expect(projectionRequests).toEqual([v2ThreadId]); |
| 886 | 886 | } |
| 887 | }).pipe(Effect.provide(TEST_CRYPTO_LAYER)), | |
| 887 | }).pipe(Effect.provide(layerTestCrypto)), | |
| 888 | 888 | ); |
| 889 | 889 | |
| 890 | 890 | it.effect("dispatches settle and unsettle commands without timestamps", () => |
| ... | ... | @@ -915,7 +915,7 @@ describe("V2 environment commands", () => { |
| 915 | 915 | reason: "user", |
| 916 | 916 | }, |
| 917 | 917 | ]); |
| 918 | }).pipe(Effect.provide(TEST_CRYPTO_LAYER)), | |
| 918 | }).pipe(Effect.provide(layerTestCrypto)), | |
| 919 | 919 | ); |
| 920 | 920 | |
| 921 | 921 | it.effect("sends an active order key without changing activity timestamps", () => |
| ... | ... | @@ -935,7 +935,7 @@ describe("V2 environment commands", () => { |
| 935 | 935 | orderKey: "mf", |
| 936 | 936 | }, |
| 937 | 937 | ]); |
| 938 | }).pipe(Effect.provide(TEST_CRYPTO_LAYER)), | |
| 938 | }).pipe(Effect.provide(layerTestCrypto)), | |
| 939 | 939 | ); |
| 940 | 940 | |
| 941 | 941 | it.effect("dismisses a pending user-input request", () => |
| ... | ... | @@ -957,7 +957,7 @@ describe("V2 environment commands", () => { |
| 957 | 957 | requestId: "request-1", |
| 958 | 958 | }, |
| 959 | 959 | ]); |
| 960 | }).pipe(Effect.provide(TEST_CRYPTO_LAYER)), | |
| 960 | }).pipe(Effect.provide(layerTestCrypto)), | |
| 961 | 961 | ); |
| 962 | 962 | |
| 963 | 963 | it.effect("dispatches an explicit idle start without fetching the full projection", () => |
| ... | ... | @@ -992,7 +992,7 @@ describe("V2 environment commands", () => { |
| 992 | 992 | dispatchMode: { type: "start_immediately" }, |
| 993 | 993 | }, |
| 994 | 994 | ]); |
| 995 | }).pipe(Effect.provide(TEST_CRYPTO_LAYER)), | |
| 995 | }).pipe(Effect.provide(layerTestCrypto)), | |
| 996 | 996 | ); |
| 997 | 997 | |
| 998 | 998 | it.effect("interrupts a known run without fetching the full projection", () => |
| ... | ... | @@ -1021,6 +1021,6 @@ describe("V2 environment commands", () => { |
| 1021 | 1021 | holdQueue: true, |
| 1022 | 1022 | }, |
| 1023 | 1023 | ]); |
| 1024 | }).pipe(Effect.provide(TEST_CRYPTO_LAYER)), | |
| 1024 | }).pipe(Effect.provide(layerTestCrypto)), | |
| 1025 | 1025 | ); |
| 1026 | 1026 | }); |
packages/client-runtime/src/relay/managedRelay.test.ts+19-19| ... | ... | @@ -13,19 +13,19 @@ import * as Schema from "effect/Schema"; |
| 13 | 13 | import * as TestClock from "effect/testing/TestClock"; |
| 14 | 14 | |
| 15 | 15 | import * as ManagedRelay from "./managedRelay.ts"; |
| 16 | import { remoteHttpClientLayer } from "../rpc/http.ts"; | |
| 16 | import * as RpcHttp from "../rpc/http.ts"; | |
| 17 | 17 | import { NETWORK_BLOCKING_HINT } from "../errors/network.ts"; |
| 18 | 18 | |
| 19 | 19 | const encodeRelayError = Schema.encodeEffect(ManagedRelay.ManagedRelayClientError); |
| 20 | 20 | const decodeRelayError = Schema.decodeUnknownEffect(ManagedRelay.ManagedRelayClientError); |
| 21 | 21 | |
| 22 | function managedRelayTestLayer( | |
| 22 | function layerManagedRelayTest( | |
| 23 | 23 | fetchFn: typeof globalThis.fetch, |
| 24 | 24 | relayUrl = "https://relay.example.test", |
| 25 | 25 | accessTokenStore?: ManagedRelay.ManagedRelayAccessTokenStore, |
| 26 | 26 | ) { |
| 27 | const httpClientLayer = remoteHttpClientLayer(fetchFn); | |
| 28 | const signerLayer = Layer.succeed( | |
| 27 | const layerHttpClient = RpcHttp.layerRemoteHttpClient(fetchFn); | |
| 28 | const layerSigner = Layer.succeed( | |
| 29 | 29 | ManagedRelay.ManagedRelayDpopSigner, |
| 30 | 30 | ManagedRelay.ManagedRelayDpopSigner.of({ |
| 31 | 31 | thumbprint: Effect.succeed("client-thumbprint"), |
| ... | ... | @@ -37,7 +37,7 @@ function managedRelayTestLayer( |
| 37 | 37 | relayUrl, |
| 38 | 38 | clientId: "t3-mobile", |
| 39 | 39 | ...(accessTokenStore ? { accessTokenStore } : {}), |
| 40 | }).pipe(Layer.provide(signerLayer), Layer.provide(httpClientLayer)); | |
| 40 | }).pipe(Layer.provide(layerSigner), Layer.provide(layerHttpClient)); | |
| 41 | 41 | } |
| 42 | 42 | |
| 43 | 43 | function clerkToken(subject: string, nonce: string): string { |
| ... | ... | @@ -114,7 +114,7 @@ describe("ManagedRelayClient", () => { |
| 114 | 114 | "clientRuntime.managedRelay.createRequestProof", |
| 115 | 115 | ]), |
| 116 | 116 | ); |
| 117 | }).pipe(Effect.withTracer(tracer), Effect.provide(managedRelayTestLayer(fetchFn))); | |
| 117 | }).pipe(Effect.withTracer(tracer), Effect.provide(layerManagedRelayTest(fetchFn))); | |
| 118 | 118 | }); |
| 119 | 119 | |
| 120 | 120 | it.effect("rejects unsafe relay URLs before sending credentials", () => { |
| ... | ... | @@ -136,7 +136,7 @@ describe("ManagedRelayClient", () => { |
| 136 | 136 | message: "Relay URL must be a secure absolute HTTPS origin.", |
| 137 | 137 | }); |
| 138 | 138 | expect(requestCount).toBe(0); |
| 139 | }).pipe(Effect.provide(managedRelayTestLayer(fetchFn, "http://relay.example.test"))); | |
| 139 | }).pipe(Effect.provide(layerManagedRelayTest(fetchFn, "http://relay.example.test"))); | |
| 140 | 140 | }); |
| 141 | 141 | |
| 142 | 142 | it.effect("reuses usable DPoP tokens and refreshes cleared or expiring cache entries", () => { |
| ... | ... | @@ -195,7 +195,7 @@ describe("ManagedRelayClient", () => { |
| 195 | 195 | yield* relayClient.resetTokenCache; |
| 196 | 196 | yield* relayClient.getEnvironmentStatus(statusInput); |
| 197 | 197 | expect(tokenExchangeCount).toBe(3); |
| 198 | }).pipe(Effect.provide(managedRelayTestLayer(fetchFn))); | |
| 198 | }).pipe(Effect.provide(layerManagedRelayTest(fetchFn))); | |
| 199 | 199 | }); |
| 200 | 200 | |
| 201 | 201 | it.effect("uses a cached token while another scope waits for an exchange", () => { |
| ... | ... | @@ -290,7 +290,7 @@ describe("ManagedRelayClient", () => { |
| 290 | 290 | expect(tokenExchangeCount).toBe(2); |
| 291 | 291 | }).pipe( |
| 292 | 292 | Effect.ensuring(Effect.sync(() => releaseExchange.resolve())), |
| 293 | Effect.provide(managedRelayTestLayer(fetchFn, undefined, accessTokenStore)), | |
| 293 | Effect.provide(layerManagedRelayTest(fetchFn, undefined, accessTokenStore)), | |
| 294 | 294 | ); |
| 295 | 295 | }); |
| 296 | 296 | |
| ... | ... | @@ -352,7 +352,7 @@ describe("ManagedRelayClient", () => { |
| 352 | 352 | yield* Effect.gen(function* () { |
| 353 | 353 | const relayClient = yield* ManagedRelay.ManagedRelayClient; |
| 354 | 354 | yield* relayClient.getEnvironmentStatus(statusInput(clerkToken("user-1", "session-1"))); |
| 355 | }).pipe(Effect.provide(managedRelayTestLayer(fetchFn, undefined, accessTokenStore))); | |
| 355 | }).pipe(Effect.provide(layerManagedRelayTest(fetchFn, undefined, accessTokenStore))); | |
| 356 | 356 | |
| 357 | 357 | expect(tokenExchangeCount).toBe(1); |
| 358 | 358 | expect(persistedTokens).toHaveLength(1); |
| ... | ... | @@ -360,7 +360,7 @@ describe("ManagedRelayClient", () => { |
| 360 | 360 | yield* Effect.gen(function* () { |
| 361 | 361 | const relayClient = yield* ManagedRelay.ManagedRelayClient; |
| 362 | 362 | yield* relayClient.getEnvironmentStatus(statusInput(clerkToken("user-1", "session-2"))); |
| 363 | }).pipe(Effect.provide(managedRelayTestLayer(fetchFn, undefined, accessTokenStore))); | |
| 363 | }).pipe(Effect.provide(layerManagedRelayTest(fetchFn, undefined, accessTokenStore))); | |
| 364 | 364 | |
| 365 | 365 | expect(tokenExchangeCount).toBe(1); |
| 366 | 366 | }); |
| ... | ... | @@ -457,7 +457,7 @@ describe("ManagedRelayClient", () => { |
| 457 | 457 | accessToken: "fresh-relay-token", |
| 458 | 458 | }, |
| 459 | 459 | ]); |
| 460 | }).pipe(Effect.provide(managedRelayTestLayer(fetchFn, undefined, accessTokenStore))); | |
| 460 | }).pipe(Effect.provide(layerManagedRelayTest(fetchFn, undefined, accessTokenStore))); | |
| 461 | 461 | }); |
| 462 | 462 | |
| 463 | 463 | it.effect("does not persist tokens when the Clerk subject cannot be decoded", () => { |
| ... | ... | @@ -513,7 +513,7 @@ describe("ManagedRelayClient", () => { |
| 513 | 513 | }); |
| 514 | 514 | |
| 515 | 515 | expect(persistedTokens).toEqual([]); |
| 516 | }).pipe(Effect.provide(managedRelayTestLayer(fetchFn, undefined, accessTokenStore))); | |
| 516 | }).pipe(Effect.provide(layerManagedRelayTest(fetchFn, undefined, accessTokenStore))); | |
| 517 | 517 | }); |
| 518 | 518 | |
| 519 | 519 | it.effect("times out stalled relay environment listing requests", () => { |
| ... | ... | @@ -536,7 +536,7 @@ describe("ManagedRelayClient", () => { |
| 536 | 536 | timeoutMs: ManagedRelay.MANAGED_RELAY_REQUEST_TIMEOUT_MS, |
| 537 | 537 | message: `Relay environment listing timed out. ${NETWORK_BLOCKING_HINT}`, |
| 538 | 538 | }); |
| 539 | }).pipe(Effect.provide(Layer.merge(TestClock.layer(), managedRelayTestLayer(fetchFn)))); | |
| 539 | }).pipe(Effect.provide(Layer.merge(TestClock.layer(), layerManagedRelayTest(fetchFn)))); | |
| 540 | 540 | }); |
| 541 | 541 | |
| 542 | 542 | it.effect("suggests checking network filtering when fetch fails without a response", () => { |
| ... | ... | @@ -555,7 +555,7 @@ describe("ManagedRelayClient", () => { |
| 555 | 555 | const encoded = yield* encodeRelayError(error); |
| 556 | 556 | const decoded = yield* decodeRelayError(encoded); |
| 557 | 557 | expect(decoded.message).toBe(error.message); |
| 558 | }).pipe(Effect.provide(managedRelayTestLayer(fetchFn))); | |
| 558 | }).pipe(Effect.provide(layerManagedRelayTest(fetchFn))); | |
| 559 | 559 | }); |
| 560 | 560 | |
| 561 | 561 | it.effect("does not suggest network filtering for an HTTP server error", () => { |
| ... | ... | @@ -569,7 +569,7 @@ describe("ManagedRelayClient", () => { |
| 569 | 569 | .listEnvironments({ clerkToken: "clerk-token" }) |
| 570 | 570 | .pipe(Effect.flip); |
| 571 | 571 | expect(error.message).toBe("Could not list relay-managed environments."); |
| 572 | }).pipe(Effect.provide(managedRelayTestLayer(fetchFn))); | |
| 572 | }).pipe(Effect.provide(layerManagedRelayTest(fetchFn))); | |
| 573 | 573 | }); |
| 574 | 574 | |
| 575 | 575 | it.effect("preserves typed relay trace IDs on client errors", () => { |
| ... | ... | @@ -596,7 +596,7 @@ describe("ManagedRelayClient", () => { |
| 596 | 596 | _tag: "ManagedRelayRequestFailedError", |
| 597 | 597 | traceId: "trace-managed-relay", |
| 598 | 598 | }); |
| 599 | }).pipe(Effect.provide(managedRelayTestLayer(fetchFn))); | |
| 599 | }).pipe(Effect.provide(layerManagedRelayTest(fetchFn))); | |
| 600 | 600 | }); |
| 601 | 601 | |
| 602 | 602 | it.effect("accepts generic DPoP errors from relays without the optional reason", () => { |
| ... | ... | @@ -633,7 +633,7 @@ describe("ManagedRelayClient", () => { |
| 633 | 633 | ) { |
| 634 | 634 | expect(error.relayError.dpopFailureReason).toBeUndefined(); |
| 635 | 635 | } |
| 636 | }).pipe(Effect.provide(managedRelayTestLayer(fetchFn))); | |
| 636 | }).pipe(Effect.provide(layerManagedRelayTest(fetchFn))); | |
| 637 | 637 | }); |
| 638 | 638 | |
| 639 | 639 | it.effect("lists account devices through the v2 Clerk bearer client endpoint", () => { |
| ... | ... | @@ -703,6 +703,6 @@ describe("ManagedRelayClient", () => { |
| 703 | 703 | androidApiLevel: 36, |
| 704 | 704 | }, |
| 705 | 705 | ]); |
| 706 | }).pipe(Effect.provide(managedRelayTestLayer(fetchFn))); | |
| 706 | }).pipe(Effect.provide(layerManagedRelayTest(fetchFn))); | |
| 707 | 707 | }); |
| 708 | 708 | }); |
packages/client-runtime/src/rpc/http.ts+3-3| ... | ... | @@ -8,7 +8,7 @@ import { |
| 8 | 8 | type EnvironmentResourceNotFoundError, |
| 9 | 9 | type EnvironmentScopeRequiredError, |
| 10 | 10 | } from "@t3tools/contracts"; |
| 11 | import { httpHeaderRedactionLayer } from "@t3tools/shared/httpObservability"; | |
| 11 | import * as HttpObservability from "@t3tools/shared/httpObservability"; | |
| 12 | 12 | import * as Data from "effect/Data"; |
| 13 | 13 | import * as Duration from "effect/Duration"; |
| 14 | 14 | import * as Effect from "effect/Effect"; |
| ... | ... | @@ -78,12 +78,12 @@ export type RemoteEnvironmentRequestError = |
| 78 | 78 | | RemoteEnvironmentAuthUndeclaredStatusError |
| 79 | 79 | | RemoteEnvironmentAuthTimeoutError; |
| 80 | 80 | |
| 81 | export const remoteHttpClientLayer = ( | |
| 81 | export const layerRemoteHttpClient = ( | |
| 82 | 82 | fetchFn: typeof globalThis.fetch, |
| 83 | 83 | ): Layer.Layer<HttpClient.HttpClient> => |
| 84 | 84 | Layer.merge( |
| 85 | 85 | FetchHttpClient.layer.pipe(Layer.provide(Layer.succeed(FetchHttpClient.Fetch, fetchFn))), |
| 86 | httpHeaderRedactionLayer, | |
| 86 | HttpObservability.layer, | |
| 87 | 87 | ); |
| 88 | 88 | |
| 89 | 89 | const remoteApiBaseUrl = (httpBaseUrl: string): string => { |
packages/client-runtime/src/rpc/session.test.ts+2-2| ... | ... | @@ -215,12 +215,12 @@ const makeFactory = Effect.fn("TestRpcSessionFactory.make")(function* ( |
| 215 | 215 | options: RpcSession.RpcSessionOptions = {}, |
| 216 | 216 | ) { |
| 217 | 217 | const sockets: TestWebSocket[] = []; |
| 218 | const constructorLayer = Layer.succeed(Socket.WebSocketConstructor, (url) => { | |
| 218 | const layerConstructor = Layer.succeed(Socket.WebSocketConstructor, (url) => { | |
| 219 | 219 | const socket = new TestWebSocket(url); |
| 220 | 220 | sockets.push(socket); |
| 221 | 221 | return socket as unknown as globalThis.WebSocket; |
| 222 | 222 | }); |
| 223 | const layer = RpcSession.layer(options).pipe(Layer.provide(constructorLayer)); | |
| 223 | const layer = RpcSession.layer(options).pipe(Layer.provide(layerConstructor)); | |
| 224 | 224 | const factory = yield* RpcSession.RpcSessionFactory.pipe(Effect.provide(layer)); |
| 225 | 225 | return { factory, sockets }; |
| 226 | 226 | }); |
packages/client-runtime/src/rpc/session.ts+4-4| ... | ... | @@ -195,10 +195,10 @@ export const make = Effect.fn("RpcSessionFactory.make")(function* ( |
| 195 | 195 | Effect.asVoid, |
| 196 | 196 | ), |
| 197 | 197 | }); |
| 198 | const socketLayer = Socket.layerWebSocket(connection.socketUrl, { | |
| 198 | const layerSocket = Socket.layerWebSocket(connection.socketUrl, { | |
| 199 | 199 | openTimeout: SOCKET_OPEN_TIMEOUT, |
| 200 | 200 | }).pipe(Layer.provide(Layer.succeed(Socket.WebSocketConstructor, webSocketConstructor))); |
| 201 | const protocolLayer = Layer.effect( | |
| 201 | const layerProtocol = Layer.effect( | |
| 202 | 202 | RpcClient.Protocol, |
| 203 | 203 | RpcClient.makeProtocolSocket({ |
| 204 | 204 | retryTransientErrors: false, |
| ... | ... | @@ -207,13 +207,13 @@ export const make = Effect.fn("RpcSessionFactory.make")(function* ( |
| 207 | 207 | ).pipe( |
| 208 | 208 | Layer.provide( |
| 209 | 209 | Layer.mergeAll( |
| 210 | socketLayer, | |
| 210 | layerSocket, | |
| 211 | 211 | RpcSerialization.layerJson, |
| 212 | 212 | Layer.succeed(RpcClient.ConnectionHooks, hooks), |
| 213 | 213 | ), |
| 214 | 214 | ), |
| 215 | 215 | ); |
| 216 | const protocolContext = yield* Layer.build(protocolLayer).pipe( | |
| 216 | const protocolContext = yield* Layer.build(layerProtocol).pipe( | |
| 217 | 217 | Effect.withSpan("environment.websocket.connect"), |
| 218 | 218 | ); |
| 219 | 219 | const protocolClient = yield* makeWsRpcProtocolClient.pipe(Effect.provide(protocolContext)); |
packages/client-runtime/src/state/boundedThreadSnapshotHttp.test.ts+6-6| ... | ... | @@ -4,8 +4,8 @@ import * as Effect from "effect/Effect"; |
| 4 | 4 | import * as Layer from "effect/Layer"; |
| 5 | 5 | |
| 6 | 6 | import { PrimaryConnectionTarget, type PreparedConnection } from "../connection/model.ts"; |
| 7 | import { remoteHttpClientLayer } from "../rpc/http.ts"; | |
| 8 | import { boundedThreadSnapshotLoaderLayer } from "./boundedThreadSnapshotHttp.ts"; | |
| 7 | import * as RpcHttp from "../rpc/http.ts"; | |
| 8 | import * as BoundedThreadSnapshotHttp from "./boundedThreadSnapshotHttp.ts"; | |
| 9 | 9 | import * as ThreadSnapshotLoader from "./threadSnapshotHttp.ts"; |
| 10 | 10 | |
| 11 | 11 | const TARGET = new PrimaryConnectionTarget({ |
| ... | ... | @@ -110,7 +110,7 @@ describe("boundedThreadSnapshotLoader", () => { |
| 110 | 110 | } |
| 111 | 111 | }).pipe( |
| 112 | 112 | Effect.provide( |
| 113 | Layer.provide(boundedThreadSnapshotLoaderLayer, remoteHttpClientLayer(fetchFn)), | |
| 113 | Layer.provide(BoundedThreadSnapshotHttp.layer, RpcHttp.layerRemoteHttpClient(fetchFn)), | |
| 114 | 114 | ), |
| 115 | 115 | ); |
| 116 | 116 | }); |
| ... | ... | @@ -138,7 +138,7 @@ describe("boundedThreadSnapshotLoader", () => { |
| 138 | 138 | expect(result).toEqual({ _tag: "missing" }); |
| 139 | 139 | }).pipe( |
| 140 | 140 | Effect.provide( |
| 141 | Layer.provide(boundedThreadSnapshotLoaderLayer, remoteHttpClientLayer(fetchFn)), | |
| 141 | Layer.provide(BoundedThreadSnapshotHttp.layer, RpcHttp.layerRemoteHttpClient(fetchFn)), | |
| 142 | 142 | ), |
| 143 | 143 | ); |
| 144 | 144 | }); |
| ... | ... | @@ -176,7 +176,7 @@ describe("boundedThreadSnapshotLoader", () => { |
| 176 | 176 | expect(fullCalls).toBe(1); |
| 177 | 177 | }).pipe( |
| 178 | 178 | Effect.provide( |
| 179 | Layer.provide(boundedThreadSnapshotLoaderLayer, remoteHttpClientLayer(fetchFn)), | |
| 179 | Layer.provide(BoundedThreadSnapshotHttp.layer, RpcHttp.layerRemoteHttpClient(fetchFn)), | |
| 180 | 180 | ), |
| 181 | 181 | ); |
| 182 | 182 | }); |
| ... | ... | @@ -206,7 +206,7 @@ describe("boundedThreadSnapshotLoader", () => { |
| 206 | 206 | expect(fullCalls).toBe(0); |
| 207 | 207 | }).pipe( |
| 208 | 208 | Effect.provide( |
| 209 | Layer.provide(boundedThreadSnapshotLoaderLayer, remoteHttpClientLayer(fetchFn)), | |
| 209 | Layer.provide(BoundedThreadSnapshotHttp.layer, RpcHttp.layerRemoteHttpClient(fetchFn)), | |
| 210 | 210 | ), |
| 211 | 211 | ); |
| 212 | 212 | }, |
packages/client-runtime/src/state/boundedThreadSnapshotHttp.ts+1-1| ... | ... | @@ -53,7 +53,7 @@ export const fetchEnvironmentBoundedThreadSnapshot = Effect.fn( |
| 53 | 53 | * endpoint still means missing. Transient failures report `unavailable` so the |
| 54 | 54 | * socket path remains a last resort for connectivity issues. |
| 55 | 55 | */ |
| 56 | export const boundedThreadSnapshotLoaderLayer: Layer.Layer< | |
| 56 | export const layer: Layer.Layer< | |
| 57 | 57 | ThreadSnapshotLoader.ThreadSnapshotLoader, |
| 58 | 58 | never, |
| 59 | 59 | HttpClient.HttpClient |
packages/client-runtime/src/state/environmentHttpAuth.test.ts+9-10| ... | ... | @@ -27,16 +27,15 @@ import { |
| 27 | 27 | type PreparedHttpAuthorization, |
| 28 | 28 | } from "../connection/model.ts"; |
| 29 | 29 | import * as ManagedRelay from "../relay/managedRelay.ts"; |
| 30 | import { remoteHttpClientLayer, type RemoteEnvironmentRequestError } from "../rpc/http.ts"; | |
| 30 | import { type RemoteEnvironmentRequestError } from "../rpc/http.ts"; | |
| 31 | import * as RpcHttp from "../rpc/http.ts"; | |
| 31 | 32 | import * as PullRequestDiffLoader from "./pullRequestDiffHttp.ts"; |
| 32 | 33 | import { withOrchestrationProtocolHeader } from "./environmentHttpAuth.ts"; |
| 33 | 34 | import { fetchEnvironmentSessionState } from "./session.ts"; |
| 34 | 35 | import { fetchEnvironmentShellSnapshot } from "./shellSnapshotHttp.ts"; |
| 35 | 36 | import * as ThreadSnapshotLoader from "./threadSnapshotHttp.ts"; |
| 36 | import { | |
| 37 | boundedThreadSnapshotLoaderLayer, | |
| 38 | fetchEnvironmentBoundedThreadSnapshot, | |
| 39 | } from "./boundedThreadSnapshotHttp.ts"; | |
| 37 | import { fetchEnvironmentBoundedThreadSnapshot } from "./boundedThreadSnapshotHttp.ts"; | |
| 38 | import * as BoundedThreadSnapshotHttp from "./boundedThreadSnapshotHttp.ts"; | |
| 40 | 39 | import { fetchEnvironmentThreadHistoryPage } from "./threadHistoryHttp.ts"; |
| 41 | 40 | import { v2Projection } from "./orchestrationV2TestFixtures.ts"; |
| 42 | 41 | |
| ... | ... | @@ -161,7 +160,7 @@ function makeHarness(reply: (requestNumber: number) => Response | Promise<Respon |
| 161 | 160 | signer: Option.some(signer), |
| 162 | 161 | remoteAuthorization: Option.some(remoteAuthorization), |
| 163 | 162 | }, |
| 164 | httpLayer: remoteHttpClientLayer(fetchFn), | |
| 163 | httpLayer: RpcHttp.layerRemoteHttpClient(fetchFn), | |
| 165 | 164 | }; |
| 166 | 165 | } |
| 167 | 166 | |
| ... | ... | @@ -360,7 +359,7 @@ describe("authenticated environment HTTP requests", () => { |
| 360 | 359 | ? credentialRejectedResponse() |
| 361 | 360 | : Response.json(encodeBoundedSnapshot(BOUNDED_THREAD)), |
| 362 | 361 | ); |
| 363 | const loaderLayer = boundedThreadSnapshotLoaderLayer.pipe( | |
| 362 | const layerLoader = BoundedThreadSnapshotHttp.layer.pipe( | |
| 364 | 363 | Layer.provide( |
| 365 | 364 | Layer.mergeAll( |
| 366 | 365 | harness.httpLayer, |
| ... | ... | @@ -376,7 +375,7 @@ describe("authenticated environment HTTP requests", () => { |
| 376 | 375 | ), |
| 377 | 376 | ); |
| 378 | 377 | const loader = yield* ThreadSnapshotLoader.ThreadSnapshotLoader.pipe( |
| 379 | Effect.provide(loaderLayer), | |
| 378 | Effect.provide(layerLoader), | |
| 380 | 379 | ); |
| 381 | 380 | const result = yield* loader.load(PREPARED, THREAD.projection.thread.id); |
| 382 | 381 | expect(result).toEqual({ |
| ... | ... | @@ -398,7 +397,7 @@ describe("authenticated environment HTTP requests", () => { |
| 398 | 397 | it.effect("uses the authorization service captured by the diff loader layer", () => |
| 399 | 398 | Effect.gen(function* () { |
| 400 | 399 | const harness = makeHarness(() => Response.json(DIFF_RESULT)); |
| 401 | const loaderLayer = PullRequestDiffLoader.layer.pipe( | |
| 400 | const layerLoader = PullRequestDiffLoader.layer.pipe( | |
| 402 | 401 | Layer.provide( |
| 403 | 402 | Layer.mergeAll( |
| 404 | 403 | harness.httpLayer, |
| ... | ... | @@ -414,7 +413,7 @@ describe("authenticated environment HTTP requests", () => { |
| 414 | 413 | ), |
| 415 | 414 | ); |
| 416 | 415 | const loader = yield* PullRequestDiffLoader.PullRequestDiffLoader.pipe( |
| 417 | Effect.provide(loaderLayer), | |
| 416 | Effect.provide(layerLoader), | |
| 418 | 417 | ); |
| 419 | 418 | const result = yield* loader.load(PREPARED, DIFF); |
| 420 | 419 |
packages/client-runtime/src/state/pullRequestDiffHttp.test.ts+3-3| ... | ... | @@ -4,7 +4,7 @@ import * as Effect from "effect/Effect"; |
| 4 | 4 | import * as Option from "effect/Option"; |
| 5 | 5 | |
| 6 | 6 | import { PrimaryConnectionTarget, type PreparedConnection } from "../connection/model.ts"; |
| 7 | import { remoteHttpClientLayer } from "../rpc/http.ts"; | |
| 7 | import * as RpcHttp from "../rpc/http.ts"; | |
| 8 | 8 | import { |
| 9 | 9 | fetchEnvironmentPullRequestDiff, |
| 10 | 10 | PullRequestDiffCredentialRejectedError, |
| ... | ... | @@ -50,7 +50,7 @@ describe("fetchEnvironmentPullRequestDiff", () => { |
| 50 | 50 | number: 42, |
| 51 | 51 | cursor: "next-page", |
| 52 | 52 | }, |
| 53 | }).pipe(Effect.provide(remoteHttpClientLayer(fetchFn))); | |
| 53 | }).pipe(Effect.provide(RpcHttp.layerRemoteHttpClient(fetchFn))); | |
| 54 | 54 | |
| 55 | 55 | expect(result).toEqual({ |
| 56 | 56 | patch: "diff --git a/file.ts b/file.ts", |
| ... | ... | @@ -104,7 +104,7 @@ describe("fetchEnvironmentPullRequestDiff", () => { |
| 104 | 104 | repository: "owner/repository", |
| 105 | 105 | number: 42, |
| 106 | 106 | }, |
| 107 | }).pipe(Effect.provide(remoteHttpClientLayer(fetchFn)), Effect.flip); | |
| 107 | }).pipe(Effect.provide(RpcHttp.layerRemoteHttpClient(fetchFn)), Effect.flip); | |
| 108 | 108 | |
| 109 | 109 | expect(error).toBeInstanceOf(PullRequestDiffCredentialRejectedError); |
| 110 | 110 | expect(error).toMatchObject({ |
packages/client-runtime/src/state/threads.ts+1-1| ... | ... | @@ -995,7 +995,7 @@ export function createEnvironmentThreadStateAtoms<R, E>( |
| 995 | 995 | |
| 996 | 996 | export * from "./archivedThreads.ts"; |
| 997 | 997 | export * from "./checkpointDiff.ts"; |
| 998 | export * from "./boundedThreadSnapshotHttp.ts"; | |
| 998 | export * as BoundedThreadSnapshotLoader from "./boundedThreadSnapshotHttp.ts"; | |
| 999 | 999 | export * as ThreadHistoryController from "./threadHistoryController.ts"; |
| 1000 | 1000 | // Flat so consumers' inferred types can name it. |
| 1001 | 1001 | export type { ThreadHistoryLoadEarlierResult } from "./threadHistoryController.ts"; |
packages/effect-acp/scripts/generate.ts+2-2| ... | ... | @@ -514,7 +514,7 @@ const generateCommand = Command.make( |
| 514 | 514 | ({ skipDownload }) => generateSchemas(skipDownload), |
| 515 | 515 | ).pipe(Command.withDescription("Generate Effect ACP schemas from the pinned ACP release assets.")); |
| 516 | 516 | |
| 517 | const runtimeLayer = Layer.mergeAll( | |
| 517 | const layerRuntime = Layer.mergeAll( | |
| 518 | 518 | Logger.layer([Logger.consolePretty()]), |
| 519 | 519 | NodeServices.layer, |
| 520 | 520 | FetchHttpClient.layer, |
| ... | ... | @@ -522,6 +522,6 @@ const runtimeLayer = Layer.mergeAll( |
| 522 | 522 | |
| 523 | 523 | Command.run(generateCommand, { version: "0.0.0" }).pipe( |
| 524 | 524 | Effect.scoped, |
| 525 | Effect.provide(runtimeLayer), | |
| 525 | Effect.provide(layerRuntime), | |
| 526 | 526 | NodeRuntime.runMain, |
| 527 | 527 | ); |
packages/effect-acp/src/agent.ts+2-2| ... | ... | @@ -316,7 +316,7 @@ export const make = Effect.fn("effect-acp/AcpAgent.make")(function* ( |
| 316 | 316 | method, |
| 317 | 317 | }); |
| 318 | 318 | |
| 319 | const agentHandlerLayer = AcpRpcs.AgentRpcs.toLayer( | |
| 319 | const layerAgentHandler = AcpRpcs.AgentRpcs.toLayer( | |
| 320 | 320 | AcpRpcs.AgentRpcs.of({ |
| 321 | 321 | [AGENT_METHODS.initialize]: (payload, { requestId }) => |
| 322 | 322 | runHandler( |
| ... | ... | @@ -423,7 +423,7 @@ export const make = Effect.fn("effect-acp/AcpAgent.make")(function* ( |
| 423 | 423 | |
| 424 | 424 | yield* RpcServer.make(AcpRpcs.AgentRpcs).pipe( |
| 425 | 425 | Effect.provideService(RpcServer.Protocol, transport.serverProtocol), |
| 426 | Effect.provide(agentHandlerLayer), | |
| 426 | Effect.provide(layerAgentHandler), | |
| 427 | 427 | Effect.forkScoped, |
| 428 | 428 | ); |
| 429 | 429 |
packages/effect-acp/src/client.test.ts+8-8| ... | ... | @@ -309,8 +309,8 @@ it.layer(NodeServices.layer)("effect-acp client", (it) => { |
| 309 | 309 | const requestContexts = yield* Ref.make<Array<AcpProtocol.AcpRequestContext>>([]); |
| 310 | 310 | const handle = yield* makeHandle(); |
| 311 | 311 | const scope = yield* Scope.make(); |
| 312 | const acpLayer = AcpClient.layerChildProcess(handle); | |
| 313 | const context = yield* Layer.buildWithScope(acpLayer, scope); | |
| 312 | const layerAcp = AcpClient.layerChildProcess(handle); | |
| 313 | const context = yield* Layer.buildWithScope(layerAcp, scope); | |
| 314 | 314 | |
| 315 | 315 | const ext = yield* Effect.gen(function* () { |
| 316 | 316 | const acp = yield* AcpClient.AcpClient; |
| ... | ... | @@ -431,8 +431,8 @@ it.layer(NodeServices.layer)("effect-acp client", (it) => { |
| 431 | 431 | Effect.gen(function* () { |
| 432 | 432 | const handle = yield* makeHandle({ ACP_MOCK_BAD_TYPED_REQUEST: "1" }); |
| 433 | 433 | const scope = yield* Scope.make(); |
| 434 | const acpLayer = AcpClient.layerChildProcess(handle); | |
| 435 | const context = yield* Layer.buildWithScope(acpLayer, scope); | |
| 434 | const layerAcp = AcpClient.layerChildProcess(handle); | |
| 435 | const context = yield* Layer.buildWithScope(layerAcp, scope); | |
| 436 | 436 | |
| 437 | 437 | const result = yield* Effect.gen(function* () { |
| 438 | 438 | const acp = yield* AcpClient.AcpClient; |
| ... | ... | @@ -598,8 +598,8 @@ it.layer(NodeServices.layer)("effect-acp client", (it) => { |
| 598 | 598 | const typedNotifications = yield* Ref.make<Array<unknown>>([]); |
| 599 | 599 | const handle = yield* makeHandle(); |
| 600 | 600 | const scope = yield* Scope.make(); |
| 601 | const acpLayer = AcpClient.layerChildProcess(handle); | |
| 602 | const context = yield* Layer.buildWithScope(acpLayer, scope); | |
| 601 | const layerAcp = AcpClient.layerChildProcess(handle); | |
| 602 | const context = yield* Layer.buildWithScope(layerAcp, scope); | |
| 603 | 603 | |
| 604 | 604 | yield* Effect.gen(function* () { |
| 605 | 605 | const acp = yield* AcpClient.AcpClient; |
| ... | ... | @@ -679,8 +679,8 @@ it.layer(NodeServices.layer)("effect-acp client", (it) => { |
| 679 | 679 | const successfulHandlers = yield* Ref.make(0); |
| 680 | 680 | const handle = yield* makeHandle(); |
| 681 | 681 | const scope = yield* Scope.make(); |
| 682 | const acpLayer = AcpClient.layerChildProcess(handle); | |
| 683 | const context = yield* Layer.buildWithScope(acpLayer, scope); | |
| 682 | const layerAcp = AcpClient.layerChildProcess(handle); | |
| 683 | const context = yield* Layer.buildWithScope(layerAcp, scope); | |
| 684 | 684 | |
| 685 | 685 | yield* Effect.gen(function* () { |
| 686 | 686 | const acp = yield* AcpClient.AcpClient; |
packages/effect-acp/src/client.ts+2-2| ... | ... | @@ -971,7 +971,7 @@ export const make = Effect.fn("effect-acp/AcpClient.make")(function* ( |
| 971 | 971 | method, |
| 972 | 972 | }); |
| 973 | 973 | |
| 974 | const clientHandlerLayer = AcpRpcs.CompatClientRpcs.toLayer( | |
| 974 | const layerClientHandler = AcpRpcs.CompatClientRpcs.toLayer( | |
| 975 | 975 | AcpRpcs.CompatClientRpcs.of({ |
| 976 | 976 | [CLIENT_METHODS.session_request_permission]: (payload, { requestId }) => |
| 977 | 977 | runHandler( |
| ... | ... | @@ -1101,7 +1101,7 @@ export const make = Effect.fn("effect-acp/AcpClient.make")(function* ( |
| 1101 | 1101 | |
| 1102 | 1102 | yield* RpcServer.make(AcpRpcs.CompatClientRpcs).pipe( |
| 1103 | 1103 | Effect.provideService(RpcServer.Protocol, transport.serverProtocol), |
| 1104 | Effect.provide(clientHandlerLayer), | |
| 1104 | Effect.provide(layerClientHandler), | |
| 1105 | 1105 | Effect.forkScoped, |
| 1106 | 1106 | ); |
| 1107 | 1107 |
packages/effect-codex-app-server/src/client.test.ts+4-4| ... | ... | @@ -35,8 +35,8 @@ it.layer(NodeServices.layer)("effect-codex-app-server client", (it) => { |
| 35 | 35 | const messageDeltas = yield* Ref.make<Array<unknown>>([]); |
| 36 | 36 | const handle = yield* makeHandle(); |
| 37 | 37 | const scope = yield* Scope.make(); |
| 38 | const clientLayer = CodexClient.layerChildProcess(handle); | |
| 39 | const context = yield* Layer.buildWithScope(clientLayer, scope); | |
| 38 | const layerClient = CodexClient.layerChildProcess(handle); | |
| 39 | const context = yield* Layer.buildWithScope(layerClient, scope); | |
| 40 | 40 | |
| 41 | 41 | const result = yield* Effect.gen(function* () { |
| 42 | 42 | const client = yield* CodexClient.CodexAppServerClient; |
| ... | ... | @@ -130,8 +130,8 @@ it.layer(NodeServices.layer)("effect-codex-app-server client", (it) => { |
| 130 | 130 | CODEX_APP_SERVER_TEST_STDERR_BYTES: String(512 * 1024), |
| 131 | 131 | }); |
| 132 | 132 | const scope = yield* Scope.make(); |
| 133 | const clientLayer = CodexClient.layerChildProcess(handle); | |
| 134 | const context = yield* Layer.buildWithScope(clientLayer, scope); | |
| 133 | const layerClient = CodexClient.layerChildProcess(handle); | |
| 134 | const context = yield* Layer.buildWithScope(layerClient, scope); | |
| 135 | 135 | |
| 136 | 136 | const initialized = yield* Effect.gen(function* () { |
| 137 | 137 | const client = yield* CodexClient.CodexAppServerClient; |
packages/effect-codex-app-server/test/examples/codex-app-server-probe.ts+2-2| ... | ... | @@ -15,7 +15,7 @@ const program = Effect.gen(function* () { |
| 15 | 15 | shell: false, |
| 16 | 16 | }), |
| 17 | 17 | ); |
| 18 | const codexLayer = CodexClient.layerChildProcess(handle, { | |
| 18 | const layerCodex = CodexClient.layerChildProcess(handle, { | |
| 19 | 19 | logIncoming: true, |
| 20 | 20 | logOutgoing: true, |
| 21 | 21 | }); |
| ... | ... | @@ -61,7 +61,7 @@ const program = Effect.gen(function* () { |
| 61 | 61 | cwds: [process.cwd()], |
| 62 | 62 | }); |
| 63 | 63 | yield* Console.log("skills/list", skills); |
| 64 | }).pipe(Effect.provide(codexLayer)); | |
| 64 | }).pipe(Effect.provide(layerCodex)); | |
| 65 | 65 | }); |
| 66 | 66 | |
| 67 | 67 | program.pipe(Effect.scoped, Effect.provide(NodeServices.layer), NodeRuntime.runMain); |
packages/shared/src/httpObservability.ts+1-1| ... | ... | @@ -2,7 +2,7 @@ import * as Effect from "effect/Effect"; |
| 2 | 2 | import * as Layer from "effect/Layer"; |
| 3 | 3 | import * as Headers from "effect/http/Headers"; |
| 4 | 4 | |
| 5 | export const httpHeaderRedactionLayer = Layer.effect( | |
| 5 | export const layer = Layer.effect( | |
| 6 | 6 | Headers.CurrentRedactedNames, |
| 7 | 7 | Effect.map(Headers.CurrentRedactedNames, (names) => [...names, "dpop"]), |
| 8 | 8 | ); |
packages/shared/src/observability.test.ts+3-3| ... | ... | @@ -99,7 +99,7 @@ const readTraceRecords = Effect.fn("readTraceRecords")(function* (tracePath: str |
| 99 | 99 | .map((line) => decodeTraceRecordLine(line)); |
| 100 | 100 | }); |
| 101 | 101 | |
| 102 | const makeTestLayer = (tracePath: string) => | |
| 102 | const layerTest = (tracePath: string) => | |
| 103 | 103 | Layer.mergeAll( |
| 104 | 104 | Layer.effect( |
| 105 | 105 | Tracer.Tracer, |
| ... | ... | @@ -550,7 +550,7 @@ describe("observability", () => { |
| 550 | 550 | }).pipe(Effect.withSpan("child-span")); |
| 551 | 551 | }).pipe(Effect.withSpan("parent-span")); |
| 552 | 552 | |
| 553 | yield* program.pipe(Effect.provide(makeTestLayer(tracePath))); | |
| 553 | yield* program.pipe(Effect.provide(layerTest(tracePath))); | |
| 554 | 554 | }), |
| 555 | 555 | ); |
| 556 | 556 | |
| ... | ... | @@ -597,7 +597,7 @@ describe("observability", () => { |
| 597 | 597 | Effect.exit( |
| 598 | 598 | Effect.interrupt.pipe( |
| 599 | 599 | Effect.withSpan("interrupt-span"), |
| 600 | Effect.provide(makeTestLayer(tracePath)), | |
| 600 | Effect.provide(layerTest(tracePath)), | |
| 601 | 601 | ), |
| 602 | 602 | ), |
| 603 | 603 | ); |
packages/shared/src/observability.ts+1-1| ... | ... | @@ -13,7 +13,7 @@ import { RotatingFileSink } from "./logging.ts"; |
| 13 | 13 | |
| 14 | 14 | export const OtlpProtocol = Schema.Literals(["http/json", "http/protobuf"]); |
| 15 | 15 | export type OtlpProtocol = typeof OtlpProtocol.Type; |
| 16 | export const otlpSerializationLayer = (protocol: OtlpProtocol) => | |
| 16 | export const layerOtlpSerialization = (protocol: OtlpProtocol) => | |
| 17 | 17 | protocol === "http/protobuf" ? OtlpSerialization.layerProtobuf : OtlpSerialization.layerJson; |
| 18 | 18 | |
| 19 | 19 | /** |
packages/shared/src/otelEnvironment.test.ts+6-3| ... | ... | @@ -488,13 +488,16 @@ describe("OtelEnvironment", () => { |
| 488 | 488 | { name: "encoded separators", raw: "a%2Cb=x%3Dy", attributes: ["a,b"] }, |
| 489 | 489 | ])("lets the exporters' own read succeed with $name", ({ raw, attributes }) => |
| 490 | 490 | Effect.gen(function* () { |
| 491 | const env = ConfigProvider.layer( | |
| 491 | const layerEnv = ConfigProvider.layer( | |
| 492 | 492 | ConfigProvider.fromEnv({ env: { OTEL_RESOURCE_ATTRIBUTES: raw } }), |
| 493 | 493 | ); |
| 494 | const otel = yield* OtelEnvironment.load.pipe(Effect.provide(env)); | |
| 494 | const otel = yield* OtelEnvironment.load.pipe(Effect.provide(layerEnv)); | |
| 495 | 495 | const resource = yield* OtlpResource.fromConfig({ serviceName: "t3" }).pipe( |
| 496 | 496 | Effect.provide( |
| 497 | Layer.provide(OtelEnvironment.layerResourceAttributes(otel.resourceAttributes), env), | |
| 497 | Layer.provide( | |
| 498 | OtelEnvironment.layerResourceAttributes(otel.resourceAttributes), | |
| 499 | layerEnv, | |
| 500 | ), | |
| 498 | 501 | ), |
| 499 | 502 | ); |
| 500 | 503 | assert.deepStrictEqual( |
packages/shared/src/relayClient.test.ts+18-18| ... | ... | @@ -18,7 +18,7 @@ import * as RelayClient from "./relayClient.ts"; |
| 18 | 18 | // POSIX exec bits that NTFS never reports; the win32 branch skips that check. |
| 19 | 19 | const windowsHost = HostProcessPlatform.defaultValue() === "win32"; |
| 20 | 20 | |
| 21 | const hostRuntimeLayer = (env: Record<string, string> = {}) => | |
| 21 | const layerHostRuntime = (env: Record<string, string> = {}) => | |
| 22 | 22 | Layer.mergeAll( |
| 23 | 23 | Layer.succeed(HostProcessPlatform, "linux"), |
| 24 | 24 | Layer.succeed(HostProcessArchitecture, "x64"), |
| ... | ... | @@ -41,7 +41,7 @@ function makeHandle(exitCode = 0) { |
| 41 | 41 | }); |
| 42 | 42 | } |
| 43 | 43 | |
| 44 | const makeHttpClientLayer = (bytes: Uint8Array) => | |
| 44 | const layerHttpClient = (bytes: Uint8Array) => | |
| 45 | 45 | Layer.succeed( |
| 46 | 46 | HttpClient.HttpClient, |
| 47 | 47 | HttpClient.make((request) => |
| ... | ... | @@ -51,7 +51,7 @@ const makeHttpClientLayer = (bytes: Uint8Array) => |
| 51 | 51 | ), |
| 52 | 52 | ); |
| 53 | 53 | |
| 54 | const makeSpawnerLayer = (commands: Array<string>) => | |
| 54 | const layerSpawner = (commands: Array<string>) => | |
| 55 | 55 | Layer.succeed( |
| 56 | 56 | ChildProcessSpawner.ChildProcessSpawner, |
| 57 | 57 | ChildProcessSpawner.make((command) => |
| ... | ... | @@ -101,9 +101,9 @@ describe("RelayClient", () => { |
| 101 | 101 | Effect.provide( |
| 102 | 102 | Layer.mergeAll( |
| 103 | 103 | NodeServices.layer, |
| 104 | makeHttpClientLayer(new Uint8Array()), | |
| 105 | makeSpawnerLayer([]), | |
| 106 | hostRuntimeLayer(), | |
| 104 | layerHttpClient(new Uint8Array()), | |
| 105 | layerSpawner([]), | |
| 106 | layerHostRuntime(), | |
| 107 | 107 | ), |
| 108 | 108 | ), |
| 109 | 109 | ), |
| ... | ... | @@ -160,9 +160,9 @@ describe("RelayClient", () => { |
| 160 | 160 | Effect.provide( |
| 161 | 161 | Layer.mergeAll( |
| 162 | 162 | NodeServices.layer, |
| 163 | makeHttpClientLayer(new TextEncoder().encode("test-cloudflared-binary")), | |
| 164 | makeSpawnerLayer([]), | |
| 165 | hostRuntimeLayer(), | |
| 163 | layerHttpClient(new TextEncoder().encode("test-cloudflared-binary")), | |
| 164 | layerSpawner([]), | |
| 165 | layerHostRuntime(), | |
| 166 | 166 | ), |
| 167 | 167 | ), |
| 168 | 168 | ), |
| ... | ... | @@ -191,9 +191,9 @@ describe("RelayClient", () => { |
| 191 | 191 | Effect.provide( |
| 192 | 192 | Layer.mergeAll( |
| 193 | 193 | NodeServices.layer, |
| 194 | makeHttpClientLayer(new TextEncoder().encode("tampered")), | |
| 195 | makeSpawnerLayer([]), | |
| 196 | hostRuntimeLayer(), | |
| 194 | layerHttpClient(new TextEncoder().encode("tampered")), | |
| 195 | layerSpawner([]), | |
| 196 | layerHostRuntime(), | |
| 197 | 197 | ), |
| 198 | 198 | ), |
| 199 | 199 | ), |
| ... | ... | @@ -226,9 +226,9 @@ describe("RelayClient", () => { |
| 226 | 226 | Effect.provide( |
| 227 | 227 | Layer.mergeAll( |
| 228 | 228 | NodeServices.layer, |
| 229 | makeHttpClientLayer(bytes), | |
| 230 | makeSpawnerLayer(commands), | |
| 231 | hostRuntimeLayer(), | |
| 229 | layerHttpClient(bytes), | |
| 230 | layerSpawner(commands), | |
| 231 | layerHostRuntime(), | |
| 232 | 232 | ), |
| 233 | 233 | ), |
| 234 | 234 | ); |
| ... | ... | @@ -270,9 +270,9 @@ describe("RelayClient", () => { |
| 270 | 270 | Effect.provide( |
| 271 | 271 | Layer.mergeAll( |
| 272 | 272 | NodeServices.layer, |
| 273 | makeHttpClientLayer(new Uint8Array()), | |
| 274 | makeSpawnerLayer([]), | |
| 275 | hostRuntimeLayer(env), | |
| 273 | layerHttpClient(new Uint8Array()), | |
| 274 | layerSpawner([]), | |
| 275 | layerHostRuntime(env), | |
| 276 | 276 | ), |
| 277 | 277 | ), |
| 278 | 278 | ); |
packages/shared/src/relayTracing.test.ts+8-11| ... | ... | @@ -6,11 +6,8 @@ import * as Tracer from "effect/Tracer"; |
| 6 | 6 | import { FetchHttpClient } from "effect/http"; |
| 7 | 7 | import { vi } from "vite-plus/test"; |
| 8 | 8 | |
| 9 | import { | |
| 10 | makeRelayClientTracingLayer, | |
| 11 | RelayClientTracer, | |
| 12 | withRelayClientTracing, | |
| 13 | } from "./relayTracing.ts"; | |
| 9 | import { RelayClientTracer, withRelayClientTracing } from "./relayTracing.ts"; | |
| 10 | import * as RelayTracing from "./relayTracing.ts"; | |
| 14 | 11 | |
| 15 | 12 | function collectingTracer(spans: Array<string>): Tracer.Tracer { |
| 16 | 13 | return Tracer.make({ |
| ... | ... | @@ -64,10 +61,10 @@ describe("withRelayClientTracing", () => { |
| 64 | 61 | |
| 65 | 62 | it.effect("preserves nested error causes in exported relay spans", () => { |
| 66 | 63 | const fetchFn = vi.fn<typeof fetch>(async () => new Response(null, { status: 202 })); |
| 67 | const httpClientLayer = FetchHttpClient.layer.pipe( | |
| 64 | const layerHttpClient = FetchHttpClient.layer.pipe( | |
| 68 | 65 | Layer.provide(Layer.succeed(FetchHttpClient.Fetch, fetchFn)), |
| 69 | 66 | ); |
| 70 | const tracingLayer = makeRelayClientTracingLayer( | |
| 67 | const layerTracing = RelayTracing.layer( | |
| 71 | 68 | { |
| 72 | 69 | tracesUrl: "https://api.axiom.test/v1/traces", |
| 73 | 70 | tracesDataset: "relay-traces", |
| ... | ... | @@ -78,18 +75,18 @@ describe("withRelayClientTracing", () => { |
| 78 | 75 | runtime: "test", |
| 79 | 76 | client: "test", |
| 80 | 77 | }, |
| 81 | ).pipe(Layer.provide(httpClientLayer)); | |
| 78 | ).pipe(Layer.provide(layerHttpClient)); | |
| 82 | 79 | const rootCause = new Error("relay socket closed"); |
| 83 | 80 | const failure = new Error("relay request failed", { cause: rootCause }); |
| 84 | const tracedApplication = Layer.effectDiscard( | |
| 81 | const layerTracedApplication = Layer.effectDiscard( | |
| 85 | 82 | Effect.fail(failure).pipe( |
| 86 | 83 | Effect.withSpan("relay.failed-operation"), |
| 87 | 84 | withRelayClientTracing, |
| 88 | 85 | Effect.exit, |
| 89 | 86 | ), |
| 90 | ).pipe(Layer.provide(tracingLayer)); | |
| 87 | ).pipe(Layer.provide(layerTracing)); | |
| 91 | 88 | |
| 92 | return Layer.build(tracedApplication).pipe( | |
| 89 | return Layer.build(layerTracedApplication).pipe( | |
| 93 | 90 | Effect.scoped, |
| 94 | 91 | Effect.andThen( |
| 95 | 92 | Effect.sync(() => { |
packages/shared/src/relayTracing.ts+3-3| ... | ... | @@ -124,7 +124,7 @@ function nonInterferingTracer(delegate: Tracer.Tracer): Tracer.Tracer { |
| 124 | 124 | }); |
| 125 | 125 | } |
| 126 | 126 | |
| 127 | export function makeRelayClientTracingLayer( | |
| 127 | export function layer( | |
| 128 | 128 | config: RelayClientTracingConfig | null, |
| 129 | 129 | resource: RelayClientTracingResource, |
| 130 | 130 | ): Layer.Layer<never, never, HttpClient.HttpClient> { |
| ... | ... | @@ -132,7 +132,7 @@ export function makeRelayClientTracingLayer( |
| 132 | 132 | return Layer.succeed(RelayClientTracer, Option.none()); |
| 133 | 133 | } |
| 134 | 134 | |
| 135 | const tracerLayer = OtlpTracer.layer({ | |
| 135 | const layerTracer = OtlpTracer.layer({ | |
| 136 | 136 | url: config.tracesUrl, |
| 137 | 137 | headers: { |
| 138 | 138 | Authorization: `Bearer ${config.tracesToken}`, |
| ... | ... | @@ -153,5 +153,5 @@ export function makeRelayClientTracingLayer( |
| 153 | 153 | return Layer.effect( |
| 154 | 154 | RelayClientTracer, |
| 155 | 155 | Tracer.Tracer.pipe(Effect.map(nonInterferingTracer), Effect.asSome), |
| 156 | ).pipe(Layer.provide(tracerLayer)); | |
| 156 | ).pipe(Layer.provide(layerTracer)); | |
| 157 | 157 | } |
packages/ssh/src/command.test.ts+9-9| ... | ... | @@ -113,8 +113,8 @@ describe("ssh command", () => { |
| 113 | 113 | const spawner = ChildProcessSpawner.make(() => |
| 114 | 114 | Effect.succeed(makeFailedProcess({ stdout: "Pairing token creation failed\n" })), |
| 115 | 115 | ); |
| 116 | const spawnerLayer = Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner); | |
| 117 | const processLayer = Layer.mergeAll(NodeServices.layer, spawnerLayer); | |
| 116 | const layerSpawner = Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner); | |
| 117 | const layerProcess = Layer.mergeAll(NodeServices.layer, layerSpawner); | |
| 118 | 118 | |
| 119 | 119 | return Effect.gen(function* () { |
| 120 | 120 | const result = yield* Effect.result( |
| ... | ... | @@ -136,15 +136,15 @@ describe("ssh command", () => { |
| 136 | 136 | assert.equal(result.failure.stdout, "Pairing token creation failed\n"); |
| 137 | 137 | assert.equal(result.failure.stderr, ""); |
| 138 | 138 | } |
| 139 | }).pipe(Effect.provide(processLayer)); | |
| 139 | }).pipe(Effect.provide(layerProcess)); | |
| 140 | 140 | }); |
| 141 | 141 | |
| 142 | 142 | it.effect("redacts credentials from stdout in non-zero command failures", () => { |
| 143 | 143 | const spawner = ChildProcessSpawner.make(() => |
| 144 | 144 | Effect.succeed(makeFailedProcess({ stdout: '{"credential":"pairing-secret"}\n' })), |
| 145 | 145 | ); |
| 146 | const spawnerLayer = Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner); | |
| 147 | const processLayer = Layer.mergeAll(NodeServices.layer, spawnerLayer); | |
| 146 | const layerSpawner = Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner); | |
| 147 | const layerProcess = Layer.mergeAll(NodeServices.layer, layerSpawner); | |
| 148 | 148 | |
| 149 | 149 | return Effect.gen(function* () { |
| 150 | 150 | const result = yield* Effect.result( |
| ... | ... | @@ -165,13 +165,13 @@ describe("ssh command", () => { |
| 165 | 165 | assert.equal(result.failure.message, '{"credential":"[redacted]"}'); |
| 166 | 166 | assert.equal(result.failure.stdout, '{"credential":"[redacted]"}\n'); |
| 167 | 167 | } |
| 168 | }).pipe(Effect.provide(processLayer)); | |
| 168 | }).pipe(Effect.provide(layerProcess)); | |
| 169 | 169 | }); |
| 170 | 170 | |
| 171 | 171 | it.effect("fails commands that never finish", () => { |
| 172 | 172 | const spawner = ChildProcessSpawner.make(() => Effect.succeed(makeNeverFinishingProcess())); |
| 173 | const spawnerLayer = Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner); | |
| 174 | const processLayer = Layer.mergeAll(NodeServices.layer, spawnerLayer, TestClock.layer()); | |
| 173 | const layerSpawner = Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner); | |
| 174 | const layerProcess = Layer.mergeAll(NodeServices.layer, layerSpawner, TestClock.layer()); | |
| 175 | 175 | |
| 176 | 176 | return Effect.gen(function* () { |
| 177 | 177 | const fiber = yield* Effect.forkChild( |
| ... | ... | @@ -196,6 +196,6 @@ describe("ssh command", () => { |
| 196 | 196 | if (Result.isFailure(result)) { |
| 197 | 197 | assert.include(result.failure.message, "SSH command timed out after 1ms."); |
| 198 | 198 | } |
| 199 | }).pipe(Effect.provide(processLayer)); | |
| 199 | }).pipe(Effect.provide(layerProcess)); | |
| 200 | 200 | }); |
| 201 | 201 | }); |
packages/ssh/src/tunnel.test.ts+15-15| ... | ... | @@ -325,14 +325,14 @@ describe("ssh tunnel scripts", () => { |
| 325 | 325 | return makeSuccessfulProcess('loaded nvm default\n{"remotePort":3774}\n'); |
| 326 | 326 | }), |
| 327 | 327 | ); |
| 328 | const spawnerLayer = Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner); | |
| 329 | const processLayer = Layer.merge(NodeServices.layer, spawnerLayer); | |
| 328 | const layerSpawner = Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner); | |
| 329 | const layerProcess = Layer.merge(NodeServices.layer, layerSpawner); | |
| 330 | 330 | |
| 331 | 331 | return Effect.gen(function* () { |
| 332 | 332 | const result = yield* SshTunnel.launchOrReuseRemoteServer(target, undefined, ARCHIVE); |
| 333 | 333 | assert.equal(result.remotePort, 3774); |
| 334 | 334 | assert.deepEqual(spawnedCommands[0]?.slice(-5, -1), ["sh", "-l", "-s", "--"]); |
| 335 | }).pipe(Effect.provide(processLayer)); | |
| 335 | }).pipe(Effect.provide(layerProcess)); | |
| 336 | 336 | }); |
| 337 | 337 | |
| 338 | 338 | it.effect("allows cold remote launches to exceed the default SSH command timeout", () => { |
| ... | ... | @@ -345,8 +345,8 @@ describe("ssh tunnel scripts", () => { |
| 345 | 345 | const spawner = ChildProcessSpawner.make(() => |
| 346 | 346 | Effect.succeed(makeDelayedSuccessfulProcess('{"remotePort":3774}\n', 75_000)), |
| 347 | 347 | ); |
| 348 | const spawnerLayer = Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner); | |
| 349 | const processLayer = Layer.mergeAll(NodeServices.layer, spawnerLayer, TestClock.layer()); | |
| 348 | const layerSpawner = Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner); | |
| 349 | const layerProcess = Layer.mergeAll(NodeServices.layer, layerSpawner, TestClock.layer()); | |
| 350 | 350 | |
| 351 | 351 | return Effect.gen(function* () { |
| 352 | 352 | const fiber = yield* Effect.forkChild( |
| ... | ... | @@ -357,7 +357,7 @@ describe("ssh tunnel scripts", () => { |
| 357 | 357 | |
| 358 | 358 | const result = yield* Fiber.join(fiber); |
| 359 | 359 | assert.equal(result.remotePort, 3774); |
| 360 | }).pipe(Effect.provide(processLayer)); | |
| 360 | }).pipe(Effect.provide(layerProcess)); | |
| 361 | 361 | }); |
| 362 | 362 | |
| 363 | 363 | it.effect("gives cold archive launches a larger budget than node-script launches", () => { |
| ... | ... | @@ -370,8 +370,8 @@ describe("ssh tunnel scripts", () => { |
| 370 | 370 | const spawner = ChildProcessSpawner.make(() => |
| 371 | 371 | Effect.succeed(makeDelayedSuccessfulProcess('{"remotePort":3774}\n', 800_000)), |
| 372 | 372 | ); |
| 373 | const spawnerLayer = Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner); | |
| 374 | const processLayer = Layer.mergeAll(NodeServices.layer, spawnerLayer, TestClock.layer()); | |
| 373 | const layerSpawner = Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner); | |
| 374 | const layerProcess = Layer.mergeAll(NodeServices.layer, layerSpawner, TestClock.layer()); | |
| 375 | 375 | |
| 376 | 376 | return Effect.gen(function* () { |
| 377 | 377 | const fiber = yield* Effect.forkChild( |
| ... | ... | @@ -382,7 +382,7 @@ describe("ssh tunnel scripts", () => { |
| 382 | 382 | |
| 383 | 383 | const result = yield* Fiber.join(fiber); |
| 384 | 384 | assert.equal(result.remotePort, 3774); |
| 385 | }).pipe(Effect.provide(processLayer)); | |
| 385 | }).pipe(Effect.provide(layerProcess)); | |
| 386 | 386 | }); |
| 387 | 387 | |
| 388 | 388 | it("allows the remote port picker to run without a state file path", () => { |
| ... | ... | @@ -453,12 +453,12 @@ describe("ssh tunnel scripts", () => { |
| 453 | 453 | `), |
| 454 | 454 | ), |
| 455 | 455 | ); |
| 456 | const spawnerLayer = Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner); | |
| 457 | const processLayer = Layer.merge(NodeServices.layer, spawnerLayer); | |
| 456 | const layerSpawner = Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner); | |
| 457 | const layerProcess = Layer.merge(NodeServices.layer, layerSpawner); | |
| 458 | 458 | return Effect.gen(function* () { |
| 459 | 459 | const result = yield* SshTunnel.issueRemotePairingToken(target, undefined, ARCHIVE); |
| 460 | 460 | assert.equal(result.credential, "LCL4R2TPHDKQ"); |
| 461 | }).pipe(Effect.provide(processLayer)); | |
| 461 | }).pipe(Effect.provide(layerProcess)); | |
| 462 | 462 | }); |
| 463 | 463 | |
| 464 | 464 | it.effect("accepts pretty-printed pairing JSON after remote shell startup noise", () => { |
| ... | ... | @@ -481,12 +481,12 @@ describe("ssh tunnel scripts", () => { |
| 481 | 481 | `), |
| 482 | 482 | ), |
| 483 | 483 | ); |
| 484 | const spawnerLayer = Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner); | |
| 485 | const processLayer = Layer.merge(NodeServices.layer, spawnerLayer); | |
| 484 | const layerSpawner = Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner); | |
| 485 | const layerProcess = Layer.merge(NodeServices.layer, layerSpawner); | |
| 486 | 486 | return Effect.gen(function* () { |
| 487 | 487 | const result = yield* SshTunnel.issueRemotePairingToken(target, undefined, ARCHIVE); |
| 488 | 488 | assert.equal(result.credential, "LCL4R2TPHDKQ"); |
| 489 | }).pipe(Effect.provide(processLayer)); | |
| 489 | }).pipe(Effect.provide(layerProcess)); | |
| 490 | 490 | }); |
| 491 | 491 | |
| 492 | 492 | it.effect.each(["successful stop", "failed stop"] as const)( |
packages/tailscale/src/tailscale.test.ts+12-12| ... | ... | @@ -101,20 +101,20 @@ function neverFinishingMockHandle() { |
| 101 | 101 | |
| 102 | 102 | // The executable name depends on the host platform (`tailscale.exe` on |
| 103 | 103 | // Windows), so pin it: these tests assert the posix spelling. |
| 104 | function spawnerLayer(spawner: ChildProcessSpawner.ChildProcessSpawner["Service"]) { | |
| 104 | function layerSpawner(spawner: ChildProcessSpawner.ChildProcessSpawner["Service"]) { | |
| 105 | 105 | return Layer.merge( |
| 106 | 106 | Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner), |
| 107 | 107 | Layer.succeed(HostProcessPlatform, "linux"), |
| 108 | 108 | ); |
| 109 | 109 | } |
| 110 | 110 | |
| 111 | function mockSpawnerLayer( | |
| 111 | function layerMockSpawner( | |
| 112 | 112 | handler: ( |
| 113 | 113 | command: string, |
| 114 | 114 | args: ReadonlyArray<string>, |
| 115 | 115 | ) => { stdout?: string; stderr?: string; code?: number }, |
| 116 | 116 | ) { |
| 117 | return spawnerLayer( | |
| 117 | return layerSpawner( | |
| 118 | 118 | ChildProcessSpawner.make((command) => { |
| 119 | 119 | const childProcess = command as unknown as { |
| 120 | 120 | readonly command: string; |
| ... | ... | @@ -178,7 +178,7 @@ describe("tailscale", () => { |
| 178 | 178 | ); |
| 179 | 179 | |
| 180 | 180 | it.effect("reads tailscale status through the process spawner service", () => { |
| 181 | const layer = mockSpawnerLayer((command, args) => { | |
| 181 | const layer = layerMockSpawner((command, args) => { | |
| 182 | 182 | assert.equal(command, "tailscale"); |
| 183 | 183 | assert.deepEqual(args, ["status", "--json"]); |
| 184 | 184 | return { |
| ... | ... | @@ -203,7 +203,7 @@ describe("tailscale", () => { |
| 203 | 203 | method: "spawn", |
| 204 | 204 | cause: systemCause, |
| 205 | 205 | }); |
| 206 | const layer = spawnerLayer(ChildProcessSpawner.make(() => Effect.fail(cause))); | |
| 206 | const layer = layerSpawner(ChildProcessSpawner.make(() => Effect.fail(cause))); | |
| 207 | 207 | |
| 208 | 208 | return Effect.gen(function* () { |
| 209 | 209 | const error = yield* readTailscaleStatus.pipe(Effect.flip, Effect.provide(layer)); |
| ... | ... | @@ -224,7 +224,7 @@ describe("tailscale", () => { |
| 224 | 224 | // inside an `Effect.callback` registration, so that throw arrives as a |
| 225 | 225 | // defect rather than a typed error - the shape reproduced here. |
| 226 | 226 | const defect = Object.assign(new Error("spawn tailscale ENOTDIR"), { code: "ENOTDIR" }); |
| 227 | const layer = spawnerLayer( | |
| 227 | const layer = layerSpawner( | |
| 228 | 228 | ChildProcessSpawner.make(() => |
| 229 | 229 | Effect.callback<never, never>(() => { |
| 230 | 230 | throw defect; |
| ... | ... | @@ -257,7 +257,7 @@ describe("tailscale", () => { |
| 257 | 257 | }); |
| 258 | 258 | |
| 259 | 259 | it.effect("keeps nonzero exit diagnostics structured", () => { |
| 260 | const layer = mockSpawnerLayer(() => ({ | |
| 260 | const layer = layerMockSpawner(() => ({ | |
| 261 | 261 | code: 7, |
| 262 | 262 | stderr: "not logged in tskey-auth-secret-token-value", |
| 263 | 263 | })); |
| ... | ... | @@ -282,7 +282,7 @@ describe("tailscale", () => { |
| 282 | 282 | }); |
| 283 | 283 | |
| 284 | 284 | it.effect("classifies unrecognized stderr without quoting it", () => { |
| 285 | const layer = mockSpawnerLayer(() => ({ | |
| 285 | const layer = layerMockSpawner(() => ({ | |
| 286 | 286 | code: 3, |
| 287 | 287 | stderr: "something novel went wrong for node fluffy-badger tskey-auth-secret-token-value", |
| 288 | 288 | })); |
| ... | ... | @@ -302,7 +302,7 @@ describe("tailscale", () => { |
| 302 | 302 | it.effect("times out tailscale status through TestClock", () => { |
| 303 | 303 | const layer = Layer.merge( |
| 304 | 304 | TestClock.layer(), |
| 305 | spawnerLayer(ChildProcessSpawner.make(() => Effect.succeed(neverFinishingMockHandle()))), | |
| 305 | layerSpawner(ChildProcessSpawner.make(() => Effect.succeed(neverFinishingMockHandle()))), | |
| 306 | 306 | ); |
| 307 | 307 | |
| 308 | 308 | return Effect.gen(function* () { |
| ... | ... | @@ -322,7 +322,7 @@ describe("tailscale", () => { |
| 322 | 322 | }); |
| 323 | 323 | |
| 324 | 324 | it.effect("configures tailscale serve through the process spawner service", () => { |
| 325 | const layer = mockSpawnerLayer((command, args) => { | |
| 325 | const layer = layerMockSpawner((command, args) => { | |
| 326 | 326 | assert.equal(command, "tailscale"); |
| 327 | 327 | assert.deepEqual(args, ["serve", "--bg", "--https=8443", "http://127.0.0.1:13773"]); |
| 328 | 328 | return {}; |
| ... | ... | @@ -332,7 +332,7 @@ describe("tailscale", () => { |
| 332 | 332 | }); |
| 333 | 333 | |
| 334 | 334 | it.effect("retains tailscale serve exit diagnostics", () => { |
| 335 | const layer = mockSpawnerLayer(() => ({ | |
| 335 | const layer = layerMockSpawner(() => ({ | |
| 336 | 336 | code: 1, |
| 337 | 337 | stderr: "serve permission denied tskey-auth-secret-token-value", |
| 338 | 338 | })); |
| ... | ... | @@ -364,7 +364,7 @@ describe("tailscale", () => { |
| 364 | 364 | readonly command: string; |
| 365 | 365 | readonly args: ReadonlyArray<string>; |
| 366 | 366 | }[] = []; |
| 367 | const layer = mockSpawnerLayer((command, args) => { | |
| 367 | const layer = layerMockSpawner((command, args) => { | |
| 368 | 368 | commands.push({ command, args }); |
| 369 | 369 | assert.equal(command, "tailscale"); |
| 370 | 370 | assert.deepEqual(args, ["serve", "--https=8443", "off"]); |