← 返回列表
未验证
让智能体自检四道闸后安全重启宿主并续跑
尚未跑自动兼容性验证,可查看页面内的依赖与入口分析。 · 最近上游提交 2026/9/13 · 已提供中文文档
DeepSeek Harness 的安全、无缝重启:智能体可以自行重启宿主,并在之后恢复。四道关卡(重启循环断路器、其他会话处于回合中、无法解析的配置文件包、不可用的 app.relaunch)会给出理由并拒绝,而不是强行重启。
综合分
29.6
GitHub 分
29.6
用户评分
—
★ Stars
0
周下载量
—
安装插件(需先安装 dsh CLI 引擎:npm install -g @deepseek-ai/dsh)
dsh plugin --profile web add Dayi-Z/dsh-safety-restart该插件未发布到 npm,走 GitHub 源安装(pnpm 若拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑)
数据截至 2026/9/16(元数据每日更新 · 实装验证按队列轮转,单条结论的验证时间见上方)
依赖的 DSH / Cordis 模块
@deepseek-ai/dsh-tools@deepseek-ai/dsh-llm用户评分
还没有人投票,来当第一个
订阅周报,不错过优质插件更新
每周一封 · 高评分插件 + 新用户活动
README
dsh-safety-restart
English TL;DR — 一个为 DeepSeek Harness 提供的安全、无缝重启插件。Agent 可以自行重启宿主(宿主半代码的改动只有在整个进程重启后才会生效),并在之后被唤醒以继续。重启前它必须通过四道闸:重启循环熔断、其他会话正在跑一轮、无法解析的 profile bundle,以及(在桌面端)不可用的 app.relaunch()。任何一道闸失败都意味着带原因拒绝,绝不强行通过。桌面端使用 Electron 的 app.relaunch();web/CLI 使用一个分离的 helper。
DSH 的安全无感重启插件。
它解决什么
改完宿主半(index.js / lib/.js)必须整进程重启才生效。这不是洁癖,是实测:
回收渲染器进程只会重载外壳。插件确实会重新 apply、日志也照打,但服务请求的那张路由表仍然是更早那次 apply 的 —— 新加的路由一律 404,而返回的是插件自己那句 not found,看起来像“路由写错了”,其实是“根本没走到新 handler”。
而在此之前 agent 没有重启的手段:只能让用户去点。这个插件把它变成一次工具调用。
四道闸(“安全”具体是什么)
每一条都对应一次真实踩坑,不是设想出来的风险。
| 闸 | 拒绝条件 | 为什么 |
|---|---|---|
| 重启循环熔断 | 窗口内已重启 ≥ loopMax(默认 3 次 / 10 分钟) | 一个起不来的配置会让每次启动都失败,而 agent 看到失败只会想再重启 —— 那是重启风暴,代价是机器一直在 boot |
| 有别的会话在跑一轮 | 除调用方外还有会话处于 turn/start 之后未 turn/end(除非 force: true) | 盲着重启会把别人正在做的事从中间切断,而且切断后那一轮的工具调用结果未知 |
| profile 的 bundle 表解析不了 | 某个 bundle 在 node_modules 里不存在、或入口文件不存在;或者根本找不到 profile 根目录 / 读不到它的 bundle 表(checked:false 一律拒绝) | 重启进一个坏掉的 profile,得到的是起不来的 app —— 连报错界面都没有;而“没查到”不等于“检查通过”—— 未知状态应当拒绝,不是绿灯 |
| 拿不到可靠拉起手段 | 桌面端 electron.app.relaunch 不可达 | 绝不退化成“杀进程”:那会把 app 关掉而不拉起来,把可恢复的状态变成不可恢复的 |
任一不过就是拒绝重启并说明原因,返回里带 gate 与 why。拒绝不是失败 —— 那道闸就是为了不让你把环境弄成不好收拾的样子。
无感在哪
1. 重启前写续作标记(会话 id),重启后把“你已经重启过、请继续”投回原来那个会话。
顺序是刻意的:先写标记,再拉起。反过来会在进程先退出时丢掉标记。
2. 续作提示里明确要求:重启前结果未知的工具调用不要盲目重试,先核查实际影响。
3. 标记消费掉就删;标记文件损坏时返回空数组而不是抛 —— 重启后的启动不能因为读标记而挂。
两条重启路径
| 环境 | 机制 | 为什么不能只用一种 |
|---|---|---|
| 桌面端(Electron 内嵌宿主) | app.relaunch() + 延迟 app.quit() | 桌面端宿主的 webServer 是 IPC 载体(app://),不开端口 —— 任何“起 helper 等健康检查通过”的做法在这里都不成立 |
| web / CLI | 写一个 detached helper(detached:true + stdio:'ignore' + unref()),老进程随后 process.exit(0) | helper 必须在父进程死掉之后继续活着;它等旧 pid 退出 → 用同一份 execPath / execArgv / argv 拉起新实例 → 等它活过 10 秒 → 若命令行里推得出 web 端口(--port 或默认 3080)再等端口恢复(健康检查) → 把结果写进审计。健康检查来自 dsh-guardian 编排器(90s 健康检查)与 start-harness.ps1(端口探测 + HTTP HEAD)的同类实践 |
装
// profiles//package.json
{
"dependencies": { "dsh-safety-restart": "link:/path/to/dsh-safety-restart" },
"dsh": { "profile": { "bundles": [ "...", "dsh-safety-restart" ] } }
}
装完必须重启一次 app —— 插件自己也要先被加载才谈得上重启。
用
Agent 侧(工具):
safety_restart({ reason: "改了宿主半的 index.js,让新路由生效" })
safety_restart({ reason: "...", force: true }) // 即使有别的会话在跑一轮也重启
人侧:设置 → 插件 → 「dsh-safety-restart」卡片(运行环境、熔断计数、最近一次审计、立即重启)。
HTTP(web 端;只接受本机请求,反代/远程访问会被 403):
GET /safety-restart/status
POST /safety-restart/restart { "reason": "ui" }
审计与状态文件
都在 $DSH_HOME(默认 ~/.dsh):
| 文件 | 内容 |
|---|---|
| safety-restart-audit.jsonl | 只追加。一次重启算一次的判定是 kind:"restart" && phase:"scheduled";其余(helper / exit / quit / refused / resume)都是诊断信息 |
| safety-restart-resume.json | 续作标记(待叫醒的会话 id),消费后删除 |
| safety-restart-helper.mjs | web 端生成的 helper(每次重写) |
| safety-restart.json | 可选配置:loopWindowMs / loopMax / delayMs / busyStaleMs |
配置
{
"loopWindowMs": 600000,
"loopMax": 3,
"delayMs": 1500,
"busyStaleMs": 1800000
}
delayMs 是“发起重启”到“真的退出”之间的等待,留出时间把这次工具结果回传给模型。设成 0 会让结果大概率丢掉。
已知限制(如实写,不粉饰)
- busy 只覆盖本进程启动之后的轮次。 它是按 session/event 维护的,更早就在跑的会话不在里面。所以第二道闸是“尽力而为”,不是权威快照。超过 busyStaleMs 的记录会被当作过期清掉(防止一条漏掉的 turn/end 把闸门永久焊死)。
- bundle 体检是轻量的:它只证明“包在、入口文件在”,不证明它能被 import 成功,更不证明客户端半在 slot 契约上是对的。要那一层用 dsh-guardian 的 bundle scan。
- web 健康检查是“端口恢复”,不是“HTTP 200”。 helper 只做 TCP 连接探测(127.0.0.1 上的端口能连上就算恢复)。它证明的是 web 在监听,不是 web 健康* —— 后者需要服务的业务路由(dsh-guardian 探 /guardian/report 就是这样)。端口怎么推:--port / --port= 优先,否则 argv 里有 web 子命令默认 3080,推不出来就跳过健康检查退回 10 秒存活检查。宁可不探测也不探测错误的端口:新实例其实活着,却因为探错端口误报失败,比不探测更糟。
- web 端退出是 process.exit(0)(非优雅)。 会话日志是只追加的 jsonl,写到哪算哪;工具结果在 delayMs 之前已回传;尾巴真被截了也有续作提示兜。不退就永远重启不成,那才是真的坏。
- 客户端半按 rc 契约写:settings.plugin.item 在 rc.12 是 list(要 id),更晚的契约是 keyed(要 key)。所以两个都传 —— 多传一个用不到的字段不会报错,少传必需的那个才会。整个 apply 包在 try/catch 里:契约再漂,结果也只是“没有这张卡片”,不会牵连界面。
这里的坑都是踩过的
开发过程中端到端测出来、并已修掉的两个:
1. web 路径最初根本没完成:helper 起来了、等了 30 秒、然后报 old pid still alive — giving up。因为没有任何人让老进程退出 —— helper 等的是“老进程死掉”,而它就那样继续服务着。修法是 helper 起来后老进程自己 process.exit(0)。
2. 熔断计数翻倍:一次重启会在审计里留下多条(begin/scheduled/helper/quit),全数进来会让“一次重启动”算成两次 —— 实测第三次就直接熔断了,而配置写的是 3。修法是只数一条。
自检
node scripts/run-all.mjs
47 条断言,都是“闸门的判定边界”:熔断窗口与计数口径、checked:false 的诚实降级、标记损坏不抛、生成的 helper 是合法 ESM(含带健康检查端口的变体)、--port 推端口的解析规则、会话 id 的三种取值回退、调用方自身的排除。
License
MIT同作者(Dayi-Z)的其他插件
扫码进群