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.
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
- Notification buttons — notifications carry Resume now (send immediately, ignoring cooldown and the consecutive cap) and Pause this session 1h actions
- Stats panel — the settings card shows today's auto-continue count, recoveries, failures, permanent skips and give-ups, 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 plugin opens two extra SSE streams in the browser — events.mux (session events) and events.host (host events). The host supports multiple consumers, so this never interferes with the built-in runtime. 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 calls sessions.prompt in queue mode with the configured text.
On page load / reconnect it also scans the most recently updated 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).
With the page open in several tabs, a localStorage mutex plus a shared per-session cooldown stamp guarantee exactly one tab sends — no duplicated 「继续」.
All knobs live in the plugin's settings card — see Configuration.
Quick Start
DSH plugins install into a profile (dsh web → web profile). Install, restart dsh web, done.
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.
Known DSH limitation (0.1.0-rc.6): the web settings surface only exposes namespaces on a hardcoded allowlist in the installed
@deepseek-ai/dsh-host-apiproxybundle. Until upstream moves exposure intosettings.register(), one idempotent vendor patch makes exposure registry-driven — every namespace a plugin registers (this one or any other) becomes visible, with no plugin-specific strings anywhere. Run it once, and re-run after reinstalling dsh:npx --yes --package dsh-client-auto-continue patch-expose dsh webThe patch script ships inside the npm package as a
bin(works from any directory in a fresh environment — no repo clone, no profile-path guessing) and covers every reachable dsh installation: the profile-linked copy, a globalnpm i -g @deepseek-ai/dshinstall, and the invoking directory's own. If you installed from this repository instead, the same script isnode scripts/patch-expose.mjsat the repo root. The auto-continue engine itself works without this patch — it only gates the GUI settings section; until then, configure everything directly in~/.dsh/settings.yaml(see Configuration).
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
reconnectScanDelayMs: 5000
reconnectBackoffMs: 3000
verbose: true
classify: true
backoffFactor: 2
backoffMaxMs: 300000
notify: false
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) |
| Guard text (unconfirmed result) | (上一步工具「{tool}」可能未完成…) |
Appended when the last tool may have partially executed; {tool} placeholder |
| Guard text (tool succeeded) | (上一步工具「{tool}」已完成…) |
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 |
| Reconnect scan delay (ms) | 5000 |
Delay before scanning after a reconnect |
| Reconnect backoff (ms) | 3000 |
SSE reconnect backoff |
| 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 only write it ever performs is
sessions.prompt— the same call the Send button makes — with the text you configured - Browser storage is limited to small
localStoragekeys: cross-tab coordination stamps, per-session pauses, and the daily stats counters - 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.mjs — 27 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 / PR 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
No comments yet. Be the first to write one.