Repository navigation
Releases
v0.3.0 — 仅保留 systemd 重启按钮
v0.3.0 — 仅保留 systemd 重启按钮
本次是删除式重构:插件从「重启 + 恢复 + 续接」的复合体,收缩为只做一件事——把 systemd 重启按钮放进 WebUI 侧边栏 footer。宿主单元名与 systemctl --user restart dsh-web 的绑定不变。
移除(不再由本插件承担)
- 自动续接(agent 续接 /
Continue.注入):不再注册agents、不再写会话快照、不再判定「被中断的 turn」、不再注入续接消息;lastTurnInterrupted与旧版dsh-restart-resume.json的兼容逻辑一并删除(该遗留文件仍会在 boot 时删除)。 - 等待恢复 / 自动刷新页面:不再轮询
fetch('/')、不再用sessionStorage兜底、不再调用location.reload。连接状态与「点击立即重连」由 DSH 核心的ConnectionIndicator提供。 /restart命令:命令面与命令注册表依赖全部移除。对外只剩POST /api/restart-dsh与侧栏按钮。- Windows 分支:不再有 detached
spawnhelper;平台判定只剩linux(含 WSL),其余返回 501。
保留
- 3 秒延迟:先回 202,3000ms 后才
spawn(['systemctl','--user','restart','dsh-web']),保证浏览器先收到响应。 - 单飞:同一调度器实例内重复请求返回 409
already-scheduled;待清理结束才释放锁,之后可重试。 - 15 秒抑制窗:boot 消费
~/.dsh/dsh-restart.flag,启动后 15000ms 内的请求返回 429suppressed,防重启循环。 - loopback 安全栅栏:非 loopback Host/socket、跨源 Origin、
sec-fetch-site: cross-site一律 403forbidden: loopback-only;不信任X-Forwarded-For。 - 固定 argv:恒为
systemctl --user restart dsh-web,不拼 shell、无用户可控参数。
语义边界(务必读)
- 202 只表示「已接受调度」,不表示服务已恢复,也不表示重启成功。3s 后的 spawn 失败只落在宿主日志,插件没有状态查询接口。
- 网络未知时不冒充成功或失败:客户端断线/超时提示「无法确认重启请求是否被接受」,且不自动重试。
- BREAKING:重启会中断进行中的 agent 任务,本版不再自动续接。 升级前请确认没有正在跑的长任务;被中断的会话需要手动恢复。
验证
npm test全绿(exit 0):test:client47/47,test:host66/66。npm run typecheck:exit 0。- 两套测试都不启动真实 systemctl、不向 3080 或真实 profile 发请求;host 侧用真实 loopback HTTP +
subprocess.spawn硬替身,DSH_HOME全部指向临时目录。 - 未验证项:真实宿主上的端到端重启未实测(只验证到 spawn 替身);跨实例的 flag 原子性与互斥不做承诺(热重载会产生新实例,共享
DSH_HOME时不提供跨实例锁)。
安装(Linux / WSL)
npm ci && npm run build
# 必须带 --config.auto-install-peers=false:pnpm 8 起 auto-install-peers 默认开启,
# 会顺着 peer 列表现抓 registry 上不存在的 @deepseek-ai/* 版本,导致整条命令失败
dsh plugin --profile web add link:$PWD --config.auto-install-peers=false
systemctl --user restart dsh-web本版未发布到 npm;分发资产为下方 dsh-restart-systemd-0.3.0.tgz。
v0.2.0 — 重连交给 DSH 核心;修复自动续接从未生效
v0.2.0 — 重连交给 DSH 核心;修复「自动续接从未生效」
本 tag 为覆盖发布(同一版本号 0.2.0 重建):首次发布后发现宿主侧自动续接存在三个缺陷,其中一个是根因性的,故重打 tag 并替换资产。
修复:自动续接从头到尾没工作过
1)inject 漏声明 agents(根因,最隐蔽)
cordis 只给已声明依赖解析服务。提供方是 dsh-base 的 agent 行,它与本插件是根级兄弟(dsh-base patch 注释原话 "ONE insert over the empty profile root"),不是祖先 —— 于是读 ctx.agents 抛 cannot get property "agents" without inject,而旧代码 catch { return [] } 把它吞成空数组。
后果:每次重启写出的快照都是 sessionIds: [],自动续接永远不可能触发;日志里只留一句 recorded 0 running agent(s),看着像"当时没任务在跑"。
现在:声明 agents;失败时 logger.warn 明说"本次不会续接任何会话";空快照打出 live=N (id:status, …),同类问题一眼可辨。
2)续接有以 boot 为基准的 60s 截止时间
会话只在被重新打开时才恢复,而"用户何时回来"不可预期。实测:会话 18:40:58 被打断,21:06 才被恢复,那时窗口早已关闭,续接完全没发生。现在取消截止时间,pending 存活整个进程;真正的门槛是「最后 turn 是否仍处于 interrupted」—— 用户若已手动恢复过,那个 turn 会以 completed/aborted 收尾而被跳过,不会打扰。
3)flag / resume 消费时序竞争
consumeStaleState 与 arm 的两次异步文件读并发;无 flag 时前者会删掉残留 resume 标记,若 arm 抢先读到,就会给"非本插件导致的重启"武装续接。现在先 await 消费、再 arm。
移除(改由核心负责)
DSH 0.1.7-rc.1 起客户端运行时自带完整连接恢复 UX,v0.1 的自研实现属重复建设:
waitForReconnect()自研探测:每 350msfetch('/')+ 20s 超时 +sawDown启发式。核心的ConnectionController(退避 base 500ms → cap 10s)能正确区分「真的重连」与「重启前的旧进程仍在应答」,自研启发式做不到。- busy/done 分阶文案、spinner 轮询、
sessionStorage兜底标记:核心的ConnectionIndicator渲染在侧边栏底部设置行,已提供「连接异常,点击立即重连 / 连接中断,正在重试,点击立即重连 / 重新连接中 / 连接成功(2s)」以及点击强制重连。
保留
- 重启后整页刷新一次,改由
ctx.connection.state驱动:核心只重连传输层、不会刷新页面(核心客户端代码里没有任何location.reload),重启后浏览器仍执行重启前的 client bundle。判定抽为纯函数restartReloadDecision,护栏是「观察到真实断线之前的connected绝不刷新」。 connection为软读取(ctx.get,不写进inject):宿主无该服务时按钮与/restart命令照常工作,仅不再自动刷新。
修复 0.1.7-rc.1 的 host API 漂移
v0.1 编译于 0.1.0-rc.6,源码对新 DSH 已不能 typecheck(编译产物还能跑,故一直未被发现):
agent.session.events已移除 →Session.snapshotEvents()turn/end.reason由字符串变为{kind}判别联合 → 读reason.kindagent/created监听需返回undefined- 消息 source 由已废弃的共享
kind: 'plugin'→ 插件自有kind: 'dsh-restart-systemd' - devDeps 对齐 0.1.7-rc.1(
dsh-client-runtime已下线 → cordisContext+dsh-client-ui-renderer的 slots 声明合并)
其中
session.events一处尤其危险:旧代码抛出的 TypeError 被try/catch吞掉,使lastTurnInterrupted()对任何会话都返回 true。实测把旧实现对着新 Session 形状跑,4/4 个「干净结束」用例全判成需要续接 —— 即会给快照里每个会话都灌一条Continue.。
验证(npm test,87 项全绿)
| 套件 | 项数 | 覆盖 |
|---|---|---|
test:client |
41 | loader 接线、重启-刷新状态机全分支、SSR 渲染与中英 13 键双向覆盖 |
test:host |
25 | lastTurnInterrupted 全分支、arm() 端到端、无截止时间、inject 声明 |
test:wiring |
11 | 真 cordis 下证明「兄弟拓扑 + 未声明 → 抛错 / 已声明 → 可达」 |
test:cycle |
10 | 真 cordis + spawn 打桩跑完整宿主链路 |
后两套用真 cordis 加载真插件并把 subprocess.spawn 打桩,不需要重启服务即可验证宿主半边,物理上也不可能触发重启。
对照实验:同一套 test:cycle 跑在修复前的构建上得 sessionIds: [],修复后得 ["session-interrupted","session-clean"] —— 测试对 bug 敏感,非空测试。
升级提示
续接注入消息的 source.kind 由 'plugin' 变为 'dsh-restart-systemd'。任何按 source.kind === 'plugin' 过滤注入消息的插件需同步更新。