DeepSeek Harness Hub
← 返回列表

jonah791/dsh-agent-guardian

DeepSeek Harnessspec-screened在 GitHub 查看 ↗
未验证

守卫插件从 dsh-agent-watch 拆分:web 保活——启动时端口空闲拉起…

尚未跑自动兼容性验证,可查看页面内的依赖与入口分析。 · 最近上游提交 2026/9/17 · 已提供中文文档

守卫插件(从 dsh-agent-watch 拆分):web 保活——启动时端口空闲拉起 web、崩溃自愈(快速退出计数+落盘事故)、收养外部 dsh web(零互踢)。崩溃自愈/拉起前也调用沙盒预检(ctx.preflight.run q

综合分
30
GitHub 分
30
用户评分
★ Stars
0
周下载量
安装插件(需先安装 dsh CLI 引擎:npm install -g @deepseek-ai/dsh)
dsh plugin --profile web add jonah791/dsh-agent-guardian
该插件未发布到 npm,走 GitHub 源安装(pnpm 若拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑)
数据截至 2026/9/18(元数据每日更新 · 实装验证按队列轮转,单条结论的验证时间见上方)
依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/schemastery@deepseek-ai/dsh-tools
用户评分
还没有人投票,来当第一个
订阅周报,不错过优质插件更新
每周一封 · 高评分插件 + 新用户活动

README

dsh-agent-guardian

一句话:DSH web 服务的保活状态机——端口空闲就拉起、异常退出就自愈(两次退出间隔 /)→ 再走预检 | ensureDataHealthy |
| 生命周期租约让位 | 「拉起前」与「唤醒前」查 .web-lifecycle-lease.json:他人持有且新鲜 → 收养而非拉起、不重复唤醒 | checkLeaseFor(src/lease.ts) |
| 拉起后唤醒 | 拉起成功后向最近活跃用户会话投递「web 已拉起」;sessionWaker 缺席时落证据行跳过 | notifyWebReady |
| 双通道告警 | Telegram 告警:主通道 spawn node 子进程 fetch(注入 NODE_USE_ENV_PROXY=1)→ 失败则 curl 兜底;结论(含所用通道)落盘 | src/alert-transport.ts |
| 有界端口等待 | 非 dsh 进程占用时每 5s 轮询、上限 300s;超时 → 落事故 + 告警(不 kill 别人) | waitPortFree |
| 优雅停止 | SIGINT/SIGTERM → 置 manualStop、清轮询、kill 自己的 child 或被收养者 | onSig |
| 证据面 | 事件行追加、事故快照覆盖写(见下节) | logEvent / writeIncident |

快速开始
1) 装依赖(自研插件家园 self-plugins/,在 watch profile 的 package.json 加 link 行):

"dsh-agent-guardian": "link:/self-plugins/dsh-agent-guardian"

2) 挂组合(watch profile 的 cordis.patch.yml,与 runtime / preflight / sentinel 同 profile):

- insert:
- id: agent-guardian
name: dsh-agent-guardian
config:
dshHome: ${DSH_HOME}
port: 3080
maxQuickExits: 3
adoptExternal: true

3) 30 秒验证:看证据面有没有就绪行与动作行——

tail -3 "$DSH_HOME/.watch-events.log"
期望看到:[] guardian 就绪 保活:3080
[] 端口空闲,guardian 自动拉起 web   (或)端口已被占用——尝试收养外部 dsh web

前置条件:dsh-agent-preflight、dsh-agent-runtime(提供 webman)必须在同一组合内——它们缺席时 guardian 整插件不激活(inject 是 cordis 激活门),保活也一并消失。这是刻意的取舍:没有预检的保活更危险(docs/semantic.md §10 U5)。

配置

键名与 export const Config(src/index.ts)逐项一致,默认值取自源码:

| 项 | 默认 | 说明 |
|----|------|------|
| dshHome | process.env.DSH_HOME \|\| '' | 证据文件、租约、凭据兜底查找的根(空则回落 process.cwd()) |
| port | 3080 | 保活目标端口 |
| crashWindowMs | 30000 | 快速退出判定窗口 |
| maxQuickExits | 3 | 熔断阈值(连续快速退出次数) |
| incidentFile | '' → /.watch-incident.json | 事故落盘路径 |
| defaultWorkspace | '' → process.cwd() | 首次拉起使用的 workspace |
| adoptExternal | true | 启动时端口被占且是 dsh web → 是否收养 |
| launchCmd | [] | 遗留字段:源码中已无引用(进程管理 2026-09-02 移交 ctx.webman) |
| bin / profile / baseUrl | '' / 'web' / 'http://127.0.0.1:3080' | 遗留字段:源码中已无引用(改由 ctx.agentRuntime 提供) |
| telegramBotToken / telegramChatId | '' | 告警凭据;为空时回退读 /.credentials.yaml 的两个具名引用(凭据不写进本仓库) |
| httpProxy | http://127.0.0.1:16888 | 兜底通道(curl)使用的 http 代理 |
| dataHealth.storagesDir | '' → /storages | 数据健康检查目录(storage-json unit) |
| dataHealth.checkpointDir | '' → /checkpoints | 存档点目录(manifest.json + files/) |
| dataHealth.soulFile | '' → /AGENTS.md | 灵魂文件恢复落点 |

落盘与自证(出问题时先看这里)

本插件有持久产物,且是「追加式证据流 + 覆盖式快照」两种形状(都在 ${DSH_HOME}):

| 落点 | 形状 | 字段 / 语义 |
|------|------|------------|
| ${DSH_HOME}/.watch-events.log | 追加,单行 [ISO 时刻] 事件描述 | 唯一的过程证据流:就绪 / 拉起 / 收养 / 让位 / 预检拦截 / 端口等待 / 退出 / 唤醒 / 告警结论全部在此 |
| ${DSH_HOME}/.watch-incident.json(或 incidentFile) | 覆盖式快照,JSON | { at, code?, signal?, message, ...detail }——最后一次事故(熔断 / 预检拦截 / 端口超时 / 数据损坏且无健康存档 / 数据已恢复) |
| ${DSH_HOME}/checkpoints/.pre-restore-/ | 恢复前备份 | 自动恢复永不原地覆盖:被替换的文件先复制到这里(可回滚证据) |
| ${DSH_HOME}/.web-lifecycle-lease.json | 只读(写入方是 sentinel) | { owner: 'sentinel'\|'guardian', atMs, ttlMs, note? };guardian 只查不取、不释放 |

事件行的阶段枚举(源码 logEvent 文案,可直接 grep 定位断点):

| 阶段 | 关键字 |
|------|--------|
| 就绪 | guardian 就绪 保活: |
| 启动自检 | 端口空闲,guardian 自动拉起 web / 端口已被占用——尝试收养外部 dsh web |
| 收养 | 收养外部 dsh web(PID …):此后由守护托管 / 收养的 web(PID …)已退出——由守护拉起 |
| 租约让位 | 生命周期租约:held-by-sentinel(…note=…) ——跳过唤醒 / ——收养而非拉起(跳过 spawn) / 生命周期租约读取异常(按空闲处理): … |
| 门控 | 预检 gate FAIL,拒绝拉起 web: … / 数据健康检查 FAIL: …——尝试从存档点恢复 / 数据损坏已自动恢复(存档 …) |
| 端口 | 端口  被非 dsh 进程占用,等待释放... / 端口  仍被占用(已等 Ns) / 端口已释放,重新拉起 web / 端口  等待超时(Ns),放弃等待 |
| 退出与自愈 | web 退出 code=… signal=… quickCount=… / web 退出但端口被活 dsh web 占用(PID …)——收养接管,跳过拉起 |
| 唤醒 | 拉起后唤醒已发送: session-… / 拉起后唤醒未发送: (候选=[…]) / sessionWaker 服务不可用(sentinel 未挂载?)——跳过唤醒 |
| 告警 | telegram 告警发送中: … / telegram 告警已送达(node-fetch\|curl)— … / telegram 告警失败(): — … |

一条命令尽量答五问:

