Просмотр исходного кода

refactor(flags): route scout through runtime flags (#27318)

Shoubhit Dash 3 месяцев назад
Родитель
Сommit
4d205027ca

+ 4 - 2
packages/opencode/src/agent/agent.ts

@@ -15,12 +15,12 @@ import PROMPT_TITLE from "./prompt/title.txt"
 import { Permission } from "@/permission"
 import { mergeDeep, pipe, sortBy, values } from "remeda"
 import { Global } from "@opencode-ai/core/global"
-import { Flag } from "@opencode-ai/core/flag/flag"
 import path from "path"
 import { Plugin } from "@/plugin"
 import { Skill } from "../skill"
 import { Effect, Context, Layer, Schema } from "effect"
 import { InstanceState } from "@/effect/instance-state"
+import { RuntimeFlags } from "@/effect/runtime-flags"
 import * as Option from "effect/Option"
 import * as OtelTracer from "@effect/opentelemetry/Tracer"
 import { type DeepMutable } from "@opencode-ai/core/schema"
@@ -81,6 +81,7 @@ export const layer = Layer.effect(
     const plugin = yield* Plugin.Service
     const skill = yield* Skill.Service
     const provider = yield* Provider.Service
+    const flags = yield* RuntimeFlags.Service
 
     const state = yield* InstanceState.make<State>(
       Effect.fn("Agent.state")(function* (ctx) {
@@ -195,7 +196,7 @@ export const layer = Layer.effect(
             mode: "subagent",
             native: true,
           },
-          ...(Flag.OPENCODE_EXPERIMENTAL_SCOUT
+          ...(flags.experimentalScout
             ? {
                 scout: {
                   name: "scout",
@@ -453,6 +454,7 @@ export const defaultLayer = layer.pipe(
   Layer.provide(Auth.defaultLayer),
   Layer.provide(Config.defaultLayer),
   Layer.provide(Skill.defaultLayer),
+  Layer.provide(RuntimeFlags.defaultLayer),
 )
 
 export * as Agent from "./agent"

+ 7 - 5
packages/opencode/src/reference/reference.ts

@@ -1,10 +1,10 @@
 import path from "path"
 import { Effect, Context, Layer, Scope } from "effect"
 import { AppFileSystem } from "@opencode-ai/core/filesystem"
-import { Flag } from "@opencode-ai/core/flag/flag"
 import { Global } from "@opencode-ai/core/global"
 import { Config } from "@/config/config"
 import { InstanceState } from "@/effect/instance-state"
+import { RuntimeFlags } from "@/effect/runtime-flags"
 import { Git } from "@/git"
 import { parseRepositoryReference, repositoryCachePath, type Reference as RepositoryReference } from "@/util/repository"
 import { RepositoryCache } from "./repository-cache"
@@ -143,6 +143,7 @@ export const layer = Layer.effect(
     const fs = yield* AppFileSystem.Service
     const git = yield* Git.Service
     const scope = yield* Scope.Scope
+    const flags = yield* RuntimeFlags.Service
 
     const state = yield* InstanceState.make<State>(
       Effect.fn("Reference.state")(function* (ctx) {
@@ -181,7 +182,7 @@ export const layer = Layer.effect(
         )
 
         const materializeAll = yield* Effect.cached(
-          Flag.OPENCODE_EXPERIMENTAL_SCOUT
+          flags.experimentalScout
             ? Effect.gen(function* () {
                 yield* Effect.forEach(
                   materializeByPath,
@@ -200,7 +201,7 @@ export const layer = Layer.effect(
 
     return Service.of({
       init: Effect.fn("Reference.init")(function* () {
-        if (!Flag.OPENCODE_EXPERIMENTAL_SCOUT) return
+        if (!flags.experimentalScout) return
         yield* InstanceState.useEffect(state, (s) => s.materializeAll).pipe(Effect.forkIn(scope), Effect.asVoid)
       }),
       list: Effect.fn("Reference.list")(function* () {
@@ -210,7 +211,7 @@ export const layer = Layer.effect(
         return yield* InstanceState.use(state, (s) => s.references.find((reference) => reference.name === name))
       }),
       ensure: Effect.fn("Reference.ensure")(function* (target?: string) {
-        if (!Flag.OPENCODE_EXPERIMENTAL_SCOUT) return
+        if (!flags.experimentalScout) return
         const full = normalizedTarget(target)
         if (!full) return yield* InstanceState.useEffect(state, (s) => s.materializeAll)
         return yield* InstanceState.useEffect(
@@ -219,7 +220,7 @@ export const layer = Layer.effect(
         )
       }),
       contains: Effect.fn("Reference.contains")(function* (target?: string) {
-        if (!Flag.OPENCODE_EXPERIMENTAL_SCOUT) return false
+        if (!flags.experimentalScout) return false
         const full = normalizedTarget(target)
         if (!full) return false
         return yield* InstanceState.use(state, (s) =>
@@ -234,6 +235,7 @@ export const defaultLayer = layer.pipe(
   Layer.provide(Config.defaultLayer),
   Layer.provide(AppFileSystem.defaultLayer),
   Layer.provide(Git.defaultLayer),
+  Layer.provide(RuntimeFlags.defaultLayer),
 )
 
 export * as Reference from "./reference"

+ 57 - 65
packages/opencode/test/agent/agent.test.ts

@@ -1,15 +1,31 @@
 import { afterEach, expect } from "bun:test"
-import { Cause, Effect, Exit } from "effect"
+import { Cause, Effect, Exit, Layer } from "effect"
 import path from "path"
 import { disposeAllInstances, TestInstance } from "../fixture/fixture"
 import { testEffect } from "../lib/effect"
 import { Agent } from "../../src/agent/agent"
+import { Auth } from "../../src/auth"
+import { Config } from "../../src/config/config"
+import { RuntimeFlags } from "../../src/effect/runtime-flags"
 import { Global } from "@opencode-ai/core/global"
-import { Flag } from "@opencode-ai/core/flag/flag"
 import { Permission } from "../../src/permission"
+import { Plugin } from "../../src/plugin"
+import { Provider } from "../../src/provider/provider"
+import { Skill } from "../../src/skill"
 import { Truncate } from "../../src/tool/truncate"
 
-const it = testEffect(Agent.defaultLayer)
+const agentLayer = (flags: Partial<RuntimeFlags.Info> = {}) =>
+  Agent.layer.pipe(
+    Layer.provide(Plugin.defaultLayer),
+    Layer.provide(Provider.defaultLayer),
+    Layer.provide(Auth.defaultLayer),
+    Layer.provide(Config.defaultLayer),
+    Layer.provide(Skill.defaultLayer),
+    Layer.provide(RuntimeFlags.layer(flags)),
+  )
+
+const it = testEffect(agentLayer())
+const scout = testEffect(agentLayer({ experimentalScout: true }))
 
 // Helper to evaluate permission for a tool with wildcard pattern
 function evalPerm(agent: Agent.Info | undefined, permission: string): Permission.Action | undefined {
@@ -21,21 +37,6 @@ function load<A>(fn: (svc: Agent.Interface) => Effect.Effect<A>) {
   return Agent.Service.use(fn)
 }
 
-function withExperimentalScout<A, E, R>(enabled: boolean, self: Effect.Effect<A, E, R>) {
-  return Effect.acquireUseRelease(
-    Effect.sync(() => {
-      const original = Flag.OPENCODE_EXPERIMENTAL_SCOUT
-      Flag.OPENCODE_EXPERIMENTAL_SCOUT = enabled
-      return original
-    }),
-    () => self,
-    (original) =>
-      Effect.sync(() => {
-        Flag.OPENCODE_EXPERIMENTAL_SCOUT = original
-      }),
-  )
-}
-
 const expectDefaultAgentError = Effect.fn("AgentTest.expectDefaultAgentError")(function* (message: string) {
   const exit = yield* load((svc) => svc.defaultAgent()).pipe(Effect.exit)
   expect(Exit.isFailure(exit)).toBe(true)
@@ -47,21 +48,18 @@ afterEach(async () => {
 })
 
 it.instance("returns default native agents when no config", () =>
-  withExperimentalScout(
-    false,
-    Effect.gen(function* () {
-      const agents = yield* load((svc) => svc.list())
-      const names = agents.map((a) => a.name)
-      expect(names).toContain("build")
-      expect(names).toContain("plan")
-      expect(names).toContain("general")
-      expect(names).toContain("explore")
-      expect(names).not.toContain("scout")
-      expect(names).toContain("compaction")
-      expect(names).toContain("title")
-      expect(names).toContain("summary")
-    }),
-  ),
+  Effect.gen(function* () {
+    const agents = yield* load((svc) => svc.list())
+    const names = agents.map((a) => a.name)
+    expect(names).toContain("build")
+    expect(names).toContain("plan")
+    expect(names).toContain("general")
+    expect(names).toContain("explore")
+    expect(names).not.toContain("scout")
+    expect(names).toContain("compaction")
+    expect(names).toContain("title")
+    expect(names).toContain("summary")
+  }),
 )
 
 it.instance("build agent has correct default properties", () =>
@@ -111,42 +109,36 @@ it.instance("explore agent asks for external directories and allows whitelisted
   }),
 )
 
-it.instance("scout agent allows repo cloning and repo cache reads", () =>
-  withExperimentalScout(
-    true,
-    Effect.gen(function* () {
-      const scout = yield* load((svc) => svc.get("scout"))
-      expect(scout).toBeDefined()
-      expect(scout?.mode).toBe("subagent")
-      expect(evalPerm(scout, "repo_clone")).toBe("allow")
-      expect(evalPerm(scout, "repo_overview")).toBe("allow")
-      expect(evalPerm(scout, "edit")).toBe("deny")
-      expect(
-        Permission.evaluate(
-          "external_directory",
-          path.join(Global.Path.repos, "github.com", "owner", "repo", "README.md"),
-          scout!.permission,
-        ).action,
-      ).toBe("allow")
-    }),
-  ),
+scout.instance("scout agent allows repo cloning and repo cache reads", () =>
+  Effect.gen(function* () {
+    const scout = yield* load((svc) => svc.get("scout"))
+    expect(scout).toBeDefined()
+    expect(scout?.mode).toBe("subagent")
+    expect(evalPerm(scout, "repo_clone")).toBe("allow")
+    expect(evalPerm(scout, "repo_overview")).toBe("allow")
+    expect(evalPerm(scout, "edit")).toBe("deny")
+    expect(
+      Permission.evaluate(
+        "external_directory",
+        path.join(Global.Path.repos, "github.com", "owner", "repo", "README.md"),
+        scout!.permission,
+      ).action,
+    ).toBe("allow")
+  }),
 )
 
-it.instance(
+scout.instance(
   "reference config does not create subagents",
   () =>
-    withExperimentalScout(
-      true,
-      Effect.gen(function* () {
-        const agents = yield* load((svc) => svc.list())
-        const names = agents.map((agent) => agent.name)
-        expect(names).toContain("scout")
-        expect(names).not.toContain("effect")
-        expect(names).not.toContain("effectFull")
-        expect(names).not.toContain("localdocs")
-        expect(names).not.toContain("localdocsFull")
-      }),
-    ),
+    Effect.gen(function* () {
+      const agents = yield* load((svc) => svc.list())
+      const names = agents.map((agent) => agent.name)
+      expect(names).toContain("scout")
+      expect(names).not.toContain("effect")
+      expect(names).not.toContain("effectFull")
+      expect(names).not.toContain("localdocs")
+      expect(names).not.toContain("localdocsFull")
+    }),
   {
     config: {
       reference: {

+ 1 - 0
packages/opencode/test/agent/plugin-agent-regression.test.ts

@@ -41,6 +41,7 @@ const agentLayer = Agent.layer.pipe(
   Layer.provide(SkillTest.empty),
   Layer.provide(provider.layer),
   Layer.provide(pluginLayer),
+  Layer.provide(RuntimeFlags.layer({ disableDefaultPlugins: true })),
 )
 
 const it = testEffect(Layer.mergeAll(agentLayer, pluginLayer))

+ 119 - 120
packages/opencode/test/reference/reference.test.ts

@@ -3,8 +3,9 @@ import path from "path"
 import { Effect, Layer } from "effect"
 import { AppFileSystem } from "@opencode-ai/core/filesystem"
 import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
-import { Flag } from "@opencode-ai/core/flag/flag"
 import { Global } from "@opencode-ai/core/global"
+import { Config } from "../../src/config/config"
+import { RuntimeFlags } from "../../src/effect/runtime-flags"
 import { Git } from "../../src/git"
 import { Reference } from "../../src/reference/reference"
 import { disposeAllInstances, provideTmpdirInstance, tmpdirScoped } from "../fixture/fixture"
@@ -14,23 +15,25 @@ afterEach(async () => {
   await disposeAllInstances()
 })
 
+const referenceLayer = (flags: Partial<RuntimeFlags.Info> = {}) =>
+  Reference.layer.pipe(
+    Layer.provide(Config.defaultLayer),
+    Layer.provide(AppFileSystem.defaultLayer),
+    Layer.provide(Git.defaultLayer),
+    Layer.provide(RuntimeFlags.layer(flags)),
+  )
+
 const it = testEffect(
-  Layer.mergeAll(AppFileSystem.defaultLayer, CrossSpawnSpawner.defaultLayer, Git.defaultLayer, Reference.defaultLayer),
+  Layer.mergeAll(AppFileSystem.defaultLayer, CrossSpawnSpawner.defaultLayer, Git.defaultLayer, referenceLayer()),
+)
+const scout = testEffect(
+  Layer.mergeAll(
+    AppFileSystem.defaultLayer,
+    CrossSpawnSpawner.defaultLayer,
+    Git.defaultLayer,
+    referenceLayer({ experimentalScout: true }),
+  ),
 )
-
-const experimentalScout = <A, E, R>(self: Effect.Effect<A, E, R>) =>
-  Effect.acquireUseRelease(
-    Effect.sync(() => {
-      const previous = Flag.OPENCODE_EXPERIMENTAL_SCOUT
-      Flag.OPENCODE_EXPERIMENTAL_SCOUT = true
-      return previous
-    }),
-    () => self,
-    (previous) =>
-      Effect.sync(() => {
-        Flag.OPENCODE_EXPERIMENTAL_SCOUT = previous
-      }),
-  )
 
 const githubBase = <A, E, R>(url: string, self: Effect.Effect<A, E, R>) =>
   Effect.acquireUseRelease(
@@ -127,118 +130,114 @@ describe("reference", () => {
     }),
   )
 
-  it.live("materializes configured git references during init", () =>
-    experimentalScout(
-      provideTmpdirInstance(
-        (_dir) =>
-          Effect.gen(function* () {
-            const fs = yield* AppFileSystem.Service
-            const cache = path.join(Global.Path.repos, "github.com", "opencode-reference-test", "repo")
-            yield* fs.remove(cache, { recursive: true }).pipe(Effect.ignore)
-            yield* Effect.addFinalizer(() => fs.remove(cache, { recursive: true }).pipe(Effect.ignore))
-
-            const source = yield* tmpdirScoped({ git: true })
-            const remoteRoot = yield* tmpdirScoped()
-            const remoteDir = path.join(remoteRoot, "opencode-reference-test")
-            const remoteRepo = path.join(remoteDir, "repo.git")
-
-            yield* Effect.promise(() => Bun.write(path.join(source, "README.md"), "configured\n"))
-            yield* git(source, ["add", "."])
-            yield* git(source, ["commit", "-m", "add readme"])
-            yield* fs.makeDirectory(remoteDir, { recursive: true }).pipe(Effect.orDie)
-            yield* git(remoteRoot, ["clone", "--bare", source, remoteRepo])
-
-            const reference = yield* Reference.Service
-            yield* githubBase(
-              `file://${remoteRoot}/`,
-              Effect.gen(function* () {
-                yield* reference.init()
-                yield* waitForContent(fs, path.join(cache, "README.md"), "configured\n")
-              }),
-            )
-
-            expect(yield* fs.existsSafe(path.join(cache, ".git"))).toBe(true)
-            expect(yield* fs.readFileString(path.join(cache, "README.md"))).toBe("configured\n")
-
-            const resolved = yield* reference.get("docs")
-            expect(resolved?.kind).toBe("git")
-            if (resolved?.kind === "git") expect(resolved.path).toBe(cache)
-          }),
-        {
-          config: {
-            reference: {
-              docs: "opencode-reference-test/repo",
-            },
+  scout.live("materializes configured git references during init", () =>
+    provideTmpdirInstance(
+      (_dir) =>
+        Effect.gen(function* () {
+          const fs = yield* AppFileSystem.Service
+          const cache = path.join(Global.Path.repos, "github.com", "opencode-reference-test", "repo")
+          yield* fs.remove(cache, { recursive: true }).pipe(Effect.ignore)
+          yield* Effect.addFinalizer(() => fs.remove(cache, { recursive: true }).pipe(Effect.ignore))
+
+          const source = yield* tmpdirScoped({ git: true })
+          const remoteRoot = yield* tmpdirScoped()
+          const remoteDir = path.join(remoteRoot, "opencode-reference-test")
+          const remoteRepo = path.join(remoteDir, "repo.git")
+
+          yield* Effect.promise(() => Bun.write(path.join(source, "README.md"), "configured\n"))
+          yield* git(source, ["add", "."])
+          yield* git(source, ["commit", "-m", "add readme"])
+          yield* fs.makeDirectory(remoteDir, { recursive: true }).pipe(Effect.orDie)
+          yield* git(remoteRoot, ["clone", "--bare", source, remoteRepo])
+
+          const reference = yield* Reference.Service
+          yield* githubBase(
+            `file://${remoteRoot}/`,
+            Effect.gen(function* () {
+              yield* reference.init()
+              yield* waitForContent(fs, path.join(cache, "README.md"), "configured\n")
+            }),
+          )
+
+          expect(yield* fs.existsSafe(path.join(cache, ".git"))).toBe(true)
+          expect(yield* fs.readFileString(path.join(cache, "README.md"))).toBe("configured\n")
+
+          const resolved = yield* reference.get("docs")
+          expect(resolved?.kind).toBe("git")
+          if (resolved?.kind === "git") expect(resolved.path).toBe(cache)
+        }),
+      {
+        config: {
+          reference: {
+            docs: "opencode-reference-test/repo",
           },
         },
-      ),
+      },
     ),
   )
 
-  it.live("refreshes configured git references on new instance init", () =>
-    experimentalScout(
-      Effect.gen(function* () {
-        const fs = yield* AppFileSystem.Service
-        const cache = path.join(Global.Path.repos, "github.com", "opencode-reference-refresh", "repo")
-        yield* fs.remove(cache, { recursive: true }).pipe(Effect.ignore)
-        yield* Effect.addFinalizer(() => fs.remove(cache, { recursive: true }).pipe(Effect.ignore))
-
-        const source = yield* tmpdirScoped({ git: true })
-        const remoteRoot = yield* tmpdirScoped()
-        const remoteDir = path.join(remoteRoot, "opencode-reference-refresh")
-        const remoteRepo = path.join(remoteDir, "repo.git")
-
-        yield* Effect.promise(() => Bun.write(path.join(source, "README.md"), "v1\n"))
-        yield* git(source, ["add", "."])
-        yield* git(source, ["commit", "-m", "add readme"])
-        yield* fs.makeDirectory(remoteDir, { recursive: true }).pipe(Effect.orDie)
-        yield* git(remoteRoot, ["clone", "--bare", source, remoteRepo])
-
-        yield* githubBase(
-          `file://${remoteRoot}/`,
-          provideTmpdirInstance(
-            (_dir) =>
-              Effect.gen(function* () {
-                const reference = yield* Reference.Service
-                yield* reference.init()
-                yield* waitForContent(fs, path.join(cache, "README.md"), "v1\n")
-              }),
-            {
-              config: {
-                reference: {
-                  docs: "opencode-reference-refresh/repo",
-                },
+  scout.live("refreshes configured git references on new instance init", () =>
+    Effect.gen(function* () {
+      const fs = yield* AppFileSystem.Service
+      const cache = path.join(Global.Path.repos, "github.com", "opencode-reference-refresh", "repo")
+      yield* fs.remove(cache, { recursive: true }).pipe(Effect.ignore)
+      yield* Effect.addFinalizer(() => fs.remove(cache, { recursive: true }).pipe(Effect.ignore))
+
+      const source = yield* tmpdirScoped({ git: true })
+      const remoteRoot = yield* tmpdirScoped()
+      const remoteDir = path.join(remoteRoot, "opencode-reference-refresh")
+      const remoteRepo = path.join(remoteDir, "repo.git")
+
+      yield* Effect.promise(() => Bun.write(path.join(source, "README.md"), "v1\n"))
+      yield* git(source, ["add", "."])
+      yield* git(source, ["commit", "-m", "add readme"])
+      yield* fs.makeDirectory(remoteDir, { recursive: true }).pipe(Effect.orDie)
+      yield* git(remoteRoot, ["clone", "--bare", source, remoteRepo])
+
+      yield* githubBase(
+        `file://${remoteRoot}/`,
+        provideTmpdirInstance(
+          (_dir) =>
+            Effect.gen(function* () {
+              const reference = yield* Reference.Service
+              yield* reference.init()
+              yield* waitForContent(fs, path.join(cache, "README.md"), "v1\n")
+            }),
+          {
+            config: {
+              reference: {
+                docs: "opencode-reference-refresh/repo",
               },
             },
-          ),
-        )
-
-        const branch = yield* git(source, ["branch", "--show-current"])
-        yield* git(source, ["remote", "add", "origin", remoteRepo])
-        yield* Effect.promise(() => Bun.write(path.join(source, "README.md"), "v2\n"))
-        yield* git(source, ["add", "."])
-        yield* git(source, ["commit", "-m", "update readme"])
-        yield* git(source, ["push", "origin", `${branch}:${branch}`])
-
-        yield* githubBase(
-          `file://${remoteRoot}/`,
-          provideTmpdirInstance(
-            (_dir) =>
-              Effect.gen(function* () {
-                const reference = yield* Reference.Service
-                yield* reference.init()
-                yield* waitForContent(fs, path.join(cache, "README.md"), "v2\n")
-              }),
-            {
-              config: {
-                reference: {
-                  docs: "opencode-reference-refresh/repo",
-                },
+          },
+        ),
+      )
+
+      const branch = yield* git(source, ["branch", "--show-current"])
+      yield* git(source, ["remote", "add", "origin", remoteRepo])
+      yield* Effect.promise(() => Bun.write(path.join(source, "README.md"), "v2\n"))
+      yield* git(source, ["add", "."])
+      yield* git(source, ["commit", "-m", "update readme"])
+      yield* git(source, ["push", "origin", `${branch}:${branch}`])
+
+      yield* githubBase(
+        `file://${remoteRoot}/`,
+        provideTmpdirInstance(
+          (_dir) =>
+            Effect.gen(function* () {
+              const reference = yield* Reference.Service
+              yield* reference.init()
+              yield* waitForContent(fs, path.join(cache, "README.md"), "v2\n")
+            }),
+          {
+            config: {
+              reference: {
+                docs: "opencode-reference-refresh/repo",
               },
             },
-          ),
-        )
-      }),
-    ),
+          },
+        ),
+      )
+    }),
   )
 })

+ 50 - 53
packages/opencode/test/tool/read.test.ts

@@ -4,8 +4,10 @@ import path from "path"
 import { Agent } from "../../src/agent/agent"
 import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
 import { AppFileSystem } from "@opencode-ai/core/filesystem"
-import { Flag } from "@opencode-ai/core/flag/flag"
 import { Global } from "@opencode-ai/core/global"
+import { Config } from "@/config/config"
+import { RuntimeFlags } from "@/effect/runtime-flags"
+import { Git } from "@/git"
 import { LSP } from "@/lsp/lsp"
 import { Permission } from "../../src/permission"
 import { Instance } from "../../src/project/instance"
@@ -36,17 +38,27 @@ const ctx = {
   ask: () => Effect.void,
 }
 
-const it = testEffect(
+const referenceLayer = (flags: Partial<RuntimeFlags.Info> = {}) =>
+  Reference.layer.pipe(
+    Layer.provide(Config.defaultLayer),
+    Layer.provide(AppFileSystem.defaultLayer),
+    Layer.provide(Git.defaultLayer),
+    Layer.provide(RuntimeFlags.layer(flags)),
+  )
+
+const readLayer = (flags: Partial<RuntimeFlags.Info> = {}) =>
   Layer.mergeAll(
     Agent.defaultLayer,
     AppFileSystem.defaultLayer,
     CrossSpawnSpawner.defaultLayer,
     Instruction.defaultLayer,
     LSP.defaultLayer,
-    Reference.defaultLayer,
+    referenceLayer(flags),
     Truncate.defaultLayer,
-  ),
-)
+  )
+
+const it = testEffect(readLayer())
+const scout = testEffect(readLayer({ experimentalScout: true }))
 
 const init = Effect.fn("ReadToolTest.init")(function* () {
   const info = yield* ReadTool
@@ -85,19 +97,6 @@ const fail = Effect.fn("ReadToolTest.fail")(function* (
 const full = (p: string) => (process.platform === "win32" ? Filesystem.normalizePath(p) : p)
 const glob = (p: string) =>
   process.platform === "win32" ? Filesystem.normalizePathPattern(p) : p.replaceAll("\\", "/")
-const experimentalScout = <A, E, R>(self: Effect.Effect<A, E, R>) =>
-  Effect.acquireUseRelease(
-    Effect.sync(() => {
-      const previous = Flag.OPENCODE_EXPERIMENTAL_SCOUT
-      Flag.OPENCODE_EXPERIMENTAL_SCOUT = true
-      return previous
-    }),
-    () => self,
-    (previous) =>
-      Effect.sync(() => {
-        Flag.OPENCODE_EXPERIMENTAL_SCOUT = previous
-      }),
-  )
 const githubBase = <A, E, R>(url: string, self: Effect.Effect<A, E, R>) =>
   Effect.acquireUseRelease(
     Effect.sync(() => {
@@ -260,44 +259,42 @@ describe("tool.read external_directory permission", () => {
     }),
   )
 
-  it.live("does not ask for external_directory permission when reading configured references", () =>
-    experimentalScout(
-      Effect.gen(function* () {
-        const fs = yield* AppFileSystem.Service
-        const cache = path.join(Global.Path.repos, "github.com", "opencode-read-reference", "repo")
-        yield* fs.remove(cache, { recursive: true }).pipe(Effect.ignore)
-        yield* Effect.addFinalizer(() => fs.remove(cache, { recursive: true }).pipe(Effect.ignore))
-
-        const source = yield* tmpdirScoped({ git: true })
-        const remoteRoot = yield* tmpdirScoped()
-        const remoteDir = path.join(remoteRoot, "opencode-read-reference")
-        const remoteRepo = path.join(remoteDir, "repo.git")
-        yield* put(path.join(source, "notes.md"), "reference notes")
-        yield* git(source, ["add", "."])
-        yield* git(source, ["commit", "-m", "add notes"])
-        yield* fs.makeDirectory(remoteDir, { recursive: true }).pipe(Effect.orDie)
-        yield* git(remoteRoot, ["clone", "--bare", source, remoteRepo])
-
-        const dir = yield* tmpdirScoped({
-          git: true,
-          config: {
-            reference: {
-              docs: "opencode-read-reference/repo",
-            },
+  scout.live("does not ask for external_directory permission when reading configured references", () =>
+    Effect.gen(function* () {
+      const fs = yield* AppFileSystem.Service
+      const cache = path.join(Global.Path.repos, "github.com", "opencode-read-reference", "repo")
+      yield* fs.remove(cache, { recursive: true }).pipe(Effect.ignore)
+      yield* Effect.addFinalizer(() => fs.remove(cache, { recursive: true }).pipe(Effect.ignore))
+
+      const source = yield* tmpdirScoped({ git: true })
+      const remoteRoot = yield* tmpdirScoped()
+      const remoteDir = path.join(remoteRoot, "opencode-read-reference")
+      const remoteRepo = path.join(remoteDir, "repo.git")
+      yield* put(path.join(source, "notes.md"), "reference notes")
+      yield* git(source, ["add", "."])
+      yield* git(source, ["commit", "-m", "add notes"])
+      yield* fs.makeDirectory(remoteDir, { recursive: true }).pipe(Effect.orDie)
+      yield* git(remoteRoot, ["clone", "--bare", source, remoteRepo])
+
+      const dir = yield* tmpdirScoped({
+        git: true,
+        config: {
+          reference: {
+            docs: "opencode-read-reference/repo",
           },
-        })
+        },
+      })
 
-        const { items, next } = asks()
-        const result = yield* githubBase(
-          `file://${remoteRoot}/`,
-          exec(dir, { filePath: path.join(cache, "notes.md") }, next),
-        )
-        const ext = items.find((item) => item.permission === "external_directory")
+      const { items, next } = asks()
+      const result = yield* githubBase(
+        `file://${remoteRoot}/`,
+        exec(dir, { filePath: path.join(cache, "notes.md") }, next),
+      )
+      const ext = items.find((item) => item.permission === "external_directory")
 
-        expect(result.output).toContain("reference notes")
-        expect(ext).toBeUndefined()
-      }),
-    ),
+      expect(result.output).toContain("reference notes")
+      expect(ext).toBeUndefined()
+    }),
   )
 })