dengnianchen/dsh-restart:在 Web GUI 里重启 DSH Host

前言

DSH 的设计是「一切皆插件」,Host 升级、配置改动、服务异常之后,都需要重启 Host 让改动生效。常规做法是打开终端手动结束进程再拉起来,Web 界面上没有任何入口,跑着的回合和终端也会被中断,因此很难在浏览器里完成一次靠谱的重启。

下面介绍的 dsh-restart 就是为了把这个动作放进 Web GUI:在 Settings → General 里加一行,在侧边栏账号菜单里加一项,同时也暴露一个 agent 工具 dsh_restart。插件由 dengnianchen 维护,托管在 GitHub,属于客户端分类,主站目录页有收录。

这是什么

一句话:给 DeepSeek Harness 的 Web 界面加一个 Host 重启入口,并且在没有 systemd 时也能工作。

它解决的问题是:

  • Web GUI 上无法触发 Host 重启,改完配置只能靠终端。
  • 在进程内直接调用 systemctl restart 会连自己一起杀掉(下面的实现部分解释原因)。
  • 重启后的页面刷新需要可靠判断,不能把一次短暂断连当成成功。

插件是普通的 bundle plugin,包含 Host 半部分和 Web 半部分(一个 ESM 模块加一段客户端代码),不改动侧边栏 footer 图标、Host 文件和 profile 里手写的配置。

核心功能

三个入口,做的是同一件事:

入口 位置 实现方式
设置行 Settings → General,排在 “Performance & usage” 之后 官方 settings.general.item 插槽,order: 35,行外壳沿用 Host 自己的 General 行样式
账号菜单项 Settings / Feedback / Restart dsh / Sign out 之间 DOM 桥接,原因见下面的限制说明
Agent 工具 dsh_restart({ delayMs? }) 与上面两个 UI 入口走同一套重启逻辑

重启动作的交互流程:

  1. 第一次点击进入警告态,提示 Click again to confirm,4.5 秒后自动失效。
  2. 第二次点击排定重启,界面显示 Restarting…。
  3. 页面每 0.7 秒轮询 /dsh-restart/status,只有当 Host 返回的 pid 与之前不同时才重新加载页面——这样重启过程中的短暂断连不会被误判为成功。
  4. 如果 120 秒内没有出现新进程,控件转为错误态并提供重试。

重启过程中,正在运行的回合和终端会被中断;会话、transcript 和设置都会保留。

安装与启用

安装命令来自插件 README:

plugin_manager install_bundle  @dyapple/dsh-restart

插件需要 DeepSeek Harness ≥ 0.2.1-rc.3,因为它使用了 ctx.connection.requestRejection 和 settings.general.item 插槽。

重启是怎么发生的

Host 侧按运行环境分两条路径,两者都会先延时,保证 HTTP 响应在 Host 退出之前回到页面。

  1. systemd 环境。插件通过 INVOCATION_ID 和 /proc/self/cgroup 里的普通 .service 判断自己是否跑在 systemd 下,并确认 systemd-run --user 可用;此时会用一个 detached transient unit 延时执行 systemctl --user restart <unit>。

这里不能直接从进程内调用 systemctl restart:dsh-web 的 KillMode=control-group,重启会先把执行调用的辅助进程一起杀掉。transient unit 跑在另一个 cgroup 里,可以绕开这个问题。

  1. 非 systemd 环境(例如手工启动的进程)。用一个 detached 的 /bin/sh 等待旧 pid 消失、端口稳定,然后 exec 同一条命令行,同时旧进程退出。

HTTP 接口

插件注册了两个接口:

路径 方法 用途
/dsh-restart/status GET 返回 {ok, version, pid, mode, unit, command, startedAt, uptimeMs, restarting}
/dsh-restart/restart POST Body 为 {delayMs?},默认 700 ms

两个接口都先经过 ctx.connection.requestRejection()(Connection 的 Host/Origin 限制加上签名 Cookie);POST 还额外要求 content-type: application/json,跨站的简单请求无法携带这个头。没有 Cookie 时两个接口都返回 401。

账号菜单项的限制

账号菜单项是 DOM 桥接实现,不是官方插槽。原因是:@deepseek-ai/dsh-client-ui-settings-account 里的菜单项是硬编码数组,没有为额外行预留插槽;它唯一的位置 settings.launcher 是单值的,占用它等于替换整个账号菜单。

因此插件的做法是:监听 [role="menu"] 节点,用同时包含 Settings 和 Feedback 标签来定位账号菜单,克隆随包发布的 Settings 行(继承其 class、几何与 hover 样式),替换图标和文案,再插到 Sign out / Sign in 之前。

具体限制:

  • 它在 React 的菜单项列表之外,Tab 键可以到达,方向键不行。
  • 它依赖随包发布的菜单文案和结构。如果后续版本改了文案,它会静默消失——不报错,也不影响其他 UI。设置行不受影响,因为它用的是官方插槽。
  • 每次菜单打开时节点会被重建,用 WeakSet 防止重复插入。

适用场景与注意

  • 适合:已经用 systemd user service 托管 DSH Host、并且主要通过 Web GUI 操作的人;想在 agent 里通过工具触发重启的开发者。
  • 不适合:需要重启立刻完成的场景。即使使用默认延时,从点击到 Host 恢复也会有数秒停顿,正在运行的回合和终端会被中断。

使用时需要注意:

  • 插件以当前 dsh 进程的权限运行,重启动作因此继承了启动该进程的用户权限。安装前应先检查源码(仓库公开在 GitHub)和许可证。本插件使用 MIT 许可证,版权人为 Nianchen Deng。
  • 账号菜单项依赖客户端 UI 的文案结构,升级客户端后如果入口消失,可回退到 Settings → General 里的那一行,或直接使用 dsh_restart 工具。

开发调试

从源码开发时按 README 的步骤:

git clone https://github.com/dengnianchen/dsh-restart.git
plugin_manager install_bundle  ./dsh-restart

profile 会链接这份 checkout,因此 client.js 的改动通过 HMR 生效;index.js 的改动需要重启 Host——恰好可以用这个插件自己来做。Host 半部分是一个单文件 ESM 模块,每个进程只引入一次。

小结

dsh-restart 把 Host 重启从终端搬进 Web GUI,用官方插槽保证设置行的可靠性,用 pid 变化判断重启是否真正完成,并在 systemd 和非 systemd 两种环境下都给出可工作的实现。代价是账号菜单项依赖客户端 UI 的当前结构,升级客户端后可能需要留意。

相关信息页:

  • 社区目录页:https://www.skillhub.cn/plugins/dengnianchen/dsh-restart
  • 代码仓库:https://github.com/dengnianchen/dsh-restart
羽毛球分组比赛记分
小程序二维码

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

小夜