Parcourir la source

fix(core): isolate location layer instances

Dax Raad il y a 2 mois
Parent
commit
0269d6f5de

+ 5 - 1
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),
+)

+ 5 - 1
packages/core/src/config.ts

@@ -200,4 +200,8 @@ export const layer = Layer.effect(
   }),
 )
 
-export const defaultLayer = layer.pipe(Layer.provide(AppFileSystem.defaultLayer), Layer.provide(Global.defaultLayer))
+export const defaultLayer = layer.pipe(
+  Layer.provide(AppFileSystem.defaultLayer),
+  Layer.provide(Global.defaultLayer),
+  Layer.provide(Policy.defaultLayer),
+)

+ 5 - 7
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<LocationServiceMap>()("@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",

+ 50 - 0
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)
+        }),
+      ),
+    ),
+  )
+})