能挂 hookWorkBuddy 到底行不行?答案是——而且配置和Claude Code 同构。
—— AI约翰

最近社区里有个开源小项目挺有意思:有人给 WorkBuddy 养了一只桌面宠物。WorkBuddy 在思考、跑工具、等你确认还是干完了,屏幕角落这只小柴犬一眼就能看出来;甚至能点它的气泡直接批权限、点一下把窗口拉到最前。

更值钱的是它顺带做了一件事——据作者说,这是目前能看到的第一份 WorkBuddy hooks 实测记录。WorkBuddy 官方还没发过相关文档,这位开发者把「能不能挂 hook、本地会话文件长什么样、怎么在不碰你任何对话内容的前提下感知它的状态」都拆开测了一遍。
这篇文章不聊宠物长得多可爱,聊它背后那个对普通用户和企业都更有用的能力:你的 AI 工作台,其实是可以「接外挂」的。
📌 本文看点
01
实测:能挂 hook,配置同 Claude Code
02
架构:只碰状态、不碰内容
03
进阶与踩坑:双向审批
💡 说明:本项目为社区个人开源作品(MIT),与腾讯 / WorkBuddy 无隶属或背书关系;文中 hook 行为与本地文件结构均为作者实测,非官方承诺,可能随版本变化。
01
ORIGIN