tail -5 "$DSH_HOME/.watch-events.log"
① 跑的是哪个构建 → 事件行不含 build 字段(已知缺口);改用 mtime 对照:stat -c %y lib/index.js 与 watch 进程启动时间比
② 谁发起 / 投给谁 → 行首 ISO 时刻 = 发起时刻;拉起后唤醒已发送: session-… 即被唤醒的目标会话
③ 断在哪一段     → 用上表关键字对号:停在「端口空闲,guardian 自动拉起 web」之后没有后续行 ⇒ 卡在闸门/端口段;有 预检 gate FAIL 即明确结论
④ 结果质量       → 计数取证:grep -c '预检 gate FAIL' / '生命周期租约' / '停止自动重启' / '已送达' 各是什么量级
⑤ 耗时与预算     → 相邻两行时间差即该段耗时(预检 quick 为毫秒级;端口等待 5000ms/轮、预算 300s)

再看事故快照(覆盖式,只保留最后一次):cat "$DSH_HOME/.watch-incident.json"。

已知缺口:事件行与事故文件都不带 build 指纹(版本 + 模块 mtime),所以「线上跑的是哪个构建」只能靠文件 mtime 与进程启动时间间接推断——这违反生态的可维护性纪律(docs/semantic.md §10)。

生效判据与回退

生效判据(三选一,按可靠性排序):
1. 进程级:lib/index.js 的 mtime ≤ watch 进程启动时间,且 src/index.ts 不新于 lib/index.js(源码改了没构建 = 跑的还是旧产物);
2. 行为级(最直接):挂载后 ≤ 1.5s 内 ${DSH_HOME}/.watch-events.log 追加 guardian 就绪 保活:;端口空闲时随后出现拉起/收养行;
3. 交互级:预检或数据损坏工况下出现 预检 gate FAIL,拒绝拉起 web + .watch-incident.json 被写——证明闸门真的在生效(而不是只有代码里有)。

重新构建 ≠ 生效:lib/ 的 mtime 新只证明「构建过」,只有进程启动时间晚于产物 mtime 才算「在跑它」(AGENTS.md §5.11 §6)。watch 侧没有 web 侧那种 hasUnverifiedBuilds() 预检兜底——构建完必须重启 watch 才会生效。

另注意:守护类改动受 §5.2 约束,watch profile 的重启由主人执行;本插件不自行重启宿住它的父进程。npm test 脚本不含构建步骤,改完源码务必先 npm run build。

回退(三档):
- 源码级:git revert (或 git checkout )→ npm run build → 预检 → 由主人重启 watch;回退后用同一判据复验(就绪行消失 / 旧文案出现);
- 组合级:watch profile patch 给该行加 disabled: true(或经插件管理面停用)→ 保活消失、端口空闲时不再有人拉起 web(此时保活真空,需先确认 sentinel 的周期重启是否足以覆盖);
- 运行期:无状态可清——本插件不持有需要清理的持久状态;.watch-events.log / .watch-incident.json 是纯证据,可保留可删除;.web-lifecycle-lease.json 不属本插件,删它会破坏哨兵的交接留痕(有 TTL 兜底,但仍不该动别人的文件)。

测试

npm run build && npm test     # npm test = node --test "tests/.test.mjs"

