DeepSeek Harness Plugin Hub

发布与管理完整 Harness Profiles,发现适合你的插件。

探索

插件目录环境预设文档中心动态

社区

发布插件联系我们报告问题

相关链接

Plugin Hub GitHubDeepSeek Harness 官方项目系统状态隐私说明
© 2026 DeepSeek Harness Plugin HubPowered byPaxTech

独立、非官方社区项目,与 DeepSeek 官方无隶属、授权或背书关系。

Hot Reload — DeepSeek Harness 插件(DSH Plugin)
← Plugins

dsh-hot-reload

Hot Reload

无需重启 dsh,即可热重载升级后的 DeepSeek Harness (dsh) 插件——运行中的插件会原地替换;如果重载失败,则回滚到可正常工作的旧版本,并提示手动重启。

插件会安装到这里;不确定时保持 web。

npx -y @deepseek-ai/dsh plugin --profile web add dsh-hot-reload@0.2.4
README兼容性版本

兼容性与来源证明

Hot Reload 以 dsh-hot-reload 发布,当前版本为 0.2.4。Plugin Hub 会校验它的 manifest,并保存精确安装来源,便于复现安装结果。

DSH 兼容范围
*
运行环境
web
发布来源
npm
Registry 更新时间
2026/9/20

版本

0.2.4stable
2026/8/20
0.2.3stable
2026/8/20
0.2.2stable
2026/8/19
查看其余 7 个版本
收起版本
0.2.1stable
2026/8/16
0.2.0stable
2026/8/16
0.1.4stable
2026/8/16
0.1.3stable
2026/8/16
0.1.2stable
2026/8/16
0.1.1stable
2026/8/16
0.1.0stable
2026/8/16

相关插件

正在加载相关插件…

最新版
0.2.4
DSH
*
HMR
重启进程
Tree shaking
未声明可安全裁剪
解包体积
91.3 kB
文件数
8
Surface
web
许可证
MIT
发布源
npm
GitHub
★ 2
周下载
118
安全扫描
✓ v0.2.4 扫描通过
最近提交
2026/8/22
查看源码 ↗项目主页 ↗
README Badge

点击下方 Badge 复制 Markdown,粘贴到 README 即可。

这是你的 Plugin?认领权益 · 优先安全扫描

验证 package.json 声明的 GitHub 仓库,即可管理这个公开页面。认领后,Hub 会优先安排当前版本的安全扫描,并在通过后公开展示结果。

认领这个 Plugin →
报告问题
DeepSeek Harness Plugin Hub
ProfilesPlugins分类动态文档登录管理 Profiles
ProfilesPlugins分类动态文档登录

README

dsh-hot-reload

English | 中文

Live-reload upgraded DeepSeek Harness (dsh) plugins without restarting dsh.

dsh's built-in hot-reload (cordis-plugin-hmr) deliberately ignores node_modules, so upgrading an installed plugin (dsh plugin add pkg@x) normally requires a full dsh restart to take effect. This plugin closes that gap: it watches your profile's pnpm-lock.yaml, and when an already-loaded plugin package's version changes, it swaps the running plugin in place.

Behavior

On a plugin package upgrade, for each affected plugin:

  • Reload it live — invalidate the module caches, re-import the new code, and re-instantiate the plugin fiber in place. dsh, your sessions, and every other plugin keep running.

  • If the reload fails — you're left with the working old version, never a dead plugin, plus a logged note that a manual dsh restart is needed to pick up the new code. Two cases, both handled:

    • a failure while loading the new code (bad import, syntax error) is caught before the running plugin is touched — the old plugin is never disturbed;
    • a failure while initializing it (the new apply throws, sync or async) is rolled back — the old version is re-instantiated in place.

    A version that failed is not retried automatically — retrying would tear down the working plugin again on every later lockfile write. Install a different version, or restart dsh, to pick the new code up.

Two cases produce no reload, by design:

  • Disabled plugin rows are skipped silently. A disabled plugin isn't running, so there is nothing to swap — and re-enabling it makes dsh load the new code anyway.
  • A plugin that has no live fiber yet (still importing, or it failed to load earlier) is reported as no live fiber to reload right now and left alone. Nothing was torn down, so this one is re-examined on every later lockfile change, and it reloads by itself as soon as a running copy appears. You are told once per version, not once per check — so if you still see the same version reported after the plugin has had time to start, restart dsh.

Upgrading dsh-hot-reload itself

