dsh-step-pause
Manifest validCounts agent steps and, every N of them, adds a progress-report instruction to the batch that is about to be sent, so the agent states what it is doing, what it finished and what comes next, then carr
dsh-step-pause
每 N 步自动停下来汇报一次进度,说完立刻继续。 不占额外回合、不等你回复、不生成文档 —— 就几行对话正文。
中文 | English summary
长任务跑到第 30、40 步,你往往不知道它在干什么、卡在哪、还要多久。这个插件按固定节奏 把进度主动说出来:正在做什么 / 已完成什么 / 接下来做什么 / 卡点 —— 然后接着干。
进度小结(第 40 步):
1. 正在做什么:给设置页补上表格导出的失败重试
2. 已完成什么:定位到 retry 没有重置 backoff;改了 3 处;单测补了 2 组
3. 接下来做什么:跑全量测试,然后提交
4. 卡点:无

30 秒看懂
| 你可能在想 | 实际情况 |
|---|---|
| 会不会多花一次模型调用? | 不会。消息追加进本步的输入批次,汇报和后续动作在同一次响应里完成,不产生额外回合 |
| 会不会停下来等我点确认? | 不会。没有任何 wait / ask 语义,写完自己接着干 |
| 会不会打断正在跑的工具? | 也不会,但代价是:一次 3 分钟的 npm test 期间它什么都说不了(它挂在两次思考之间的边界上) |
| 会不会污染子任务? | 默认不会。mainAgentOnly: true 时子智能体一次都不注入(v0.3.0 起 header 读不到也不注入) |
| 会不会把对话刷爆? | 有 maxReportsPerTurn 兜底;提示词原文默认隐藏,对话里只留你写的进度小结 |
| 提示词会不会覆盖我的格式要求? | 有这个风险,见高风险第 2 条。缓解办法是把 N 调大、在提示词里显式声明"原任务约束仍然有效" |
默认节奏是每 10 步,可在两个地方改,保存即生效、不用重启:
侧边栏左下角头像 →「设置」→ 步数暂停,或 设置 → 插件 → 分步暂停 → › → 配置。
安装
桌面版不要用 dsh plugin --profile desktop 直接改 profile —— 它由应用内的插件管理器独占。二选一:
- 应用内安装:
设置 → 插件 → 添加插件 → 从本地目录,选这个仓库的根目录; - 命令行(只对非 desktop 的 profile):
dsh plugin --profile <你的profile> add file:<这个仓库的绝对路径>
也可以直接用 Releases 里的 .tgz。
装完按 文件 → 退出 完全退出应用再启动(不是刷新页面)。
宣传片(52 秒 · 含配音与讲解员)
点封面看 52 秒 1080p 宣传片:痛点 → 原理 → 真机效果 → 配置页 → 能力清单 → 结尾。 全片配中文旁白与同步字幕,左下角的「大肥鱼」讲解员会随音量点头、起浮、亮光晕。 分镜总览见 contact-sheet.png。
它解决什么问题
- 每满 N 步,往当前步骤的输入批次里追加一条消息,要求模型先汇报再继续;
- 因为它属于「本步输入」而不是新任务,所以不产生额外回合,汇报和后续动作在同一次响应里完成;
- 不会把任务挂成「等待用户确认」,也不会打断正在跑的工具调用(边界本来就是两次思考之间)。
配置
两个入口,同一份表单(任何一处保存,另一处立刻同步):
- 侧边栏左下角头像 →「设置」→ 步数暂停(
settings.section,排在账号、模型这些官方段之后) - 设置 → 插件 → 分步暂停 → › → 配置(插件页那一行)
六个字段都带说明与草稿态:
| 键 | 默认 | 说明 |
|---|---|---|
enabled | true | 总开关。关掉后插件仍然加载,但不再汇报 |
everyNSteps | 10 | 每多少步汇报一次(按轮次内的步骤计,1–500) |
maxReportsPerTurn | 0 | 每轮最多汇报几次;0 表示不限制(兜底防失控轮次刷屏) |
prompt | 见下 | 注入的提示词,支持 {{step}} {{every}} {{turn}} |
showPromptInChat | false | 是否在对话里显示这段提示词原文。默认隐藏,只留你的进度小结;不影响模型收到指令 |
mainAgentOnly | true | 只在主智能体里汇报,子智能体一律不汇报。默认开:避免并行子任务各报一份 |
改动保存即生效,不用重启:字段声明为 volatile(),主机每一步都重新读值。
保存会写进当前 profile 的 cordis.patch.yml:
- id: dsh-step-pause
name: dsh-step-pause
config:
everyNSteps: 6
不想要配置页的话,直接手写上面这段完全等价。
默认提示词:
[自动 · 分步暂停 · 第 {{step}} 步(每 {{every}} 步一次)]
先暂停手上的工作,直接在对话正文里用 5-10 行说明当前进度,然后立刻继续原任务:
1. 正在做什么:当前任务与所处步骤
2. 已完成什么:本轮已完成的动作或产出
3. 接下来做什么:下一步计划
4. 卡点:需要我决策或补充信息的地方(没有就写「无」)
要求:直接在回复正文里写,不要新建或修改任何文档/计划文件;
说完不要停下来等我确认,继续把原任务做完。
工作原理
挂在驱动器的步骤边界 agent/pre-step(waterfall)上:
ctx.on('agent/pre-step', async (payload, next) => {
const decision = await next() // 先拿到本来的决策
if (decision.kind !== 'enter') return decision // reject / aborted 原样透传
return { ...decision, messages: [...decision.messages, notice] }
}, { prepend: true })
计数用 (turn, step) 去重,换轮重置;payload 里带 agent,所以子 agent 也会各自计数
(不想要就按 agent.id 过滤)。
一个必须踩对的细节:source.kind 决定这条消息显不显示
DSH 客户端按消息来源决定怎么渲染(dsh-client-ui-chat 的 input-message 分类):
if (event.data.source.kind !== "user") return { ...contextMessage(event, event.data) } // 降级为 context
return { kind: "user" /* 画成对话气泡 */ }
降级成 context 之后,isVisibleChatNode 还会把普通 context 从可见行里过滤掉:
node.kind !== "context" || node.data.content.some(b => b.type === "tool-addition" || b.type === "tool-removal")
所以就两个选项,没有第三条路:
kind: 'user'→ 显示成一条用户气泡,提示词原文全部可见;- 其它值 → 归为 context,对话里完全看不见。
两边都不影响模型:请求组装只看 role(见 dsh-llm 的 createMessage),
所以 showPromptInChat: false 是"藏起来但照样生效"。
本项目第一版用了 kind: 'step-pause',模型收到了、行为也对,屏幕上却什么都没有,
一度误判为"插件没生效"。现在默认 showPromptInChat: false(用 kind: 'step-pause' 隐藏原文),
需要排查"到底注入了什么"时再把这个开关打开。
兼容性
- DSH
0.2.0-rc.2与0.2.1-rc.2上实测可用(dsh.compatibility.dshReleases声明); - Node
^22.19.0 || >=24.0.0; - 主机侧唯一依赖是
@deepseek-ai/schemastery(peer,由宿主解析); - 浏览器半边只
require('react'),不依赖@deepseek-ai/dsh-client-ui-primitives(该包不随 SDK 发布)。
已知问题与风险
这一节是把源码逐行读过、并在真机上验证过之后写的,不是免责声明。装之前请至少看完"高风险"两条。
风险分档:高 = 可能让你丢掉正在做的工作或看不到界面;中 = 影响体验或结果,可配法规避;
低 = 已知瑕疵,不影响主流程。
高风险
1. 客户端半边加载失败,会让整个桌面窗口白屏
这不是本插件独有的问题,而是 DSH 前端加载器的行为,但它决定了"装插件"这件事的风险上限。
前端在启动时遍历所有客户端插件条目,只要有任何一个没进入 active 状态,就抛异常、整个窗口起不来。
源码在 @deepseek-ai/dsh-web-frontend 的启动函数 bZ():
for (const i of e.loader.entries()) {
const n = i.options.name
if (i.fiber === void 0) { /* import failed */ r.push(...) ; continue }
const s = vx[i.fiber.state]
if (s !== "active") {
if (s === "pending") {
const l = Object.keys(i.fiber.inject).filter(d => e.get(d) === void 0)
r.push(`${n}: pending (waiting for service: ${l.join(", ")})`)
} else r.push(`${n}: ${s}`)
}
}
if (r.length > 0) throw new Error(`web boot: ${r.length} entries did not activate\n${r.join("\n")}`)
注意三件事:
pending也算失败。inject里声明了某个服务、而那个服务没被提供时,条目会永远停在pending (waiting for service: …),然后整个窗口白屏。- 没有 optional / required 之分,
bZ()不做任何过滤。 - 本插件的
lib/client.js声明了const inject = ["slots", "locale", "configForms"]——三个都是硬依赖。 如果将来 DSH 移除或重命名configForms(它是配置页机制的服务),本插件就会从"配置页没了"直接升级成 "整个界面起不来",而且是静默升级:你只会看到一个打不开的窗口。
真机上见过这种情况,崩溃日志在 %APPDATA%\@deepseek-ai\dsh-desktop\logs\crash-*-web-boot.log:
Error: web boot: 6 entries did not activate
billion-context: import failed: client-modules: could not load "billion-context": ...
缓解:如果你只想用主机逻辑、不需要配置页,把客户端半边整个删掉就行(见文末"去掉客户端半边")。
删掉之后这条路彻底不存在——主机插件的加载失败只会被 try/catch 捕获、留一条 dsh: warning,profile 照常启动
(这点用三个故意弄坏的插件实测过:import 不存在的包 / apply() 抛异常 / apply() 返回永不 resolve 的 Promise,
三种情况下 profile 都能正常启动,第三种连 warning 都没有)。
2. 注入的是真正的 user 消息,会永久写进会话历史并影响后续上下文
这是本插件最关键的设计取舍,也是它跟"旁路提示"类工具的根本区别。
- 它被追加进本步的
user/message批次,随会话日志落盘(~/.dsh/sessions/<工作区>/…), 会进上下文窗口、会被dsh-context之类压缩、会跟着"导出会话"一起出去、重开应用恢复会话时它还在; - 它的
role是user,所以模型可能把它读成用户新下达的指令。你的原始任务里如果有强约束 ("只输出代码不要解释"、"严格 JSON"、"不要中途提问"),这条进度请求在当步会压过那个约束; - 它每一次都花 token(默认提示词约 150–200 token / 次)。N=5 跑 100 步 ≈ 20 次注入。
缓解:把 everyNSteps 调大(默认 10,不急就 20–30);用 maxReportsPerTurn 限死每轮次数;
在 prompt 里把它写成"汇报完立刻回到原任务,原任务里的一切格式约束仍然有效",能明显降低劫持概率。
中风险
3. 不是硬中断,长工具调用期间不汇报
一次 step = 一次模型请求+它的工具调用。插件挂在步骤边界(agent/pre-step)上,
所以它不会打断正在跑的工具。如果你有一个跑了 3 分钟的 npm test 或一次大文件生成,
这期间插件什么都不会说——你等的那段沉默,它治不了。
同理,模型的思考过程(reasoning)再长,也只在一次请求内部,插件看不到、也切不进去。
4. 子 agent 各自独立计数(默认已关掉汇报)
计数状态按 payload.agent 用 WeakMap 分开存,每个子 agent 一套。
默认已缓解:mainAgentOnly: true 让子智能体一次都不汇报,只有你直接对话的主智能体出小结。
判定走两道判据,任一能给出确定答案就用它:
- 会话 header(
agent.session.header)里的origin === 'subagent'(dsh-subagent创建子会话时写入)或delegationDepth > 0——两者任一成立即视为子智能体。 不把非空parentSession当作依据:用户手动 fork 出来的会话也有parentSession, 但它属于你自己会话的主智能体。本机 226 个真实会话日志验证过:111 个真子智能体 全部满足前两个字段之一,3 个 fork 会话则是origin=undefined/delegationDepth=0, 按parentSession兜底会把它们误判成子智能体、导致分叉会话里永远不再汇报。 - 运行时归属(
AgentRegistry.roots())——header 读不到或没写全时靠它兜底。 官方文档原话是「持久化的会话谱系不影响这个运行时关系,所以被恢复的 fork 仍可能是 root」,所以它和上面那条 fork 判据不冲突:在 roots 里的必是主智能体,不在的必是子智能体。
这是 v0.3.0 补上的泄漏面:v0.2.1 在 header 缺失时保守回退为「主智能体」,
于是任何没写 origin / delegationDepth 的派生路径(外部 SDK、IM 桥接)都会被灌入提示词。
注册表也判不了(没注册表、roots 为空、不认识这个 id、调用抛错)时才回退为主智能体——
原则仍是「实在不认识就照旧汇报」,绝不静默吞掉主智能体的进度。
要恢复旧行为(每个智能体各报各的),把 mainAgentOnly 关掉即可。
5. 模型可以不照做;默认设置下你看不见它有没有触发
提示词只是请求,不是强制约束。极端情况下模型可能只回一句"收到,继续"就接着干活。
更麻烦的是默认配置:showPromptInChat: false 时消息的 source.kind 是 'step-pause',
客户端按 source.kind !== 'user' 把它归为 context,而 isVisibleChatNode 又会把普通 context
从可见行里过滤掉——结果是它在对话里完全不可见,你只能从模型有没有输出进度小结来反推插件有没有生效。
排查时把 showPromptInChat 打开,就能看到注入的原文(会让对话里多一条用户气泡)。
6. 每轮第 1 步不汇报;everyNSteps: 1 也是从第 2 步开始
计数从 1 起、只在 step % N === 0 时触发,第 1 步被显式跳过。所以:
- 默认 N=10 → 实际在第 10、20、30… 步汇报;
- N=1 → 不是"每步都报",而是第 2、3、4… 步都报(第 1 步仍然跳过)。
7. 计数状态在内存里,重启就没了
WeakMap 键是 agent 对象,进程重启、或 turn 边界重置后计数归零。
如果你在第 7 步退出应用再恢复会话,它会从 0 重新数——不是"接着第 7 步往下数"。
低风险 / 已知瑕疵
| # | 问题 | 说明 |
|---|---|---|
| 8 | everyNSteps 兜底值与 schema 默认值不一致 | lib/index.js:101 的 clampInt(everyNSteps, 5, 1, 500) 兜底是 5,而 schema 默认是 10。正常走 schema 不会触发;只有直接构造 apply(ctx, {}) 时才看得见 5。 |
| 9 | Boolean("false") 是 true | 手写 cordis.patch.yml 时若写成 enabled: "false"(带引号的字符串),Boolean("false") === true,开关会失效。写裸的 false 才对。 |
| 10 | 测试覆盖不到真实生命周期 | test/index.test.mjs 的 fakeCtx 只实现了 on(),没有 effect()/dispose,所以"插件卸载时监听是否干净移除"这条没有测(依赖 cordis 的作用域自动 dispose,理论上没问题,但没有测试兜底)。11 组断言覆盖的是注入逻辑本身。 |
| 11 | 兼容性只声明到 0.2.1-rc.2 | dsh.compatibility.dshReleases 只写了 0.2.0-rc.2 / 0.2.1-rc.2。更新的 DSH 上如果 agent/pre-step 的 payload 形状变了,插件不会报错,只会静默不生效。 |
| 12 | 与其它 agent/pre-step 监听器的顺序耦合 | 本插件用 { prepend: true } 抢在最外层,先 await next() 拿到下游(含 compaction)的决策,再往批次末尾追加。如果将来有别的插件也 prepend 并且不调 next(),本插件会被短路。 |
它不会造成什么(也验证过)
- 主机半边不会拖垮 profile:
lib/index.js的 import 失败或apply()抛异常,loader 只记一条dsh: warning,profile 照常启动(cordis-plugin-loader的 entry 加载有try/catch)。 - 不会被压缩吞掉:
dsh-compaction-basic的agent/pre-step监听不读payload.messages, 它压的是 session surface 最旧区间;本插件在其之后追加,故注入内容不会在当步被吃掉。 - 不产生额外回合、不多花一次模型调用:消息属于"本步输入批次",汇报与后续动作在同一次响应里完成。
- 不会挂成等待确认:没有任何
wait/ask语义,说完就继续。
去掉客户端半边(推荐给只想用主机的用户)
不需要配置页时,可以只留主机插件:
- 删除
lib/client.js、locale/、icon.svg、docs/config-page.png; - 清掉
package.json里的dsh.client字段,以及exports中指向./lib/client.js的条目; - 行为完全不受影响,只是没有
设置 → 插件 → 分步暂停 → 配置那个页面——配置改成手写cordis.patch.yml(格式见上一节)。
这样风险表里的第 1 条对你就不存在了。
开发
npm install # 只装 @deepseek-ai/schemastery(dev)
npm test # 19 组断言
测试覆盖:只在第 N 步注入、同步去重、消息形状与冻结、showPromptInChat 两种来源、
原批次不被改写、reject/aborted 透传、每轮配额重置、占位符渲染、
enabled: false 不注入但保留注册、schema 默认值、运行中改配置立即生效、
子智能体不汇报(header 判定 + fork 不误伤 + 读不到 header 且无注册表时不误伤 +
header 缺失时靠 AgentRegistry.roots() 兜底 + 注册表判不了/抛错时不误伤 + header 优先于注册表 +
可关回旧行为)。
另有 test/verify-subagent-real.mjs:只读扫描本机真实会话日志(~/.dsh/sessions/**/session.v4.jsonl.zstd),
用真实 header 调用插件里的 isSubagent 断言判定结果,避免用假数据自证。当前样本 227 个会话
(112 个真子智能体 / 3 个 fork / 其余主智能体)全部通过。
dsh-step-pause/
├── package.json # exports / files / dsh.bundle.patch / dsh.client / dsh.compatibility
├── cordis.patch.yml # loader 补丁(一条 insert)
├── lib/index.js # 主机逻辑:Config + agent/pre-step 监听
├── lib/client.js # 浏览器半边:插件页配置页 + 侧边栏头像→设置 的配置段
├── locale/{zh,en}.json # 插件页标题与描述
├── icon.svg # 插件页图标
├── test/index.test.mjs # 离线行为测试(19 组断言)
├── test/verify-subagent-real.mjs # 用本机真实会话日志验证子智能体判定
└── docs/config-page.png # 实测截图
cordis.patch.yml 只插一条 loader 条目;不要把它手写进 profile 的 loader 列表,
否则会撞 duplicate loader entry id。
English summary
dsh-step-pause is a plugin for DeepSeek Harness that periodically interrupts a long agent turn with a short progress report, then lets it carry on without waiting for you.
Every N agent steps (default 10, configurable), it appends one message to the current step's input batch asking the model to state what it is doing, what it finished, what is next, and whether it is blocked — then continue immediately.
Three properties matter:
- No extra turn. The notice rides along with the step's existing input, so the report and the next action happen in a single model response. No extra request, no extra cost.
- No blocking. There is no
waitorasksemantics anywhere. It never parks the task waiting for confirmation. - No documents. The report is plain chat text. Nothing is written to disk.
Defaults: hidden prompt (the injected text is not rendered in the chat, but the model still gets it), main agent only (subagents never receive it), and a per-turn cap you can set to avoid spam.
Configuration lives in two places sharing one form: sidebar avatar → Settings → Step Pause, or Settings → Plugins → dsh-step-pause → Configure. Changes apply on save, no restart.
Read the Known issues and risks section before installing — in particular the
first two high-risk items: a broken client half can white-screen the desktop window, and the
injected message is a real user message that lands in session history.
# desktop: use Settings → Plugins → Add plugin → From local directory
dsh plugin --profile <profile> add file:<absolute path to this repo>
Requires DeepSeek Harness 0.2.0-rc.2 / 0.2.1-rc.2 and Node ^22.19.0 || >=24.0.0.
License
Comments
Loading…
Similar plugins
Group-chat multi-agent orchestration console for DSH: autonomous decision-making, task dispatch, verification and iterative optimization for long tasks; bundles its own backend, installs with `dsh plu
★ 0
dsh plugin --profile web add dsh-agent-hubby Momojie-S
DSH plugin: steer_subagent - insert a new instruction into a running background subagent's current turn (step-boundary steering) or hard-restart its turn; send_message queueing stays the default
★ 0
MIT
TypeScript
Sep 5, 2026
dsh plugin --profile web add @momojie-s/dsh-subagent-steerby yunxiyang
Resumes an agent turn that ended after narrating its next action without calling a tool: the guard replays the turn log on agent/turn-stopping, asks a judge model one true/false question, and steers t
★ 3
↓ 349/wk
MIT
JavaScript
Sep 29, 2026
dsh plugin --profile web add dsh-loop-continueby Jesse-njx
Scheduled agents on a cron: run a prompt on a schedule and get the digest where you already are, with overlap/missed-run/timeout safety defaults.
★ 0
MIT
TypeScript
Aug 28, 2026
dsh plugin --profile web add @dsh-routines/bundleby LeslieWylie
Cross-session task queue with handoff notes: sessions and subagents push, claim, complete and cancel tasks on a shared file-backed queue, and leave a handoff summary for whoever picks up next.
★ 2
MIT
TypeScript
Aug 18, 2026
dsh plugin --profile web add dsh-task-relayby 1015677515
Captures the agent's own self-reported mistakes into a categorised lesson library that is re-injected into the system prompt every turn (each captured draft is confirmed or edited by the user before i
★ 4
MIT
JavaScript
Oct 20, 2026
dsh plugin --profile web add github:1015677515/dsh-prompt-ErrorNotes-desk