OpenAI 的 Codex 今年内置了「Codex Pets」——一只挂在屏幕上的小动物,思考时转圈、要你确认时举红铃、干完了打绿勾。社区顺势长出一整套生态。
作者平时用 WorkBuddy 跑任务,经常一边等它一边刷别的,回头发现它早停在「等我确认」了。于是想:能不能也给 WorkBuddy 挂一只这样的状态宠物?
一只宠物要能反映 agent 的状态,前提是它得能感知 agent 在干什么。Codex 靠的是官方 hooks + 本地会话日志。WorkBuddy 有没有对应的东西?这就是整件事的第一个、也是最关键的未知。
02
TEST
能。而且配置位置和格式与 Claude Code 几乎一模一样——用户级配置在~/.workbuddy/settings.json的hooks 字段。
...json
{
"hooks": {
"UserPromptSubmit": [
{ "hooks": [{ "type": "command", "command": "/path/to/your-hook.sh UserPromptSubmit" }] }
],
"PreToolUse": [
{ "matcher": ".*", "hooks": [{ "type": "command", "command": "/path/to/your-hook.sh PreToolUse" }] }
]
}
}
同源的 CodeBuddy CLI 官方文档列了 26 个事件(PreToolUse / PostToolUse / UserPromptSubmit / Stop / PermissionRequest / Notification / SessionStart …)。但「文档里列了」不等于「WorkBuddy 桌面版真的会触发」——这得实测。
作者写了个探针脚本挂上去,跑一个真实任务(让 WorkBuddy 在小程序工程里改代码),一次任务打满了整条生命周期。下面是实测会触发的事件(节选):
事件 | 触发 | payload 关键字段 |
|---|---|---|
SessionStart | ✅ | session_id / source / transcript_path |
UserPromptSubmit | ✅ | prompt / session_id / permission_mode |
PreToolUse | ✅(一次任务 100+ 次) | tool_name / tool_input / call_id / transcript_path |
PostToolUse | ✅ | tool_name / call_id |
PermissionRequest | ✅ | tool_name / permission_mode |
Notification | ✅ | notification_type(auth_success / idle_prompt) |
Stop | ✅ | last_assistant_message / session_crons |
几个对「做宠物」特别有用的点:
1
每个 payload 都带 transcript_path 和 session_id——等于 WorkBuddy 主动告诉你「该读哪个会话文件、属于哪个会话」,多会话仲裁的路由问题白送。
2
PreToolUse 带 tool_name + tool_input,可以区分「只读工具(Read/Grep)→ 在看」和「写工具(Write/Bash)→ 在改」。
3
Stop 带 last_assistant_message,可以判断「agent 是不是以一个问句结尾」——如果是,说明它在等你回答,宠物应显示「等待」而非「完成」。
除了 hook,WorkBuddy 在~/.workbuddy/下明文落盘了不少东西(同样是第三方实测、非官方承诺):
...text
~/.workbuddy/
├── workbuddy.db # SQLite:sessions 表
├── projects/<编码>/<会话id>.jsonl # 对话转录
└── sessions/<pid>.json # 活跃进程心跳
projects/*.jsonl 里 reasoning 行 = 在思考、function_call 还没配对 function_call_result = 正在执行工具——这是 hook 之外的兜底信号源。
03
ARCH
拿到「能挂 hook」这个地基,整条链路就清晰了:
...text
WorkBuddy ──hook──▶ 隐私投影脚本 ──▶ events.spool(JSONL)
│
tail 事件流 → 状态机(7 态 + 优先级仲裁 + TTL 衰减)
│
Tauri 透明悬浮窗渲染精灵
第一原则:只碰状态,不碰内容。宠物只需要知道「WorkBuddy 在哪个状态」,完全不需要你的 prompt、命令参数、对话正文。所以在最上游的 hook 脚本里就做「隐私投影」——只挑结构字段,正文当场丢掉:
...python
def project(event, payload):
d = payload if isinstance(payload, dict) else {}
return {
"event": event,
"ts": int(time.time() * 1000),
"session_id": d.get("session_id"),
"tool_name": d.get("tool_name"),
"permission_mode": d.get("permission_mode"),
"notification_type": d.get("notification_type"),
"ends_with_question": ends_with_question(d.get("last_assistant_message"))
if event == "Stop" else None,
}
prompt / tool_input / last_assistant_message 这些含内容的字段,一个都不写进磁盘。项目里有一条硬测试专门守这个契约。
状态机核心是一段纯逻辑,把事件映射成 7 个状态,并解决两个现实问题:
...rust
pub enum State { Idle, Thinking, Working, Review, Waiting, Done, Failed }
// 多会话仲裁:failed > waiting > working > review > thinking > done > idle
// 无「会话结束」事件?给每态存活时长(working 3 分、waiting 24 时…)到点淡回 idle
多个会话同时在跑时,宠物显示「最要紧」的那个。没有「会话结束」事件?给每个状态一个存活时长,到点自动淡回 idle。Notification 里那个 idle_prompt(agent 空闲、在等你)就映射成 waiting——这条是靠真实任务的数据才发现的。
04
BOTH WAYS
前面都是单向的(WorkBuddy → 宠物显示)。真正好玩的是双向——WorkBuddy 要跑一条命令、需要你批准时,宠物弹一个「允许 / 拒绝」气泡,你点一下,决定直接回传给 WorkBuddy。
这里有个关键未知:WorkBuddy 会执行 hook 返回的决定吗?作者先挂了个「无脑拒绝」的探针,让它拒掉所有 Bash,然后跑「运行 ls」。结果:命令被拦、agent 收到拒绝理由后改用别的工具绕路——hook 的决定确实被执行了,而且在「不询问(dontAsk)」模式下也生效(hook 决定的优先级高于权限模式)。
确认机制成立后,闭环就是:WorkBuddy 要跑 Bash → hook 阻塞 → POST 到宠物的本地服务 → 宠物弹气泡 → 你点按钮 → 决定回传 hook → WorkBuddy 执行你的选择。
关键设计是 fail-open:宠物没开、你没点、超时了——hook 就静默退出,WorkBuddy 完全当宠物不存在。一个状态指示器永远不该卡死你的正经工作流。
四条没人写进文档的坑(真实血泪)
1
hook 配置在启动时缓存。改完 settings.json 必须完全重启 WorkBuddy 才加载,热改无效。
2
关窗 ≠ 退出。WorkBuddy 有常驻能力,点关闭按钮只是关窗口,进程还活着。得 Cmd+Q 彻底退出,或 pkill -f WorkBuddy.app。
3
dontAsk 模式不产生 PermissionRequest。想测权限相关的 hook,得先把会话权限模式切回「询问 / 默认」。
4
没打开工作目录,任务根本不进 agent。WorkBuddy 需要一个 workspace / 文件夹才会真正跑 agent(也才会写 transcript、发 hook)。
∞
THE END
因为感知层就是「hooks + 本地文件」这套通用机制,这只宠物其实不绑死 WorkBuddy——把 host app 换个名字,就能给 Claude Code、Codex、CodeBuddy 用(它们的 hook 事件命名都对齐 Claude Code 约定)。
如果你也在用 WorkBuddy,欢迎 clone 下来养一只,或者贡献一只你设计的伙伴。仓库在这:github.com/FlashFamily/workbuddy-buddy

— 同一只柴犬的七种状态

— 内置 15 只手绘伙伴,也能自己加
再次声明:个人开源项目,与腾讯 / WorkBuddy 无隶属关系;文中本地文件与 hook 行为为实测记录,非官方承诺,可能随版本变化。
END
我是 AI约翰,热衷于分享 AI 观察与实操干货。
如果你觉得今天这篇有收获,欢迎点赞、在看、转发三连,我们下篇见。