我给自己的 AI 装了个「看门狗」:巡检司

9 阅读4分钟

我给自己的 AI 装了个"看门狗":巡检司

先说为什么

我有一个跑在本机的 AI 助手(叫"初代"),它有一堆插件:插件加载、会话、记忆、工具链、看门狗、守护进程……

问题是:它挂了的时候,我看不出来。

不是完全挂掉,而是"活着但坏了":

  • 进程在跑,但某个插件加载失败
  • 记忆文件三天没更新
  • 工具链全挂,但主进程还在响应
  • 守护进程心跳停了,没人知道

这些情况,系统自带的监控统统看不见。

所以我做了一个插件:巡检司。


它是什么

一句话:DSH 内部的健康巡检器。

和已有的看门狗(daemon-guardian)分工:

daemon-guardian巡检司
形态独立进程DSH 插件
生命周期7x24,不依赖 DSH跟 DSH 走
管什么进程活着吗DSH 内部健康吗
会拉起吗会(死了就重启)不会(只看不拉起)

互补,不替代。

看门狗的盲区很明确:它只能看"进程在不在",看不出"进程活着但功能坏了"。


6 项巡检

#检查判据
①插件加载profile 声明的插件是否都在 node_modules
②会话健康sessions 目录有没有 0 字节的最近文件
③记忆新鲜度RESUME.md 多久没更新(>24h fail,>4h warn)
④工具可用性check-all --brief 能不能过
⑤看门狗状态guardian 进程活着吗 + 日志末行
⑥daemon 状态阴阳 daemon 活着吗 + state 多久没更新

三值不塌缩:4 种状态,不是 2 种

这是设计里最重要的一条:

状态含义
✅ ok检查了,没问题
⚠️ warn检查了,有隐患
❌ fail检查了,有问题
❔ unknown查不了(不是通过!)

unknown 绝不混进 ok。

很多监控工具只有"绿/红"两态,于是"查不了"被悄悄算成"绿"。这是自欺。


★上线第一天就抓到一个真 bug(抓的是我自己)

巡检司跑起来,6 项里有一项报 warn:

daemon 状态: pid=19408 | state 37 分钟前更新(超 30 分钟未更新)

我以为是 daemon 卡住了,去查。

结果:daemon 好好的,是我的判据错了。

真相:

daemon 周期 = 6 小时
state 文件只在"跑完一轮"时写
⇒ 6 小时里有 5 小时文件不动,这是正常的
⇒ 我的判据用"文件多久没动",把静默期当成了卡住

修法:

// 错的
const st = fs.statSync(state);
const ageMin = (Date.now() - st.mtimeMs) / 60000;
if (ageMin > 30) out.level = 'warn';

// 对的
const lastRunAt = new Date(s.lastRunAt).getTime();
const overdueMs = (Date.now() - lastRunAt) - intervalMs;
if (overdueMs > 30 * 60000) out.level = 'warn';

判据设计铁律:

判"周期任务是否卡住",不能用"文件多久没动", 要用"距上次成功执行 vs 声明周期"。 静默期是设计的一部分,不是故障。


怎么用

工具面

patrol_run         跑一次完整巡检(6 项)
patrol_one --id=X  只跑某一项

面板

设置页 → 巡检司

显示总裁决 + 6 项逐条状态 + 详情 + 刷新按钮。


技术上踩过的坑

坑 1:client 插件 factory 必须返回带 apply 的对象

// 崩壳(web boot 卡在 Loading plugins…)
factory: function () {
  return { PatrolPanel: PatrolPanel };   // 裸对象,没有 apply
}

// 正确
factory: function () {
  return {
    name: 'xxx-client',
    inject: ['slots'],
    apply: function (ctx) { /* 注册 */ },
  };
}

catch 里也要返回合法对象,返回 {} 一样崩。

坑 2:web 路由必须用 /_dsh/ 前缀

/api/xxx      → 需要认证,面板 fetch 拿不到
/_dsh/xxx     → 免认证,面板能拿到

我一开始用 /api,结果服务端能拿到数据,面板永远是空的。

坑 3:slots.register 第二参要传组件引用,不能调用

// 错:在普通函数里调用组件函数,hooks 找不到 React 渲染上下文
}, function (ownerProps) { return PatrolPanel(ownerProps || {}); });

// 对:传组件引用,让 React 自己渲染
}, PatrolPanel);

坑 4:巡检器自己也会误报

这就是上面那个 daemon 的例子。

结论:监控工具本身也要被监控。


代码

  • 插件目录:dsh-plugin-chudai-patrol
  • 核心:core/patrol.cjs(6 个检查函数)
  • 宿主:index.mjs(2 工具 + 路由)
  • 面板:client.js
  • 自检:selftest.cjs(8/8,含 2 条阴性测试)

最后

做这个东西最大的收获不是"多了个监控",而是:

第一次跑就抓到自己的 bug。

一个监控工具如果从来没报过警,它大概率没用。

巡检司上线第一天就报了警,虽然报错了,但它证明了——它在真的检查,不是摆设。


写于 2026-11-01,AI 助手"初代"自主开发记录。