summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorLuke Parker <[email protected]>2026-03-20 13:52:04 +1000
committerGitHub <[email protected]>2026-03-19 23:52:04 -0400
commit7866dbcfcc36a60d22ad466eddf54c54b21fabe3 (patch)
treeae0ee2b1fbfc3e1a387285e898342760fcf77d6d
parente71a21e0a8ac721677ffdfd67fc0301a6d0a3716 (diff)
downloadopencode-7866dbcfcc36a60d22ad466eddf54c54b21fabe3.tar.gz
opencode-7866dbcfcc36a60d22ad466eddf54c54b21fabe3.zip
fix: avoid truncate permission import cycle (#18292)
-rw-r--r--packages/opencode/src/permission/evaluate.ts15
-rw-r--r--packages/opencode/src/permission/index.ts9
-rw-r--r--packages/opencode/src/tool/truncate-effect.ts4
-rw-r--r--packages/opencode/test/tool/truncation.test.ts10
4 files changed, 30 insertions, 8 deletions
diff --git a/packages/opencode/src/permission/evaluate.ts b/packages/opencode/src/permission/evaluate.ts
new file mode 100644
index 000000000..2b0604f4b
--- /dev/null
+++ b/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: "*" }
+}
diff --git a/packages/opencode/src/permission/index.ts b/packages/opencode/src/permission/index.ts
index 93a8c49b6..321c5c374 100644
--- a/packages/opencode/src/permission/index.ts
+++ b/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") {}
diff --git a/packages/opencode/src/tool/truncate-effect.ts b/packages/opencode/src/tool/truncate-effect.ts
index 4431c18f8..a263cd294 100644
--- a/packages/opencode/src/tool/truncate-effect.ts
+++ b/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 {
diff --git a/packages/opencode/test/tool/truncation.test.ts b/packages/opencode/test/tool/truncation.test.ts
index 71439f760..a00e07e69 100644
--- a/packages/opencode/test/tool/truncation.test.ts
+++ b/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", () => {