openAgent
gitlabThe open source UNIVERSAL agent.
clone
https://gitlab.com/bitzzdev/openAgent.gitAIagentautomationopen-sourceuniversal-agent
import { PermissionV1 } from "@opencode-ai/core/v1/permission"import { LayerNode } from "@opencode-ai/core/effect/layer-node"import { describe, test, expect } from "bun:test"import { Effect } from "effect"import { Permission } from "../src/permission"import { Config } from "@/config/config"import { testEffect } from "./lib/effect"const it = testEffect(LayerNode.compile(Config.node))const load = Config.use.get()describe("Permission.evaluate for permission.task", () => {const createRuleset = (rules: Record<string, "allow" | "deny" | "ask">): PermissionV1.Ruleset =>Object.entries(rules).map(([pattern, action]) => ({permission: "task",pattern,action,}))test("returns ask when no match (default)", () => {expect(Permission.evaluate("task", "code-reviewer", []).action).toBe("ask")})test("returns deny for explicit deny", () => {const ruleset = createRuleset({ "code-reviewer": "deny" })expect(Permission.evaluate("task", "code-reviewer", ruleset).action).toBe("deny")})test("returns allow for explicit allow", () => {const ruleset = createRuleset({ "code-reviewer": "allow" })expect(Permission.evaluate("task", "code-reviewer", ruleset).action).toBe("allow")})test("returns ask for explicit ask", () => {const ruleset = createRuleset({ "code-reviewer": "ask" })expect(Permission.evaluate("task", "code-reviewer", ruleset).action).toBe("ask")})test("matches wildcard patterns with deny", () => {const ruleset = createRuleset({ "orchestrator-*": "deny" })expect(Permission.evaluate("task", "orchestrator-fast", ruleset).action).toBe("deny")expect(Permission.evaluate("task", "orchestrator-slow", ruleset).action).toBe("deny")expect(Permission.evaluate("task", "general", ruleset).action).toBe("ask")})test("matches wildcard patterns with allow", () => {const ruleset = createRuleset({ "orchestrator-*": "allow" })expect(Permission.evaluate("task", "orchestrator-fast", ruleset).action).toBe("allow")expect(Permission.evaluate("task", "orchestrator-slow", ruleset).action).toBe("allow")})test("matches wildcard patterns with ask", () => {const ruleset = createRuleset({ "orchestrator-*": "ask" })expect(Permission.evaluate("task", "orchestrator-fast", ruleset).action).toBe("ask")const globalRuleset = createRuleset({ "*": "ask" })expect(Permission.evaluate("task", "code-reviewer", globalRuleset).action).toBe("ask")})test("later rules take precedence (last match wins)", () => {const ruleset = createRuleset({"orchestrator-*": "deny","orchestrator-fast": "allow",})expect(Permission.evaluate("task", "orchestrator-fast", ruleset).action).toBe("allow")expect(Permission.evaluate("task", "orchestrator-slow", ruleset).action).toBe("deny")})test("matches global wildcard", () => {expect(Permission.evaluate("task", "any-agent", createRuleset({ "*": "allow" })).action).toBe("allow")expect(Permission.evaluate("task", "any-agent", createRuleset({ "*": "deny" })).action).toBe("deny")expect(Permission.evaluate("task", "any-agent", createRuleset({ "*": "ask" })).action).toBe("ask")})})describe("Permission.disabled for task tool", () => {// Note: The `disabled` function checks if a TOOL should be completely removed from the tool list.// It only disables a tool when there's a rule with `pattern: "*"` and `action: "deny"`.// It does NOT evaluate complex subagent patterns - those are handled at runtime by `evaluate`.const createRuleset = (rules: Record<string, "allow" | "deny" | "ask">): PermissionV1.Ruleset =>Object.entries(rules).map(([pattern, action]) => ({permission: "task",pattern,action,}))test("task tool is disabled when global deny pattern exists (even with specific allows)", () => {// When "*": "deny" exists, the task tool is disabled because the disabled() function// only checks for wildcard deny patterns - it doesn't consider that specific subagents might be allowedconst ruleset = createRuleset({"orchestrator-*": "allow","*": "deny",})const disabled = Permission.disabled(["task", "bash", "read"], ruleset)// The task tool IS disabled because there's a pattern: "*" with action: "deny"expect(disabled.has("task")).toBe(true)})test("task tool is disabled when global deny pattern exists (even with ask overrides)", () => {const ruleset = createRuleset({"orchestrator-*": "ask","*": "deny",})const disabled = Permission.disabled(["task"], ruleset)// The task tool IS disabled because there's a pattern: "*" with action: "deny"expect(disabled.has("task")).toBe(true)})test("task tool is disabled when global deny pattern exists", () => {const ruleset = createRuleset({ "*": "deny" })const disabled = Permission.disabled(["task"], ruleset)expect(disabled.has("task")).toBe(true)})test("task tool is NOT disabled when only specific patterns are denied (no wildcard)", () => {// The disabled() function only disables tools when pattern: "*" && action: "deny"// Specific subagent denies don't disable the task tool - those are handled at runtimeconst ruleset = createRuleset({"orchestrator-*": "deny",general: "deny",})const disabled = Permission.disabled(["task"], ruleset)// The task tool is NOT disabled because no rule has pattern: "*" with action: "deny"expect(disabled.has("task")).toBe(false)})test("task tool is enabled when no task rules exist (default ask)", () => {const disabled = Permission.disabled(["task"], [])expect(disabled.has("task")).toBe(false)})test("task tool is NOT disabled when last wildcard pattern is allow", () => {// Last matching rule wins - if wildcard allow comes after wildcard deny, tool is enabledconst ruleset = createRuleset({"*": "deny","orchestrator-coder": "allow",})const disabled = Permission.disabled(["task"], ruleset)// The disabled() function uses findLast and checks if the last matching rule// has pattern: "*" and action: "deny". In this case, the last rule matching// "task" permission has pattern "orchestrator-coder", not "*", so not disabledexpect(disabled.has("task")).toBe(false)})})// Integration tests that load permissions from real config filesdescribe("permission.task with real config files", () => {it.instance("loads task permissions from opencode.json config",() =>Effect.gen(function* () {const config = yield* loadconst ruleset = Permission.fromConfig(config.permission ?? {})// general and orchestrator-fast should be allowed, code-reviewer deniedexpect(Permission.evaluate("task", "general", ruleset).action).toBe("allow")expect(Permission.evaluate("task", "orchestrator-fast", ruleset).action).toBe("allow")expect(Permission.evaluate("task", "code-reviewer", ruleset).action).toBe("deny")}),{git: true,config: {permission: {task: {"*": "allow","code-reviewer": "deny",},},},},)it.instance("loads task permissions with wildcard patterns from config",() =>Effect.gen(function* () {const config = yield* loadconst ruleset = Permission.fromConfig(config.permission ?? {})// general and code-reviewer should be ask, orchestrator-* deniedexpect(Permission.evaluate("task", "general", ruleset).action).toBe("ask")expect(Permission.evaluate("task", "code-reviewer", ruleset).action).toBe("ask")expect(Permission.evaluate("task", "orchestrator-fast", ruleset).action).toBe("deny")}),{git: true,config: {permission: {task: {"*": "ask","orchestrator-*": "deny",},},},},)it.instance("evaluate respects task permission from config",() =>Effect.gen(function* () {const config = yield* loadconst ruleset = Permission.fromConfig(config.permission ?? {})expect(Permission.evaluate("task", "general", ruleset).action).toBe("allow")expect(Permission.evaluate("task", "code-reviewer", ruleset).action).toBe("deny")// Unspecified agents default to "ask"expect(Permission.evaluate("task", "unknown-agent", ruleset).action).toBe("ask")}),{git: true,config: {permission: {task: {general: "allow","code-reviewer": "deny",},},},},)it.instance("mixed permission config with task and other tools",() =>Effect.gen(function* () {const config = yield* loadconst ruleset = Permission.fromConfig(config.permission ?? {})// Verify task permissionsexpect(Permission.evaluate("task", "general", ruleset).action).toBe("allow")expect(Permission.evaluate("task", "code-reviewer", ruleset).action).toBe("deny")// Verify other tool permissionsexpect(Permission.evaluate("bash", "*", ruleset).action).toBe("allow")expect(Permission.evaluate("edit", "*", ruleset).action).toBe("ask")// Verify disabled toolsconst disabled = Permission.disabled(["bash", "edit", "task"], ruleset)expect(disabled.has("bash")).toBe(false)expect(disabled.has("edit")).toBe(false)// task is NOT disabled because disabled() uses findLast, and the last rule// matching "task" permission is {pattern: "general", action: "allow"}, not pattern: "*"expect(disabled.has("task")).toBe(false)}),{git: true,config: {permission: {bash: "allow",edit: "ask",task: {"*": "deny",general: "allow",},},},},)it.instance("task tool disabled when global deny comes last in config",() =>Effect.gen(function* () {const config = yield* loadconst ruleset = Permission.fromConfig(config.permission ?? {})// Last matching rule wins - "*" deny is last, so all agents are deniedexpect(Permission.evaluate("task", "general", ruleset).action).toBe("deny")expect(Permission.evaluate("task", "code-reviewer", ruleset).action).toBe("deny")expect(Permission.evaluate("task", "unknown", ruleset).action).toBe("deny")// Since "*": "deny" is the last rule, disabled() finds it with findLast// and sees pattern: "*" with action: "deny", so task is disabledconst disabled = Permission.disabled(["task"], ruleset)expect(disabled.has("task")).toBe(true)}),{git: true,config: {permission: {task: {general: "allow","code-reviewer": "allow","*": "deny",},},},},)it.instance("task tool NOT disabled when specific allow comes last in config",() =>Effect.gen(function* () {const config = yield* loadconst ruleset = Permission.fromConfig(config.permission ?? {})// Evaluate uses findLast - "general" allow comes after "*" denyexpect(Permission.evaluate("task", "general", ruleset).action).toBe("allow")// Other agents still denied by the earlier "*" denyexpect(Permission.evaluate("task", "code-reviewer", ruleset).action).toBe("deny")// disabled() uses findLast and checks if the last rule has pattern: "*" with action: "deny"// In this case, the last rule is {pattern: "general", action: "allow"}, not pattern: "*"// So the task tool is NOT disabled (even though most subagents are denied)const disabled = Permission.disabled(["task"], ruleset)expect(disabled.has("task")).toBe(false)}),{git: true,config: {permission: {task: {"*": "deny",general: "allow",},},},},)})