改完插件代码自动重载并刷新界面,无需重启
30/100
综合分
0
GitHub 星标
—
npm 周下载
—/5
用户评分
数据截至 2026/10/19 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add Monter-code/dsh-live-reload该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:待复验
是什么
dsh 原生插件 · dev
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 14 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
读写/删除本地文件发起外部网络请求
查看扫描证据(共 5 处,展示前 5 条)
- 发起网络请求lib/client.js:40
const response = await fetch(url, { cache: 'no-store', signal }) - 读取本地文件lib/snapshot.mjs:52
entries = await readdir(dir, { withFileTypes: true }); - 读取本地文件lib/snapshot.mjs:69
const info = await stat(abs); - 读取本地文件lib/roots.mjs:24
const manifest = JSON.parse(await readFile(join(profileDir, 'package.json'), 'utf8')); - 读取本地文件lib/roots.mjs:34
const manifest = JSON.parse(await readFile(join(dir, 'package.json'), 'utf8'));
扫描于 2026/10/31(1 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
DSH 免重启热刷新插件:监视插件源码变化,重新加载 host 侧插件模块并刷新 Web 界面,让插件更新立刻生效,无需重启桌面端。
README
由 DeepSeek 最新模型翻译生成// 在其它插件里
ctx.inject(['liveReload'], (ctx) => {
// 订阅变更:每次热重载后回调(参数是本次重载的插件名数组)
ctx.liveReload.onChange((names) => {
console.log('已热重载:', names)
})
// 主动触发一次检查(等价于 live_reload action=check) await ctx.liveReload.check()
// 主动触发一次重载(可带筛选子串) await ctx.liveReload.reload({ target: 'dsh-image-insight' }) })
服务还暴露 status()、start()、stop()、roots(),与工具 action 一一对应。
6. 工作原理
1. 找目录:从 loader 里挑出所有 link: 来源的 entry,取其绝对路径;再并上配置里的 watch,减掉 exclude。
2. 算指纹:轮询每个目录下所有文件的 mtimeMs + size,拼成一个字符串哈希。变了就进入去抖窗口。
3. 重载:对命中的 entry 依次做「清宿主模块缓存 → 重新 import → 重新 apply」,并换掉 registry 里的绑定。等价于宿主内置 HMR 的流程,只是触发源换成了磁盘轮询。
展开完整文档(README 剩余 1 节)
// 在其它插件里
ctx.inject(['liveReload'], (ctx) => {
// 订阅变更:每次热重载后回调(参数是本次重载的插件名数组)
ctx.liveReload.onChange((names) => {
console.log('已热重载:', names)
})
// 主动触发一次检查(等价于 live_reload action=check) await ctx.liveReload.check()
// 主动触发一次重载(可带筛选子串) await ctx.liveReload.reload({ target: 'dsh-image-insight' }) })
服务还暴露 status()、start()、stop()、roots(),与工具 action 一一对应。
6. 工作原理
1. 找目录:从 loader 里挑出所有 link: 来源的 entry,取其绝对路径;再并上配置里的 watch,减掉 exclude。
2. 算指纹:轮询每个目录下所有文件的 mtimeMs + size,拼成一个字符串哈希。变了就进入去抖窗口。
3. 重载:对命中的 entry 依次做「清宿主模块缓存 → 重新 import → 重新 apply」,并换掉 registry 里的绑定。等价于宿主内置 HMR 的流程,只是触发源换成了磁盘轮询。
4. 通知界面:rev 自增,按钮轮询到变化就点小圆点;refreshUi: auto 且用户空闲时直接 location.reload()。
7. 为什么不用 fs.watch
fs.watch 在 Windows 上对「编辑器先写临时文件再改名」这种保存方式经常丢事件,在 Linux 上对深层目录又要为每个子目录挂一个 watcher。轮询虽然笨,但行为确定、跨平台一致,而且插件目录通常只有几十个文件,700ms 一轮的开销可以忽略。
8. 排错
| 现象 | 先看 |
| 改了代码没反应 | live_reload action=status 看目录是否在监视列表里;action=cache 看模块缓存键是否真的被清掉 |
| 报告重载成功但行为没变 | 多半是插件把状态存进了闭包外的模块级变量,重新 import 拿到的还是旧引用;改用 ctx.provide / 服务 |
| 按钮不出现 | 确认宿主版本 ≥ 0.2.1,且 conversation.session.header.utilities 槽位存在 |
| 页面反复自动刷新 | 把 refreshUi 调成 manual,或调大 debounceMs |
9. 许可
MIT
const live = ctx.get('liveReload');
const off = live.onChange((items) => console.log('热重载完成', items));
await live.reload({ target: 'my-plugin' }); // 立刻重载
live.status(); // 与工具 status 同一份数据
live.roots(); // 监视目录数组
6. 界面刷新怎么工作
两个半边各司其职:
host 半边(lib/index.js,延迟注入 webServer):
- ctx.inject(['webServer'], (sub) => sub.effect(() => webServer.register(...))) —— 0.2.x 里 apply 阶段拿不到 webServer(提供者 fiber 还没 ACTIVE),必须等依赖就绪;不 inject 声明只 ctx.get() 会静默失败(旧 0.1.0 就是这么 404 的)。
- GET /__dsh-live-reload/state → { rev, at, items, startedAt, roots, stats, ui }(几十字节 JSON,no-store)
- GET /__dsh-live-reload/check → 先比对磁盘、必要时热重载,再返回结果(按钮点击时调用)
- GET /__dsh-live-reload/reload → 强制重载一次
浏览器半边(lib/client.js,手写 lazy-CJS,dsh.client.immediately: true):
- ctx.slots.inject('conversation.session.header.utilities', …) 注册 dsh-live-reload:refresh(order: 30,排在 better-sidebar 的 10/20 之后)。
- 组件自己轮询 /state;rev 变化 → 按钮变品牌色 + 小圆点;refreshUi: auto 且当前焦点不在输入框 → 600ms 后自动 location.reload()。
- 为什么刷新要由页面发起:0.2.x 全库没有 location.reload(),宿主只能通过 SSE 通知「这个 bundle 换了」,页面自己决定何时换。
为什么不往 index.html 注入脚本:桌面壳的 index.html 由安装包静态 dist 直出,tapIndex 不参与渲染;webserver/index-inject 的结构化行只在宿主启动时收集一次、之后没有刷新路径。client half 才是 0.2.x 的正式通道。
7. 实现要点(为什么这么写)
1. 必须「先删 registry,再 apply 新模块」。宿主 Fiber.dispose 里有一句
if (this.ctx.registry.has(runtime.callback)):如果先 dispose 旧 fiber 再删 registry,Loader 的
internal/plugin 监听会走到 case 6,把 entry 写成 disabled: true 并持久化 —— 插件从此不再加载。
顺序错了就是这个后果,所以代码里把它写成显式步骤并加了断言测试。
2. 必须清 Node 的模块缓存。ESM/CJS 缓存不清,重新 import 拿到的还是旧模块(Node 24 的 LoadCache 用 Map,而且它的 delete 只是把槽位置空,所以用 Map.prototype.delete.call())。
而且键未必是监视目录的路径:link: 装入的插件在宿主里的模块标识常常是 <profile>/node_modules/<pkg>/...(junction 路径),与配置里监视的真实目录字面不同。
所以匹配同时用四层线索:路径前缀、realpath 穿透、目录 basename、插件包名。
真机上「一次都没有清掉」曾导致报告成功但代码没换,这正是 cache action 存在的原因。
2.5. 清不掉缓存时退化为「带版本号重新导入」:给模块 URL 加 ?dshlr=<时间戳> 会强制 Node 产生新的模块实例,于是即使宿主没有 --expose-internals(loadCache 不可见)也能拿到新代码。代价是旧的模块实例会留在缓存里(开发期可接受);工具输出会明确告诉你这一次用的是哪种方式(mode: purge | bust | none)。
两种方式都拿不到新代码时不会假装成功:卡片上会打出显式警告。
3. 用宿主自己的 entry.parent.tree.import(name, stack) 重新加载,而不是自己 import() 一个 URL:这条路径就是宿主 HMR 用的那条,--expose-internals 存在时走 loader.internal.import,不存在时退化为普通 import,行为一致。
4. import 失败绝不碰正在运行的插件:先把新模块 import 成功,才动 registry;apply 抛错则把旧插件装回去(rolledBack: true)。
5. 为什么不用 fs.watch:Windows 上编辑器普遍「写临时文件 → 改名覆盖」,fs.watch 经常漏事件;漏一次就等于改了代码不生效。插件源码目录很小(默认最多 5000 文件),700ms 扫一次指纹的开销可以忽略。
6. webServer 用延迟注入、不写进 inject 数组:headless profile 没有 webServer 服务,硬注入会让插件在 headless 里起不来;ctx.inject(['webServer'], …) 既能等到服务就绪,又能在没有该服务的 profile 里继续跑 host 半边。
7. 浏览器半边手写而不是打包:lib/client.js 就是 window.__ModuleLoader__.load({id, factory}) 的产物,零依赖、零构建;dsh.client.immediately 必须为 true,否则惰性模块表只「提供」不执行。
8. 自测
text
node tests/selftest.mjs # 29 例:忽略/指纹/差异、根发现、重载内核调用顺序、轮询、端点尾段、client half
node tests/integration.mjs # 21 例:假宿主 apply → 工具 → 改动 → 重载 → 延迟注入路由 → 服务通知
integration 用的假宿主完整复刻了宿主的「无损 JSON 快照 + schema 严格校验 + render 必须返回
ContentBlock[] + presentResult 必须返回视图」,因此 schema 写错、字段漏声明都会被测出来。
client half 的用例会在 node:vm 里真的执行 lib/client.js,用假 window.__ModuleLoader__ 与假 React 把按钮渲染出来(data-dsh-live-reload="refresh")。
9. 排错
| 现象 | 原因 / 处理 |
| live_reload status 里 roots 是空的 | 没有 link: 装入的插件。用配置项 watch 手工指定目录,或 exclude 把它排除了 |
| 改了代码没重载 | live_reload action=check 手动催一次;或看 ignore 是否命中了你的文件;或该插件不在被监视目录里 |
| 报告「已热重载」但工具行为没变 | 先跑 live_reload action=cache:命中数为 0 说明缓存键没对上,会走 mode=bust;两者都没效果时重启一次 DSH 并反馈 cache 输出 |
| reloaded[].mode = "none" | 既没清掉缓存,也无法把模块名解析成文件(dsh plugin add link: 未装好或包名不对);此时没有换代码 |
| reloaded[].reason = "import-failed" | 新代码有语法/导入错误,旧插件仍在运行(先修好语法) |
| reloaded[].reason = "entry-not-loaded" | 该插件当前没在运行(被禁用或未加载),自动路径会跳过 |
| 看不到刷新按钮 | 按钮挂在会话标题栏右侧——没打开会话时没有这个槽位;另外确认 package.json 里 dsh.client.immediately === true、页面是刷新后重新加载的(旧页面拿不到新的 client bundle) |
| 按钮点了没反应 | /__dsh-live-reload/state 是否 200(curl -H 带 token 或看浏览器网络面板);refreshUi: never 时按钮仍然可点,但不会自动刷新 |
| 页面不自动刷新 | refreshUi 为 auto 时只在「没有输入框/可编辑区获得焦点」时自动刷新;manual 只点小圆点提示,需手动点按钮 |
10. 已验证
host 半边:在 headless profile(真实宿主进程,不是模拟宿主)里做过端到端验证:装入一个 link: 小白鼠插件(dsh-lr-victim,模块级 const VERSION、每次求值生成随机 LOAD_NONCE)→ 它改写自己的源码 → 热重载 → 再次调用它的工具:拿到的是新版本号 + 新的模块加载随机数,而进程 PID 不变(没有重启)。
第 3 轮真机记录(live_reload status 与工具返回原文,节选自 fixtures/lr-e2e-3.json): text lr_victim_version → version=2 nonce=lbaet2tf loadedAt=2026-10-15T09:20:41.060Z pid=9072 lr_victim_bump → 已写入 …\profiles\headless\node_modules\dsh-lr-victim\lib\index.js:2 → 3
live_reload status(自动监视触发后) ♻️ 热重载状态正在监视 3 个插件目录,已热重载 2 次。
· dsh-lr-victim → D:/test-dsh/experiments/lr-victim ✅ dsh-lr-victim:清理 ESM 8 / CJS 0(清缓存后重新导入)
· 变更 D:/test-dsh/experiments/lr-victim:lib/index.js 统计:扫描 6 次 · 触发 2 次 · 重载 2 次 · 失败 0 次
lr_victim_version → version=3 nonce=csj16ukc loadedAt=2026-10-15T09:20:44.705Z pid=9072 ← 新代码生效
live_reload reload target=victim
♻️ 热重载 · reload ✅ dsh-lr-victim:清理 ESM 1 / CJS 0(清缓存后重新导入)
lr_victim_version → version=3 nonce=wjfzpsm3 loadedAt=2026-10-15T09:20:47.742Z pid=9072 ← 又一次全新实例
结论:nonce 每次都不同 ⟹ 每次都是全新的模块求值(不是复用旧实例);VERSION 跟随磁盘源码(2 → 3)⟹ 加载的确实是磁盘上的新代码;三次读数 pid 恒为 9072 ⟹ 进程没有重启。
一个反面记录也留在仓库里:修 bug 之前,宿主里的模块标识是 profile 的 junction 路径(…\profiles\headless\node_modules\dsh-lr-victim\lib\index.js),与配置里监视的真实目录(D:/test-dsh/experiments/lr-victim)字面对不上,导致缓存清了 0 条、报告成功但代码没换。
现在的四层匹配线索(前缀 / realpath / basename / 包名)与 mode=bust 兜底就是为此加的, live_reload action=cache 可以在真机上直接看到命中情况。
0.2.1 实机(本地 dsh web 测试台,真实宿主进程): text GET /__dsh-live-reload/state → 200 {"rev":0,…,"ui":{"mode":"auto","pollMs":1500,"path":"/__dsh-live-reload","enabled":true}} ⟹ 延迟注入生效(0.1.0 用 ctx.get('webServer') 时这里是 404)
页面 HTML 的 client 预加载批次里出现:…,dsh-live-reload/client.js,…&rev=32aa13e50aee 批次响应 1,926,557 字节,含 conversation.session.header.utilities ⟹ client half 被宿主发现并投递(dsh.client.immediately=true 生效)
0.2.1 之前的版本没有 HTTP 端点:旧写法在 apply 里 ctx.get('webServer', false) 拿到 undefined,路由与 index 注入都被 try/catch 静默吞掉。0.2.1 起改走 ctx.inject(['webServer']) + client half,这两个失败模式都已在测试里固定住(界面:延迟注入后注册 prefix 路由、client half 真的能被模块表加载)。预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
Monter-code/dsh-live-reload
改完插件代码自动重载并刷新界面,无需重启
相关插件 · 开发 / 工程
隐私简历生成器reactive-resume/reactive-resume
Reactive Resume 已迁至 GitHub 上的…
★ 45,125已汉化MCP
71.7
AI 架构文档生成器sopaco/deepwiki-rs
🚀 Litho 已进化为 Terrain —— 给你的 AI 智能体一张会呼吸的代码库地图。
★ 3,157已汉化DSH
70.8
长时任务执行闭环AMAP-ML/LongHorizon-Harness
面向 Computer-Use Agent 的 Loop Engineering
★ 1,764已汉化DSH
70.5
AI 编程纪律方法包GanyuanRan/Aegis
让 AI 编程 agent 变得可信:少返工、更安全、说“完成”前先给证据。
★ 1,384已汉化DSH
69.6
多代理编排运行时Enderfga/claw-orchestrator
面向编程智能体的运行时。把 Claude Code、Codex、Antigravity、Grok…
★ 586已汉化MCP
69.4
记忆进化插件csyangwen/dsh-memory-evolve
dsh-memory-evolve 使用场景指南
★ 461已汉化DSH
67.9