这四种扩展方式是四个完全不同的风险面,所以它们不共用一层权限判定。
| 方式 | 是什么 | 能做什么 |
|---|---|---|
| 技能 Skills | 渐进披露的说明文档 | 只能提供说明,不能提供可调用的工具 |
| MCP | 跨进程协议 | 最小代价也是一个常驻子进程;只在 trusted 模式才可能生效 |
| 插件 Plugins | 同进程 ESM 模块 | 在工作区放一个目录就能加工具——代价是它和宿主跑在同一份地址空间 |
| 钩子 Hooks | 宿主内注册的 JS 函数 | 门禁钩子可拒绝、失败关闭;观察钩子异步、永不改变控制流 |
插件的五条硬规则
一、声明即全集
模块导出了清单里没声明的工具名 → 拒绝加载整个插件。「顺手多给一个工具」是隐式扩权。同样地,清单里写了 tools 能力却没有工具,也会被拒绝——声明与实现不一致,代价是权限面。
二、目录名必须等于插件 id
不一致直接拒绝。否则「谁贡献了这个工具」在审计上无法对齐。
三、同名必须显式让位
插件工具与内置工具重名时,清单里必须显式声明 reject 还是 yield。没有声明就拒绝加载,并如实告诉你和哪个内置工具同名。
没有任何一条路径会静默覆盖内置工具。 让位行为会记进账本,卸载插件后内置工具原样回到工具面。
四、卸载是全量替换
工具彻底消失——不是打禁用标记,也不是留着但拒绝执行。不残留任何未声明的插件工具。
五、开启必须给绝对路径
开了开关却没有目录 → 配置校验直接拒绝,不是静默回退。相对路径会随工作目录漂移,让「加载了哪段代码」变成一个不可审计的问题。
插件拿得到什么
插件工具不自己执行任何东西。它们被注册成执行器的一个选项,只能走既有的工具分支,顺序是:
taskPolicy → preToolGuards → 钩子门禁 → perms.checkPlugin() → 一次性审批 → 插件函数
插件函数只收到一个冻结的极小上下文:{plugin_id, tool, effect, sessionId, taskId, signal}。
插件不是权限沙箱。API 保证的是「插件工具的执行经过既有审批路径」,不是「插件代码被隔离」——它是宿主同进程的代码,拿到上下文之后仍然可以使用 Node 的 import 与 process。所以 safe 模式一律拒绝,且默认关闭。请只把你自己信任的代码放进插件目录。
为什么默认关闭
关闭时,程序不读插件目录(连一次 existsSync 都不做)、不加载任何模块、工具面逐位不变。开启后可以随时在设置里改回来,开关立即生效。
单个插件加载失败不会影响其它插件——一个写坏的不该拖垮全部。