Hello! 欢迎来Regret的小站!

DSH 越用越卡?加一个按钮,不重启把插件换掉


avatar
Regret 2026-11-06 37
按一下按钮,做了两件事
关键在于宿主进程不重启,任务照跑

卡这件事,agent 用户都遇过

DSH 用久了会慢,慢的方式还很有特点:不是一次比一次明显,而是某个时刻开始,打字有延迟、滚动发涩、切换会话要等一下。

原因不神秘。它是一个长期驻留的单页应用,插件在内存里越堆越多,界面状态只增不减。浏览器里打开十个小时的页面,谁都这样。

问题是你没法随便关。因为你可能:

  • 正在跑一个四十分钟的重构任务
  • 有三个子代理在并行查资料
  • 队列里还排着两条等着发的话
  • 输入框里有一行没发出去的草稿

关掉重开,这些全没了。所以真实情况往往是:明明卡得要命,还是忍着用。

它到底做了什么

按钮在窗口右上角。按下去做两件事:

  1. 插件代码原地换掉——走宿主自己的插件重载入口,不重启进程
  2. 界面重建一次——重新载入同一个地址,屏幕上显示的内容刷新一遍

关键在于宿主进程不重启。这是它和「关掉 App 重开」的本质区别:

  • 正在跑的轮次:宿主自己的任务不受影响
  • 子代理:继续跑
  • 终端进程:继续跑
  • 排队消息:排在宿主里,重建文档不影响
  • 文字草稿:重建前先提交焦点里的内容,App 自己存下来,回到同一会话还在

桌面 App 的窗口里有这个按钮,dsh web 打开的浏览器页面里也有。两边认得出来自己在哪:桌面壳会把自己的桥交出来,网页没有这座桥。

两个日常场景,一句话说清

情况 按一下的结果
改了界面类插件,手里还有任务在跑 插件当场换掉,宿主自己的任务继续跑完
界面越用越卡 直接恢复流畅,会话、队列、草稿都还在

以前这两种都得关掉 App 再来。现在不用挑空闲时间——马上换,不等。

边界:什么会丢,什么不会

这一节是整篇文章最该仔细看的地方。作者把边界写得极其具体,我按「会丢」和「不会丢」重新整理了一遍。

不会丢的:

  • 宿主自己的轮次、子代理、终端进程
  • 会话记录
  • 排队消息
  • 文字草稿(附件不算,见下)
  • 已经发出去的图片

可能会丢的:

  • 被换掉的那个插件自己负责的任务。比如 agent-loop 这种由插件驱动的循环,插件实例一关,这个任务就跟着结束。这不是 bug,是「换掉插件」的必然结果——记住这条,改这类插件前先看有没有活跑在里面。
  • 草稿里的附件。文字草稿会存,附件只存在当前页面里。所以草稿里真还有没发出去的附件时,按钮会停住并告诉你要先发出去。消息发出后留在记录里的图片不算草稿,不会挡刷新。

会被拦下的(宁可不刷):

  • 有未保存改动
  • 开着弹窗
  • 下个页面要用的模块读不全
  • 四个开关读不到

拦下的时候会写明卡在哪一项,不会含糊地说「刷新失败」。

管不到的(会明说,不硬来):

  • 给插件加原生能力
  • 改服务声明或权限
  • 独立进程和进程级的东西:宠物窗口自己的配置、DSH 自身升级、原生依赖变化

这三类不在一次点击的范围里。想改这些,老老实实重启。

三个开关,一个配置项

右键按钮,菜单里有三行:

  • 按下刷新时换掉插件代码(默认开)
  • 按下刷新时重建界面(默认开)
  • Agent 可以自己刷新(默认关)

前两行都开=先换代码再重建;只开一个=另一项完全不动;都不开=点击无反应。

第四项不在菜单里,在配置里:改完文件自动刷新(默认关)。开了之后不用按按钮,改完自己刷新。

第三行「Agent 可以自己刷新」值得单独说。打开之后,会话里的 Agent 可以自己按这个按钮,工具名 unlag_refresh。但它有个硬约束:只换宿主侧的插件代码,页面不会重开,也不会替你按下按钮。还有子任务在跑时,Agent 调它会被拒绝并列出这些子任务——因为换插件会把子任务插件一起换掉,在跑的子任务会被中止。

关着的时候 Agent 调它会被告知先打开开关,不会偷偷刷新。这个设计是对的:能自己刷新的 agent 很危险,它可能在你不注意的时候把你正在看的状态刷没。

它凭什么能做到

靠的是宿主自己公布的约定,不是黑魔法。三条:

  1. 页面启动时,宿主把插件清单写成 window.__DSH_BOOT__;脚本地址形如 /plugins/??<包>/client.js&rev=<修订>,修订由文件的修改时间、变更时间、大小决定
  2. 宿主对未知、被改过、过期的修订返回 404,不会拿别的字节顶上
  3. 插件代码重建、但入口没重启时,宿主用 rebuilt() 发布新修订

本插件只做这些约定允许的事:换代码走宿主的插件重载入口,重建走一次页面载入。

有一处故意偏离官方约定,也是它能用起来的原因:

官方的插件资源路由对未知修订返回 404。本插件对旧页面记着的旧修订返回当前内容。

为什么必须这么干?因为桌面壳在窗口启动时就固定了启动清单,页面按旧修订请求脚本是常态。返回 404 会让新页面起不来、App 直接退出。所以这是兼容措施,不是官方行为。

同一原因下的第二种情况:启动清单里记着、但配置里已经被关掉或删掉的插件,官方路由只会 404。本插件改为返回一段空脚本,让这一行不再 404。日志会写明哪个插件被留下。

排错:别信它自己说「成功」

作者在排错这一节写得很实在,我提炼四条最常用的:

日志:~/.dsh/unlag-reload.log,换代码/跳过/被拦各一行,含原因。

演练:

npm run smoke

不按按钮,只按当前启动清单把下一个文档要加载的字节在沙箱里跑一遍,看每个插件是否都注册(rehearsal.ok),并当场核对三件事:写死的旧版本号还拿不拿得到现在的字节(shim.staleRev)、按下前会做的那个判断此刻放不放行(pressPath)、那个判断本身会不会误拒。

重点是最后一句:它证明的是「按钮此刻真的可按」,不换任何代码。演练不等于整机刷新已经安全。

状态:

GET /dsh-unlag/state

返回里有 version、code、plugins、page、shim、served。这两个字段的用途很明确:

  • code 用来分清「版本号没刷」和「旧代码真的还在跑」
  • served 用来分清「按钮没出现」是插件图里没这一行(missing)、还是图还没就绪(unknown)

按钮不出现且 served 是 missing:先看 profile 的 package.json 里 dsh.profile.bundles 还列不列着 dsh-unlag。宿主装别的插件时会按 dependencies 重算这份有序清单,手装或软链进来的条目可能被一并丢掉。加回去、重启即可。

装和用

dsh plugin install dsh-unlag

安装后由 DSH 完成首次加载,之后的更新都走按钮。

给插件作者的一条经验也适用于你自己改插件:只改版本号是不够的。插件的装载按文件时间判断要不要重新读,时间没变就还是旧代码在跑。要 touch index.js client.js,再在插件列表里关掉再打开,等约 20 秒读 /state,version 和 code 都对上了才算真换上。

一句话

「重启一下」是四个字,代价可能是四十分钟。 这个插件把「重启」拆成了「换代码」和「重建界面」两件小事,各自都能安全地做——剩下的活,宿主自己接着跑。


*参考:GitHub regrevia/dsh-unlag(MIT,49 stars),2026-11-06 更新。*

暂无评论

发表评论