Skip to content

Releases

v0.3.0 — 仅保留 systemd 重启按钮

Choose a tag to compare

@RoyougiShiki RoyougiShiki released this 07 Nov 02:59

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 spawn helper;平台判定只剩 linux(含 WSL),其余返回 501。

保留

  • 3 秒延迟:先回 202,3000ms 后才 spawn(['systemctl','--user','restart','dsh-web']),保证浏览器先收到响应。
  • 单飞:同一调度器实例内重复请求返回 409 already-scheduled;待清理结束才释放锁,之后可重试。
  • 15 秒抑制窗:boot 消费 ~/.dsh/dsh-restart.flag,启动后 15000ms 内的请求返回 429 suppressed,防重启循环。
  • loopback 安全栅栏:非 loopback Host/socket、跨源 Origin、sec-fetch-site: cross-site 一律 403 forbidden: loopback-only;不信任 X-Forwarded-For。
  • 固定 argv:恒为 systemctl --user restart dsh-web,不拼 shell、无用户可控参数。

语义边界(务必读)

  • 202 只表示「已接受调度」,不表示服务已恢复,也不表示重启成功。3s 后的 spawn 失败只落在宿主日志,插件没有状态查询接口。
  • 网络未知时不冒充成功或失败:客户端断线/超时提示「无法确认重启请求是否被接受」,且不自动重试。
  • BREAKING:重启会中断进行中的 agent 任务,本版不再自动续接。 升级前请确认没有正在跑的长任务;被中断的会话需要手动恢复。

验证

  • npm test 全绿(exit 0):test:client 47/47,test:host 66/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 核心;修复自动续接从未生效

Choose a tag to compare

@RoyougiShiki RoyougiShiki released this 24 Sep 10:29

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() 自研探测:每 350ms fetch('/') + 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.kind
  • agent/created 监听需返回 undefined
  • 消息 source 由已废弃的共享 kind: 'plugin' → 插件自有 kind: 'dsh-restart-systemd'
  • devDeps 对齐 0.1.7-rc.1(dsh-client-runtime 已下线 → cordis Context + 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' 过滤注入消息的插件需同步更新。