🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

解除卡顿 regrevia/dsh-unlag

DeepSeek Harnessv1.14.9
regrevia1 天前有提交已提供中文文档扫描:含敏感能力

不重启也能清掉卡顿的 DeepSeek Harness 工作台:就地重建界面、替换插件代码,运行中的回合、排队消息和未发送草稿都保留。

44.9/100
综合分
30
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/10/31 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add dsh-unlag
npm 源 · dsh-unlag 的发布者账号已校验与本仓库一致

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:已验证
是什么
dsh 原生插件 · tool
装得上吗
本站已在 dsh 0.2.1-rc.2 下真实安装成功
非静态推断
L5 · 运行时冒烟等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 1 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查✓ 自动检查通过
展开查看检查明细 ↓

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

✓npm 包dsh-unlag @ 1.14.9
✓Node 引擎未声明 engines.node
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/29 08:40:26

安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

读写/删除本地文件发起外部网络请求
查看扫描证据(共 25 处,展示前 17 条)
  • 读取本地文件
    index.js:69
    const raw = readFileSync(new URL('./package.json', import.meta.url), 'utf8')
  • 读取本地文件
    index.js:82
    return statSync(new URL('./index.js', import.meta.url)).mtime.toISOString()
  • 写入本地文件
    index.js:356
    mkdirSync(target.slice(0, cut), { recursive: true })
  • 写入本地文件
    index.js:402
    renameSync(target, `${target}.1`)
  • 读取本地文件
    index.js:450
    names = readdirSync(dir, { withFileTypes: true })
  • 读取本地文件
    index.js:465
    const stat = statSync(path)
  • 读取本地文件
    index.js:486
    return readFileSync(fd, 'utf8')
  • 读取本地文件
    index.js:511
    if (existsSync(path)) roots.push(path)
  • 写入本地文件
    index.js:3702
    mkdirSync(join(stateHome(), '.dsh'), { recursive: true })
  • 写入本地文件
    index.js:3705
    writeFileSync(temporary, body)
  • 写入本地文件
    index.js:3706
    renameSync(temporary, SWITCH_PATH)
  • 发起网络请求
    client.js:234
    const response = await withTimeout(fetch(`${HOST_BASE}/state`, { cache: 'no-store', credentials: 'same-origin' }));
  • 发起网络请求
    client.js:252
    const response = await withTimeout(fetch(`${HOST_BASE}/state`, { cache: 'no-store', credentials: 'same-origin' }));
  • 发起网络请求
    client.js:282
    void fetch(`${HOST_BASE}/boot`, {
  • 发起网络请求
    client.js:377
    fetch(`${HOST_BASE}/report?${query}`, {
  • 发起网络请求
    client.js:400
    const response = await withTimeout(fetch(`${HOST_BASE}/reload?requestId=${encodeURIComponent(requestId)}`, {
  • 发起网络请求
    client.js:467
    response = await withTimeout(fetch(`${HOST_BASE}/verify`, {
扫描于 2026/10/31(0 天前) · 范围 入口闭包 + 全包扫描 · 来源 npm 发布包

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
v1.14.142026-10-29
开关保存按点击排队发出,晚到的旧回答不回写;保存进行中按下刷新会先等这份保存(≤4 秒)落地再读开关,这一按看到的就是屏幕上那个开关。证据:node scripts/test.mjs 66 个文件 1453 项全绿;ui-copy-lint / zh-lint / style-lint 0 error。
v1.14.132026-10-29
· 1.14.13 · 一次按压只认一个基线(上一次评审的第一条):一次按压在排队前先记住「开始那一刻在跑的插件」,之后的重试、补问只改变问什么,不改变判什么;最终结论永远和这份基线比。于是「这一按停掉的插件」一定记为 newlyFailed,「这一按之前就停着的」只进 carriedEntries/preexistingInactive——同一次按压不会因为多问了一次就把别人的旧账算成自己的失败,也不会把真实损失说成没事。 · 取消落在应用之前就真的不动(第二条):页面的路由在响应发出前连接断开、助手的调用被停,都能穿过队列到达「问应用之前」这一道;被停的一次直接中止,不重试、不写失败、不写条目记忆,答 cancelled/rebuild: unknown,/state.operation 记 cancelled,日志写 a press was stopped before it reached the app; nothing was replaced。已经问过应用的更新不受等待被停影响,仍按真实结果记账;已经发出「还在应用」的响应不算断开,页面在等就照常跑完。 · 同一个名字只答它那一次更新(第三条):页面给一次按压起的名字在更新结束时写进那份记录(中途加入的也算它这一次),两次更新之后重问最早的名字,答的仍是它那一次、带那次自己的 operationId;重问任何旧名字都不会再触发新的更新。 · 证据:新增 test/press-final.test.mjs(24 项)、test/press-retry-baseline.test.mjs(11 项)、test/press-cancel.test.mjs(14 项)、test/press-old-id.test.mjs(10 项);node scripts/test.mjs 66 个文件、1447 项全绿(EXIT 0);node --check index.js client.js ok;ui-copy-lint 0 error(8 warn 与基线同)、zh-lint 0 error、style-lint 通过。本版不改按钮外观、不新增开关或刷新模式,rebuild 在仍有停掉条目时依旧拒绝报 safe。
v1.14.122026-10-29
· 1.14.12 · 第一次按就成功(#3 现场,本机日志 2026-10-29T06:24:49Z):上一版的「再问一次」只在「这一按停掉的条目」被读到时才触发。本机实测的那次拒绝里,停掉的是这一按之前就已经停着的插件(adb (dsh-mobile-use),它自己导入时抛 JsonSchemaError: unsupported schema 里的 type 数组),不是这一按降下来的,于是条目清单里找不到它、重问没有发生,这一按答「插件更新没完成」;同一个人 5 秒后再按一次就答 done。现在改成:只要应用拒绝了这批条目(不是排队冲突那类),就先等一小会儿再问一次,第二次按现在的运行状态判。日志写 the update was rejected; asked once more on the set that runs now;如果第二次问的时候确实读到这一按停掉的条目,仍写原来的 this update left an entry stopped (…); asked once more without it,并按「去掉它」的清单问。 · 等一小会儿再读条目状态:Loader 在拒绝之后过一会儿才写下新的条目状态,紧挨着异常读到的可能还是「运行中」,按那份清单重问会重演同一个失败。等待时间默认 250 ms,DSH_UNLAG_RETRY_MS 可改(上限 2000 ms),定时器 unref,不拖住宿主退出。 · 仍然只重问一次:retriedOnce 不变,第二问还失败就走原来的失败路径并照常写日志,永不进入循环。 · 测试:新增 test/press-retry-stale.test.mjs(4 项:拒绝但不是这一按造成的 → 答 done、该插件记在 carriedEntries、应用恰好被问两次、日志写明第二次)与 test/press-retry-fails.test.mjs(3 项:一直拒绝 → 答 failed、只问两次、日志可见);test/agent-tool.test.mjs、test/button.test.mjs 里钉住「应用被问了几次」的断言按新合同改成两次。 · 证据:node scripts/test.mjs 63 个文件、1410 项全绿;node --check 两入口 ok;zh-lint、ui-copy-lint 0 error,style-lint 通过。本版不改按钮外观、不新增开关或刷新模式。
数据来自仓库的 GitHub Release,可能与插件实际版本不一致。

README

由 DeepSeek 最新模型翻译生成
dsh-unlag(解除卡顿) DSH 桌面端自己的刷新按钮。 开发插件要重启,可手里还有任务在跑?刷新一下! 开久了页面越来越卡?刷新一下! 一个按钮解决桌面端这些烦恼,正在进行的会话不受影响。窗口右上角点一下:插件代码原地换掉,卡顿的界面重建一次,宿主自己的轮次、子代理、终端进程继续跑完。 只在桌面 App 的窗口里出现:浏览器直接打开同一个地址时没有这个按钮,本插件不做网页版。 两个日常场景 | 情况 | 按一下 | | 改了界面类插件,手里还有任务在跑 | 插件当场换掉,宿主自己的任务继续跑完 | | 界面越用越卡 | 直接恢复流畅,会话、队列、草稿都还在 | 以前两种都得关掉 App 再来。现在不用,也不用挑空闲时间——马上换,不等。 边界说清楚:宿主自己的轮次、子代理、终端进程不受影响;被换掉的那个插件如果自己负责某个正在跑的任务(例如 agent-loop),这个任务会随实例结束。主脚本以外的资源不能配对时,会保留当前页面并说明原因,不会硬重建。 装 dsh plugin install dsh-unlag 安装后由 DSH 完成首次加载,之后的更新都走按钮。 用 点右上角那个按钮。
展开完整文档(README 剩余 1 节)
dsh-unlag(解除卡顿)

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

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

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

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

两个日常场景

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

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

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

装

dsh plugin install dsh-unlag

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

用

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

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

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

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

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

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

谁能让它刷新

默认只有人:自动刷新关着,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 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。
预览分享卡片长什么样 ↓regrevia/dsh-unlag
分享 · DeepSeek Harness Hub
regrevia/dsh-unlag

不重启也能清掉卡顿的 DeepSeek Harness 工作台:就地重建界面、替换插件代码,运行中的回合、排队消息和未发送草稿都保留。

回到顶部