dsh-hot-reload can hot-reload itself too. When its own package is upgraded, the running instance imports the new module, commits its own version and persists it, closes its watcher, and then swaps its own fiber in place. The new instance re-reads the state file and opens its own watcher — the old watcher is closed before the new one opens, so there is never more than one live watcher.

To make that handoff — and a plain restart — safe, the plugin keeps its tracked state in a file: profileDir/.dsh-hot-reload-state.json. It holds the committed versions, the never-retried failedVersions, and the once-announced noticedVersions. The file is written atomically — a .tmp file is written and then renamed over the target, so a crash mid-write never leaves a truncated file — and read back at startup, so a fresh instance inherits what the previous one committed instead of treating already-loaded plugins as new and re-reloading them.

This state file is required. If it cannot be written at startup, the plugin refuses to start (it throws) rather than running with in-memory-only state that a later self-reload or restart would lose.

It never restarts dsh for you — restarting is left to you (and your supervisor, if any).

How you see what happened

The plugin writes every result to dsh's log. But dsh does not print its log to your terminal, so those lines are easy to miss. Two extra places show you what happened.

1. One line in your terminal, for every outcome. You get this in every profile:

dsh-hot-reload: hot-reloaded some-plugin@1.2.0 (1 module(s))

2. A short pop-up message in the dsh web app. You get one when a reload works. You also get one in every case where the new code did not load, so the old code is still running:

  • the reload failed, and the old version was put back
  • the plugin has no running copy to swap out
  • the plugin turned off hot reload with dsh.hotReload: false
  • dsh did not provide the internal parts the reload needs

The second case is announced once per version, not once per check. That case is retried on every later lockfile write — including writes for other packages — so without this limit you would get the same pop-up over and over, with no way to dismiss it. The other three are announced each time they happen.

The message slides in, stays a few seconds, then fades out. If one upgrade reloads several plugins, the messages line up and show one after another.

The web part only loads in a profile that runs a web server. It sends the messages over GET /dsh-hot-reload/events. A profile with no web server, such as tui, still gets the terminal line and the log.

Messages are not saved. If no browser tab is open when a reload happens, that message is gone. The log still has the record.

If you restart dsh with a tab open, the tab opens a new channel by itself and pop-ups keep working. It tries for about three minutes. If it still cannot connect after that, it writes one line to the browser console and stops trying; reload the page to start again.

If you want every line in your terminal

The terminal line above covers every reload outcome — reloaded, failed, and stale (not attempted — the old code is still running). To see everything else this plugin writes to the log (its warnings and diagnostics), add dsh's console logger to your profile. It is a separate package:

dsh plugin --profile web add @deepseek-ai/cordis-plugin-logger-console

Then add a row for it in that profile's cordis.patch.yml and restart dsh:

- insert:
    - id: logger-console
      name: '@deepseek-ai/cordis-plugin-logger-console'

This prints all dsh log lines, not only this plugin's.

Note for full-screen profiles. The terminal line is written straight to the screen. In a profile that draws a full-screen interface, such as tui, the line can land in the middle of the drawing and make the screen look wrong. It looks wrong only until the screen is drawn again.

Install

dsh plugin --profile web add dsh-hot-reload

Then restart dsh once (bundle patch layers load at boot). After that, upgrades apply live:

dsh plugin --profile web add some-plugin@newer   # reloaded automatically

Works in any profile — swap web for whichever profile you use; it watches the profile it's loaded into.

Compatibility

Built and tested against dsh 0.1.0-rc.6 (Node 22 / 24). It reaches into cordis/loader internals — mostly the same ones cordis-plugin-hmr uses — so a future dsh that changes any of them may require an update:

InternalUsed for
loader.internal.loadCacheinvalidating the ESM module cache
loader.internal.resolve / resolveSyncresolving a specifier to a URL (dispatched on internal.version)
loader.import / loader.unwrapExportsre-importing the fresh module and unwrapping its plugin export
registry.plugin / registry.deleteswapping the plugin instance
fiber.entry, fiber.runtimere-attaching the new plugin to the running rows
entry.disabledskipping disabled rows (inherited getter)
entry.options.groupskipping group container rows

The pop-up message in the web app (and only that part) also uses:

dsh partUsed for
ctx.webServer.registerserving the message channel
window.__ModuleLoader__loading the browser half
the shell.overlay slotplacing the message over the app
Toast from @deepseek-ai/dsh-client-ui-primitivesdrawing it

