dsh-restart-systemd:给 DSH WebUI 加一个会自动续接会话的重启按钮

前言

如果你用 systemctl --user 跑 dsh-web,大概经历过这套动作:打开终端,敲 systemctl --user restart dsh-web,然后回到浏览器手动刷新页面。如果有 agent 任务正在跑,那一轮 turn 就此中断,还得手动补一句话让它继续。

dsh-restart-systemd 把这件事搬进了 WebUI:点一下侧栏的重启按钮(或者输入 /restart 命令),服务在约 3 秒后重启,浏览器自动重连,被中断的会话自动续接。下面介绍它的实现思路、安装方法与使用边界。

这是什么

dsh-restart-systemd 是一个 DSH 插件,定位是「DSH WebUI 重启按钮(systemd 版)」,由 RoyougiShiki 维护,许可证 MIT,当前版本 0.1.0。

它只做一件事:调度 systemctl --user restart dsh-web,并把善后处理干净——浏览器自动重连、被中断的 agent 会话自动续接。主场景是 WSL/Linux 下的 systemd 用户态(已实测),Windows 原生分支为占位。

核心实现

触发与调度

插件提供两个触发入口,行为一致:

  1. 侧边栏 footer 的重启按钮,位于 remote-web-ui 电话图标旁,点击后弹出二次确认:「进行中的 agent 任务将中断并自动续接,页面约几秒后自动重连。」
  2. 聊天输入 /restart 命令,返回确认文本。

确认后前端 POST /api/restart-dsh。host 侧先把当前 running 的 agent id 快照到续接列表,写入 flag 文件,随即返回 202 {scheduled:true, delayMs:3000},3 秒后再 spawn systemctl --user restart dsh-web。spawn 用白名单 argv,没有 shell 拼串。延迟 3 秒的目的,是让浏览器先收到 202。

防滥用保护

重启是破坏性操作,插件做了几层防护:

  • 单飞锁:已有一发在途时,重复 POST 返回 409 already-scheduled
  • 残留窗口:插件驱动的 boot(flag 被消费)后 15 秒内再次点击返回 429,防重试乒乓。
  • loopback fence:socket/Host 非 127.0.0.1/::1sec-fetch-sitecross-site 或 Origin 不同源,返回 403X-Forwarded-For 永不被信任。

flag 文件 $DSH_HOME/dsh-restart.flag 充当一次性 boot token:spawn 前写入,boot 时消费一次即删,绝不自行触发重启。残留 flag 因此无法造成二次重启循环。

自动重连与会话续接

服务重启后,浏览器经 ConnectionController 指数退避(500ms→10s)自动重连,无需手动刷新。

新 boot 上,插件消费 flag、装上 agent/created 监听、读取续接列表 $DSH_HOME/dsh-restart-resume.json:对最后 turn 被中断(存在未闭合 turn/start,或最近一次 turn/end.reason === 'interrupted')的会话,执行 agent.followup("Continue.") 自动续接。正常结束的会话绝不打扰。恢复是尽力而为:60 秒 RECOVERY_TIMEOUT_MS 窗口内未重建的 agent 会被丢弃。

双面插件

这是一个双面插件,经 cordis.patch.yml 注入一行,安装后 host 与 client 两半同时挂载:

半边 运行环境 职责
host node(宿主) /api/restart-dsh 路由、/restart 命令、flag/resume 状态
client 浏览器 侧栏按钮 UI、二次确认、状态反馈

路由只注册 /api/restart-dsh,避开 /plugins 前缀,无 bundle 供数冲突。

安装与启用

先构建,需要 devDependencies:

cd dsh-restart-systemd
npm install
npm run build    # tsc -b → lib/

装入 web profile:

dsh plugin --profile web add /tmp/dsh-restart-systemd

安装后无热重载,需重启服务生效:

systemctl --user restart dsh-web

验证插件已加载:新开一个会话,journalctl --user -u dsh-web 出现 dsh-restart-systemd: 开头的日志,且侧边栏 footer 行出现重启图标。

典型用法

完整流程是四步:

  1. 点击侧栏重启按钮(或输入 /restart),完成二次确认。
  2. 前端 POST /api/restart-dsh,收到 202
  3. 约 3 秒后服务重启,页面短暂显示「正在重启…」,随后自动重连。
  4. 若重启前有 running agent,journald 出现 recovery armed for N session(s)resuming interrupted agent <id>,对应会话自动继续。

整个过程可以在 journald 里按顺序看到三条日志:

restart scheduled  delayMs=3000
spawning restart for dsh-web consumed leftover restart flag

验证 loopback fence,伪造 Host 发跨站请求,预期返回 403

curl -H 'Host: evil.example' -X POST http://127.0.0.1:3080/api/restart-dsh
# 403 forbidden: loopback-only

单飞可以快速连点两次按钮验证,第二次返回 409 already-scheduled

回滚:

dsh plugin --profile web remove ui-dsh-restart-systemd
systemctl --user restart dsh-web
rm -f ~/.dsh/dsh-restart.flag ~/.dsh/dsh-restart-resume.json

重启后按钮与命令即消失,不改动其它配置;两个状态文件随时可删。

适用场景与注意

这个插件适合主环境为 WSL/Linux、用 systemctl --user 启动 dsh-web、且频繁需要重启服务加载插件或改配置的开发者。

使用前了解几个边界:

  • 单一服务单元:systemctl 命令固定为 dsh-web,没有配置项可改。
  • 非 systemctl 启动则不生效:若以 node … 手跑 dsh-web,spawn systemctl 会失败并记日志,不产生重启。
  • 恢复是尽力而为:60 秒窗口内未重建的 agent 被丢弃;clean 会话永不被主动续接。
  • Windows 为占位:原生 Windows(非 WSL)走 detached helper 分支,但 helper 本体未随包附带,机器相关;WSL 主场景不进入此分支。
  • 版本 0.1.0:package.json 的 homepage/bugs 字段仍是 <your-org> 占位。

最后是一条通用提醒:插件以当前 dsh 进程的权限运行,而这个插件能触发服务重启。虽然它自带 loopback fence,但插件机制本身意味着相当的信任——安装前应检查源码与许可证(本项目为 MIT)。

结尾

dsh-restart-systemd 做的事情小而克制:让「重启服务」不再打断你的工作流。点一下按钮,浏览器自动重连,被中断的会话自动续接,防重启循环的保护做在调度层里。如果你的 dsh-web 跑在 systemd 上,值得一试。

  • 目录页(社区维护的独立站点,与 DeepSeek / 幻方无官方从属关系):https://www.skillhub.cn/plugins/RoyougiShiki/dsh-restart-systemd
  • 源码:https://github.com/RoyougiShiki/dsh-restart-systemd
羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

Xiaoye