agent-notifier:让 Coding Agent 跑完一整轮给你推一张卡片

32 阅读 3218 字 · 约 11 分钟

Coding agent 一跑就是几分钟,跑完还静默。你走开一会儿,不知道它到底成功了、失败了、还是卡在等你确认。

agent-notifier 把这种「沉默」变成一张推送卡片——你的 agent 跑完一整轮,标题是这一轮的 prompt,正文是 agent 的最终回复。

为什么做这个

AI 编程助手跟传统 IDE 的工作节奏完全不一样。传统 IDE:写一行看一行,错就立刻报。AI 编程助手:丢出去一个任务,几分钟之后才返回结果,中间过程没有可观察的反馈窗口。

我经常干的事:丢一个中等规模的活给 Claude Code 或者 Kimi Code,然后去做别的事情。中间切到浏览器、Email、Terminal 别的任务。等我回来看的时候,agent 在那干等——它早就出结果了,但屏幕闪一下,光标停在那。我错过去就错过了。

更糟糕的是失败。Agent 跑了几分钟结果失败,光标停了,没人通知我。我不知道是它真的完成了、还是出错了、还是一直在跑,得手动去查。

Bluebird 已经做过一版通知网关,处理 webhook 类事件。但 agent 的通知跟 webhook 不是一回事——它们有自己的生命周期事件,自己的事件格式,得 hook 进 agent 自己的配置里。

于是就有了 agent-notifier 这件事:一个专门给 coding agent 用的通知层。

它解决的核心问题

只有一个:让 agent 的状态变化发到你的手机和 IM 上。

Coding agent 一跑就是几分钟到几十分钟,中间完全静默。agent-notifier hook 进 agent 自己的生命周期事件,agent 跑完一整轮 turn,就推一张卡片出来,卡片标题是这一轮的 prompt(不是会话标题、不是第一条消息),正文是 agent 的最终回复。

实现层面就几件事:

  1. 三个 agent 一个命令——自动识别 Claude Code、Codex CLI、Kimi Code,按它的 hook 机制装进去
  2. 八个渠道——飞书、Lark、Slack、Bark、钉钉、企业微信、PushDeer、Bluebird
  3. 并发推送——装多个渠道一起跑,一个挂了不影响其他的
  4. 幂等安装——重复跑没事,原配置自动备份成 .bak.<ts>
  5. 干净卸载——恢复 agent 配置,备份留着方便比对

跟 Bluebird 的关系

agent-notifier 是 Bluebird 的下游,专门处理 coding agent 的事件通知。两者分工清晰:

  • Bluebird:通用的 webhook → 多渠道网关,处理的是来自 GitHub、自定义 HTTP 等外部系统的入站事件
  • agent-notifier:专门感知 coding agent 的 turn 结束事件,按蓝鸟等渠道的 API 协议发出

简单说:Bluebird 是「通知网关」,agent-notifier 是「agent 端的通知适配器」。两者一起用刚好——agent-notifier 把 agent 事件喂给 Bluebird,Bluebird 转推到任何渠道。

设计上的几个关键决策

为什么是 hook 而不是 daemon

Coding agent 都有自己的 hook 机制(Claude Code 的 settings.json、Codex CLI 的 notify、Kimi Code 的 hooks)。这些 hook 配置是声明式的——某个事件发生时调用某个命令,标准 JSON 走 stdin。

agent-notifier 选择用 hook 而不是跑一个 daemon。原因很简单:hook 是一次性触发的,daemon 得一直跑。daemon 要写健康检查、要管进程生命周期、要管日志轮转——这些都是「通知工具」不该承担的重量。hook 触发一次,干完就结束,没有后台进程需要维护。

代价是每个 agent 都要单独装一次。这点通过「安装幂等、配置可备份可还原」解决了,重装成本几乎为零。

为什么每轮 prompt 当标题

通知的标题最重要——手机锁屏弹出来你能看的就是那一行。

