
卡这件事,agent 用户都遇过
DSH 用久了会慢,慢的方式还很有特点:不是一次比一次明显,而是某个时刻开始,打字有延迟、滚动发涩、切换会话要等一下。
原因不神秘。它是一个长期驻留的单页应用,插件在内存里越堆越多,界面状态只增不减。浏览器里打开十个小时的页面,谁都这样。
问题是你没法随便关。因为你可能:
- 正在跑一个四十分钟的重构任务
- 有三个子代理在并行查资料
- 队列里还排着两条等着发的话
- 输入框里有一行没发出去的草稿
关掉重开,这些全没了。所以真实情况往往是:明明卡得要命,还是忍着用。
它到底做了什么
按钮在窗口右上角。按下去做两件事:
- 插件代码原地换掉——走宿主自己的插件重载入口,不重启进程
- 界面重建一次——重新载入同一个地址,屏幕上显示的内容刷新一遍
关键在于宿主进程不重启。这是它和「关掉 App 重开」的本质区别:
- 正在跑的轮次:宿主自己的任务不受影响
- 子代理:继续跑
- 终端进程:继续跑
- 排队消息:排在宿主里,重建文档不影响
- 文字草稿:重建前先提交焦点里的内容,App 自己存下来,回到同一会话还在
桌面 App 的窗口里有这个按钮,dsh web 打开的浏览器页面里也有。两边认得出来自己在哪:桌面壳会把自己的桥交出来,网页没有这座桥。
两个日常场景,一句话说清
| 情况 | 按一下的结果 |
|---|---|
| 改了界面类插件,手里还有任务在跑 | 插件当场换掉,宿主自己的任务继续跑完 |
| 界面越用越卡 | 直接恢复流畅,会话、队列、草稿都还在 |
以前这两种都得关掉 App 再来。现在不用挑空闲时间——马上换,不等。
边界:什么会丢,什么不会
这一节是整篇文章最该仔细看的地方。作者把边界写得极其具体,我按「会丢」和「不会丢」重新整理了一遍。
不会丢的:
- 宿主自己的轮次、子代理、终端进程
- 会话记录
- 排队消息
- 文字草稿(附件不算,见下)
- 已经发出去的图片
可能会丢的:
- 被换掉的那个插件自己负责的任务。比如
agent-loop这种由插件驱动的循环,插件实例一关,这个任务就跟着结束。这不是 bug,是「换掉插件」的必然结果——记住这条,改这类插件前先看有没有活跑在里面。 - 草稿里的附件。文字草稿会存,附件只存在当前页面里。所以草稿里真还有没发出去的附件时,按钮会停住并告诉你要先发出去。消息发出后留在记录里的图片不算草稿,不会挡刷新。
会被拦下的(宁可不刷):
- 有未保存改动
- 开着弹窗
- 下个页面要用的模块读不全
- 四个开关读不到
拦下的时候会写明卡在哪一项,不会含糊地说「刷新失败」。
管不到的(会明说,不硬来):
- 给插件加原生能力
- 改服务声明或权限
- 独立进程和进程级的东西:宠物窗口自己的配置、DSH 自身升级、原生依赖变化
这三类不在一次点击的范围里。想改这些,老老实实重启。
三个开关,一个配置项
右键按钮,菜单里有三行:
- 按下刷新时换掉插件代码(默认开)
- 按下刷新时重建界面(默认开)
- Agent 可以自己刷新(默认关)
前两行都开=先换代码再重建;只开一个=另一项完全不动;都不开=点击无反应。
第四项不在菜单里,在配置里:改完文件自动刷新(默认关)。开了之后不用按按钮,改完自己刷新。
第三行「Agent 可以自己刷新」值得单独说。打开之后,会话里的 Agent 可以自己按这个按钮,工具名 unlag_refresh。但它有个硬约束:只换宿主侧的插件代码,页面不会重开,也不会替你按下按钮。还有子任务在跑时,Agent 调它会被拒绝并列出这些子任务——因为换插件会把子任务插件一起换掉,在跑的子任务会被中止。
关着的时候 Agent 调它会被告知先打开开关,不会偷偷刷新。这个设计是对的:能自己刷新的 agent 很危险,它可能在你不注意的时候把你正在看的状态刷没。
它凭什么能做到
靠的是宿主自己公布的约定,不是黑魔法。三条:
- 页面启动时,宿主把插件清单写成
window.__DSH_BOOT__;脚本地址形如/plugins/??<包>/client.js&rev=<修订>,修订由文件的修改时间、变更时间、大小决定 - 宿主对未知、被改过、过期的修订返回 404,不会拿别的字节顶上
- 插件代码重建、但入口没重启时,宿主用
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 更新。*
相关阅读
- DSH 插件怎么选?大肥鱼必装的 8 个插件 —— 还有哪些插件值得装
- dsh-fast-mode 实测:输出砍一半,速度快一倍 —— 跑得慢也可能是输出太长
- DSH 崩溃自救:把崩溃日志变成拦截规则 —— 崩了怎么自救