为 DSH Web UI 自动续跑因网络等原因中断的请求,遇到认证失败等永久性错误会跳过并通知。
- 语言
- TypeScript
- License
- MIT
- 分支
- main
安装
$ dsh plugin --profile web add github:HsiangNianian/dsh-auto-continue在终端中运行以上命令,通过 dsh CLI 安装此插件。可在右上角切换 Profile。 第一次用 dsh?看这篇新手教程
一句话定位
为 DSH Web UI 在请求被网络中断时自动续跑的插件——把"继续"模拟成普通用户消息自动发送,遇到永久性错误会跳过并通知。
核心能力
- 检测回合结束事件,对临时性失败(网络/超时/5xx/429 等)和 token 超限自动发送"继续"消息
- 自适应退避:连续失败时冷却时间按倍数递增(默认 20s → 40s → 80s…),避免对故障上游狂发请求
- 幂等护栏:续跑前检查上一步工具调用状态,若工具结果未确认或已完成,给模型加上一段提示,避免重复执行
- 循环守卫:识别运行中回合的空转(连续短句、同工具反复调用、相同消息重复),自动取消并用循环提示文本重启回合
- 启动/重连扫描:浏览器关闭期间若宿主崩溃遗留中断的回合,恢复后会扫描并自动续跑
- 设置卡片统计今日自动继续次数、跳过数、恢复数、失败数、达上限停止、循环打断,可按错误码分类并一键清零
- 可选浏览器通知:自动继续/放弃/永久性错误时弹窗,可直接点"立即续跑"或"暂停该会话 1 小时"
技术实现
- 语言: TypeScript
- 关键依赖: @deepseek-ai/cordis、@deepseek-ai/schemastery、@deepseek-ai/dsh-settings、@deepseek-ai/dsh-client-runtime
- 架构模式: 宿主-浏览器双半插件;宿主侧单实例引擎订阅会话事件流(
session/event),通过agent.followup发送、agent.cancel取消;浏览器侧瘦壳只提供设置卡片和 SSE 状态桥 - 入口文件: src/index.ts(宿主侧)/ src/client/index.ts(浏览器侧)
适用场景
使用 DSH Web 跑长时间任务时,网络抖动或服务端瞬时 5xx 会让回合中断,常见做法是手动点重发。本插件免去这个动作——只要网络或服务端是临时性故障,等几秒就会自动接着跑。也适合浏览器关闭后回来发现任务半途而废的场景,开页即恢复。
前置依赖与兼容性
| 依赖 | 最低版本 | 说明 |
|---|---|---|
| DSH | ≥ 0.1.0-rc.7 | 低于 rc.7 时设置卡片加载失败(list slot ... requires options.id),必须升级 DSH |
| Node.js | ≥ 18 | 仅在从源码手动构建时要求;通过 dsh plugin add 安装预构建包不要求 |
| 平台 | 跨平台 | 插件无原生模块依赖,无平台限制 |
| 原生模块 | 无 | 引擎基于宿主进程内的内存状态,无 node-pty / sqlite 等原生依赖 |
安装方式
dsh plugin --profile web add github:HsiangNianian/dsh-auto-continue
配置项
| 配置 | 类型 | 说明 | 默认值 |
|---|---|---|---|
| paused | 布尔 | 全局暂停:实时与扫描都不再自动发送,已排队的待发送也会取消 | 关 |
| continueText | 文本 | 中断后自动发送的消息内容,支持 {code} {tool} {message} 等占位符 | 继续 |
| continueTextMaxTokens | 文本 | 达到输出 token 上限时自动发送的文本,支持相同占位符 | 继续 |
| guardTools | 布尔 | 续跑前检查上一步工具调用并加护栏提示,避免重复执行 | 开 |
| guardPendingText | 文本 | 工具结果未确认时附加的提示文本,支持 {tool} 占位符 | (上一步工具「{tool}」可能未完成, 先确认状态再继续, 不要重复执行) |
| guardDoneText | 文本 | 工具已成功时附加的提示文本,支持 {tool} {result} 占位符 | (上一步工具「{tool}」已完成, 结果: {result}; 不要重复执行, 直接继续) |
| graceMs | 数字 | 中断后等待宿主自行恢复的宽限期,期间恢复则取消自动发送(毫秒) | 3000 |
| cooldownMs | 数字 | 同一会话两次自动"继续"的最小间隔,失败尝试也计入(毫秒) | 20000 |
| maxConsecutive | 数字 | 同一会话连续自动"继续"的上限;超过后停止,等用户介入或成功回合 | 3 |
| scanOnBoot | 布尔 | 页面启动或重连时扫描最近中断的会话并自动续跑(如浏览器关闭期间宿主崩溃) | 开 |
| scanLimit | 数字 | 启动扫描最多检查多少个最近更新的会话(不含运行中和子代理会话) | 8 |
| freshMs | 数字 | 扫描只处理该时间窗内的中断(毫秒) | 900000(15 分钟) |
| classify | 布尔 | 错误分类:仅自动恢复临时性错误;认证/余额/模型不存在等永久性错误跳过并通知 | 开 |
| backoffFactor | 数字 | 连续失败时冷却间隔的倍率 | 2 |
| backoffMaxMs | 数字 | 自适应退避上限,防止等待过久(毫秒) | 300000(5 分钟) |
| notify | 布尔 | 自动继续/放弃/永久性错误时弹浏览器通知(首次会请求权限) | 关 |
| loopGuard | 布尔 | 检测运行中的回合空转并自动取消重启 | 开 |
| loopShortChars | 数字 | 模型消息短于该字符数视为一条短句(空转信号) | 40 |
| loopWindowMs | 数字 | 连续短句必须落在该时间窗内才计入空转(毫秒) | 30000 |
| loopShortCount | 数字 | 时间窗内连续多少条短句且无工具调用时判定空转循环 | 12 |
| loopRepeatText | 数字 | 连续多少条完全相同的消息时判定空转循环(不限长度,最强信号) | 4 |
| loopToolRepeat | 数字 | 同工具+同参数+同结果连续调用多少次时判定死循环 | 5 |
| loopText | 文本 | 循环守卫打断后重启回合时发送的文本,支持 {tool} 占位符 | (检测到你可能陷入循环, 请停止重复刚才的动作, 换一种方式继续) |
| verbose | 布尔 | 在浏览器控制台输出 [auto-continue] 日志 | 开 |
常见问题
Q: 哪些中断会触发自动续跑,哪些不会?
A: 临时性失败(网络/超时/5xx/429)会触发;达到输出 token 上限也会。用户主动停止(aborted)、策略拒绝(blocked)以及永久性错误(401/403 状态码,或匹配认证/凭据/余额/配额/模型不存在/上下文超限等关键词)一律不续跑,而是跳过并通知。
Q: 多个浏览器标签页同时打开会重复发送吗?
A: 不会。自 0.8.0 起引擎跑在宿主进程内(src/host/engine.ts),全局只有一个实例,多标签同时打开也不会重复发送;浏览器标签全部关掉引擎仍在值守。
Q: 需要重启 dsh web 才能生效吗?
A: 安装后首次启用需要重启 dsh web。之后所有改动通过设置卡片即时保存(写入 ~/.dsh/settings.yaml),保存后立即生效,已打开的页面也会刷新;如遇个别字段不生效,重启 dsh web 即可。
Q: 怎样手动关闭自动继续?
A: 打开"设置 → 插件",找到"自动继续"卡片,把全局"暂停自动继续"开关打开即可实时取消一切自动发送。需要只暂停某一个会话时,可点浏览器通知里的"暂停该会话 1 小时"按钮,或在卡片底部"已暂停会话"列表里逐个解除。
Q: 卸载插件后留在 ~/.dsh/settings.yaml 的段落需要清理吗?
A: 可以不清理。卸载插件时该段落保留在配置文件里无害;想彻底清理时手动删除 auto-continue 段落即可。
Q: 循环守卫会不会误判?会把我手动停止的回合重启吗?
A: 不会误判你手动停止。循环守卫发起的取消带有内部来源标记,引擎只对守卫取消触发自动重启,用户手动停止不会触发任何动作。阈值、时间窗、提示文本均可调,遇到误判可放宽 loopShortCount / loopWindowMs。
Q: 提示文本里的占位符怎么写?
A: continueText 与 continueTextMaxTokens 支持 {code} {message} {status} {tool} {turn} {errorCount} {sessionTitle} {elapsed}。护栏文本(guardPendingText / guardDoneText / loopText)支持 {tool} 与 {result}(结果摘要)。例如 继续 ({tool}: {code}) 会渲染成 继续 (git push: UPSTREAM)。
上手难度
入门 — 安装一行命令,默认配置即可工作,需要调整时打开设置卡片对照说明修改,无需写代码。
已知问题与限制
reconnectScanDelayMs(重连扫描延迟,默认 5000ms)与reconnectBackoffMs(重连退避,默认 3000ms)为遗留字段:引擎自 0.8.0 迁入宿主后不再使用,仅为兼容旧配置保留;改动这两个值不会生效。- 在 DSH < 0.1.0-rc.7 上安装会因
settings.plugin.itemslot 不支持 keyed 注册而加载失败,需要先升级 DSH。 isTransientFailure通过正则匹配错误关键词(auth、credential、api_key、balance、quota、context length 等),若上游错误信息使用不常见的措辞且未命中 401/403,可能被误归类为临时性错误触发无意义的重试——可通过关闭classify改为全量续跑。- 浏览器通知依赖浏览器 Notification API 与用户授权,被拒绝后不会再次请求。
dsh-auto-continue
DSH Web UI plugin — when a request is interrupted by a network error or any other non-human cause, it automatically types 「继续」 and sends it for you.
English · 中文
What It Does
For DeepSeek Harness (dsh web): whenever a request in the web GUI gets interrupted by a non-human cause, the plugin simulates the user typing 「继续」 and sends it, so the agent keeps working without manual intervention. The message enters the session log exactly like a manual prompt — the model sees it, and the interrupted work resumes. Since 0.8.0 the engine runs inside the host process (single instance), so it keeps watching even with every browser tab closed, and multiple open tabs can never double-send.
Smart recovery (all configurable):
- Error classification — transient failures (network / timeout / 5xx / 429…) are auto-resumed; permanent ones are skipped and notified, because retrying them never helps. A failure counts as permanent when its HTTP status is 401/403 or its code/message matches auth, credential/API-key, balance/quota, unknown-model, or context-length/overflow keywords. Turn classification off to resume everything
- Adaptive backoff — consecutive failures wait longer each time (cooldown × factor: 20s → 40s → 80s…), capped at the max backoff, instead of hammering a broken upstream
- Templated continue text —
continueTextsupports{code}{message}{status}{tool}{turn}{errorCount}{sessionTitle}{elapsed}placeholders, so the resume message can carry the failure context ("继续 (git push failed: UPSTREAM)"); a separate template fires onmax-tokens(e.g. "继续输出, 不要重复已生成的内容") - Idempotency guard — before resuming, the plugin inspects the last tool call: if its result is unconfirmed (the turn died mid-tool, e.g. a
git pushthat may have gone through), the resume message tells the model to check state first and not to rerun; if the tool is confirmed done, it says so and asks not to repeat it; a failed tool gets no guard (retrying it is the point). Both guard texts are configurable ({tool}/{result}placeholders) - Pause — a global Pause auto-continue toggle in the settings card stops everything (live + scan) instantly; per-session pauses (e.g. via a notification button) suspend only one session until they expire. The Resume now notification button is the one explicit exception: pressing it is the user asking for exactly one send, pause or not
- Notification buttons — notifications carry Resume now (send immediately, ignoring cooldown, the consecutive cap and any pause) and Pause this session 1h actions
- Loop guard — watches running turns too. Three signals trip the guard, which cancels the turn and restarts it with a configurable loop text ("stop repeating, try another way"): the model repeating the exact same message several times (any length — e.g. "Let me test variants of the regex…" ×7), many short messages inside a short time window with no tool call in between (the "Let me read…" spin), or the same tool called repeatedly with the same arguments and the same results (a changed argument or result counts as progress). The cancel carries an internal marker so it is never confused with a user stop — the restart only happens for guard-initiated cancels. Thresholds, the time window and the loop text are configurable
- Stats panel — the settings card shows today's auto-continue count, recoveries, failures, permanent skips, give-ups and loop breaks, broken down by error code, with a one-click reset
- Browser notifications — optional alerts when auto-continue fires, gives up, or hits a permanent error; the browser asks for permission on first use, and nothing is shown again after a denial
It watches the live event streams and reacts to:
| Event | Meaning |
|---|---|
turn/end → error | Turn failed (model / network / timeout, …) |
turn/end → interrupted | Crash-orphaned turn left behind by a host restart (recovered by the startup scan) |
turn/end → max-tokens | Output token ceiling reached |
host/agent-error | Agent failure with no turn position (only network/timeout-class messages auto-resume) |
Never auto-continues: user-aborted turns (aborted) or policy rejections (blocked); live interrupted turn-ends too — that marker is only written by crash repair when the host reloads, so orphaned turns are recovered by the startup scan, not the live path; sessions the host already resumed itself; running sessions or sessions with queued messages; subagent sessions; anything inside the cooldown / consecutive-cap windows (configurable in the settings card, below).
How It Works
The host-side engine subscribes to the session event firehose inside the dsh host process — exactly one engine, regardless of how many tabs are open (the duplicate-send class of bugs cannot exist by construction). On an interruption it waits a grace period (default 3 s) — if the host starts a new turn by itself (turn/start), the auto-continue is cancelled — then sends the configured text through the agent registry (agent.followup, the same queue the Send button uses).
On host boot it also scans the live sessions: a session whose last turn ended with a non-human reason within the scan window (default 15 minutes), with no later turn/start or user message, gets resumed automatically too (e.g. the host crashed while the browser was closed — the agent-loop resumes the session and the engine picks it up).
The browser half is a thin shell: the settings card, plus a status bridge that shows notifications (with Resume now / Pause this session 1h buttons, routed back to the host engine) and feeds the card's stats / paused-sessions panels.
Quick Start
DSH plugins install into a profile (dsh web → web profile). Install, restart dsh web, done.
Requires DSH ≥ 0.1.0-rc.7 — newer versions register the settings card through the keyed
settings.plugin.itemslot; on rc.6 and earlier the plugin fails to load (list slot ... requires options.id). Upgrade dsh first.
From npm (recommended)
Published as dsh-client-auto-continue:
dsh plugin --profile web add dsh-client-auto-continue
dsh web
Directly from GitHub (no clone needed)
Installs straight from the repository's default branch — built artifacts are committed, so no local clone or build step:
dsh plugin --profile web add github:HsiangNianian/dsh-auto-continue
dsh web
This tracks the
mainbranch rather than released tags — great for trying the latest changes, while the npm method above is the stable choice. Switching between install sources is just re-runningdsh plugin --profile web add <other-spec>; the profile dependency is replaced in place.
From this repository
Requires Node.js ≥ 18.
git clone https://github.com/HsiangNianian/dsh-auto-continue.git
cd dsh-auto-continue
npm install
npm run build
# the package carries its own cordis.patch.yml (dsh.bundle.patch),
# so the plugin row registers itself
dsh plugin --profile web add link:$(pwd)
dsh web
Manual (no pnpm / dsh plugin needed)
ln -sfn "$(pwd)" ~/.dsh/profiles/node_modules/dsh-client-auto-continue
# then append to ~/.dsh/profiles/web/cordis.patch.yml:
# - insert:
# - id: auto-continue
# name: 'dsh-client-auto-continue'
dsh web
Switching from a manual install to
dsh plugin add? Remove the manualinsertentry first — the bundle patch registers the row and a duplicate would conflict.
Settings exposure: since DSH 0.1.0-rc.7 the web settings surface is registry-driven — every namespace a plugin registers is served, so the settings card works out of the box, no vendor patch needed (the plugin requires rc.7+, see Quick Start).
Verify & uninstall
dsh --profile web --dump-config | grep auto-continue # config layer mounted
In the browser console (Ctrl/Cmd+Shift+I): [auto-continue] 已启动(文本="继续", …) — every detection and auto-send is logged.
dsh plugin --profile web remove dsh-client-auto-continue # npm / repo install
# or remove the symlink + the insert entry # manual install
dsh web
Configuration
Everything is configurable from the GUI — no file or console edits needed. Open Settings → Plugins and find the dsh-client-auto-continue configuration card, right where every other plugin's config lives. Besides the fields below, the card shows a live stats panel (today's activity with a reset button) and the list of paused sessions (each with a per-session resume button).
Or skip the GUI and edit the config file directly — the engine reads the plugin's section from ~/.dsh/settings.yaml (one shared file for every plugin's sections), so this works in any install, patched or not. The file is watched and re-read automatically, so changes apply live; restart dsh web if a page that was already open doesn't pick them up. Fields you leave out fall back to the defaults in the table below:
auto-continue:
paused: false
continueText: '继续'
continueTextMaxTokens: '继续'
guardTools: true
guardPendingText: '(上一步工具「{tool}」可能未完成, 先确认状态再继续, 不要重复执行)'
guardDoneText: '(上一步工具「{tool}」已完成, 结果: {result}; 不要重复执行, 直接继续)'
graceMs: 3000
cooldownMs: 20000
maxConsecutive: 3
scanOnBoot: true
scanLimit: 8
freshMs: 900000
verbose: true
classify: true
backoffFactor: 2
backoffMaxMs: 300000
notify: false
loopGuard: true
loopShortChars: 40
loopWindowMs: 30000
loopShortCount: 12
loopRepeatText: 4
loopToolRepeat: 5
loopText: '(检测到你可能陷入循环, 请停止重复刚才的动作, 换一种方式继续)'
How the card works:

- Edits are staged — nothing reaches the disk until you hit Save; an unsaved badge marks the card while drafts are pending, and Discard drops them
- A field you changed shows an Overridden badge with a per-field Reset to default button that restores the built-in value
- Boolean fields are tri-state: Inherit (use the default) / On / Off
- Invalid drafts (non-numbers, values below the minimum) block the save with a hint
- In a read-only deployment the card shows the stored values but disables every control
- Changes apply immediately after Save and persist in
~/.dsh/settings.yaml(uninstalling the plugin leaves the section behind — harmless, delete it by hand if you like)
| Field | Default | Description |
|---|---|---|
| Pause auto-continue | off | Global pause: no live or scan auto-send fires, queued pending sends are cancelled |
| Continue text | 继续 | Text automatically sent after an interruption |
| Continue text (max tokens) | 继续 | Text sent when the output token ceiling is reached (same placeholders) |
| Idempotency guard | on | Inspect the last tool call before resuming and steer the model (see What It Does) |
| Loop guard | on | Detect a running turn spinning in place and restart it (see What It Does) |
| Short-sentence max (chars) | 40 | A model message shorter than this counts as a short sentence (spinning signal) |
| Short-sentence window (ms) | 30000 | Consecutive short sentences must land inside this window; normal thinking spread over time is not misjudged |
| Short-sentence threshold | 12 | Consecutive short sentences inside the window, with no tool call in between, trip the loop guard |
| Identical message count | 4 | Consecutive identical messages (any length) trip the loop guard — the strongest spinning signal |
| Same-tool repeat count | 5 | Consecutive calls of the same tool with identical arguments and results trip the loop guard |
| Loop text | (检测到你可能陷入循环, 请停止重复刚才的动作, 换一种方式继续) | Text sent after the loop guard restarts a turn; {tool} placeholder |
| Guard text (unconfirmed result) | (上一步工具「{tool}」可能未完成, 先确认状态再继续, 不要重复执行) | Appended when the last tool may have partially executed; {tool} placeholder |
| Guard text (tool succeeded) | (上一步工具「{tool}」已完成, 结果: {result}; 不要重复执行, 直接继续) | Appended when the last tool is confirmed done; {tool} / {result} placeholders |
| Grace period (ms) | 3000 | Wait after an interruption; cancelled if the host recovers on its own |
| Cooldown (ms) | 20000 | Min interval between auto-continues per session (failed attempts count too) |
| Max consecutive | 3 | Max consecutive auto-continues; stops until a user intervenes or a turn completes |
| Scan on load / reconnect | on | Scan recently interrupted sessions on load / reconnect |
| Scan limit | 8 | Max sessions scanned (running / subagent sessions excluded) |
| Scan window (ms) | 900000 | Scan only considers interruptions inside this window |
| Verbose logs | on | [auto-continue] console logs |
| Classify errors | on | Auto-resume transient failures only; auth / balance / model errors are skipped and notified |
| Backoff factor | 2 | Cooldown multiplier per consecutive failure (2 = 20s → 40s → 80s…) |
| Max backoff (ms) | 300000 | Cap on the adaptive backoff interval |
| Browser notifications | off | Notify when auto-continue fires, gives up, or hits a permanent error |
continueText (and continueTextMaxTokens) accept the placeholders {code}, {message}, {status}, {tool} (last tool call before the failure), {turn}, {errorCount} (consecutive failures including this one), {sessionTitle} (from the session list) and {elapsed} (time since the failure, e.g. 1m5s) — e.g. 继续 ({tool}: {code}) becomes 继续 (git push: UPSTREAM). The guard texts accept {tool} and {result} (a truncated excerpt of the last tool output).
Privacy & permissions
The plugin is browser-only and touches no files, credentials, or network beyond the dsh host:
- It opens the same two read-only event streams the web UI already uses (no extra server, no third-party endpoints)
- The engine's only automatic write is
sessions.prompt— the same call the Send button makes — with the text you configured (saving the settings card writes theauto-continuesection of~/.dsh/settings.yamlthrough the normal settings API, exactly like any other setting) - No browser storage at all: the single host-side engine keeps its cooldowns, send caps, pauses and stats in process memory
- Browser notifications are opt-in (
notifysetting) and permission is requested on first use only
Development
npm run typecheck # tsc --noEmit
npm run build # lib/client.js + lib/index.js + lib/types
npm run watch # rebuild on change; host HMR hot-reloads without a page refresh
npm run test # node tests/simulate-host.mjs — 15 host-side behavioral scenarios
While npm run watch runs, the profile's client-hmr row polls lib/client.js every 500 ms and hot-reloads the plugin in the browser — no server restart needed for code changes.
CI runs dsh-plugin-check on every push to main and every pull request, and gates releases — the tag workflow refuses to publish while the check fails.
Activity
Links
- Repository: github.com/HsiangNianian/dsh-auto-continue
- LINUX DO: linux.do
- DeepSeek Harness: github.com/deepseek-ai/deepseek-harness
- dsh-plugin-check: github.com/omdsh-dev/dsh-plugin-check — health-check your own DSH plugin repos
License
MIT © Hsiang Nianian