候选有四个:

  • 项目名(不利于区分别的项目的通知)
  • 会话标题(可能很泛,agent 自己定的)
  • 第一条消息(很可能是「帮我看看这个 bug」之类的开场语,不准确)
  • 这一轮 turn 的 prompt(具体、唯一、能反映这次任务在干什么)

选第四个。用户最想知道的是「这次 agent 干的是什么」。一轮的 prompt 准确刻画了这件事。

为什么 hook 进程要静默

Agent 的 hook 调用是同步的——agent 等 hook 返回后才继续。如果 hook 写出日志到 stdout,agent 会以为有正常输出、或者更糟的,把这个输出混入 agent 的回复。

所以 hook 进程严格静默:所有日志走 stderr,只在出错时打印;正常路径一个字符都不写。node CLI 默认会写 usage 横幅,得用 -s 之类的方式抑制掉。

这条规则听起来简单,但每加一个新渠道或者新事件都要重审一遍。静默是不可商量的约束,写代码必须有这个意识。

为什么 fan-out 而不是单一渠道

单渠道配置看着简单:一个 webhook URL、完事。但实际用起来很重——你今天用飞书,明天改 Bark;公司电脑上配的是钉钉,个人手机上想用 Bark;想并行推飞书和 Bark 还要做合并。所有「想换一个」「想加一个」「想同时推几个」都是配置改动。

agent-notifier 选 fan-out:装的时候就把所有可用的渠道都列出来,你勾哪些它就推哪些。任意一个渠道挂了不影响其他的,推送是并发的不是串行的。

代价是配置文件稍微复杂一点——每个渠道一份配置。但配置只装一次,后续用就好。

一些工程上的取舍

Node.js 不用 TypeScript。这是开发者工具,需要在用户的机器上跑。TypeScript 编译产物多了 runtime 依赖,npx 链路也更长。Node.js 直接写,相对笨一点但能跑就行。这个项目大部分逻辑是「读 JSON → 配置 → 调 webhook」,不值得引入构建系统。

安装器是一个 bash 脚本而不是 npm install。原因有两个:1) 用户机器不一定装了 Node,得先检测环境;2) hook 配置要写进 agent 的 settings.json,得直接操作用户的文件系统。bash 脚本干这事最顺手,curl | sh 一行就能跑。

配置走环境变量或参数,不走全局配置文件。Agent 的 hook 调用方式是 stdin 一个 JSON,命令行带几个 channel 名 + webhook URL。每个 channel 的「怎么推」都是模块化的小函数,配置只管 webhook/secret 这种凭据类信息。这避免了「往哪放 token」「怎么加密」之类永远做不好的设计。

谁适合用

符合下面任意一条就值得装:

  • 你用 Claude Code 或 Codex CLI 或 Kimi Code 跑中等以上规模的任务
  • 你需要让 agent 跑完之后通知你(去吃饭、开会、看别的屏幕)
  • 你不想每次都开终端看 agent 是不是还活着
  • 你已经有蓝鸟/Bark/飞书之类的通知渠道

如果是纯浏览器 IDE 用户或者习惯每次都盯着终端看的,可以不装。

接下来

工具已经能稳定跑,覆盖三个 agent + 八个渠道。短期内不打算加新渠道——先把每个渠道的错误处理做扎实、把日志做清楚。

中期计划:

  • 更丰富的卡片格式:现在卡片就是 prompt 标题 + reply 正文,下一步想做 markdown 渲染、代码块折叠
  • 选择性静默:有些场景下(agent 自检类短任务)不想被打扰,加个白名单机制
  • 跨会话汇总:一连串 turn 结束之后给个阶段总结而不是逐条推
  • 更多 agent:Kimi、Codex、Claude 之后,看看要不要支持 Aider、Cline、Continue

长期看,agent 的可观察性是个普遍问题。agent-notifier 做的是「通知」这一片,但「agent 中间过程的可视化」「任务进度的实时跟踪」「失败时的可重试入口」这些都是相关的方向。

相关项目