The plugin fails safe. If a part it needs is missing, it reports "restart needed" instead of breaking dsh. The pop-up behaves the same way. A missing web server, a browser module it cannot load, an unknown slot, a repeated registration, or a dsh build with no Toast each cost you the pop-up only. Reloading still works, and the web app still starts.

One exception: the browser half asks dsh for a service named slots. dsh's web app refuses to start if any plugin never becomes ready. So if some future dsh build had no slots service at all, this part would wait forever and show up in dsh's start-up error list. Every other failure listed above is caught and simply does nothing.

Opting out

A plugin that knows it isn't safe to hot-reload can force the restart-needed path (no reload attempt) by declaring, in its own package.json:

{ "dsh": { "hotReload": false } }

Configuration

Set on the hot-reload row in your profile's cordis.patch.yml:

KeyDefaultMeaning
debounce300ms to wait after a lockfile change before acting
profileDirautoabsolute path to the profile dir to watch (auto-detected from the loader base URL if omitted)

Limitations — read this

This plugin is optimistic, not verified. It attempts the reload and falls back to "restart needed" only when something throws (or when there is no live fiber to swap). It does not detect silent leaks:

  • A plugin that acquires a raw resource outside cordis — a bare setInterval, a net/http server, a WebSocketServer, an fs.watch, a child_process — without a ctx.effect disposer can reload without throwing yet leave that resource dangling (a stray timer, a duplicate listener, an orphaned watcher). These accumulate across upgrades and are cleared only by an eventual restart.

  • Cordis auto-unwinds everything a plugin registers through ctx (ctx.effect, ctx.on, ctx.provide, tool schemas, adapters), so well-behaved plugins reload cleanly. The risk is limited to plugins that bypass ctx. If in doubt, have such a plugin set dsh.hotReload: false.

  • Reloading a plugin that holds live connections (e.g. a WebSocket bridge) drops and re-establishes them; clients must reconnect. That's expected, not an error.

  • The reload path relies on the cordis/loader internals listed under Compatibility. If they are unavailable (no --expose-internals and no node-addon-require-builtin addon), the plugin degrades to reporting "restart needed" for every change instead of reloading.

  • The lockfile is only the trigger. Version numbers are read from each package's installed package.json, because that is the only file that says what an import would really get. On pnpm 11 (measured on 11.21.0) the files on disk are written first and the lockfile last, so by the time this plugin acts, the versions it reads have settled. But nothing here checks that. If some future pnpm wrote the lockfile first, a check could read the old version, skip it, and never look again — the lockfile is the only thing watched, so that upgrade would be missed silently, with no message, until you install another version or restart dsh. The debounce setting does not help: the gap measured 2.5–4 seconds, far longer than any sane debounce.

  • The message channel (GET /dsh-hot-reload/events) has no password check, the same as dsh's own /plugins/events. It sends plugin names and version numbers. dsh already shows those through its plugin list, so this adds no new secret. But if you bind dsh to 0.0.0.0, count it as one more address that anyone on your network can open.

Scope note: this handles upgrades of already-loaded plugins. Installing a brand-new plugin is a separate concern (adding its row to cordis.patch.yml, which dsh already hot-applies).

License

MIT © Stuart Hu

  • A plugin that loads after dsh-hot-reload in the bundle order is reloaded once — to its current version — on the first lockfile write after boot, even when that write was for an unrelated package. Until this plugin has tracked the package across one cycle, it cannot tell whether the running code is the old or the current version, so it reloads rather than adopting a version that may never have run. For an HMR-safe plugin this is a harmless single redundant reload.

  • The state file is required. The plugin writes its tracked state to profileDir/.dsh-hot-reload-state.json. If that file cannot be written — for example because the profile directory is read-only — the plugin refuses to start (it throws) rather than running with in-memory-only state.

  • 相关插件

    继续浏览 developer-tools 分类下经过校验的插件。

    Web App@deepseek-ai/dsh-web-appdsh 浏览器界面捆绑包:位于 dsh-base 之上的 Web 补丁层,加上运行时粘合插件(提供前端 dist、Web 界面提示符、bash 运行时变量和 URL 行)Sdk Minimal@deepseek-ai/dsh-sdk-minimal独立的最小 SDK 配置包:JSON-RPC、一个 DeepSeek 适配器、持久化 Shell 和 JSONL 会话Sdk App@deepseek-ai/dsh-sdk-appdsh SDK 配置包:基于 dsh-base 提供 stdio JSON-RPC 服务和进程生命周期管理Subagent Codex@deepseek-ai/dsh-subagent-codex基于官方 app-server 协议的一次性 Codex 子代理提供程序