别再盯着终端等 AI:Claude Code Hooks 和 OpenCode Plugins 实战

从 Claude Code Hooks 到 OpenCode Plugins:AI Coding Agent 的可扩展机制实践

项目案例:Claude Code Sentinel,一个原生 macOS 审批浮窗工具,用来接管 Claude Code 的权限确认、多选问题和任务完成通知。

GitHub 地址:github.com/hlongc/clau...

如果你也经常让 Claude Code 在后台跑任务,却回来才发现它卡在权限确认上,这个项目就是为这个场景做的。本文会借它讲清楚 Claude Code Hooks 和 OpenCode Plugins 这两套扩展机制。

最近越来越多人开始深度使用 AI Coding Agent。刚开始大家关注的是模型效果、上下文长度、代码生成质量,但用久之后会发现,真正影响日常体验的还有另一层能力:Agent 能不能被工程化地接入我们的工作流

比如:

  • 执行危险命令前,能不能接入自定义审批?
  • 改完文件后,能不能自动跑格式化或测试?
  • Agent 卡在权限确认时,能不能弹通知提醒我?
  • 多个终端任务同时跑时,能不能知道是哪一个项目在等我?

这些能力背后,靠的不是提示词,而是 Agent 产品提供的扩展机制。

本文主要聊两个机制:

  • Claude Code 的 Hooks
  • OpenCode 的 Plugins

最后会用我们做的一个 macOS 小工具作为实现案例,看看这些机制怎么落到真实项目里。

为什么需要 Hook / Plugin

AI Coding Agent 的核心循环大概是:

text 复制代码
用户输入需求
-> 模型规划下一步
-> 调用工具,比如读文件、改文件、跑命令
-> 拿到结果
-> 继续推理
-> 最终回复

如果这个循环完全封闭,用户只能通过 prompt 间接影响 Agent。

但工程场景里,我们经常需要确定性的控制:

text 复制代码
在工具执行前拦截
在工具执行后检查
在权限请求时接入审批
在任务结束时发通知
在会话出错时记录日志

这些事情不适合只靠"请你记得做"。更好的方式是:Agent 在关键生命周期点暴露事件,我们把自己的程序挂进去。

这就是 Claude Code Hooks 和 OpenCode Plugins 要解决的问题。

Claude Code Hooks 是什么

Claude Code Hooks 是一种事件驱动机制。Claude Code 在特定时机触发 hook,然后执行你配置的命令。

最典型的数据流是:

text 复制代码
Claude Code event
-> 调用本地命令
-> 通过 stdin 传入 JSON
-> 命令处理后通过 stdout 返回 JSON
-> Claude Code 根据结果继续、拒绝或修改行为

配置通常放在 Claude Code settings 里,例如 ~/.claude/settings.json 或 managed settings。

一个简化配置可能长这样:

json 复制代码
{
  "hooks": {
    "PermissionRequest": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "/opt/homebrew/bin/claude-code-sentinel permission-request"
          }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "/opt/homebrew/bin/claude-code-sentinel stop",
            "async": true
          }
        ]
      }
    ]
  }
}

这里有几个关键点:

概念 含义
event 触发时机,比如 PreToolUsePermissionRequestStop
matcher 匹配工具或事件,比如只匹配 EditBash
command 实际执行的本地命令
stdin JSON Claude Code 传给 hook 的上下文
stdout JSON hook 返回给 Claude Code 的决策

Claude Code 常用 Hook 事件

实际开发里,最常用的是这些事件。

Hook 触发时机 典型用途
PreToolUse 工具执行前 拦截危险命令、限制读写路径、修改 tool input
PostToolUse 工具执行后 自动格式化、记录审计日志、跑轻量检查
PermissionRequest Claude Code 即将弹权限确认时 自定义审批 UI、远程审批、自动批准部分规则
Notification Claude Code 需要用户注意时 系统通知、声音提醒、IM 推送
Stop 当前响应结束时 任务完成通知、自动跑测试、汇总结果
StopFailure 响应失败时 失败通知、错误日志收集

其中 PermissionRequestPreToolUse 都能参与权限控制,但语义不完全一样。

PreToolUse 更偏"工具执行前的通用拦截"。

PermissionRequest 更偏"Claude Code 原本就要问用户权限时,我来接管这个问题"。

