refactor: simplify task tool subagent filtering (#7165)
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
fe57d7bb38
commit
34c9d106ee
@@ -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")
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user