23 例离线测试、3 个 suite,全部 pass(实测输出:# tests 23 / # suites 3 / # pass 23 / # fail 0,耗时 ~176ms),跑的是构建产物(tests/.test.mjs 从 ../lib/*.js 导入,改源码必须先构建):

- tests/alert-transport.test.mjs(6 例)——告警送达判据 judgeTelegramResponse:好样本(exit 0 + ok:true)+ 4 条尸体样本(curl TLS 失败 exit 35 空响应体 / exit 0 但 API ok:false / 子进程 spawn 失败写 ERR  前缀 / 非 JSON 的 HTML 网关页)+ 缺凭据时 channel==='none' 且给出明确原因(不许静默放弃)。
- tests/lease.test.mjs(17 例)——租约裁决表与文件层:真实时刻表尸体样本(哨兵 22:33:13 取租 → 守护 22:33:35 巡检必须 hold)、持租方自己不受影响、过期接管、无租约放行、边界(age === ttlMs 即过期)、时钟偏移(atMs 在未来)放行、note 保留、取租 → 让位 → 持租方自清 → 恢复空闲、不得夺权(guardian 清不掉 sentinel 的租约)、损坏/空/缺租约文件一律不锁死、释放写「已释放」证据而非删文件。
不需要网络,也不需要真实外部依赖:telegram 只测判据纯函数与「缺凭据」分支(不真发);租约测试用临时目录。

未覆盖(显式):熔断路径、数据健康自动恢复路径、SIGINT/SIGTERM 优雅停止都没有单测——它们的逻辑内联在 apply() 闭包里,未抽纯函数,这是离线无法取证的根本原因;让位的线上观测、熔断告警端到端(真发消息)亦未完成。四条都在 docs/semantic.md §10 登记为未决问题。

设计要点

- 四道闸门顺序不可换:租约 → 数据健康 → 预检 → 端口。数据健康必须在预检之前——数据损坏会让 preflight 的试运行失败,fail-closed 于是拒绝拉起,服务永远起不来。
- 唯一拉起入口:所有路径(启动自检 / 崩溃自愈 / 端口释放重拉 / 收养轮询)都走同一个 spawnWeb()——一处设卡处处生效;新增拉起路径若不经过它 = 闸门形同不存在(docs/semantic.md §5.4 调用点清单 [MUST])。
- fail-closed 是取舍不是口号:预检不过就不拉起。旧实例可能有问题,但一个新实例没验证过更危险。
- sessionWaker 不进 inject:cordis 的 inject 是激活门,把可选服务写进去,sentinel 缺席时会连保活本身一起被挡住(本末倒置)。故用 ctx.get('sessionWaker') 可选消费,缺席时落证据行跳过。
- 零互踢:端口被非 dsh 进程占用时只做有界等待(5s/轮、300s 上限)+ 超时告警,从不 kill 别人的进程;被 dsh web 占用时收养(含起自愈轮询——否则收养的实例崩了没人拉,2026-08-27 修过的竞态)。
- 租约是「同语义副本」不是跨包依赖:src/lease.ts 是 dsh-agent-sentinel/src/lease.ts 的副本(跨包 file: 依赖会在消费方产生陈旧副本),语义主副本在 sentinel 仓的语义文档;改任一侧必须同步另一侧,两侧各有单测守护一致。guardian 侧只读租约(不取租不释放)。
- 告警通道为什么不直接 fetch:Node 的 EnvHttpProxyAgent 只在进程启动时读 NODE_USE_ENV_PROXY(进程内设置无效),该 flag 只被注入 web 子进程——守护用它承载告警,只能 spawn 一个带 env 的 node 子进程;curl.exe(Schannel 版)经同一代理时 CONNECT 成功但 TLS 握手必失败(实测 exit 35),故只作兜底。子进程输出写临时文件而非管道:Windows 沙箱下管道 stdio 的 spawn 会 EPERM。
- 反定位:不是重启协调器(归 sentinel)、不是进程管理底层(归 runtime)、不是预检判据真源(归 preflight)、不是备份器(只做消费式恢复)、不是沙箱或权限边界——它防「服务静默死掉没人知道」,不防恶意进程。

相关文档

| 文档 | 内容 |
|------|------|
| docs/semantic.md | 权威契约:定位与反定位、术语表、概念模型与不变量、契约(配置 / 证据面 / 纯函数 / 调用点清单)、边界与信任、可证伪验收清单(16 条:proven 11 / pending 5)、实践修订记录、未决问题 6 条 |
| alice-digital-life | 本插件所属生态的中心索引(全部自研插件) |
| 技能 guardian-lifecycle | 守护型进程的安全替换/重启(先新后旧、验证再杀、自愈闭环验证) |
| 技能 guardian-robustness-audit | 守护链健壮性审计:8 项清单(无超时/静默失败/误杀/假活/事件丢弃/上层保活语义错配) |
| 技能 preventive-lifecycle | 预防性存活:启动自检、冷启动自救、能力迁移核对、告警证据链 |

License

MIT © jonah791

本插件属于我的数字生命爱丽丝(alice-digital-life)的 DSH 自研插件生态——50 个插件按生命/认知/感知/行动/通信/治理/呈现七层组织。

上游仓库有新提交时邮件通知你(每天最多一封,无更新不打扰),随时一键退订。

💬 加入 DPharness 群聊

插件用法、部署报错、新插件第一时间同步——群里问,比一个人翻文档快。

点击加入 QQ 群
DPharness 群聊二维码,手机 QQ 扫码进群
扫码进群