Claude Code Hook 的决策返回

以权限审批为例,Claude Code 会把工具名、参数、权限建议等信息传给 hook。

我们的程序可以返回类似这样的 JSON:

json 复制代码
{
  "hookSpecificOutput": {
    "hookEventName": "PermissionRequest",
    "permissionDecision": "allow"
  }
}

拒绝时可以返回:

json 复制代码
{
  "hookSpecificOutput": {
    "hookEventName": "PermissionRequest",
    "permissionDecision": "deny",
    "permissionDecisionReason": "This command is not allowed."
  }
}

如果希望"这次允许,并记住一条规则",也可以带上 updatedPermissions

这类能力非常适合做团队安全策略,例如:

text 复制代码
允许 git status
允许 npm test
执行 rm、curl | bash、写入系统目录时必须人工确认

Claude Code Hooks 的优点和限制

Claude Code Hooks 的优点很明显:

优点 说明
简单 本质是命令行程序,任何语言都能写
稳定 stdin / stdout JSON 协议清晰
可阻塞 某些 hook 可以阻止工具执行
易集成 Shell、Swift、Node、Python、Go 都可以接
适合本地工具 特别适合通知、审批、安全检查

但也有一些边界:

限制 说明
只能处理 Claude Code 暴露的事件 已经运行中的子进程交互提示不属于 hook
async hook 不能控制行为 异步 hook 适合通知,不适合审批
不同事件返回结构不同 PermissionRequestPreToolUse 等字段要分别处理
UI 要自己实现 Claude Code 只负责调用 hook,不负责你的外部 UI

所以 Hooks 更像一个"确定性执行点"。它给你控制权,但具体体验要自己做。

OpenCode Plugins 是什么

OpenCode 的扩展方式和 Claude Code 不一样。

Claude Code 更像:

text 复制代码
配置一个 command hook
-> 事件触发时执行外部命令

OpenCode 更像:

text 复制代码
加载一个 JS/TS plugin
-> plugin 订阅 OpenCode 内部事件
-> plugin 直接调用 OpenCode SDK 或修改事件输出

OpenCode 官方支持把插件放在:

text 复制代码
~/.config/opencode/plugins/
.opencode/plugins/

也支持通过 opencode.jsonplugin 字段加载 npm 包:

json 复制代码
{
  "$schema": "https://opencode.ai/config.json",
  "plugin": ["my-opencode-plugin"]
}

一个最小插件大概长这样:

js 复制代码
export const MyPlugin = async ({ project, client, $, directory, worktree }) => {
  return {
    "tool.execute.before": async (input, output) => {
      if (input.tool === "bash") {
        console.log("about to run bash:", output.args.command)
      }
    }
  }
}

OpenCode 插件事件

OpenCode 的事件覆盖面比较广,常见的有:

事件 用途
permission.asked 权限请求出现
permission.replied 用户已经回复权限请求
tool.execute.before 工具执行前
tool.execute.after 工具执行后
session.idle 会话进入空闲,通常表示当前轮结束
session.error 会话出错
message.updated 消息更新
file.edited 文件被修改
tui.toast.show TUI toast 提示
shell.env 注入 shell 环境变量

这套机制比 command hook 更"应用内"。插件能拿到 OpenCode 的上下文对象,也能使用 OpenCode SDK client。

OpenCode 的权限模型

OpenCode 通过 permission 配置控制工具权限。

例如:

json 复制代码
{
  "$schema": "https://opencode.ai/config.json",
  "permission": {
    "edit": "ask",
    "bash": "ask"
  }
}

权限值通常有三种:

含义
allow 直接允许
ask 执行前询问用户
deny 禁止执行

OpenCode 的审批结果通常是:

结果 含义
once 只允许本次
always 本会话内记住类似规则
reject 拒绝

这和 Claude Code 的 allow / deny / updatedPermissions 很接近,但不是同一套协议。

Claude Code Hooks 和 OpenCode Plugins 的对比

