본문으로 건너뛰기
R

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.

설치

dsh plugin --profile web add github:regrevia/dsh-unlag

README

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 后让插件的记录重载一次即可。

  1. 改版本号,并 touch index.js client.js。只改版本号是不够的:插件的装载按文件时间判断要不要重新读,时间没变就还是旧代码在跑,/state 里会看到版本号没变(或变了但 code 是旧的)。
  2. 在插件列表里把本插件关掉再打开。这两步之后 ~/.dsh/unlag-reload.log 会出现一行新的 dsh-unlag <版本>。
  3. 等约 20 秒,读 GET /dsh-unlag/state:version 是新版本、code 是刚才 index.js 的写入时间,才算真的换上了。
  4. npm run live-smoke 对着运行中的 App 复核一遍(见下一节)。
  5. 发布用 ./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。

관련 플러그인