跳转到内容

权限规则与求值模型

skill_management 改动的全部内容就是一条权限规则。读完这一页,你就能在运行工具之前 预测它会报告什么。

{ "action": "skill", "resource": "windows-shell", "effect": "deny" } // skill_management
字段含义
action权限动作。skill 权限使用字面量 skill。
resourceskill id。可以精确,也可以是 glob。
effectallow、deny 或 ask。

规则位于全局配置的顶层 permissions 数组。这是 V2 形态:

{
"permissions": [
{ "action": "skill", "resource": "windows-shell", "effect": "deny" }
]
}

宿主对调用者 agent 的规则集从后往前遍历,取第一个命中。等价说法:后匹配的规则胜出。 action 与 resource 都按 glob 匹配(* 与 ?)。

rules[0] { action: "skill", resource: "*", effect: "allow" }
rules[1] { action: "skill", resource: "windows-shell", effect: "deny" }
→ windows-shell 被 deny(rules[1] 更靠后)

由此得到:

  • 顺序是语义的一部分。 追加规则会让它在同一数组里优先于更早的规则。disable 采用追加。
  • 宽规则可能压过精确规则,只要它更靠后。inspect 会点名这种情况;再给同一个 id 加一条 规则并不能解决问题。
  • 没有隐式 allow。 一条规则都不命中时,宿主回退到 ask,不是 allow。
没有任何规则命中 → ask

这就是 ask 有歧义的原因:

你看到的可能原因
ask命中了显式的 { action: "skill", resource: "<id>", effect: "ask" } 规则。
ask没有任何规则命中,宿主回退到 ask。

list 对两者都显示 ask。inspect 通过报告 命中 rules[i] 或 无规则命中 → 回退 ask 来区分。

效果模型可见列表通过 skill 工具加载
allow列出允许
ask列出询问确认
deny隐藏拒绝(permission.rejected)

两者都能让 skill 不被悄悄加载,但拦住的位置不同。

denyask
模型可见列表隐藏仍在列表里,描述也还在
加载尝试拒绝(permission.rejected)卡在确认上
打断这一轮不会 —— 直接失败会 —— 要有人回答
谁能覆盖调用者自己规则集里更靠后的规则(如 agent 级 allow),或 policies 条目调用者自己规则集里更靠后的规则
典型用途完全不想让它进入上下文的 skill用得上、但不该由模型自作主张调用的 skill

ask 不是「弱一点的 deny」。它不降低模型尝试使用这个 skill 的概率:skill 仍在列表里、 仍有描述、仍是候选。ask 换来的是一个决定点,代价是一次打断。

三个真实例子,以及这样选效果的理由:

Skill为什么要开关选的效果
codebuddy-second-opinion调另一个模型,花额度 / creditsask —— 你主动要第二意见时有用,模型自己想起来就是浪费
disk-clean扫缓存并真的删文件ask 让人过一眼;不想让模型碰就 deny
meld-deepresearch联网 + 长耗时deny —— 误调的代价远高于那点便利

粒度是 skill id,不是某个文件或某次调用。id 不存在时报 Unable to load skill <id>,这与权限拒绝是两个不同的错误。

每个 agent 都有自己的有效规则集。插件只对调用者 agent 求值,绝不把所有 agent 的规则 合并成一张表。合并是错的:子 agent(explorer、title……)带着自己的 deny 规则, 它们不适用于调用者。

典型分歧:全局配置 deny 了某个 skill,但某个 agent 自己有更靠后的 allow,或反过来。此时:

  • list 显示调用者的状态,并提示其它可见 agent 不同(隐藏的内部 agent 只在 inspect 里列出)。
  • inspect 在 agents[] 里返回逐 agent 结果。
  • disable 会说明规则确实写入、但调用者实际效果仍不同,而不是谎称成功。

写在 agents.<id>.permissions 里的规则,本工具不编辑,只报告。

全局配置按以下顺序解析:

  1. <config>:设置了 $OPENCODE_CONFIG_DIR 时就是它;否则设置了 $XDG_CONFIG_HOME 时是 $XDG_CONFIG_HOME/opencode,再否则是 ~/.config/opencode (Windows 上 C:\Users\<你>\.config\opencode)。
  2. 候选是 <config> 下的 opencode.json、opencode.jsonc;当 ~/.config/opencode 是另一个目录时,再加上那里的同名文件。opencode.jsonc 优先于 opencode.json。
  3. 两个位置都含 permissions 数组时,选 ~/.config/opencode 下的文件,并在输出里加警告。
  4. 没有任何候选含 permissions 时,取最后一个已存在的候选;一个都不存在就新建 <config>/opencode.jsonc。

绝不写 opencode.managed.json。那是 OpenChamber 托管的第二份全局配置,写它会和宿主打架。

配置不会被解析成对象再序列化回去。插件按文本插入或删除特定条目,因此注释、缩进、键顺序、 尾逗号,以及文件自己的 CRLF/LF 与 BOM 都会保留。写前会解析并校验结果。

写入是原子的:同目录临时文件 rename 就位。写之前,在 <config>/skill_management.backups/ 保存一份备份。

<config>/skill_management.state.json 记录本工具写过哪些规则(id、效果、文件、时间戳)。 它只是提示,不是真源:

  • enable 优先认文件内的标记注释,只有找不到时才退回 sidecar。
  • sidecar 与配置不一致时,list 报告差异,不「重放」任何东西。

行尾标记是刻意的:它是 sidecar 丢失后唯一还能存活的来源信息,其格式属于 1.x 兼容承诺。

模式匹配
windows-shell恰好这个 id
*所有 skill
pdf-*以 pdf- 开头的 id
?恰好一个字符

优先用精确 id。通配的 deny 会被 enable 报告且不会自动删除 —— 删掉它影响的范围 远大于你点名的那一个 skill。

{
"permissions": [
// 完全隐藏一个 skill
{ "action": "skill", "resource": "windows-shell", "effect": "deny" },
// 保留在列表里,加载时询问
{ "action": "skill", "resource": "pdf-tools", "effect": "ask" }
]
}

手写规则没有标记时,只要 sidecar 有对应记录且规则唯一,enable 仍能删除。否则 enable 会把规则原文打印出来,由你手工处理。

下一步:故障排查与 FAQ,看实践中的失败模式。