维度 Claude Code Hooks OpenCode Plugins
扩展形态 外部命令 JS/TS 插件
配置入口 Claude settings opencode.json 或 plugins 目录
数据传递 stdin / stdout JSON 函数参数、SDK client、事件对象
权限接入 PermissionRequestPreToolUse permission.askedpermission.replied
通知接入 NotificationStopStopFailure session.idlesession.error
语言自由度 任意语言 主要是 JS/TS
部署方式 安装一个二进制或脚本 安装插件文件或 npm 包
控制粒度 强事件边界,协议清晰 更贴近 OpenCode 内部运行时
适合场景 本地命令、审批、安全策略、通知 深度集成、工具扩展、运行时增强

一句话总结:

text 复制代码
Claude Code Hooks 像"外部自动化接口"。
OpenCode Plugins 像"运行时扩展模块"。

实现案例:用 Sentinel 做 macOS 审批浮窗

我们做的 Claude Code Sentinel 是一个很小的 macOS 原生工具。

它解决的问题是:Claude Code 跑任务时经常停在权限确认、问题选择或任务完成提醒上。如果用户已经切到浏览器、文档或其他 IDE,很容易过很久才发现任务卡住了。

Sentinel 做的事情是:

text 复制代码
Claude Code hook 触发
-> Sentinel 收到 JSON
-> 在 macOS 右上角显示原生浮窗
-> 用户点击 Yes / No
-> Sentinel 把结果返回给 Claude Code
-> Claude Code 继续执行或拒绝

支持的场景包括:

场景 对应 Claude Code Hook
权限审批 PermissionRequest
多选问题 PreToolUse + AskUserQuestion
普通通知 Notification
任务完成 Stop
任务失败 StopFailure

Sentinel 的核心设计

1. 用 Swift 写原生 macOS UI

因为目标是 macOS 桌面浮窗,所以主程序使用 Swift + AppKit。

这样有几个好处:

  • 不依赖 Node.js 运行时
  • 不受用户当前 nvm / pnpm / bun 环境影响
  • 可以直接调用 macOS Notification Center
  • 可以做真正的浮窗、置顶、拖动、文本换行

2. Hook 输入统一格式化

Claude Code 传入的 tool input 不同工具结构不一样。

例如 Bash 关注:

json 复制代码
{
  "tool_name": "Bash",
  "tool_input": {
    "command": "npm test",
    "description": "Run test suite"
  }
}

Edit 关注:

json 复制代码
{
  "tool_name": "Edit",
  "tool_input": {
    "file_path": "src/App.tsx",
    "old_string": "...",
    "new_string": "..."
  }
}

Sentinel 会把这些结构格式化成适合用户阅读的审批内容。

3. 支持"本次允许"和"记住规则"

弹窗按钮映射大概是:

按钮 Claude Code 决策
Yes allow
Yes, don't ask again allow + updatedPermissions
No deny

这样既能快速通过当前请求,也能把一些重复请求变成会话内规则。

4. 活跃终端不打扰

如果用户当前就在 Claude Code 终端里,Sentinel 不应该再弹一个桌面浮窗抢焦点。

所以它会判断:

  • 当前前台 app 是不是终端类应用
  • 当前窗口标题是否像同一个 Claude Code 会话
  • 系统最近是否有键盘或鼠标输入

如果用户正在操作终端,就保持安静,让 Claude Code 原生交互继续处理。

5. 避免重复提醒

有些场景下,任务刚刚 Stop 完成,Claude Code 过一会又发 idle_prompt,表示"等待用户继续输入"。

从 hook 语义上这是合理的,但用户体验上会变成:

text 复制代码
刚收到完成通知
-> 过一分钟又收到等待输入通知

Sentinel 里对同一 session 做了短时间抑制,避免这种重复噪音。

如果要支持 OpenCode,怎么做

OpenCode 不能直接复用 Claude Code 的 command hook 协议。

更合理的方案是做一个 OpenCode plugin bridge:

text 复制代码
OpenCode permission.asked
-> plugin 调用 sentinel opencode-permission
-> Sentinel 显示 macOS 审批浮窗
-> 用户选择 once / always / reject
-> plugin 把结果 reply 回 OpenCode

完成通知则是:

text 复制代码
OpenCode session.idle
-> plugin 调用 sentinel opencode-notification
-> Sentinel 发系统通知

也就是说,未来可以把 Sentinel 拆成两层:

text 复制代码
UI / 通知 / 活跃检测层
Claude Code adapter
OpenCode adapter

