Skill 方案 S1 · 站点健康巡检与自愈
一句话:把「检测门户 3000、词汇站 3001、笔记站 3102、隧道,坏了自动重启,域名变了发邮件」这句每次都要重说一遍的话,固化成一条命令;253 次巡检会话消耗的 0.99 亿 token,可压到 0.15 亿以内,省 85%。
一、为什么需要它
按「首条消息模板相似度」聚类,命中巡检模板的会话有 253 个。它们的用户原话几乎逐字相同:
「检测考研学习导航门户(3000)、词汇站(3001)和 natapp 隧道的健康状态,坏了就自动重启,域名变了就发邮件通知」
| 指标 | 数值 | 说明 |
|---|---|---|
| 会话数 | 253 | 占 277 个总会话的 91% |
| 累计 token | 0.99 亿 | 占两个月总消耗 6.3% |
| 工具调用 | 1402 次 | 平均每会话 5.5 次 |
| 平均每会话 token | 39 万 | 其中输入占 96% |
| 用户消息 | 1–2 条 | 没有一次是"讨论",全是"执行" |
痛点不在判断难度,而在三处结构性浪费:
- 开场靠记忆。每次都要重新列端口、任务名、日志路径、隧道模式,约 1.2 万 token 的固定开销乘以 253 次,就是 300 万 token 白烧。
- 判据不统一。有时看端口监听、有时 curl 首页、有时读日志里的
Tunnel established,同一个故障在两次会话里可能得出相反结论。 - 自愈无分级。现状只有「不动」和「全量重启」两档,进程活着但反代 502 这种中间态,往往被直接推成全量重启,把本来正常的词汇站也打断。
二、触发词与边界
| 触发词 | 不适用场景 |
|---|---|
| 「网站能打开吗」「巡检一下」「体检」 | 要改代码或路由 → S3 构建发布 |
| 「网站 502 / 404 / 打不开」「白屏」 | 要重建计划任务本身 → S2 附录 |
| 「坏了就自动重启」「重启一下网站」 | 要验证并发/压测 → 不做,明确拒绝 |
| 「域名是什么」「把链接发我」 | 要看一月内的可用率趋势 → 查日志,不跑巡检 |
| 「cag.quest 通吗」「隧道正常吗」 | 只要一张页面长图 → S6 |
边界一条:巡检只读 + 重启,不改配置。任何涉及 config.yml、mode.txt、计划任务定义的改动,一律转 S2 并单独确认。
三、输入与前置检查
站点与探针是固定的,写死在 skill 里,不再向用户追问:
| 服务 | 端口 | 本地探针 | 通过标准 |
|---|---|---|---|
门户 portal/server.js | 3000 | /healthz | JSON 里 portal=ok |
| 词汇站 Next | 3001 | /vocab/ | 200 且含站名 |
| 笔记站 Next | 3102 | /notes/ | 200 且含「笔记」 |
| C 语言实验室 | 3103 | /c-lab/ | 200 |
| Cloudflare Tunnel | — | https://cag.quest/ | 200 且 TLS 正常 |
跑之前先核三件事:
Get-ScheduledTask -TaskName "Kaoyan*" | Select-Object TaskName, State
Get-NetTCPConnection -LocalPort 3000,3001,3102,3103 -State Listen | Select-Object LocalPort, OwningProcess
Get-Content "C:\Users\cag\WorkBuddy\2026-10-11-06-00-16\kaoyan-vocab\natapp\mode.txt"
判定:KaoyanPortal/KaoyanNextServer/KaoyanNotes/KaoyanTunnel 四个任务必须是 Running 或 Ready;mode.txt 内容为 tunnel 才用 https 探公网,否则说明已回落 natapp,只能用 http——这条判错会把「隧道正常」误报成「隧道已断」。
四、执行步骤
步骤 1 · 只读体检。 先不碰任何进程:
& "C:\Users\cag\.workbuddy\binaries\node\versions\22.22.2-6\node.exe" `
"C:\Users\cag\WorkBuddy\2026-10-11-06-00-16\kaoyan-vocab\natapp\selfheal.mjs" --dry-run --json
步骤 2 · 四层检查矩阵。 缺一层就会有盲区:
| 层级 | 命令 | 失败含义 |
|---|---|---|
| 端口 | Get-NetTCPConnection -LocalPort 3001 -State Listen | 进程没起来 |
| HTTP | Invoke-WebRequest -UseBasicParsing http://127.0.0.1:3001/vocab/ -TimeoutSec 8 | 起来了但不服务 |
| 内容指纹 | 响应体长度 > 2000 字节且匹配 <title> 关键字 | 反代返回了错误页却仍是 200 |
| 隧道 | node "C:\Users\cag\WorkBuddy\2026-10-11-06-00-16\ops\cf\verify-tunnel.mjs" --json | 本地全好但公网不通 |
内容指纹是最容易被省掉的一层,也是唯一能抓住「反代把 500 页面当 200 吐出来」的手段。
步骤 3 · 分级自愈。 按最小必要动作升级,不许跳级:
| 级别 | 触发条件 | 动作 | 耗时 |
|---|---|---|---|
| L0 | 全绿 | 只记录,不动作 | 3 s |
| L1 | 单端口无监听 | Start-ScheduledTask -TaskName "KaoyanNotes" | 15 s |
| L2 | L1 后仍不通 | launch.ps1 -Name notes(先杀端口占用者再起) | 40 s |
| L3 | 门户 3000 或隧道异常 | 全量:vocab/notes → portal → tunnel | 120 s |
L1 遇到 0x800710E0 说明有卡死实例,必须先 Stop-ScheduledTask 再 Start,脚本已内置这一分支。
步骤 4 · 告警。 后台进程调不到邮箱连接器,所以只落 outbox/mail-*.json + 桌面 toast;用户要邮件时先 --flush 看积压,再用 mcp__agent-mail__SendMessage 发一封汇总,发完把对应 json 的 status 改成 sent。cag.quest 已固定,正常不该再有域名变化通知;真出现就说明 mode.txt 落回了 natapp,按故障处理而不是按通知处理。
步骤 5 · 收敛成四句话。 可直接点的链接、三个入口状态、本次动作(actions 为空就说"没动,本来就正常")、域名是否刚变。
步骤 6 · 幂等。 一次自愈最长 3 分钟,30 分钟一跑也可能叠加,用 selfheal.lock 加锁,超过 10 分钟的锁视为陈旧锁回收;重复执行不产生第二个 cloudflared,否则两个连接器会抢 20241 指标端口。
五、输出与验收
{
"healthy": true,
"domain": "https://cag.quest",
"entries": { "portal": 200, "vocab": 200, "notes": 200, "clab": 200, "public": 200 },
"actions": [],
"mode": "tunnel",
"checkedAt": "2026-11-14T03:20:11.000Z"
}
三条硬断言,任一不满足即非零退出,不许静默通过:
healthy=true时actions必须是空数组——有动作就说明刚才不健康,两者不能同时成立。- 每个入口的 HTTP 码必须是 200,且响应体大于 2000 字节;只验状态码不算通过。
checkedAt距当前时间不超过 90 秒,防止把上一次的缓存结果当本次结论。
六、失败处理与回滚
| 现象 | 根因 | 动作 |
|---|---|---|
Start-ScheduledTask 报 0x800710E0 | 有卡死实例占着 | 先 Stop-ScheduledTask 再 Start |
| 重启后端口仍不通 | 卡死进程只占端口不退 | 按端口找 PID,Stop-Process -Force 后再拉一次 |
| 隧道任务正常但公网 502 | Stop-ScheduledTask 不带走 cloudflared,成了孤儿进程 | Get-Process cloudflared 后强杀,再 Start-ScheduledTask -TaskName KaoyanTunnel |
| 公网探测全红、本地全绿 | 误用了 https 探 natapp 模式 | 读 mode.txt,改 http 重探 |
| 全量重启后仍失败 | 代码或数据库层面坏了 | 停止自愈,读 portal/portal-server.log、kaoyan-vocab/next-server.log、ops/cf/cloudflared.out.err 尾部,转人工 |
回滚:隧道退回 natapp 用 ops\cf\rollback.ps1(免提权,支持 -Check 试运行);服务层面没有"回滚"概念,重启即恢复,真正的回滚是 S3 的构建产物回退。
与现有 kaoyan-site-health skill 的关系:S1 是它的超集,不是替代。原 skill 已经解决了 80% 的问题(端口、健康端点、隧道校验、mode.txt 分流),S1 增量补三块:把 3103 纳入矩阵、加内容指纹层、把自愈从两档拆成 L0–L3。落地方式是把原 skill 收缩成薄入口——保留它的名字和触发词做别名,正文只写一句「执行 site-report.mjs,按 S1 的矩阵解读」,巡检逻辑全部沉到 selfheal.mjs,避免两份文档各说一套。
七、收益测算
| 项目 | 现状 | 改造后 |
|---|---|---|
| 单次 token | 39 万(重载上下文 + 5.5 次工具往返) | 1.5 万(一条命令 + 读 JSON) |
| 年化次数 | 253 次 | 253 次(其中 150 次由计划任务静默完成,不进对话) |
| 对话内消耗 | 0.99 亿 | 0.15 亿 |
| 单次耗时 | 2–6 分钟 | 20–40 秒 |
拆开看:固定开场 1.2 万 × 253 ≈ 300 万;工具往返 1402 次 × 约 6 万 ≈ 0.84 亿。前者靠把站点清单写进 skill 消掉,后者靠一条脚本命令把 5.5 次往返压成 1 次。节省 0.84 亿,约 85%,与父篇测算一致。