前言¶
在 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 插件¶
链路可以概括为:
- 文件变更即使能到达内置 HMR,模块 URL 含
/node_modules/时,loadDependencies()与analyzeChanges()会直接跳过。 - 依赖集为空时,重载队列判断恒为
continue,该插件永远不会进入重载。 - 内置 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 等。
工作原理(概要)¶
- 定位包根:从 loader entry 解析 URL,向上找
package.json。 - 轮询比对:扫描包内 JS/JSON,比较
mtimeMs与size(profile 的node_modules里通常解析不到 chokidar 等,故用轮询)。 - 防抖:等待
debounceMs,避开编辑器中间写入态。 - 清缓存:清除该包在 Node ESM LoadCache 与 CJS
require.cache中的条目,不连坐清理包内嵌套的第三方依赖。 - 重建:重新 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