Browse Source

fix: avoid truncate permission import cycle (#18292)

Luke Parker 3 weeks ago
parent
commit
7866dbcfcc

+ 15 - 0
packages/opencode/src/permission/evaluate.ts

@@ -0,0 +1,15 @@
+import { Wildcard } from "@/util/wildcard"
+
+type Rule = {
+  permission: string
+  pattern: string
+  action: "allow" | "deny" | "ask"
+}
+
+export function evaluate(permission: string, pattern: string, ...rulesets: Rule[][]): Rule {
+  const rules = rulesets.flat()
+  const match = rules.findLast(
+    (rule) => Wildcard.match(permission, rule.permission) && Wildcard.match(pattern, rule.pattern),
+  )
+  return match ?? { action: "ask", permission, pattern: "*" }
+}

+ 3 - 6
packages/opencode/src/permission/index.ts

@@ -13,6 +13,7 @@ import { Wildcard } from "@/util/wildcard"
 import { Deferred, Effect, Layer, Schema, ServiceMap } from "effect"
 import os from "os"
 import z from "zod"
+import { evaluate as evalRule } from "./evaluate"
 import { PermissionID } from "./schema"
 
 export namespace PermissionNext {
@@ -125,12 +126,8 @@ export namespace PermissionNext {
   }
 
   export function evaluate(permission: string, pattern: string, ...rulesets: Ruleset[]): Rule {
-    const rules = rulesets.flat()
-    log.info("evaluate", { permission, pattern, ruleset: rules })
-    const match = rules.findLast(
-      (rule) => Wildcard.match(permission, rule.permission) && Wildcard.match(pattern, rule.pattern),
-    )
-    return match ?? { action: "ask", permission, pattern: "*" }
+    log.info("evaluate", { permission, pattern, ruleset: rulesets.flat() })
+    return evalRule(permission, pattern, ...rulesets)
   }
 
   export class Service extends ServiceMap.Service<Service, Interface>()("@opencode/PermissionNext") {}

+ 2 - 2
packages/opencode/src/tool/truncate-effect.ts

@@ -3,7 +3,7 @@ import { Cause, Duration, Effect, Layer, Schedule, ServiceMap } from "effect"
 import path from "path"
 import type { Agent } from "../agent/agent"
 import { AppFileSystem } from "@/filesystem"
-import { PermissionNext } from "../permission"
+import { evaluate } from "@/permission/evaluate"
 import { Identifier } from "../id/id"
 import { Log } from "../util/log"
 import { ToolID } from "./schema"
@@ -28,7 +28,7 @@ export namespace TruncateEffect {
 
   function hasTaskTool(agent?: Agent.Info) {
     if (!agent?.permission) return false
-    return PermissionNext.evaluate("task", "*", agent.permission).action !== "deny"
+    return evaluate("task", "*", agent.permission).action !== "deny"
   }
 
   export interface Interface {

+ 10 - 0
packages/opencode/test/tool/truncation.test.ts

@@ -4,12 +4,14 @@ import { Effect, FileSystem, Layer } from "effect"
 import { Truncate } from "../../src/tool/truncate"
 import { TruncateEffect } from "../../src/tool/truncate-effect"
 import { Identifier } from "../../src/id/id"
+import { Process } from "../../src/util/process"
 import { Filesystem } from "../../src/util/filesystem"
 import path from "path"
 import { testEffect } from "../lib/effect"
 import { writeFileStringScoped } from "../lib/filesystem"
 
 const FIXTURES_DIR = path.join(import.meta.dir, "fixtures")
+const ROOT = path.resolve(import.meta.dir, "..", "..")
 
 describe("Truncate", () => {
   describe("output", () => {
@@ -125,6 +127,14 @@ describe("Truncate", () => {
       if (result.truncated) throw new Error("expected not truncated")
       expect("outputPath" in result).toBe(false)
     })
+
+    test("loads truncate effect in a fresh process", async () => {
+      const out = await Process.run([process.execPath, "run", path.join(ROOT, "src", "tool", "truncate-effect.ts")], {
+        cwd: ROOT,
+      })
+
+      expect(out.code).toBe(0)
+    }, 20000)
   })
 
   describe("cleanup", () => {