|
|
@@ -1,147 +1,9 @@
|
|
|
import { describe, test, expect } from "bun:test"
|
|
|
-import type { Agent } from "../src/agent/agent"
|
|
|
-import { filterSubagents } from "../src/tool/task"
|
|
|
import { PermissionNext } from "../src/permission/next"
|
|
|
import { Config } from "../src/config/config"
|
|
|
import { Instance } from "../src/project/instance"
|
|
|
import { tmpdir } from "./fixture/fixture"
|
|
|
|
|
|
-describe("filterSubagents - permission.task filtering", () => {
|
|
|
- const createRuleset = (rules: Record<string, "allow" | "deny" | "ask">): PermissionNext.Ruleset =>
|
|
|
- Object.entries(rules).map(([pattern, action]) => ({
|
|
|
- permission: "task",
|
|
|
- pattern,
|
|
|
- action,
|
|
|
- }))
|
|
|
-
|
|
|
- const mockAgents = [
|
|
|
- { name: "general", mode: "subagent", permission: [], options: {} },
|
|
|
- { name: "code-reviewer", mode: "subagent", permission: [], options: {} },
|
|
|
- { name: "orchestrator-fast", mode: "subagent", permission: [], options: {} },
|
|
|
- { name: "orchestrator-slow", mode: "subagent", permission: [], options: {} },
|
|
|
- ] as Agent.Info[]
|
|
|
-
|
|
|
- test("returns all agents when permissions config is empty", () => {
|
|
|
- const result = filterSubagents(mockAgents, [])
|
|
|
- expect(result).toHaveLength(4)
|
|
|
- expect(result.map((a) => a.name)).toEqual(["general", "code-reviewer", "orchestrator-fast", "orchestrator-slow"])
|
|
|
- })
|
|
|
-
|
|
|
- test("excludes agents with explicit deny", () => {
|
|
|
- const ruleset = createRuleset({ "code-reviewer": "deny" })
|
|
|
- const result = filterSubagents(mockAgents, ruleset)
|
|
|
- expect(result).toHaveLength(3)
|
|
|
- expect(result.map((a) => a.name)).toEqual(["general", "orchestrator-fast", "orchestrator-slow"])
|
|
|
- })
|
|
|
-
|
|
|
- test("includes agents with explicit allow", () => {
|
|
|
- const ruleset = createRuleset({
|
|
|
- "code-reviewer": "allow",
|
|
|
- general: "deny",
|
|
|
- })
|
|
|
- const result = filterSubagents(mockAgents, ruleset)
|
|
|
- expect(result).toHaveLength(3)
|
|
|
- expect(result.map((a) => a.name)).toEqual(["code-reviewer", "orchestrator-fast", "orchestrator-slow"])
|
|
|
- })
|
|
|
-
|
|
|
- test("includes agents with ask permission (user approval is runtime behavior)", () => {
|
|
|
- const ruleset = createRuleset({
|
|
|
- "code-reviewer": "ask",
|
|
|
- general: "deny",
|
|
|
- })
|
|
|
- const result = filterSubagents(mockAgents, ruleset)
|
|
|
- expect(result).toHaveLength(3)
|
|
|
- expect(result.map((a) => a.name)).toEqual(["code-reviewer", "orchestrator-fast", "orchestrator-slow"])
|
|
|
- })
|
|
|
-
|
|
|
- test("includes agents with undefined permission (default allow)", () => {
|
|
|
- const ruleset = createRuleset({
|
|
|
- general: "deny",
|
|
|
- })
|
|
|
- const result = filterSubagents(mockAgents, ruleset)
|
|
|
- expect(result).toHaveLength(3)
|
|
|
- expect(result.map((a) => a.name)).toEqual(["code-reviewer", "orchestrator-fast", "orchestrator-slow"])
|
|
|
- })
|
|
|
-
|
|
|
- test("supports wildcard patterns with deny", () => {
|
|
|
- const ruleset = createRuleset({ "orchestrator-*": "deny" })
|
|
|
- const result = filterSubagents(mockAgents, ruleset)
|
|
|
- expect(result).toHaveLength(2)
|
|
|
- expect(result.map((a) => a.name)).toEqual(["general", "code-reviewer"])
|
|
|
- })
|
|
|
-
|
|
|
- test("supports wildcard patterns with allow", () => {
|
|
|
- const ruleset = createRuleset({
|
|
|
- "*": "allow",
|
|
|
- "orchestrator-fast": "deny",
|
|
|
- })
|
|
|
- const result = filterSubagents(mockAgents, ruleset)
|
|
|
- expect(result).toHaveLength(3)
|
|
|
- expect(result.map((a) => a.name)).toEqual(["general", "code-reviewer", "orchestrator-slow"])
|
|
|
- })
|
|
|
-
|
|
|
- test("supports wildcard patterns with ask", () => {
|
|
|
- const ruleset = createRuleset({
|
|
|
- "orchestrator-*": "ask",
|
|
|
- })
|
|
|
- const result = filterSubagents(mockAgents, ruleset)
|
|
|
- expect(result).toHaveLength(4)
|
|
|
- expect(result.map((a) => a.name)).toEqual(["general", "code-reviewer", "orchestrator-fast", "orchestrator-slow"])
|
|
|
- })
|
|
|
-
|
|
|
- test("longer pattern takes precedence over shorter pattern", () => {
|
|
|
- const ruleset = createRuleset({
|
|
|
- "orchestrator-*": "deny",
|
|
|
- "orchestrator-fast": "allow",
|
|
|
- })
|
|
|
- const result = filterSubagents(mockAgents, ruleset)
|
|
|
- expect(result).toHaveLength(3)
|
|
|
- expect(result.map((a) => a.name)).toEqual(["general", "code-reviewer", "orchestrator-fast"])
|
|
|
- })
|
|
|
-
|
|
|
- test("edge case: all agents denied", () => {
|
|
|
- const ruleset = createRuleset({ "*": "deny" })
|
|
|
- const result = filterSubagents(mockAgents, ruleset)
|
|
|
- expect(result).toHaveLength(0)
|
|
|
- expect(result).toEqual([])
|
|
|
- })
|
|
|
-
|
|
|
- test("edge case: mixed patterns with multiple wildcards", () => {
|
|
|
- const ruleset = createRuleset({
|
|
|
- "*": "ask",
|
|
|
- "orchestrator-*": "deny",
|
|
|
- "orchestrator-fast": "allow",
|
|
|
- })
|
|
|
- const result = filterSubagents(mockAgents, ruleset)
|
|
|
- expect(result).toHaveLength(3)
|
|
|
- expect(result.map((a) => a.name)).toEqual(["general", "code-reviewer", "orchestrator-fast"])
|
|
|
- })
|
|
|
-
|
|
|
- test("hidden: true does not affect filtering (hidden only affects autocomplete)", () => {
|
|
|
- const agents = [
|
|
|
- { name: "general", mode: "subagent", hidden: true, permission: [], options: {} },
|
|
|
- { name: "code-reviewer", mode: "subagent", hidden: false, permission: [], options: {} },
|
|
|
- { name: "orchestrator", mode: "subagent", permission: [], options: {} },
|
|
|
- ] as Agent.Info[]
|
|
|
-
|
|
|
- const result = filterSubagents(agents, [])
|
|
|
- expect(result).toHaveLength(3)
|
|
|
- expect(result.map((a) => a.name)).toEqual(["general", "code-reviewer", "orchestrator"])
|
|
|
- })
|
|
|
-
|
|
|
- test("hidden: true agents can be filtered by permission.task deny", () => {
|
|
|
- const agents = [
|
|
|
- { name: "general", mode: "subagent", hidden: true, permission: [], options: {} },
|
|
|
- { name: "orchestrator-coder", mode: "subagent", hidden: true, permission: [], options: {} },
|
|
|
- ] as Agent.Info[]
|
|
|
-
|
|
|
- const ruleset = createRuleset({ general: "deny" })
|
|
|
- const result = filterSubagents(agents, ruleset)
|
|
|
- expect(result).toHaveLength(1)
|
|
|
- expect(result.map((a) => a.name)).toEqual(["orchestrator-coder"])
|
|
|
- })
|
|
|
-})
|
|
|
-
|
|
|
describe("PermissionNext.evaluate for permission.task", () => {
|
|
|
const createRuleset = (rules: Record<string, "allow" | "deny" | "ask">): PermissionNext.Ruleset =>
|
|
|
Object.entries(rules).map(([pattern, action]) => ({
|
|
|
@@ -277,12 +139,6 @@ describe("PermissionNext.disabled for task tool", () => {
|
|
|
|
|
|
// Integration tests that load permissions from real config files
|
|
|
describe("permission.task with real config files", () => {
|
|
|
- const mockAgents = [
|
|
|
- { name: "general", mode: "subagent", permission: [], options: {} },
|
|
|
- { name: "code-reviewer", mode: "subagent", permission: [], options: {} },
|
|
|
- { name: "orchestrator-fast", mode: "subagent", permission: [], options: {} },
|
|
|
- ] as Agent.Info[]
|
|
|
-
|
|
|
test("loads task permissions from opencode.json config", async () => {
|
|
|
await using tmp = await tmpdir({
|
|
|
git: true,
|
|
|
@@ -300,8 +156,10 @@ describe("permission.task with real config files", () => {
|
|
|
fn: async () => {
|
|
|
const config = await Config.get()
|
|
|
const ruleset = PermissionNext.fromConfig(config.permission ?? {})
|
|
|
- const result = filterSubagents(mockAgents, ruleset)
|
|
|
- expect(result.map((a) => a.name)).toEqual(["general", "orchestrator-fast"])
|
|
|
+ // general and orchestrator-fast should be allowed, code-reviewer denied
|
|
|
+ expect(PermissionNext.evaluate("task", "general", ruleset).action).toBe("allow")
|
|
|
+ expect(PermissionNext.evaluate("task", "orchestrator-fast", ruleset).action).toBe("allow")
|
|
|
+ expect(PermissionNext.evaluate("task", "code-reviewer", ruleset).action).toBe("deny")
|
|
|
},
|
|
|
})
|
|
|
})
|
|
|
@@ -323,8 +181,10 @@ describe("permission.task with real config files", () => {
|
|
|
fn: async () => {
|
|
|
const config = await Config.get()
|
|
|
const ruleset = PermissionNext.fromConfig(config.permission ?? {})
|
|
|
- const result = filterSubagents(mockAgents, ruleset)
|
|
|
- expect(result.map((a) => a.name)).toEqual(["general", "code-reviewer"])
|
|
|
+ // general and code-reviewer should be ask, orchestrator-* denied
|
|
|
+ expect(PermissionNext.evaluate("task", "general", ruleset).action).toBe("ask")
|
|
|
+ expect(PermissionNext.evaluate("task", "code-reviewer", ruleset).action).toBe("ask")
|
|
|
+ expect(PermissionNext.evaluate("task", "orchestrator-fast", ruleset).action).toBe("deny")
|
|
|
},
|
|
|
})
|
|
|
})
|