DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

Dayi-Z /

Dayi-Z/dsh-safety-restart

Verified

Safe, seamless restart for DeepSeek Harness: the agent can restart the host itself and resume afterwards. Four gates (restart-loop breaker, other sessions mid-turn, unresolvable profile bundles, unavailable app.relaunch) refuse with a reason instead of forcing it.

★ 0 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@e702262e

dsh-safety-restart

English TL;DR — A safe, seamless restart plugin for DeepSeek Harness. The agent can restart the host itself (host-half code changes only take effect after a full process restart) and gets woken up afterwards to continue. Before restarting it must pass four gates: restart-loop breaker, other sessions mid-turn, unresolvable profile bundles, and (on desktop) an unavailable app.relaunch(). Any gate that fails means refuse with a reason, never force it through. Desktop uses Electron app.relaunch(); web/CLI uses a detached 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/<name>/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 <n> / --port=<n> 优先,否则 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

—/ 5

No ratings yet

Verified DSH bundle

Commit e702262e1dd8

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout