Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions packages/opencode/src/permission/evaluate.ts
Original file line number Diff line number Diff line change
@@ -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: "*" }
}
9 changes: 3 additions & 6 deletions packages/opencode/src/permission/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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() })
Copy link

Copilot AI Mar 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PermissionNext.evaluate() now flattens rulesets for logging and then evalRule() flattens again internally. This adds avoidable work on every permission check. Consider flattening once and passing the flattened list into the helper (e.g., change the helper to accept a single rules array), or move the logging/flattening into the helper so it only happens once.

Suggested change
log.info("evaluate", { permission, pattern, ruleset: rulesets.flat() })
log.info("evaluate", { permission, pattern, rulesets })

Copilot uses AI. Check for mistakes.
return evalRule(permission, pattern, ...rulesets)
}

export class Service extends ServiceMap.Service<Service, Interface>()("@opencode/PermissionNext") {}
Expand Down
4 changes: 2 additions & 2 deletions packages/opencode/src/tool/truncate-effect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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 {
Expand Down
10 changes: 10 additions & 0 deletions packages/opencode/test/tool/truncation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down Expand Up @@ -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", () => {
Expand Down
Loading