From 72cd2f5fa7afa08345f4e69390a94db266919e59 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sat, 30 May 2026 19:59:56 -0400 Subject: [PATCH] fix(core): isolate location layer instances --- packages/core/src/catalog.ts | 6 ++- packages/core/src/config.ts | 1 + packages/core/src/location-layer.ts | 12 +++--- packages/core/test/location-layer.test.ts | 50 +++++++++++++++++++++++ 4 files changed, 61 insertions(+), 8 deletions(-) create mode 100644 packages/core/test/location-layer.test.ts diff --git a/packages/core/src/catalog.ts b/packages/core/src/catalog.ts index a12de5d476..22edc13402 100644 --- a/packages/core/src/catalog.ts +++ b/packages/core/src/catalog.ts @@ -317,4 +317,8 @@ export const layer = Layer.effect( const SMALL_MODEL_RE = /\b(nano|flash|lite|mini|haiku|small|fast)\b/ -export const defaultLayer = layer.pipe(Layer.provide(EventV2.defaultLayer), Layer.provide(PluginV2.defaultLayer)) +export const defaultLayer = layer.pipe( + Layer.provide(EventV2.defaultLayer), + Layer.provide(PluginV2.defaultLayer), + Layer.provide(Policy.defaultLayer), +) diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index 88ff2ef91e..25d9f21111 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -199,4 +199,5 @@ export const layer = Layer.effect( export const defaultLayer = layer.pipe( Layer.provide(AppFileSystem.defaultLayer), Layer.provide(Global.defaultLayer), + Layer.provide(Policy.defaultLayer), ) diff --git a/packages/core/src/location-layer.ts b/packages/core/src/location-layer.ts index 7e9c3f1237..23655faef5 100644 --- a/packages/core/src/location-layer.ts +++ b/packages/core/src/location-layer.ts @@ -2,18 +2,16 @@ import { Layer, LayerMap } from "effect" import { Location } from "./location" import { Catalog } from "./catalog" import { PluginBoot } from "./plugin/boot" -import { Policy } from "./policy" import { Config } from "./config" import { AgentV2 } from "./agent" export class LocationServiceMap extends LayerMap.Service()("@opencode/example/LocationServiceMap", { lookup: (ref: Location.Ref) => { - const result = Layer.mergeAll( - Catalog.defaultLayer, - PluginBoot.defaultLayer, - Config.defaultLayer, - AgentV2.defaultLayer, - ).pipe(Layer.provideMerge(Policy.defaultLayer), Layer.provideMerge(Location.defaultLayer(ref))) + const result = Layer.fresh( + Layer.mergeAll(Catalog.defaultLayer, PluginBoot.defaultLayer, Config.defaultLayer, AgentV2.defaultLayer).pipe( + Layer.provideMerge(Location.defaultLayer(ref)), + ), + ) return result }, idleTimeToLive: "60 minutes", diff --git a/packages/core/test/location-layer.test.ts b/packages/core/test/location-layer.test.ts new file mode 100644 index 0000000000..b4efc07459 --- /dev/null +++ b/packages/core/test/location-layer.test.ts @@ -0,0 +1,50 @@ +import fs from "fs/promises" +import path from "path" +import { describe, expect } from "bun:test" +import { Effect } from "effect" +import { Catalog } from "@opencode-ai/core/catalog" +import { LocationServiceMap } from "@opencode-ai/core/location-layer" +import { PluginBoot } from "@opencode-ai/core/plugin/boot" +import { ProviderV2 } from "@opencode-ai/core/provider" +import { AbsolutePath } from "@opencode-ai/core/schema" +import { tmpdir } from "./fixture/tmpdir" +import { testEffect } from "./lib/effect" + +const it = testEffect(LocationServiceMap.layer) + +describe("LocationServiceMap", () => { + it.live("isolates location state while sharing location policy with catalog", () => + Effect.acquireRelease( + Effect.promise(() => Promise.all([tmpdir(), tmpdir()])), + (dirs) => Effect.promise(() => Promise.all(dirs.map((dir) => dir[Symbol.asyncDispose]())).then(() => undefined)), + ).pipe( + Effect.flatMap(([blocked, allowed]) => + Effect.gen(function* () { + yield* Effect.promise(() => + fs.writeFile( + path.join(blocked.path, "opencode.json"), + JSON.stringify({ + experimental: { policies: [{ effect: "deny", action: "provider.use", resource: "test" }] }, + }), + ), + ) + + const update = (directory: string) => + Effect.gen(function* () { + yield* PluginBoot.Service.use((boot) => boot.wait()) + const catalog = yield* Catalog.Service + const transform = yield* catalog.transform() + yield* transform((editor) => editor.provider.update(ProviderV2.ID.make("test"), () => {})) + return yield* catalog.provider.all() + }).pipe( + Effect.scoped, + Effect.provide(LocationServiceMap.get({ directory: AbsolutePath.make(directory) })), + ) + + expect((yield* update(blocked.path)).some((provider) => provider.id === ProviderV2.ID.make("test"))).toBe(false) + expect((yield* update(allowed.path)).some((provider) => provider.id === ProviderV2.ID.make("test"))).toBe(true) + }), + ), + ), + ) +})