dsh-breather

sessiontested: passnpmMIT

Take a breath mid-task: the current step finishes, then it holds. Resume sends no new request and spends no tokens; the timer freezes. Current session only.

下载量 / Downloads
1,078
依赖体积 / node_modules
0.4 MB
测试时间 / Tested
2026-11-06
DSH 版本 / Version
0.2.2-rc.1

Install

dsh plugin --profile web add dsh-breather

Sandbox test results

StageResult
1 install✓
2 list✓
3 mount✓
4 boot—

Four stages run in an isolated sandbox: install (network on) → list → mount → boot (last three with no network).

README

dsh-breather

给 DSH 的对话加一个「暂停 / 继续」状态——介于「正在运行」和「终止」之间。

零成本暂停:当前步骤做完后挂起,继续时不重发请求、不重复花 token;只有点继续或发新消息才接着跑,子代理结算与定时提醒唤不醒。侧栏显示暂停图标,计时器与动画冻结、不计暂停时段,恢复后从冻结值接着走。按钮区分「暂停中」与「已暂停」,暂停只作用于当前会话。

七点说明

1. 零成本暂停:暂停本身不发出新请求。当前步骤做完后停住,只有手动点按钮或发送新提示词才会继续;后台行为(子代理结算、定时提醒等)唤不醒它。

2. 侧栏与计时:左侧会话列表加入暂停图标;暂停期间计时器文字与动画冻结,计时器不计暂停时段,恢复后从暂停前的时间接着算。

3. 两种状态:按钮区分「暂停中」(请求已发、当前步骤还在跑)与「已暂停」(闸门真的挂住)。

4. 作用域:单次暂停只作用于当前会话。

5. 回合总结统计的是总时间:那行「深度思考了 X」按 turn.end - turn.start 算,包含暂停时段。这是底层架构限制;为了兼容更多版本,插件不去改它——你可以自行修改,但改过的文件会随 DSH 版本更新失效。

6. 重启不保留暂停:暂停后重启 DSH,再点「继续」本质上是终止对话并花 token 发了一条「继续」提示词。目前不存在关闭 DSH 后仍保持暂停的路径。

7. 计时模块坏了也不影响暂停:如果 DSH 更新导致计时接管(核心 DOM 钩子)出错,只会退回核心原生显示,暂停 / 继续照常工作,不会因此多消耗 token。

它做什么

  • 会话运行时,输入框右侧(模型选择器左边、停止按钮同一行)多一个 暂停 按钮。
  • 点暂停:当前这一步先跑完(正在输出的回复、正在跑的工具都不打断),然后在

发起下一次模型请求之前把这一轮挂住。按钮变成 继续。

  • 点继续:闸门放行,模型从停住的地方接着跑。不重发请求、不重复工作、不多花 token。
  • 挂住之前按钮显示「暂停中…」,此时再点一次等于取消这次暂停请求。
  • 挂住之后,对话下方那行「深度求索中,用时 X」会冻住并变成「已暂停 · X」,流光动画

一起停;暂停的那段时间不计入——继续之后从冻住的数字往下走,不会一下子跳掉。

  • 左侧会话列表里,被暂停的那一行会把转圈圈换成蓝色的暂停符号;其他会话照常转。
  • 那一行的悬停卡片里会多一行「已暂停 · 点输入框旁的「继续」从停住的地方接着跑」——

这一处走的是核心本来就有的 sidebar.session.row.hover 插槽,不碰 DOM。

  • 暂停期间按停止(或 Esc Esc)照常终止:闸门会跟着回合取消一起松开,不会卡住。
  • 没有超时:暂停之后你不点继续,它就一直是暂停状态,放一小时也一样。
  • 暂停期间你自己又发了消息会自动恢复(当成「我要继续」);子代理结算、定时提醒

这类后台唤醒不会解除暂停。

它做不到什么

  • 不能打断正在流式输出的那一条回复。 暂停是「下一次请求前」生效,所以长工具

调用 / 长回复期间点暂停,要等这一步结束才真正停住。想立刻停,用原来的停止按钮。

  • 暂停只作用于这一轮对话(turn);不会暂停子代理、后台任务和定时提醒。
  • 同屏有子代理内嵌会话时,只接管主对话那个计时器;如果核心的结构让插件认不出

