fix(core): preserve agent permission precedence

This commit is contained in:
Aiden Cline 2026-07-09 17:59:44 -05:00
commit e4ef2acb0b
3 changed files with 88 additions and 28 deletions

View file

@ -15,6 +15,7 @@ import { PermissionV2 } from "../../permission"
import type { LocationMutation } from "../../location-mutation"
import type { ReadTool } from "../../tool/read"
import type { EditTool } from "../../tool/edit"
import { AgentPlugin } from "../../plugin/agent"
const legacySources = [
{ pattern: "{agent,agents}/**/*.md", primary: false },
@ -76,7 +77,22 @@ export const Plugin = define({
const configuredDefault = Config.latest(loaded.documents, "default_agent")
if (configuredDefault !== undefined) draft.default(AgentV2.ID.make(configuredDefault))
for (const current of draft.list()) {
draft.update(current.id, (agent) => agent.permissions.push(...permissions))
draft.update(current.id, (agent) => {
const initial = AgentV2.Info.empty(AgentV2.ID.make(current.id)).permissions.length
const hasBuiltInDefaults = AgentPlugin.defaultPermissions.every((rule, index) => {
const existing = agent.permissions[initial + index]
return (
existing?.action === rule.action &&
existing.resource === rule.resource &&
existing.effect === rule.effect
)
})
agent.permissions.splice(
hasBuiltInDefaults ? initial + AgentPlugin.defaultPermissions.length : initial,
0,
...permissions,
)
})
}
for (const document of loaded.documents) {

View file

@ -13,6 +13,21 @@ import { PermissionV2 } from "../permission"
const SHELL_OUTPUT_GLOB = path.join(Global.Path.data, "shell", "*", "*")
const BUILD_SYSTEM =
"You are an AI coding agent. Help the user accomplish software engineering tasks by inspecting the workspace, making targeted changes, and using tools according to the configured permissions."
const readonlyExternalDirectory: PermissionV2.Ruleset = [
{ action: "external_directory", resource: "*", effect: "ask" },
{ action: "external_directory", resource: SHELL_OUTPUT_GLOB, effect: "allow" },
{ action: "external_directory", resource: path.join(Global.Path.tmp, "*"), effect: "allow" },
]
export const defaultPermissions: PermissionV2.Ruleset = [
...readonlyExternalDirectory.slice(1),
{ action: "question", resource: "*", effect: "deny" },
{ action: "plan_enter", resource: "*", effect: "deny" },
{ action: "plan_exit", resource: "*", effect: "deny" },
{ action: "read", resource: "*", effect: "allow" },
{ action: "read", resource: "*.env", effect: "ask" },
{ action: "read", resource: "*.env.*", effect: "ask" },
{ action: "read", resource: "*.env.example", effect: "allow" },
]
const PROMPT_EXPLORE = `You are a file search specialist. You excel at thoroughly navigating and exploring codebases.
@ -104,24 +119,6 @@ export const Plugin = define({
effect: Effect.fn(function* (ctx) {
const location = yield* Location.Service
const worktree = location.directory
const whitelistedDirs = [SHELL_OUTPUT_GLOB, path.join(Global.Path.tmp, "*")]
const readonlyExternalDirectory: PermissionV2.Ruleset = [
{ action: "external_directory", resource: "*", effect: "ask" },
...whitelistedDirs.map(
(resource): PermissionV2.Rule => ({ action: "external_directory", resource, effect: "allow" }),
),
]
const defaults: PermissionV2.Ruleset = [
{ action: "*", resource: "*", effect: "allow" },
...readonlyExternalDirectory,
{ action: "question", resource: "*", effect: "deny" },
{ action: "plan_enter", resource: "*", effect: "deny" },
{ action: "plan_exit", resource: "*", effect: "deny" },
{ action: "read", resource: "*", effect: "allow" },
{ action: "read", resource: "*.env", effect: "ask" },
{ action: "read", resource: "*.env.*", effect: "ask" },
{ action: "read", resource: "*.env.example", effect: "allow" },
]
yield* ctx.agent.transform((draft) => {
draft.update(AgentV2.defaultID, (item) => {
@ -129,7 +126,7 @@ export const Plugin = define({
item.description = "The default agent. Executes tools based on configured permissions."
item.mode = "primary"
item.permissions.push(
...PermissionV2.merge(defaults, [
...PermissionV2.merge(defaultPermissions, [
{ action: "question", resource: "*", effect: "allow" },
{ action: "plan_enter", resource: "*", effect: "allow" },
]),
@ -141,7 +138,7 @@ export const Plugin = define({
item.description = "Plan mode. Disallows all edit tools."
item.mode = "primary"
item.permissions.push(
...PermissionV2.merge(defaults, [
...PermissionV2.merge(defaultPermissions, [
{ action: "question", resource: "*", effect: "allow" },
{ action: "plan_exit", resource: "*", effect: "allow" },
{ action: "external_directory", resource: path.join(Global.Path.data, "plans", "*"), effect: "allow" },
@ -161,7 +158,9 @@ export const Plugin = define({
item.description =
"General-purpose agent for researching complex questions and executing multi-step tasks. Use this agent to execute multiple units of work in parallel."
item.mode = "subagent"
item.permissions.push(...PermissionV2.merge(defaults, [{ action: "subagent", resource: "*", effect: "deny" }]))
item.permissions.push(
...PermissionV2.merge(defaultPermissions, [{ action: "subagent", resource: "*", effect: "deny" }]),
)
})
draft.update(AgentV2.ID.make("explore"), (item) => {
@ -172,7 +171,7 @@ export const Plugin = define({
item.mode = "subagent"
item.permissions.push(
...PermissionV2.merge(
defaults,
defaultPermissions,
[
{ action: "*", resource: "*", effect: "deny" },
{ action: "grep", resource: "*", effect: "allow" },
@ -192,7 +191,9 @@ export const Plugin = define({
item.mode = "primary"
item.hidden = true
item.system = PROMPT_COMPACTION
item.permissions.push(...PermissionV2.merge(defaults, [{ action: "*", resource: "*", effect: "deny" }]))
item.permissions.push(
...PermissionV2.merge(defaultPermissions, [{ action: "*", resource: "*", effect: "deny" }]),
)
})
draft.update(AgentV2.ID.make("title"), (item) => {
@ -200,7 +201,9 @@ export const Plugin = define({
item.mode = "primary"
item.hidden = true
item.system = PROMPT_TITLE
item.permissions.push(...PermissionV2.merge(defaults, [{ action: "*", resource: "*", effect: "deny" }]))
item.permissions.push(
...PermissionV2.merge(defaultPermissions, [{ action: "*", resource: "*", effect: "deny" }]),
)
})
draft.update(AgentV2.ID.make("summary"), (item) => {
@ -208,7 +211,9 @@ export const Plugin = define({
item.mode = "primary"
item.hidden = true
item.system = PROMPT_SUMMARY
item.permissions.push(...PermissionV2.merge(defaults, [{ action: "*", resource: "*", effect: "deny" }]))
item.permissions.push(
...PermissionV2.merge(defaultPermissions, [{ action: "*", resource: "*", effect: "deny" }]),
)
})
})
}),