Claude Code adapter 负责 stdin/stdout JSON。

OpenCode adapter 负责 plugin 事件和 OpenCode SDK。

这样 UI 能复用,Agent 接入层可以扩展。

工程上的几个经验

1. 不要把 Hook 当成万能终端监听器

Hook 只能处理 Agent 暴露出来的生命周期事件。

如果 Claude Code 或 OpenCode 启动了一个子进程,而这个子进程自己在终端里问:

text 复制代码
Continue? [y/N]

这类交互不一定会进入 hook 或 plugin 权限流。要处理这种问题,通常需要 PTY wrapper,而不是普通 hook。

2. 审批内容一定要可读

权限系统最大的问题不是"能不能拦",而是"用户能不能看懂自己在批准什么"。

不要只显示:

text 复制代码
Tool: Edit

应该尽量展示:

text 复制代码
Do you want to edit App.tsx?

File:
src/App.tsx

Input:
old_string: ...
new_string: ...

用户看懂了,审批才有意义。

3. 保留用户已有配置

无论是写 Claude Code settings,还是写 OpenCode opencode.json,都要保留用户已有配置。

比如用户本来有 MCP:

json 复制代码
{
  "mcp": {
    "feishu-mcp-pro": {}
  }
}

安装工具时只应该追加 hooks/plugin,不应该覆盖整个文件。

4. 调试日志非常重要

Hook 和 plugin 都属于"被 Agent 调用"的代码。出问题时用户很难第一时间看到 stdout/stderr。

所以一定要写日志,例如:

text 复制代码
~/Library/Logs/ClaudeCodeSentinel/hooks.log

日志里记录:

  • event 类型
  • tool 名称
  • project
  • session
  • 前台 app
  • 是否 suppress
  • 用户选择结果

这对排查重复弹窗、没弹窗、错误审批非常关键。

小结

Claude Code Hooks 和 OpenCode Plugins 本质上都在解决同一个问题:

text 复制代码
把 AI Coding Agent 从一个封闭黑盒,变成可以接入工程系统的运行时。

但它们的设计取向不同:

text 复制代码
Claude Code Hooks 更像命令行自动化接口。
OpenCode Plugins 更像应用内运行时插件。

如果只是做通知、审批、安全拦截,Claude Code Hooks 非常直接。

如果要做更深的 OpenCode 集成,比如订阅事件、扩展工具、修改运行时行为,OpenCode Plugins 更自然。

对团队来说,理解这些机制的价值不只是"写几个脚本",而是可以把 AI Agent 纳入已有工程规范:

  • 权限审批
  • 安全边界
  • 日志审计
  • 自动检查
  • 团队默认配置
  • 多工具统一体验

我们现在做的 Sentinel 只是一个很小的例子:用原生 macOS 浮窗接管 Claude Code 的权限确认和任务通知。后续如果接 OpenCode,也会沿着同一个思路扩展成多 Agent 的审批与通知层。

如果你对这个方向感兴趣,可以看下文中提到的实现案例:

Claude Code Sentinel: macOS approval popovers for Claude Code

参考资料:

相关推荐
OpenTiny社区2 小时前
一行命令添加 AI 对话入口!TinyRobot 也太省事了~
前端·vue.js·ai编程
麦哲思科技任甲林2 小时前
小步重构:从 Flash 提示到 Toast 组件的演进
重构·ai编程·skills
coderwei1233 小时前
从OpenAI到Strip:用六大支柱读懂Harness Engineering的生产实践
python·ai·ai编程
wuhen_n3 小时前
AI Agent 入门:从零实现 LangChain 基础智能体
前端·langchain·ai编程
悟空码字3 小时前
用代码智能体写了三个月代码,总结了这5个避坑指南
ai编程
人月神话-Lee4 小时前
【图像处理】图像直方图——从“频率分布“到“智能决策“
图像处理·人工智能·ios·ai编程·swift
wuhen_n4 小时前
LangChain 自定义 Tool 封装:打造专属 AI 能力工具集
前端·langchain·ai编程
guyoung4 小时前
BoxAgnts 运行时(4)——要能力安全,不要 Root 权限
agent·ai编程
FelixBitSoul4 小时前
AI Coding 方法论与实战指南(2026 增强版)
人工智能·ai编程·vibecoding