dsh-hot-reloader:改完 DSH 插件源码即刻生效,无需重启宿主

前言

在 DeepSeek Harness(DSH)里写插件,常见循环是:改一行逻辑、重启宿主、等进程起来、再手动复现场景。宿主侧自带 @deepseek-ai/dsh-hmr,但 profile 安装的第三方插件实际落在 ~/.dsh/profiles/<profile>/node_modules/<包名>/ 路径下,内置 HMR 对 node_modules 有两处硬编码排除,监听根也多为空,改 lib/*.js 往往进不了重载队列,只能重启。

dsh-hot-reloader 由社区维护者 xfgil 发布,分类为工作流,面向插件开发者:保存源码后约一秒内在运行中的宿主里原地重新加载该插件,不必重启 DSH。它补上内置 HMR 覆盖不到的第三方插件场景;Web 客户端侧另有独立 HMR,本插件针对的是 host 侧服务端代码。

这是什么

dsh-hot-reloader 是 DSH 插件,零外部依赖(仅使用 Node 内置 node:fs、node:path、node:url、node:module)。它遍历已安装插件的包根,轮询比对源码变更,清缓存后按原有配置重建 Cordis fiber。

维护者:xfgil。许可证:MIT(见仓库)。当前 package 版本为 0.1.0;README 标明在 DSH 0.2.0-rc.2 上实测。

内置 HMR 为何管不到 profile 插件

链路可以概括为:

  1. 文件变更即使能到达内置 HMR,模块 URL 含 /node_modules/ 时,loadDependencies() 与 analyzeChanges() 会直接跳过。
  2. 依赖集为空时,重载队列判断恒为 continue,该插件永远不会进入重载。
  3. 内置 HMR 在 base 组合里 root: [],主要面向 profile 配置与宿主模块,排除 node_modules 是保守设计,并非本插件要替代的「官方 bug 修复」。

本插件重新实现监听与重载链路,不排除 node_modules,并尽量复用内置 HMR 的 _resolve() 与 runExclusive() 事务,避免与内置 HMR 撞车;拿不到 hmr 时有自实现兜底。

环境要求与前置条件

项 说明
DSH 0.2.0-rc.2(README 实测)
Node(插件 engines) >=22
宿主启动 须带 --expose-internals;DSH 桌面版宿主进程默认已带,否则插件构造函数会抛错
安装位置 装在 profile 侧,目录宜在 profile 的 node_modules 之外,或使用 link: 安装
内置 @deepseek-ai/dsh-hmr 可选但推荐同时启用,本插件与其协作

不需要额外端口,走宿主 webServer。安装本插件后同样不需要重启 DSH 即可让其生效(profile 清单 reconcile)。

安装

开发时用 link 指向本地目录(路径换成你的绝对路径):

dsh plugin --profile <你的profile> add link:<本目录绝对路径>

示例:

dsh plugin --profile desktop add link:D:/path/to/plugins/dsh-hot-reloader

若使用发布版,可从 npm 安装:

dsh plugin --profile <你的profile> add dsh-hot-reloader

装完后 profile 清单会更新,宿主内置 HMR reconcile 后本插件即可工作。

典型用法

默认行为: 安装后无需额外操作。修改任意已安装插件的 lib/*.js(及包内其它被扫描的 .js/.mjs/.cjs/.json),约一秒左右自动热重载。日志打在宿主日志;也可通过 HTTP 接口查看与控制。

默认端口示例为 19387(以你本地宿主为准):

# 查看状态
curl http://127.0.0.1:19387/dsh-hot-reloader

# 只重载指定包
curl -X POST http://127.0.0.1:19387/dsh-hot-reloader/reload \
     -H 'content-type: application/json' \
     -d '["dsh-onebot-qq"]'

# 全部重载
curl -X POST http://127.0.0.1:19387/dsh-hot-reloader/reload

# 暂停 / 恢复自动重载
curl -X POST http://127.0.0.1:19387/dsh-hot-reloader/toggle
方法 路径 用途
GET /dsh-hot-reloader 监听包、扫描统计、近期日志等完整状态
POST /dsh-hot-reloader/reload 手动重载;body 留空为全部,或 ["包名"] / {"packages":["包名"]}
POST /dsh-hot-reloader/toggle 暂停或恢复自动重载
POST /dsh-hot-reloader/self-reload 重载本插件自身(开发本插件时用)

配置

在 profile 的 cordis.patch.yml 中覆盖插件项,例如:

- id: dsh-hot-reloader
  config:
    intervalMs: 1000      # 轮询间隔(毫秒)
    debounceMs: 400       # 变更后等待写入稳定
    reloadSelf: false     # 是否自动重载本插件自身
    only: []              # 只监听这些包名(空 = 全部)
    except: []            # 跳过的包名
    extraRoots: []        # 额外监听的绝对路径
    includeAsar: false    # 是否监听 app.asar 内宿主包
    ignore: ['.bak', '.test.', '.spec.', '.map']
    maxDepth: 8
    quiet: false          # 静默:只记录失败
    installQuietMs: 8000  # 检测到包管理操作后的让路时长;0 关闭
    profileDir: ''        # 默认从 loader 树推导

只盯正在开发的包、或做大重构时临时降频/静默,可按 README 示例调整 only、quiet、intervalMs 等。

工作原理(概要)

  1. 定位包根:从 loader entry 解析 URL,向上找 package.json。
  2. 轮询比对:扫描包内 JS/JSON,比较 mtimeMs 与 size(profile 的 node_modules 里通常解析不到 chokidar 等,故用轮询)。
  3. 防抖:等待 debounceMs,避开编辑器中间写入态。
  4. 清缓存:清除该包在 Node ESM LoadCache 与 CJS require.cache 中的条目,不连坐清理包内嵌套的第三方依赖。
  5. 重建:重新 import 入口,卸载旧 plugin 实例,按原 config 重建;失败则整体回滚,避免半死状态。

同一包根多个 entry 会一并重挂;默认不碰 app.asar 内宿主包。

适用场景与注意

适合: 频繁改 DSH 第三方插件 host 侧代码的开发者,希望把「改 → 重启 → 复现」压成「改 → 看结果」。

限制(README 已说明):

  • 重载会丢掉该插件持有的内存状态(连接、缓存、定时器等);长连接类插件可能在瞬间短暂中断。
  • 依赖宿主 --expose-internals 与 Node loader 内部结构,属开发向能力。
  • 插件以当前 DSH 进程权限运行;安装前应自行审阅 GitHub 源码 与 MIT 许可证,勿在未信任的来源安装任意插件。

DSH 社区插件目录为独立站点 SkillHub 插件页,与 DeepSeek / 幻方无官方从属关系。

链接

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

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

Xiaoye