Claude Code Mods 深度解析:能画界面、能拦工具调用的插件新形态
Claude Code Mods 深度解析:能画界面、能拦工具调用的插件新形态
Skill 教模型做事,MCP 给模型接工具,Mods 改的是工具本身。
今天日报里有一条新闻:DeepSeek Harness v0.2.1-alpha.1 加了 Claude Code Mods 兼容层,作者崔添翼说这是一次"验证"——验证 Claude Code Mods 提供给插件作者的扩展能力,是否大致是 DSH"一切皆插件"架构能力的子集。看完官方文档我大概理解他为什么这么说了:Mods 不是"再给 Claude 加个工具",而是把 Claude Code 这个工具本身变成可编程的。
官方对它的定义是一句话:Mod 是一个改变 Claude Code 外观与行为的插件,由 JavaScript 或 TypeScript 的事件处理函数组成;当工具调用、提示词提交、界面绘制等事件发生时,Claude Code 调用你的函数,你可以观察它、改写它,或者直接接管它。
关键是最后半句——这些函数跑在 Claude Code 自己的进程里。已有的 settings hooks(在 settings.json 里配的 shell 命令 / HTTP 请求 / prompt)是从外面执行脚本;Mod 是从里面改行为。这一个字的差别,撑开了完全不同的能力边界。
本文提纲
- 和 Skill、MCP、settings hook 的分工
- 能做什么:从画界面到接管工具调用
- 事件与 API:可编程的边界
- 权力与信任:它是代码,且不在沙箱里
- 多 Mod 共存:中间件链、执行顺序与失败语义
- 限额、可用范围与生态信号
和 Skill、MCP、settings hook 的分工
官方给了一张很清楚的对照表,我按中文习惯整理(术语保留英文):
| Mod | Settings hook | Skill | MCP server | |
|---|---|---|---|---|
| 它是什么 | 插件里的一组函数,由 Claude Code 在自己进程内调用 | 生命周期事件触发的 shell 命令 / HTTP 请求 / prompt | 一份 SKILL.md 指令文件 |
提供工具的外部进程或服务 |
| 能改变什么 | 工具调用、提示词、命令、整轮对话,以及界面画什么 | 工具调用或提示词是否放行、工具参数与返回值、给 Claude 追加的上下文 | Claude 知道什么、怎么做 | Claude 有哪些工具 |
| 能在界面里画东西 | 能 | 不能 | 不能 | 不能 |
| 你要写什么 | JavaScript / TypeScript | 脚本 + settings.json 配置 |
Markdown | 任意语言的服务端 |
| 什么时候选它 | 想要一个面板、prompt 上方的状态带、自定义命令,或想改写事件 | 想用现成脚本拦截、放行、记录事件 | 你老是把同一段指令粘进对话 | Claude 需要访问外部系统 |
一句话总结三者关系:Skill 决定 Claude 怎么想,MCP 决定 Claude 能碰到什么,Mod 决定 Claude Code 这个程序本身长什么样、怎么运行。 而且三者不互斥——一个插件可以同时包含 Mod、Skill 和 MCP server。
能做什么:从画界面到接管工具调用
官方在 overview 里列了五类别的机制做不到的事:
- 画出可交互界面:transcript 旁边的 pane(面板),或 prompt 上方的 band(状态带),里面可以有 tab、按钮、输入框。
- 重绘 Claude Code 自己画的东西:比如替换或改造工具调用那一行、spinner、提问对话框。
- 介入工具调用或请求:把一次工具调用挂住去问用户、不执行工具直接给答案、或者把某个请求发给另一个模型。
- 注册自己的命令:
/command立刻执行你的函数,不产生一次 Claude turn,Claude 正在忙的时候也能用。 - 在多个 hook 之间共享状态:同一个模块里的 hook 共享变量——一个 hook 记录数据,另一个负责显示。
第 5 条用官方那个最小的完整例子最能说明问题。整个 Mod 只要三个文件:
first-mod/
├── .claude-plugin/
│ └── plugin.json
└── hooks/
├── hooks.json
└── register.jsregister.js 里注册两个 hook,统计 Claude 调用了多少次工具,并把计数加到 spinner 后面:
// The count, shared by the two hooks below
let calls = 0
// Claude Code calls this once when the mod loads
export function register(on) {
// Runs each time Claude is about to use a tool
on('tool.call', async ($, e, next) => {
calls += 1
// Ask Claude Code to draw the interface again, so the new count shows
$.ui.invalidate('ui.render')
// Let the tool run as usual
return next(e)
})
// Runs each time Claude Code draws the spinner
on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
// Keep Claude Code's spinner, with the count added after its word
return next({ ...e, props: { ...e.props, suffix: ' · tool calls: ' + calls + '…' } })
})
}效果是 spinner 上显示 Thinking · tool calls: 3…。注意三个细节:hook 签名是 ($, e, next)——$ 是 mods API 入口,e 是事件,next 是把控制权交给下一个 handler(最后会交给 Claude Code 自己的行为);ui.render 里的 { component: 'Spinner' } 是事件过滤,只在你关心的组件渲染时触发;而 next({...e, props: {...}}) 是"改写后继续",不是"取代"。
事件与 API:可编程的边界
官方文档里能看到的事件覆盖了会话与界面的关键节点:
session.start turn.start turn.step turn.complete
tool.call tool.check prompt.context prompt.submit
prompt.section skill.prompt ui.render result.usage和事件配套的 mods API 分几个命名空间(写法都是 $.xxx):
界面与状态:$.ui.invalidate / $.ui.log / $.ui.status / $.ui.toast $.store
命令与工具:$.command.register / $.tool.register
模型: $.model.complete / $.model.fork
调度: $.clock.after / $.clock.every / $.clock.now / $.clock.sleep
系统: $.fs.list / $.fs.read / $.process.run / $.http.fetch / $.env / $.settings
会话: $.session.send / $.agent.list / $.prompt.submit $.mcp对每个事件,hook 有三种处理方式:观察(记录后原样放行)、改写(改完再往下传)、接管(自己处理,后续行为不执行,比如直接拒绝一条命令)。
这里有一条设计上的关键点:hook 想做任何"自己代码之外"的事——画界面、注册命令、调模型、读文件、起进程、发网络请求——都必须经过 mods API。 没有别的路径。这正是 claude plugin validate 能在你不运行它的情况下,列出这个 mod "想干什么"的前提。
权力与信任:它是代码,且不在沙箱里
这一段是我认为最需要被更多人看到的。官方的警告很直白:
A mod is code that runs with your permissions.
一旦加载,一个 Mod 可以:
- 以你的身份操作机器:在你能读写的任何位置读写文件、启动程序、发起网络请求;
- 读你的密钥:环境变量和设置文件,包括你放在里面的 API key;
- 看到你的会话:你发的每一条 prompt、Claude 的每一次工具调用;
- 改变你的会话:改写 prompt 或工具调用、以你的名义提交 prompt、给你的另一个会话发消息;
- 替你做决定:在你被问之前就把工具调用批准掉;
- 花你的额度:用你套餐或 API key 调模型。
而且——Mods 不在沙箱里。就算你开了 sandboxing,那个沙箱隔离的是 Claude 执行的 Bash 命令;Mod 自己起的进程跑在沙箱外面。
有个细节挺耐人寻味:Mod 可以给 Claude Code 的界面做大量重绘,但改不了权限提示本身,不能改变权限提示给你看的内容。这等于在 UI 层给"权限确认"留了一块不可篡改的自留地。
安装前的审计方式是命令行校验:
claude plugin validate ./some-mod输出里的 hooks: 和 calls: 两行会告诉你这个 mod 处理哪些事件、要求 Claude Code 做什么(读文件、发请求……),不用运行它。
想关掉也有三层粒度,从细到粗:
- 单个 mod:在
/plugin的 Installed 标签里禁用它或卸载插件; - 本次会话全部禁用:用
--safe-mode启动(会连带禁用你其它的定制); - 所有会话全部禁用:在
~/.claude/settings.json里设"disableAllHooks": true(你的 settings hooks 和自定义 status line 也会停,但组织管理的仍会运行)。
组织还可以用 allowManagedModsOnly 之类的受管设置限制用户装哪些 mod。另外两个版本细节:Mods 需要 Claude Code v2.1.287 或更高,且默认开启;早期访问阶段用过的 CLAUDE_CODE_ENABLE_FUNCTION_HOOKS 环境变量在这个版本之后被忽略——设成 0 并不能关掉它。
多 Mod 共存:中间件链、执行顺序与失败语义
这是工程上最有意思的部分,也是最容易踩坑的地方。同一个事件上的多个 hook 组成一条中间件链:每个 mod 的 next 调用链上下一个 hook,最后一个 next 落到 Claude Code 自己的行为。第一个 mod 在最外层——它先看到事件、后看到结果,并且它决定后面的 mod 到底跑不跑;反过来,靠后的 mod 无法阻止靠前的 mod 看到事件。
顺序不是随机的,官方按来源固定:
- 内置守卫
sec-default@builtin、组织在prependPlugins里列的 mod、以及算作组织的其它 mod(不在appendPlugins里的); - 你自己安装的 mod;
- 组织在
appendPlugins里列的 mod; - Claude Code 其它内置 mod。
你安装的 mod 之间,dependencies 里声明的前置会先跑;同一个模块内,按 register 调用 on 的顺序。
settings hooks 也被插在这条链的固定位置:受管设置里的 PreToolUse 跑在第一个 mod 的 tool.call 之前,且它的拦截是终局的(任何 mod 都看不到这次调用);其它设置文件和插件 hooks.json 里的 PreToolUse 跑在最后一个 mod 调用 next 之后。而 tool.check 事件在权限规则判定之后触发,所以挂在它上面的 hook 可以批准一个被第二组 hook 拦下来的调用。
失败语义也很明确:没有 .catch 的 hook 抛异常、超时、或返回形状不对时——
- 如果它在调用
next之前失败:Claude Code 跳过它,下一个 handler 顶上; - 如果它在
nextresolve 之后失败:那个结果算数,不会重跑。
会话里会出现一行说明,形如 my-mod: tool.call hook skipped: threw Error: boom。
对于"拦工具调用"这种安全相关的 mod,官方给了一个 fail-closed 的写法——给这一个 hook 单独挂 .catch,失败时返回 deny:
// on returns a registration, and .catch attaches a handler to that one hook
on('tool.call', { tool: 'Bash' }, guard).catch(async ($, e, next) => {
// next.error.kind is 'throw' or 'timeout', which says how guard failed
return { deny: 'The command guard failed, so this command was not run: ' + next.error.kind }
})没有这个 handler 时,guard 失败会被跳过、命令照常执行;加上之后,失败会变成拒绝执行,Claude 会读到带 throw 或 timeout 的原因。这就是"安全组件失败时要向安全一侧倒"的具体实现,而且它有独立的、更短的时间预算(1 秒)。
限额、可用范围与生态信号
限额这张表值得单独看,因为它决定了你能写出什么样的 mod(节选):
| 限制项 | 值 |
|---|---|
一个 hook 处理单个事件的执行时间(不含 next 内和 mods API 调用) |
10 秒;prompt.edit hook 只有 50 毫秒 |
.catch handler 执行时间 |
1 秒 |
$.process.run 超时 |
默认 30 秒,最多 10 分钟 |
$.model.complete 的 maxTokens |
默认 1024,最多 64000 或模型输出上限 |
$.fs.read / $.fs.write 单文件 |
4 MiB |
$.store 总量 |
4 MiB JSON |
$.ui.invalidate('ui.render') 重绘 |
节流 10 次/秒;终端里可见面板、展开的状态带、提示行下方可到 30 次/秒,更快的调用会被合并 |
| 未经用户同意就打开的面板 | 终端宽度 144 列起才放,用户主动打开过一次后降到 110 |
另外两个工程细节值得记:$.store 给你 4MiB 的持久状态,而 $.clock.every / $.clock.after 让你在会话里跑定时任务——这已经是一个小型运行时了。
画界面这件事不是哪里都能用:终端(含编辑器内置终端和 JetBrains 插件)和 Desktop 应用的 Code 标签能显示;VS Code 扩展的聊天面板、claude -p、Agent SDK、云会话里 hook 会跑但界面不显示;WSL 会话里插件不可用,两者都没有。所以一个会画东西的 mod 需要自己判断运行环境,并在画不出来的地方退化成一行文字或命令回复。
生态上的信号也值得留意:
- 一些内置功能本身就是 mod:
/diff面板(cc-plugin-diff)、AGENTS.md加载(cc-plugin-agents-md)、cc-plugin-you-should-know那个"帮你盯着长任务"的旁路 agent,都是 mod;它们的源码公开在anthropics/claude-code仓库的mods/目录里,带 hooks 模块和测试,是学写 mod 最好的范本。 - 官方示例 mod 放在
anthropics/claude-code-playground的claude-code/mods/:token-weather(把上下文占用画成天气预报)、blast-radius(拦住rm -rf、force push 这类命令,展示影响范围再让你选)、replay-theater(加一个/replay命令回放上一轮文件改动)。 - 其他 harness 的反应:DeepSeek Harness 直接做了兼容层来"验证",并公开表示自己的插件架构能力是其超集。这件事反过来解释了 Mods 的定位——它定义的不只是一个功能点,而是一套插件 API 的语义边界:事件有哪些、能改写什么、失败怎么办。谁定义了这套语义,谁就在生态里占了位。
参考链接
- Mods overview(官方) — 本文主要来源,含能力清单与信任警告
- Mods reference — 事件、模块布局、API 方法、渲染位点与全部限额
- React to events with a mod — 观察/改写/接管、事件过滤、中间件链与失败语义
- Use the mods API — 命令、工具、模型调用、定时任务、文件与网络
- Draw in the interface with a mod — pane、band、按钮、输入框与状态保持
- Create a mod — 让 Claude 帮你写,以及开发和热重载循环
- Manage mods for your organization — 受管设置、审计一个 mod、用 mod 执行策略
- Test a mod / Troubleshoot a mod — 无会话自动化测试与"为什么没生效"
- claude-code-playground 示例 mod — token-weather、blast-radius、replay-theater
- Claude Code 内置 mod 源码 — diff、agents-md、sec-default、telemetry
- DeepSeek Harness 加入 Mods 兼容层(IT之家) — 今天日报里的对应新闻与官方回应
- Claude Code 插件文档 — mod 所属的插件体系与 marketplace 机制
你会用 Mods 干什么?给上下文做个仪表盘,还是给危险命令加一道闸?评论区聊聊,觉得有用点个赞让更多折腾 Agent 工具链的人看到。
作者: itech001 来源: 公众号:AI人工智能时代(the-ai-era) 网站: https://www.theaiera.top/ 关注每日最新AI新闻和技术博客,主页有更多的文章的AI 技术参考:https://www.theaiera.top
本文首发于 AI人工智能时代,转载请注明出处。