前言¶
如果你用 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 原生分支为占位。
核心实现¶
触发与调度¶
插件提供两个触发入口,行为一致:
- 侧边栏 footer 的重启按钮,位于 remote-web-ui 电话图标旁,点击后弹出二次确认:「进行中的 agent 任务将中断并自动续接,页面约几秒后自动重连。」
- 聊天输入
/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/::1、sec-fetch-site为cross-site或 Origin 不同源,返回403;X-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 行出现重启图标。
典型用法¶
完整流程是四步:
- 点击侧栏重启按钮(或输入
/restart),完成二次确认。 - 前端 POST
/api/restart-dsh,收到202。 - 约 3 秒后服务重启,页面短暂显示「正在重启…」,随后自动重连。
- 若重启前有 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