- Home
- Plugins
- Dev & Plugin Tools
- dsh-unlag
dsh-unlag
regrevia/dsh-unlag
Press once to refresh the interface and swap plugin code in place without restarting; running turns, queued messages and unsent drafts are kept.
Install
dsh plugin --profile web add github:regrevia/dsh-unlagREADME
dsh-unlag(解除卡顿)
dsh 桌面端会话开多了、开久了卡了?点一下!
插件更新要我重启?一堆任务在跑呢,咋办?直接点一下!一键刷新,任务照常跑完,大部分需要重启的场景就这么过去了。
刷新解千愁 —— DSH 桌面端自己的刷新按钮。心动不如行动,赶紧来试试。
窗口右上角一个按钮,不重启进程、不刷新掉会话:插件代码原地换掉,老旧卡顿的界面重建一次。
只在桌面 App 的窗口里出现:浏览器直接打开同一个地址时没有这个按钮,本插件不做网页版。
两个日常场景
| 情况 | 按一下 |
|---|---|
| 改了界面类插件,手里还有任务在跑 | 插件当场换掉,宿主自己的任务继续跑完 |
| 界面越用越卡 | 直接恢复流畅,会话、队列、草稿都还在 |
以前两种都得关掉 App 再来。现在不用,也不用挑空闲时间——马上换,不等。
边界说清楚:宿主自己的轮次、子代理、终端进程不受影响;被换掉的那个插件如果自己负责某个正在跑的任务(例如 agent-loop),这个任务会随实例结束。主脚本以外的资源不能配对时,会保留当前页面并说明原因,不会硬重建。
装
dsh plugin install dsh-unlag
安装后由 DSH 完成首次加载,之后的更新都走按钮。
用
点右上角那个按钮。设置里三行决定它做什么:
- 按下刷新时换掉插件代码(默认开)
- 按下刷新时重建界面(默认开)
- 改完文件自动刷新,不必按按钮(默认关)
- 让 Agent 也能按这个按钮(默认关)
前两行都开=先换代码再重建;只开一个=另一项完全不动;都不开=点击无反应。最后一行只决定谁能按,不改变按下去做什么。
谁能让它刷新
默认只有人:自动刷新关着,Agent 入口也关着,本插件不装任何文件监视,本插件自己永远不会去换代码。
右键按钮里打开「Agent 可以自己刷新」后,会话里的 Agent 可以自己按这个按钮(工具名 unlag_refresh),走和按钮完全相同的检查与拦截:内容没保存、插件没恢复、下一页起不来时照样停住并说明原因。关着时 Agent 调它会被告知先打开开关,不会偷偷刷新。
这不等于 App 自己也不会换:宿主自带的插件热更新有自己的开关和监视,装了开发插件、开着那边的自动应用时,改文件仍可能由宿主应用;本插件开关关掉只是本插件不发起。
不会丢什么
- 正在跑的轮次、子代理、终端进程:宿主进程不重启,会话记录和排队消息都留着。被换掉的插件实例会先关掉:如果某个正在跑的任务由那个插件自己负责(例如 agent-loop),这个任务会随实例结束;宿主自己的轮次不受影响。
- 界面重建会重新进入同一个地址:屏幕上正在显示的内容重新载入一次,会话记录还在。
- 排队消息:排在宿主里,重建文档不影响它。
- 输入草稿:重建前先提交焦点里的内容,文字草稿由 App 自己存下来,回到同一会话还在。
- 附件:草稿里的附件只存在当前页面里,文字草稿会存下来,附件不会。草稿里确实还有没发出去的附件时,按钮会停住并说明先发出去;消息发出后留在记录里的图片不算草稿,不会挡住刷新。
- 有未保存改动、开着弹窗、或下个页面要用的模块读不全:拦下并提示,提示里写明卡在哪一项,宁可不刷。
- 原来就起不来的插件不挡换代码,但也算不算「可以重建」:启动以来就没跑过的插件只记日志;上一次更新打坏、还没恢复的必要插件,会在结果里点名,并且下一次按也不会被当成已修好。
- 四个开关读不到时不硬刷:提示稍后再试,日志记下是哪一步没读到,绝不谎称代码已是最新。
- 旧页面记着的启动清单分组变了(装过/卸过插件):按插件逐个补齐再交给新页面,不会因为清单 404 把 App 带走。
- 从不自己重启或退出 App。
管不到(它会拦下并说明,不会硬来):
- 改动替插件加原生能力、改服务声明或权限:这类改动不在一次点击的范围里。
- 独立进程和进程级的东西:宠物窗口自己的配置、DSH 自身升级、原生依赖变化。
- 万一新页面还是起不来:App 会进入错误恢复(退出,或请你停用第三方插件)。本插件不替你选,也不会自己重启或退出 App。
依据的官方约定
- 页面启动时,宿主把插件清单写成
window.__DSH_BOOT__;脚本地址形如/plugins/??<包>/client.js&rev=<修订>,修订由文件的修改时间、变更时间、大小决定(docs/subsystems/client-modules.md)。 - 宿主对未知、被改过、过期的修订返回 404,不会拿别的字节顶上,也不会让单页应用的回退把 HTML 当脚本发出去(同上)。
- 插件代码重建、但入口没重启时,宿主用
rebuilt()发布新修订,页面按新地址只换掉那一个插件(同上)。 - 页面只应用自己启动时那一版宿主的清单;宿主换了版本,页面要重新载入才能跟上(同上,Host release changes)。
本插件只做这些约定允许的事:换代码走宿主的插件重载入口,重建走一次页面载入。唯一的差异在下一节。
与官方约定的差异
官方的插件资源路由对未知修订返回 404。本插件对旧页面记着的旧修订返回当前内容:桌面版在窗口启动时固定了启动清单,页面按旧修订请求脚本是常态,返回 404 会让新页面起不来、App 直接退出。这是本插件的兼容措施,不是官方行为。右键按钮里的开关只决定按下去做什么(换插件代码/重建页面/自动刷新/允许 Agent 按),关掉开关不会停掉这套兼容措施。
同一原因下的第二种情况:启动清单里记着的插件在配置里已经被关掉或删掉了,官方路由对那一行只会 404。本插件改为返回一段空脚本,让这一行不再 404;那个插件因此不会被加载,页面是否能起来仍取决于其余启动行和官方启动流程。日志会写明哪个插件被留下,/dsh-unlag/verify 的结论里也带 leftOut 字段。
排错
- 日志:
~/.dsh/unlag-reload.log,换代码/跳过/被拦各一行,含原因。 - 演练:
npm run live-smoke不按按钮,只按当前启动清单把下一个文档要加载的字节在沙箱里跑一遍,看每个插件是否都注册(rehearsal.ok),并当场核对三件事:写死的旧版本号仍然拿得到现在的字节(shim.staleRev),按下前会做的那个判断此刻是否放行(pressPath),以及那个判断本身会不会误拒(verify.sound应为ok、verify.broken应为blocked、verify.refuses与verify.accepts为true)——它证明的是「按下去之后新页面能起来、按钮现在真的可按、拦住的是坏清单」,不换任何代码。 - 同一支脚本还现场核对路由的守门规则(
contract):读路由只认读、写路由缺x-dsh-unlag头或来自别的源一律 403、未知路径 404。它用的是和页面一样的请求,所以这条规则在运行中的 App 上是否还在,一眼可见。 - 状态:
GET /dsh-unlag/state→{"version":"<当前版本>","code":"<index.js 写入时间>","plugins":true,"page":true,"shim":{...}}。code用来分清「版本号没刷」和「旧代码真的还在跑」。 - 重建后的日志带起页面代次:
interface rebuilt; the page runs the plugins as served now (page <rev>),据此判断新文档加载的是新脚本还是旧脚本。
每类说法对应的测试与现场输出整理在 EVIDENCE.md,含还没证到的两件事。
与官方文档的对照
判定按下后能不能重建,依赖的是 Client Modules 的读接口和它自己公布的路由规则。逐条核对的是 App 内随包发布的文档 .research/asar-dsh/dsh-client-modules/README.zh.md(15968 B,sha256 前缀 33cee13e7b61a151)。
| 官方规则(该文档行) | 这里的做法 | 为什么 |
|---|---|---|
| 未知资源或 revision 返回 404(80) | 冻结的启动清单里的行,旧 revision 照样给现在的字节;表里已经没有的插件仍然 404 | 桌面壳从启动时读到的清单重启文档,任何一行 404 都会让壳以退出码 1 结束,正在跑的会话随之死掉。只在「这一行确实还在表里」时接管,别的 404 原样放行 |
| 启动图与批次 URL 是应用目录相对形式(80) | 校验时按同样形式(plugins/??…&rev=…)读,相对路径补前导 / 后再取 | 与文档一致,避免把文档相对形式当绝对路径 |
| 每个阶段在 URL 超过 3 KiB 前分区(80) | 一行超过 8 KiB 就拒检(blocked/list) | 官方既然在 3 KiB 前分区,真实启动行就短于 3 KiB(这台机器实测最长 2834 B),8 KiB 只挡伪造清单,不会误拒正常行 |
| revision 由 mtime/ctime/大小派生,不哈希内容(80、136) | 只用官方读接口拿到的 revision,不自己算版本;比对的是正文里注册的插件 id | 哈希会引入一套自己的代际,和官方的 404 规则打架 |
bundle 内容变更只能通过 rebuilt() 进入图(78) | 不调用 rebuilt() | 调了会让运行中的页面就地换模块,草稿和正在跑的任务受影响;这里只换宿主侧代码,页面由人来按按钮重开 |
| 图更新在同一个 Loader 上串行执行(90) | 重建走宿主自己的插件重载,不自己改 Loader | 自己改会和官方协调逻辑冲突,这是不能碰的那层 |
改完后让运行中的 App 用上
不用重启 App,也不用重启页面:换掉 index.js 后让插件的记录重载一次即可。
- 改版本号,并
touch index.js client.js。只改版本号是不够的:插件的装载按文件时间判断要不要重新读,时间没变就还是旧代码在跑,/state里会看到版本号没变(或变了但code是旧的)。 - 在插件列表里把本插件关掉再打开。这两步之后
~/.dsh/unlag-reload.log会出现一行新的dsh-unlag <版本>。 - 等约 20 秒,读
GET /dsh-unlag/state:version是新版本、code是刚才index.js的写入时间,才算真的换上了。 npm run live-smoke对着运行中的 App 复核一遍(见下一节)。- 发布用
./scripts/release.sh,它发完会回查 registry 是否真的列出版本。npm view读的是 CDN 缓存,发布成功后的一两分钟里可能还显示上一版;npm view与 registry 不一致时,以版本号文档(https://registry.npmjs.org/dsh-unlag/<版本>)是否 200 为准。
页面那一半(按钮点击后的重建)不在这一步里:那是按钮本人的事,只有人能按。
测试
npm test
对着正在运行的 App 走一遍真实判定(页面半、宿主半都是线上代码,用的是 App 现在渲染的启动清单;只回答「能不能刷」,绝不按按钮),并核对路由契约:读接口 200、写方法 405、不带页面头的按键路由 403:
npm run smoke
没有 App 在监听时打印 skip 并退出 0。
Related plugins
deepseek-harness
deepseek-ai/deepseek-harness
dsh-web (dsh-plugin-manager)
zhu1090093659/dsh-web
dsh-market
dsh-market/dsh-market
dsh-web
zhu1090093659/dsh-web