哪一个是主对话的(比如两个都在聊天节点外),它一个都不改并登记失效——宁可让

数字继续走,也不把子代理的数字改成主对话的。

  • 核心如果把「深度求索中,用时 1分23秒」拆成两个文本节点,插件只冻结里面的数字,

不动标签;那种情况下不会显示「已暂停」几个字(数字停住本身就是信号)。

自检:接管失效时会怎样

有两处效果是靠核心的 DOM 钩子实现的(运行计时器冻结、侧栏行首图标)。核心改版把

这些 data 属性改名时,客户端会在失效发生的几秒内发现,而不是默默退化成原样:

  • 计时器:见过 [data-chat-running] 之后又连续 6 次(约 4.2 秒)找不到 → 登记 timer

失效;一次都没见过(刚刷新、长会话还没渲染出来)则给 20 次(约 14 秒)宽限,避免把

「还没渲染」误判成「钩子没了」。

  • 侧栏行:有 [role="treeitem"] 行,但整页一个 data-row-key 都没有时,才登记

rows 失效——要求整页都没有,是为了不把文件树等别的树形控件误判成会话行改名。

  • 计时器作用域:页面上有多个 [data-chat-running],但其中恰好没有一个能确定是主对话

(不在聊天节点外、或不止一个在节点外)时,登记 timer 失效并一个都不改。这种失效

不停掉 700ms 的重挂载循环,局面一清楚就自动恢复。

登记之后:控制台 console.warn 一次(带一句「其余功能正常」),暂停按钮的 title

追加「(部分接管失效:核心页面结构变了,详见控制台)」,并且停掉对应的接管

(停止 700ms 的重挂载循环),不再空转。功能本身(宿主闸门、按钮三态、继续零成本)

不受影响。

失效标记会自愈:钩子后来又出现时,连续两次轮询/重挂载都看到它,就撤销标记并

console.info 一句「xx 接管已恢复」——不会一次抖动就翻案,也不会一直挂着红字到刷新。

界面文案跟随核心的 <html lang>(读不到时看 navigator.language):中文一套,**其他

语言一律英文兜底**(日语、法语用户看英文,而不是中文);暂停 / 继续 / 暂停中…、按钮

title、行首图标与悬停卡文字都有中英两套。计时文案里的时长单位不是按界面语言猜的,而是

从核心那句真实文案里认——「已暂停」这三个字在没有证据时(只抓到数字)才回退到界面语言。

可选:把行首图标改由核心插槽渲染

原版核心的侧栏行首是二选一:运行中画状态点,空闲才渲染

sidebar.session.row.leading 插槽——所以暂停图标只能靠 DOM + mask 补。想让图标也走

上游插槽,可以打一个本地补丁(改的是安装包 app.asar,不是插件):

# 只检查(默认,什么都不改;预览写系统临时目录)
node tools/patch-core-row-slot.mjs --check

# 想在仓库里留下打过补丁的副本来看 diff
node tools/patch-core-row-slot.mjs --check --preview

# 完全退出 DSH 后,用有写权限的终端打补丁
node tools/patch-core-row-slot.mjs --apply
# 还原
node tools/patch-core-row-slot.mjs --restore

它做的事:把 showStatus ? 状态点 : 插槽 改成「插槽先渲染、状态点照旧」,新代码多出

的 77 字节从紧邻的文档注释里扣回来,因此文件字节数不变;再把该文件新的 SHA256

写回 asar 头的 integrity.hash / integrity.blocks[0]。asar 总大小与其余条目都不动,

备份留在 tools/.backup/。

补丁生效后,插件对“行”的 DOM 依赖会再降一层:不再给行打标记,藏核心状态点改成

CSS 按「这一行里有没有我们的图标」自己算([data-row-key]:has([data-dshp-row-icon]) 和

*:has(> [data-dshp-row-icon]) 两条规则,兼容插槽输出被包一层的情况)。所以补丁模式

下就算行键改名,最坏也只是“暂停符号和转圈并排”,不会整个图标消失。

风险和边界,先说清楚:

  • 脚本带版本与哈希双锚点(核心 0.2.1-rc.3,文件 SHA256 固定)。DSH 升级后两者对不上,

