npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

dsh-unlag

v1.15.0

Published

DSH 桌面端自己的刷新按钮:改插件不用重启、开久了卡了也点一下,正在进行的会话不受影响。

Readme

dsh-unlag(解除卡顿)

DSH 桌面端自己的刷新按钮。

开发插件要重启,可手里还有任务在跑?刷新一下! 开久了页面越来越卡?刷新一下!

一个按钮解决桌面端这些烦恼,正在进行的会话不受影响。窗口右上角点一下:插件代码原地换掉,卡顿的界面重建一次,宿主自己的轮次、子代理、终端进程继续跑完。

只在桌面 App 的窗口里出现:浏览器直接打开同一个地址时没有这个按钮,本插件不做网页版。

两个日常场景

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

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

边界说清楚:宿主自己的轮次、子代理、终端进程不受影响;被换掉的那个插件如果自己负责某个正在跑的任务(例如 agent-loop),这个任务会随实例结束。主脚本以外的资源不能配对时,会保留当前页面并说明原因,不会硬重建。

装

dsh plugin install dsh-unlag

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

用

点右上角那个按钮。右键菜单有三行,另有一项在配置里:

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

前两行都开=先换代码再重建;只开一个=另一项完全不动;都不开=点击无反应。最后一行只决定谁能按,不改变按下去做什么。

「改完文件自动刷新」不是菜单行,是配置项(默认关),开了也不用按按钮。

页面自己缺插件时会主动说一句:应用在两次启动之间换过插件代码,新开的页面按启动时那份冻结清单去要脚本会答 404,那个插件就没加载,屏幕上原本什么都不会说。现在页面启动后读一次开机时记下的应答,只有应用明确答「这个地址没了」时,才在按钮旁说「缺插件,重启应用后恢复」——不用按任何东西。读不到、读不全或清单太长时保持安静。这句话不是某次刷新的结论,所以不会到点消失,按下一次后由新的结果接替。

插件换过之后,页面按旧版本号要的懒加载分块,只要这个文件还在就按它现在的版本答:文件还在、只是编号变了,不该算缺插件,也不该让人只剩重启。文件真没了才拒绝并点名是哪个文件。

谁能让它刷新

默认只有人:自动刷新关着,Agent 入口也关着,本插件不装任何文件监视,本插件自己永远不会去换代码。

右键按钮里打开「Agent 可以自己刷新」后,会话里的 Agent 可以自己按这个按钮(工具名 unlag_refresh),复用按钮那套判定与拦截,但只换宿主侧的插件代码:页面不会重开,也不会替你按下按钮;内容没保存、插件没恢复时照样停住并说明原因。关着时 Agent 调它会被告知先打开开关,不会偷偷刷新。还有子任务(subagent)在跑时,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 smoke 不按按钮,只按当前启动清单把下一个文档要加载的字节在沙箱里跑一遍,看每个插件是否都注册(rehearsal.ok),并当场核对三件事:写死的旧版本号仍然拿得到现在的字节(shim.staleRev),按下前会做的那个判断此刻是否放行(pressPath),以及那个判断本身会不会误拒(verify.sound 应为 ok、verify.broken 应为 blocked、verify.refuses 与 verify.accepts 为 true)——它证明的是「启动清单里的每个插件都注册了、每个分块都取得到、按钮此刻真的可按、拦住的是坏清单」,不换任何代码;演练里的 Loader 只收集注册的 ID,不执行插件的 factory、import 与 apply,所以它不等于整机刷新已经安全。
  • 同一支脚本还现场核对路由的守门规则(contract):读路由只认读、写路由缺 x-dsh-unlag 头或来自别的源一律 403、未知路径 404。它用的是和页面一样的请求,所以这条规则在运行中的 App 上是否还在,一眼可见。
  • 状态:GET /dsh-unlag/state → {"version":"<当前版本>","code":"<index.js 写入时间>","plugins":true,"page":true,"shim":{...},"served":{"status":"served|missing|unknown","entries":<当前插件图行数>,"id":"dsh-unlag"}}。code 用来分清「版本号没刷」和「旧代码真的还在跑」;served 用来分清「按钮没出现」是插件图里没这一行(missing)、还是图还没就绪(unknown)。
  • 重建后的日志带起页面代次:interface rebuilt; the page runs the plugins as served now (page <rev>),据此判断新文档加载的是新脚本还是旧脚本。
  • 按钮不出现且 served 是 missing:先看 profile 的 package.json 里 dsh.profile.bundles 还列不列着 dsh-unlag。宿主装别的插件时会按 dependencies 重算这份有序清单,手装或软链进来的条目可能被一并丢掉,加回去、重启即可。装清单失败与插件本身无关,已提上游:https://github.com/deepseek-ai/deepseek-harness/discussions/11383。
  • 装不上、报 ECONNREFUSED 或 UND_ERR_CONNECT_TIMEOUT:先看 ~/.npmrc 里的代理是否在跑,代理没起时换镜像直连也能装。

每类说法对应的测试与现场输出整理在 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 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。