我的飞书通知体系:从 dev 运维到 AI 额度管理
在上一篇《聊聊我的 AI 开发工作流:从想法到 dev 验收》里,我写了自己怎样和 AI 一起开发、部署和验证个人项目。这篇接着聊另一件事:项目运行起来之后,我怎么知道它现在怎么样。
服务状态在监控面板里,发布结果在 GitHub Actions 里,AI 编程工具的额度又在另一套界面里。如果每一项都要主动打开检查,开发过程中就会多出很多来回切换。
我把需要关注的信息接到了飞书。现在打开消息列表,主要会看到四个群:Dev巡检、上游服务状态、AI 成本&额度、Dev 基础设施与发布。一个群告诉我服务器里发生了什么,一个群跟踪外部依赖,一个群汇总 AI 额度,另一个群接收发布结果和基础设施告警。
这些消息来自不同的程序:有的是服务定时检查后产生的,有的是流水线结束时发出的,还有一些先记录下来,到固定时间再汇总。下面就从这四个群出发,讲讲它们各自做什么,以及消息是怎么到达飞书的。
文中的通知截图取自我的飞书独立聊天窗口,保留了原始卡片和消息内容。日期、状态和数值对应截图中的历史记录。
1. 先分清楚,每个群要回答什么问题
我按消息对应的处理方式来分群。
| 飞书群 | 主要来源 | 我打开它时想知道什么 |
|---|---|---|
| Dev巡检 | WatchMend 的内部巡检、Codex 任务通知和重置事件监控 | 哪个服务出现异常,哪个任务需要我介入,有什么开发相关事件需要关注 |
| 上游服务状态 | WatchMend 的厂商状态监控、Diun 镜像更新摘要 | 外部服务是否异常,我使用的基础镜像有什么更新 |
| AI 成本&额度 | Mac 上运行的 ai-cost-hub | 订阅额度和账户余额还剩多少,是否需要调整接下来的使用安排 |
| Dev 基础设施与发布 | Uptime Kuma、GitHub Actions | 服务是否可达,这次部署结果如何,应该去哪里查看详情 |
例如,镜像发布了新版本,我通常会集中查看更新说明;某个服务持续不可达,则需要尽快排查。把它们放进不同的群,查看消息时就更容易判断优先级。
这里还有一个容易混淆的地方:群名、机器人名称、实际产生消息的程序,是三个不同的东西。
一个群可以接收多个程序的消息,同一个 WatchMend 也可以把不同类型的信息发到两个群。比如 Diun 的更新摘要发到“上游服务状态”,但它由独立脚本生成,并不经过 WatchMend 的巡检状态机。
2. 消息背后有几条独立的链路
从整体上看,我现在的通知来源可以画成下面这样。
这些链路共用飞书作为阅读入口,各自保留自己的采集和判断逻辑:
- WatchMend 处理内部巡检和上游状态,并根据消息类型选择飞书 Webhook。
- Diun 与汇总脚本 跟踪镜像变化,先保存事件,再发送更新摘要。
- ai-cost-hub 在我的 Mac 上读取 CodexBar 数据,通过飞书 CLI 发送消息。
- Uptime Kuma 在监控状态变化时发出可用性通知。
- GitHub Actions 在部署任务结束后发送执行结果。
这样的组织方式,也让我可以单独调整某一类通知。例如修改额度告警阈值,只需要修改 ai-cost-hub;调整镜像更新的汇总时间,则修改 Diun 相关调度。
3. Dev巡检:把异常和需要介入的任务交给我
3.1 WatchMend 负责收集和判断
WatchMend 是我用于个人 dev 环境的运维哨兵。它可以从几个角度观察服务:HTTP 探针检查接口,Docker 提供容器状态,Prometheus 提供资源指标,Loki 提供日志线索。
一次检查发现异常之后,还需要判断是否达到通知条件。例如,某个接口短暂请求失败,与一个容器持续不可用,处理方式应该有所区别。
内部巡检的基本过程是:
采集服务状态、指标和日志
↓
按规则判断异常是否成立
↓
结合已有事件判断:新异常、持续异常、恢复
↓
按紧急程度选择实时通知或摘要
↓
发送到 Dev巡检服务不可用、容器异常、资源耗尽或监控数据源持续故障等情况,在达到相应规则条件后,会进入实时通知路径。当前 dev 配置也开启了非紧急消息汇总,将这类信息放进早晚摘要,便于集中查看。
内部体检日报的可见部分:服务可用率、p95 延迟、探针统计和未决事件放在同一张卡片中。图中保留了当时的异常项;这些是监控观察结果,具体原因仍需排查,已经下线的服务也需要同步维护监控清单。
这样我收到一条告警时,可以沿着服务名称、异常时间和相关证据继续排查。需要更详细的信息,再打开监控面板或日志系统。
WatchMend 还提供可选的 LLM 解释和只读调查能力。它与确定性告警规则分开配置:规则负责判断什么时候应该通知,模型可以帮助归纳证据、解释可能的原因。是否发送严重故障告警,不依赖模型临时作判断。
3.2 Codex 任务通知为什么要等一会儿
Dev巡检里还有一类消息,来自 Codex 的任务生命周期。
AI 执行一个较长任务时,我不一定一直盯着窗口。任务需要审批、需要补充输入、执行失败,或者较长任务已经结束时,飞书通知可以提醒我回来查看。
这条链路是:
Codex 生命周期 Hook
↓
本机 Hook 客户端提交事件摘要
↓
WatchMend 判断是否生成通知候选
↓
等待宽限期,观察候选是否被取消
↓
仍然需要关注时,再推送到飞书我当前 dev 上的宽限期是 15 分钟。如果我本来就在电脑前,已经继续输入,或者审批后的工具已经开始执行,程序能从相应事件中观察到任务继续推进,就可以取消待发送的候选。
这里的判断依据是工具提供的事件。它无法可靠地知道我是否正在看屏幕、是否正在输入一段还没有提交的文字,也不能把每一种界面操作都理解成“已经处理”。
Hook 客户端只负责快速提交经过筛选和脱敏的摘要,等待与发送由服务端处理。普通短问答、单次工具完成和子任务结束,也不需要全部变成飞书消息。
3.3 Codex 重置:从发现信号到确认落地
Dev巡检还有一条我很关注的链路:Codex 重置信号和到账确认。使用 AI 编程工具时,额度变化会影响接下来的任务安排。我希望尽早看到重置消息,也希望分清消息还处于预告阶段,还是已经有了落地依据。
这部分由 WatchMend 中独立的 ResetMonitor 处理。它定期读取重置追踪源提供的 JSON、RSS 和网页记录,整理原始链接、来源、事件类型和时间,再把同一次重置的相关证据关联起来。本机参考账号的只读额度探针,则提供账户侧的核验信息。
这些信息会进入不同的通知阶段:
| 阶段 | 通知表达的含义 |
|---|---|
| 疑似预告 | 已发现重置意图,但类型或时间信息还不完整 |
| 明确预告 | 已识别出符合规则的预告,能够说明重置类型和预计时间 |
| 超过窗口 | 对有明确截止时间的直接重置预告,超过截止时间及宽限期后,仍未得到落地确认 |
| 落地确认 | 已满足对应类型的确认规则,卡片列出确认时间或范围、核验方式和证据数量 |
这些阶段不要求逐一经过。疑似预告之后可能直接确认;此前没有发现预告,也可能收到“静默重置已确认”。只有存在明确时间窗口的相应事件,才有条件判断是否延迟。
重置信号通知的状态部分。这条消息在 2026 年 9 月 8 日 03:26 发出,当时只有 1 条、1 个来源族的证据,类型仍为“待确认”。
当公开消息表述比较含糊时,监控还可以使用模型辅助识别重置意图、翻译摘要。模型识别的结果只补充到“疑似预告”;明确窗口和落地确认仍按代码中的证据规则推进。翻译失败时,原始通知可以照常发送。
3.4 “重置到账”要看清到账的是什么
我在这里区分两种情况。
第一种是直接重置:关注使用额度是否发生了重置。
本机探针通过 Codex app-server 的 account/rateLimits/read 读取额度信息,选择共享 Codex 的周额度窗口,根据窗口长度和结束时间推算起点,并要求连续两次观察一致。这个窗口信号还需要与公开重置记录或正式预告相互印证,才能通过相应的确认规则。单次百分比变化或一个模型专属的空窗口,不能承担同样的证明作用。
下面就是同一天后续收到的确认通知:
落地确认通知的状态部分。卡片记录的确认时间是 2026 年 9 月 8 日 10:00,机器人在 10:58 推送;核验方式为“官方重置记录 + 本机共享 Codex 周额度窗口”。文中时间均按东八区表示。
这两张图能看出一次实际的状态推进:凌晨先提示疑似预告,后续补充证据,上午再发送确认。确认卡片还保留了“此前发送过疑似预告,但没有明确时间窗口”,方便我把前后两条消息对应起来。
卡片上的“4 条 / 4 个来源族”表示系统汇总的证据规模,不能简单理解成“凑够四个网页就算确认”。程序会区分来源族和原始链接,同一来源的不同接口、对同一原帖的转载,不能直接当成相互独立的确认依据。这里的“确认时间”也要结合证据理解,并不代表所有账号在同一秒恢复额度。
第二种是 Banked reset:关注账户新增了多少次可用重置机会。
这条路径读取参考账号返回的 rateLimitResetCredits.availableCount。第一次读数用于建立基线;此后连续两次读到稳定的新数量,并且相对基线增加,才生成“Codex Banked reset 已到账”通知。字段缺失时保留未知,不把它当成零。
这种通知会把到账范围标成“本机参考账号”。公开预告本身不能证明我的账号已经获得次数,而获得重置次数,也不等于已经使用这些次数恢复额度。这里的探针负责读取和通知,不会自动消耗重置机会。
3.5 按事件推进通知,避免每次轮询都提醒
重置监控把事件、证据和投递记录保存下来。同一事件进入一个新的阶段时,才生成对应的通知任务;重复读到相同阶段的信息,不再反复排队。
公开重置信号 + 本机参考账号信息
↓
归并事件,保留来源、原始链接和时间
↓
按重置类型核验,更新事件阶段
↓
为新的阶段生成投递记录
↓
通过 Dev巡检的 Webhook 推送飞书卡片如果发送失败,投递记录会保留尝试次数,按退避策略重试,并设置最大尝试次数。这能减少重复轮询带来的刷屏,也让暂时的发送失败有机会补发;它不等于网络异常下的严格“恰好一次”投递保证。
我把这类消息放在 Dev巡检,是为了及时了解影响开发安排的事件。后面的“AI 成本&额度”群则回答另一层问题:我现在实际采集到的剩余额度是多少。 两边的信息一起看,才能把外部重置消息与自己的使用状态对应起来。
4. 上游服务状态:跟踪外部故障,也跟踪镜像更新
4.1 厂商状态变化
我的项目依赖外部模型服务、代码托管和网络基础设施。因此,内部接口报错时,我也会查看相关上游是否正在发生故障。
目前 WatchMend 配置了 Anthropic、OpenAI、GitHub、Cloudflare 和 Google Cloud 的状态来源。它定期读取状态信息,识别变化,再把相关消息发到“上游服务状态”。
这些消息提供了一个排查方向。例如,模型调用集中出现异常,同时对应上游正在报告事故,我就有必要把两边的时间和影响范围对照起来。
状态页信息仍然需要结合自己的请求结果判断。厂商公布了事故,不代表我的每条请求都受影响;状态页显示正常,也不能直接排除某个区域、账户或接口的问题。
4.2 Diun 先记录更新,再集中通知
镜像更新也放在这个群里。我用 Diun 跟踪一份明确维护的基础镜像清单,而自有业务镜像的部署结果由发布流水线报告。
这是一个实际的降噪选择:业务镜像本来就会随着代码发布频繁变化,如果再把每次变化都当成上游更新推一遍,会产生很多重复信息。
Diun 支持在发现镜像变化时调用脚本,并把镜像名称、事件状态和相关链接等字段传给脚本。我使用的就是这个入口。Diun 官方 Script 通知文档说明了这组字段和配置方式。
我当前的配置在每天 10:00 安排镜像检查,事件处理脚本将变化追加到本地 JSONL 文件。每天 10:10,另一个脚本读取这个文件,整理成一条更新摘要后再发送到飞书。这里的时间按东八区计算。
10:00:Diun 检查镜像清单
↓
发现变化,调用脚本写入本地 JSONL
↓
10:10:汇总脚本读取待发送记录
↓
按完整镜像引用去重,保留最新一条
↓
通过 Webhook 发送更新摘要汇总脚本的核心逻辑很小,下面是去掉环境细节后的示意:
# 按完整镜像引用保留本批次最后一条记录。
latest_by_image = {}
for event in pending_events:
latest_by_image[event["image"]] = event
if latest_by_image:
send_digest(latest_by_image.values())这里的 image 包含具体镜像引用。同一仓库的两个不同 tag 仍然可能分别出现在摘要里。
同一个群中的三种消息:状态页采集失败提醒、外部依赖巡检汇总,以及 Diun 更新摘要。下方两个 redis_exporter tag 分别保留,也能看出汇总是按完整镜像引用去重。
如果没有新记录,汇总任务安静结束。如果发送遇到临时错误,当前脚本会进行有限次数的退避重试;仍然失败时,把本批记录放回待发送文件,供后续调度再次处理。
更新摘要只提供镜像名称和查看入口。收到后,我会看更新说明、兼容性和自己的使用情况,再决定什么时候升级、固定到哪个版本。
5. AI 成本&额度:每天看全貌,变化时收提醒
5.1 为什么它运行在 Mac 上
这个群的主要数据由 ai-cost-hub 收集。当前重点是 AI 编程订阅的剩余额度,以及能够采集到的账户余额。
这部分任务运行在我的 Mac 上,因为我这套部署中使用的 CodexBar 数据入口和登录环境就在本机。采集程序调用 codexbar usage --json,再把不同来源的数据整理成统一的记录。
例如,订阅接口返回已用百分比时,程序会换算成剩余百分比;账户余额则根据来源提供的结构化字段或文本进行提取。缺少的数据保留为未知,不用零值代替。
整个处理过程是:
macOS launchd 定时启动任务
↓
CodexBar 返回订阅额度和账户信息
↓
ai-cost-hub 整理字段、判断状态
↓
保存历史或生成提醒
↓
调用飞书 CLI,以机器人身份发到指定群这里通过 lark-cli 的发消息能力完成投递。飞书的应用机器人发消息接口需要明确接收对象,与绑定到某个群的自定义机器人 Webhook 是不同的接入方式,相关接口可以参考飞书发送消息文档。
5.2 晨报不要求恰好在 09:00 执行
我为它配置了两个 launchd 任务:一个每小时检查晨报是否应该发送,另一个每两小时检查额度阈值。
晨报使用 Asia/Shanghai 计算日期:当天已经到 09:00,并且今天还没有成功发送,就生成一张汇总卡。发送成功之后,再记录当天已经发送。
因此,飞书里的消息不一定恰好出现在 09:00。电脑休眠、任务实际启动时间和执行耗时,都可能影响到达时间。截图里的晨报出现在 09:55,也不能只凭这个时间判断任务是否异常。
每天采集的历史会保存到 SQLite。晨报发送失败后,后续调度可以再次尝试投递,同时避免因为重发而重复写入当天的历史样本。
额度群中的实际消息顺序:额度偏低提醒、每日晨报、恢复通知,再到次日晨报。图中的百分比和余额为相应采样时点的数据,消息列表显示的 09:55 是当天晨报的到达时间。
5.3 两小时检查一次,不等于两小时提醒一次
我比较在意额度通知的重复问题。额度已经偏低时,如果每次检查都重复提醒,很快就会让人习惯性忽略它。
当前实现会记住上一次已经成功通知的状态,主要在这些变化发生时提醒:
| 观察到的情况 | 处理方式 |
|---|---|
| 第一次低于配置阈值 | 发出提醒,保存告警状态 |
| 仍处于同一风险等级 | 保留状态,日常情况由晨报汇总 |
| 进入更严重的风险等级 | 再提醒一次 |
| 回到安全范围 | 清除对应告警状态;当前 Codex 额度还会发送恢复通知 |
| 本轮没有采集到对应数据 | 保留之前的状态,等待后续有效数据 |
例如,订阅剩余额度已经触发提醒,之后又下降到配置的严重等级,就值得再通知一次。这里判断的是“进入更严重的等级”,所以额度在同一等级里继续小幅变化,不会每次都推送。
如果通知发送失败,本轮新增或升级的告警状态会回退,避免下一轮误以为已经通知过。
这个群目前帮助我安排工具使用和补充余额。网关逐请求成本核算、消耗趋势预测,则需要额外的数据采集和分析,不能直接从一张订阅额度晨报推导出来。
6. Dev 基础设施与发布:可用性与部署结果放在一起看
6.1 Uptime Kuma 观察服务是否可达
这个群接收 Uptime Kuma 的监控通知。我的监控项覆盖了业务接口、页面、数据库和 Redis 端口,以及部分公网入口。
Uptime Kuma 本身提供飞书通知适配,具体实现可以在它的官方仓库中查看。配置监控项时,需要把相应通知渠道关联上。
它与 WatchMend 有部分观察对象重合,但我关注的角度有所不同:Uptime Kuma 提供直接的可达性变化,WatchMend 还会结合容器、资源和日志形成巡检事件。两个来源同时提示同一服务时,我会对照发生时间继续定位。
WatchMend 自己也在 Uptime Kuma 的监控清单里。这样巡检进程停止时,可以通过另一条链路发出提醒。
不过,两者都运行在同一台 dev 服务器时,共享宿主机和部分网络依赖。这个安排能覆盖巡检进程自身的一部分故障;整台机器或它的外部网络出问题,仍需要主机之外的观察点才能进一步覆盖。
6.2 GitHub Actions 把部署结果发回来
部署通知也发到这个群。它由 GitHub Actions 的部署流程产生,消息卡片包含仓库、分支、提交信息、执行结果,以及查看运行日志的入口。
收到失败通知时,我可以直接打开对应那一次 Actions 运行,不必先去翻最近的工作流列表。收到成功通知时,也能知道它对应哪一次提交。
Fusion 的一次实际部署通知。卡片保留了分支、提交版本和“查看运行日志”入口;原始卡片采用 UTC 时间,图中的 2026-09-07 12:06 UTC 对应北京时间当天 20:06。
实现上,部署结果通知会放在合适的收尾步骤中,让失败路径也有机会发送。通知投递失败则单独记录,避免把“服务部署结果”和“消息是否成功送出”混在一起。
读这些卡片时,我会保留三个不同层次的判断:
| 结果 | 它说明了什么 |
|---|---|
| 部署成功 | 对应部署任务按它配置的检查条件完成 |
| 服务恢复或监控正常 | 对应探针当前满足健康条件 |
| 功能验收通过 | 具体需求在实际使用流程中经过了验证 |
部署成功之后,重要功能仍然需要到页面或接口里走一遍。通知负责把执行结果和定位入口交给我,后续验收沿着上一篇里的开发流程继续进行。
7. 推送机制里,我最关注的几个细节
7.1 检查频率和通知频率分开
前面几条链路体现了不同的处理节奏:内部巡检持续采集,异常达到条件后通知;额度定时检查,风险等级变化时提醒;镜像更新先落盘,固定时间汇总;Codex 任务事件先等待宽限期,再判断候选是否仍然有效。
我希望采集足够及时,同时让每条消息有明确用途。因此,“程序执行了一次”通常只是一次观察,后面还要经过状态比较和通知规则。
7.2 发送完成,要检查应用层结果
通过 Webhook 发消息时,除了 HTTP 状态,还需要检查飞书返回的业务结果。WatchMend 的客户端和 Diun 汇总脚本都会解析返回值,识别飞书是否接受了消息。
消息格式、机器人安全设置和签名方式,需要按飞书自定义机器人使用指南配置。我的脚本通过环境变量或私有配置读取 Webhook 和签名密钥,正文里只保留排查所需的信息。
对于 Hook 通知,同样需要控制上传内容。我只传递必要的事件标识和经过处理的摘要,避免把完整会话、工具输入输出或凭据一起带进通知。
7.3 去重状态应该跟着投递结果走
一个常见的处理顺序问题是:程序先记下“已经通知”,随后消息发送失败。下一轮再检查时,就可能因为去重而跳过这条本来需要发送的消息。
我在几条关键通知链路里都处理了这个问题,不过具体方式不同:
- WatchMend 的内部告警与恢复,根据投递结果提交对应通知状态;全部渠道失败时,保留后续重新处理的机会。
- ai-cost-hub 发送失败时回退本轮新增或升级的告警状态。
- Diun 汇总发送失败时,把当前批次还原到待发送记录中。
- 晨报只有发送成功才记录当天已经发送。
这些方式让失败后的补发更可控,但还不等于严格的“恰好送达一次”。例如,飞书已经接受消息,而本地在保存回执前退出,后续就可能重复发送。
个人通知里,我可以接受偶尔重复的一条提醒;代码和日志则需要让我看清它为什么重复,以及是否还有未完成的投递。
7.4 数据缺失要有自己的状态
如果额度接口没有返回数据,不能据此判断额度恢复;如果日志或指标服务暂时不可用,也不能因为没有读到异常,就把原来的故障标记为恢复。
所以我会区分“观察到正常”和“这次没观察到”。前者可以作为状态恢复的依据,后者需要保留历史状态,并根据情况报告采集链路的问题。
同样,群里一段时间没有新消息,只能说明没有收到新消息。需要结合日报、采样时间和监控系统自身的状态,判断整个链路是否仍在正常工作。
8. 我平时怎样使用这些通知
日常开发时,我会先用额度晨报了解当天可用的工具资源,再集中查看镜像更新和上游状态。发布完成后,飞书把对应的版本和 Actions 入口送过来,我继续进行页面或接口验收。
如果中途出现服务异常,我会从告警里的对象和时间出发,看监控、查日志;如果是 AI 任务等待处理,就回到对应的开发任务继续。原始数据仍然留在各自的系统里,飞书帮助我发现变化并找到下一步的入口。
这套通知方式也在影响我继续接入新来源时的判断:这条消息什么时候需要被看到,应该发给哪个群,重复出现时怎样处理,收到之后能采取什么行动。
这些问题回答清楚之后,接入一个新的数据源,才会真正减少一次主动检查。