它会直接拒绝,不会瞎改。

  • 改的是安装包,升级/重装 DSH 会被覆盖,需要重新打;不打也完全能用(回到 mask 方案)。
  • 打了补丁之后,插件渲染图标时会自动给 <html> 加 data-dshp-rowslot,CSS 里的 mask

规则随即让位,不会画两遍。

安装

dsh plugin --profile desktop add dsh-breather

装了之后完全退出 DSH 再打开(宿主半只在启动时加载);浏览器半随页面刷新生效。

包声明了 dsh.bundle,安装时会把 cordis.patch.yml 这一层追加进 profile,宿主半作为一个

普通插件行被注册;浏览器半由 Web 端按 dsh.client.platform 自动加载。engines.dsh 写的是

>=0.2.1-rc.2 <0.3.0-0——下界必须带 -0,否则 semver 不匹配 0.2.1-rc.x 这类预发布版本,

rc 版 DSH 会直接拒绝安装。卸载:

dsh plugin --profile desktop remove dsh-breather

旧安装怎么办(只和开发机有关)

老包名是 dsh-session-pause,老装法是「Junction + 手写 patch row」。不用迁移:Node 按

目录名解析,旧 row 照样能加载,包名对不上只影响 dsh plugin list 的观感。硬要换装的唯一

红线是:先删 profile 里手写的那一行,再 remove 旧依赖——反过来(先删依赖、留着 row)

DSH 下次启动会直接报找不到模块,那才是真的起不来。

但换装会顺手弄坏别的东西:pnpm add/install/remove 会把没写进 dependencies 的手装

插件目录挪进 node_modules\.ignored\(dsh-session-folder、dsh-session-delete 这类)。

所以验证"发布出去的包"别动主 profile,用一次性的独立 profile:

dsh --profile breathe-test --from-default-profile desktop   # 建一个干净 profile
dsh plugin --profile breathe-test add dsh-breather          # 从 npm 装(未发布时可 add <插件目录>)
dsh --profile breathe-test                                  # 起来点一遍,验完关掉、删掉这个 profile

本地开发

源码目录沿用了历史名字 <workspace>/.dsh-plugins/dsh-session-pause,主 profile 里仍以

旧包名挂着(Junction),改源码立刻生效。这条路径只适合开发机;正常用户走上面的

dsh plugin add。

配置(cordis.patch.yml 的 config)

字段 · 默认 · 说明

enabled · true · 总开关

engagedTimeoutMs · 0 · 可选的挂住超时;0 = 永不自动放行(默认)。只有自己明确填毫秒数才会兜底放行

路由不可配置:固定是 /api/session-pause,浏览器半把这条路径写死了。以前留过一个

path 配置项,结果只有宿主听那个开关——一改客户端就永远读不到状态,是最难查的一类故障。

填了也不会生效(0.3.0 起移除)。

实现

宿主半(index.js):

  • ctx.on('agent/pre-step', …):dsh-agent-loop 在每次发起模型请求前用

dispatch.waterfall 调用这个钩子,且会 await 监听器返回的 Promise。所以在这里

await 一个闸门就等于把这一轮挂住;松开后 return next() 继续。

  • 中止信号来自钩子载荷里的 signal(本回合的 AbortSignal):停止 / 取消时它 abort,

闸门立刻松开并返回 { kind: 'reject' },回合按取消路径干净结束。

  • ctx.connection.fetch.register({ path, methods: ['GET','POST'] }):状态查询与

暂停 / 继续操作;精确路由只有 owner peer(本机浏览器)能访问。

  • 回合结束(agent/status 非 running)清掉暂停状态,避免残留到下一轮。
  • 用户消息(source.kind === 'user')进入 inbox 时自动恢复;其他来源的唤醒不解除暂停。
  • 默认没有超时定时器,闸门只由「继续 / 用户发言 / 回合被终止 / 插件卸载」松开。
  • 计时基准:浏览器在暂停挂住时把「这一轮已经跑了多久」上报(action: 'baseline'),

宿主存在会话状态里并随快照返回;继续时记下「继续时刻」,继续之后这一轮没结束前都以

clocks 的形式留在快

…(truncated)

Rendered from the README published on npm. Formatted only, not edited.

Versions

Published on npm::5,最后更新 2026-10-31

Links

GitHub · Homepage · npm

Same category