参考实现对比#
三个参考框架是如何控制"某个 agent 可以使用哪些扩展"的。在考虑设计变更之前阅读本文,这样你就能知道哪些方案已经尝试过了。
横向对比#
| 维度 | claude-code-leaked | opencode | hermes | OpenProgram |
|---|---|---|---|---|
| 是否存在按 agent 的门控 | ✅ | ✅ | 部分(仅 channel 级别) | ✅ |
| 机制 | 按类型的显式列表 | 单个 permission: Ruleset |
YAML adapter 配置 | 按类型的列表 + fnmatch 通配符 |
| 通配符 | ✗ 仅精确名称 | ✅ glob 模式 | ✗ | ✅ fnmatch |
| 被门控的类型 | tools、skills、mcpServers、hooks | 统一的模式空间(tools:*、mcp:*、…) |
platform adapter | tools、skills、mcp |
| 必需依赖 | requiredMcpServers |
(通过默认 deny) | 无 | mcp.required |
| 插件门控 | 仅信任级别 | 信任级别 + permission ruleset | manifest 权限 | 信任级别(host 级别,非按 agent) |
claude-code-leaked#
来源:references/claude-code-leaked/src/tools/AgentTool/loadAgentsDir.ts:75-100
const AgentJsonSchema = z.object({
description: z.string(),
prompt: z.string(),
tools: z.array(z.string()).optional(), // 白名单
disallowedTools: z.array(z.string()).optional(), // 黑名单
skills: z.array(z.string()).optional(), // 需预加载的名称
mcpServers: z.array(AgentMcpServerSpecSchema).optional(),
requiredMcpServers: z.array(z.string()).optional(), // 模式;缺失 = 不可用
hooks: HooksSchema().optional(),
permissionMode: z.enum(PERMISSION_MODES).optional(),
...
})
模式:每种扩展类型都有自己的列表字段。列表是精确名称——没有通配符。阅读很容易("这个 agent 用了这些工具"),但在覆盖宽泛场景时书写很啰嗦。
它更进一步的地方:
mcpServers既可以是对一个现有 server 的引用("slack"),也可以是一个内联定义——agent 可以自带 MCP 配置,而无需全局注册。requiredMcpServers在缺失时会让整个 agent 不可用 —— 本设计以mcp.required的形式采纳。hooks是按 agent 的 —— 在 agent 启动时注册 session 作用域的 hook。OpenProgram 只有openprogram/plugins/hooks.py的全局 hook 分发,没有按 agent 的作用域。
opencode#
来源:references/opencode/packages/opencode/src/agent/agent.ts:31-50 + src/permission/index.ts:138-184
Info = Schema.Struct({
name, description, mode, model, prompt,
permission: Permission.Ruleset, // 单个字段门控一切
...
})
// Ruleset = {pattern, action: "allow" | "deny"} 的列表
// 针对 "tools:bash"、"mcp:slack/*" 这类权限键进行求值
模式:每个 agent 一份 ruleset,按 glob 匹配带命名空间的权限键(tools:、mcp:、skills:)。每条规则是一个 {pattern, action} 对;首条匹配生效。
它更进一步的地方:
- 单一事实来源——新增一种扩展类型只需选定一个命名空间前缀(
prompts:*);无需改 schema。 - 模式组合——一条规则就能表达在按类型方案里需要好几条才能表达的内容(
{pattern: "mcp:*", action: "deny"}会禁用所有 MCP)。 - 权衡:更抽象——用户必须学习模式语法。
为什么不采用 opencode 风格:tools 已有按类型的 tools.disabled,skills 与 mcp 沿用同一形态。迁移到单一 ruleset 是纯重构,除语法上的收益之外没有新能力。若将来新增第 4 种被门控的类型,值得重新考虑。
hermes#
来源:references/hermes-agent/plugins/platforms/*/plugin.yaml
Hermes 以 platform adapter 为中心(Discord、Slack、ntfy、…)。每个 adapter 都附带一个带权限的 plugin.yaml,但它是 channel 级别的(该 adapter 在网络上被允许做什么),而非 agent 级别的(该 agent 角色被允许做什么)。没有与 agent-profile 门控对应的东西。
从 hermes 采纳的:这一层没有采纳任何东西。
设计理由#
本模型沿用 claude-code 的按类型字段形态,因为:
AgentSpec.skills.disabled与AgentSpec.tools.disabled本就存在,延续这一形态没有额外成本。- 这种形态是自解释的(
tools.disabled准确地说明了它的作用)。 - 往同一个结构体里加
allowed/categories/required是机械化的工作。
fnmatch 通配符叠加在这一形态之上,因为:
- 实现起来很简单(单个辅助函数
match_any)。 - 在不放弃"每类型一个字段"清晰性的前提下,解决了纯列表方案啰嗦的问题。
- 向后兼容——精确名称是 fnmatch 的平凡情形。
最终形态是 claude-code 骨架加 opencode 通配符。如果未来某个框架引入了一个有实质差异的模型,回头看本文并考虑迁移。