Auto-resumes interrupted DSH turns: sends a queued 「继续」 after turn/end with error, interrupted or max-tokens reasons, with a grace period, cooldown, exponential backoff, a consecutive-attempts cap, boot-time scanning, a settings card and a per-session stop/restore button in the composer toolbar; it never switches models or providers.
Install
# from npm (prebuilt)
dsh plugin --profile web add @frog755/dsh-client-auto-retry
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:Frog755/dsh-client-auto-retry
Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time — pnpm blocks those until you allow them, so an install can stop with ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED or ERR_PNPM_IGNORED_BUILDS; dsh prints the exact key to add under allowBuilds in your profile’s pnpm-workspace.yaml, and the install works on the next run. Allowing a build is a trust decision: only install sources you trust, and pin a commit (github:owner/repo#sha).
README
A DeepSeek Harness (DSH) client plugin that detects interrupted / errored / overlong (
max-tokens) turns and automatically sends a "继续 / continue" prompt to resume them. It only retries — it does not switch models or providers. Ships with a settings card.
中文版: README.md
📺 Video Demo
Douyin explainer video (real 429 auto-resume demo + 1-minute walkthrough):
https://v.douyin.com/FAT_Vlsd_AU/
What it does
DSH turns occasionally get interrupted by network flakiness, provider errors, timeouts, or hitting the output token ceiling. In most cases the model has already done most of the work — sending one more "continue" prompt lets it finish, with no human intervention and no model switch.
This plugin:
- Listens to the session event stream (
api.events.mux); - When a
turn/endarrives withreason.kind ∈ { error, interrupted, max-tokens }, waits a grace period (default 5s, giving the host time to reconnect/recover); - Auto-sends "继续" (configurable text) to that session;
- Has guards: cooldown, max consecutive attempts, and boot-time scanning of recently interrupted sessions.
Install
A. From npm (recommended)
Inside your DSH profile directory (e.g. ~/.dsh/profiles/web):
pnpm add @frog755/dsh-client-auto-retry
Then add @frog755/dsh-client-auto-retry to dsh.profile.bundles in the profile's
package.json (the package ships a cordis.patch.yml that inserts the
auto-retry row into the plugin roster as a bundle layer):
{
"dependencies": {
"@frog755/dsh-client-auto-retry": "^0.4.0"
},
"dsh": {
"profile": {
"bundles": [
"@deepseek-ai/dsh-base",
"@deepseek-ai/dsh-web-app",
// ... other bundles ...
"@frog755/dsh-client-auto-retry"
]
}
}
}
Then:
pnpm install
Restart DeepSeek Harness (host-side plugins load only once per process), then refresh the browser page.
B. Local link for development
{
"dependencies": {
"@frog755/dsh-client-auto-retry": "link:C:/Users/frog/.dsh/projects/dsh-client-auto-retry"
}
}
Client changes under lib/ apply on page refresh; host changes (lib/index.js)
need a restart.
Settings
Settings entry: Settings → General → Auto Retry. All fields apply live
(applies: "live").
| Field | Default | Meaning |
|---|---|---|
graceMs |
5000 |
How long to wait after an interruption before auto-sending "continue" (ms) |
cooldownMs |
20000 |
Minimum interval between two auto-continues for the same session |
maxConsecutive |
4 |
Stop auto-retrying after this many consecutive attempts; wait for a human |
continueText |
继续 |
The text to send |
echoWindowMs |
30000 |
Self-echo window: a message with the same text arriving within this window after an auto-send counts as the plugin's own echo, not human input |
scanOnBoot |
true |
Scan recently interrupted sessions on page load and resume them |
freshMs |
900000 |
Scan window: only sessions touched within this many ms |
verbose |
true |
Print [auto-retry] debug logs to the browser console |
How it works
flowchart LR
A[api.events.mux stream] --> B{turn/end?}
B -- "error / interrupted / max-tokens" --> C{stopped or pending?}
C -- yes --> D[skip, wait for human]
C -- no --> E{cooldown passed? under cap?}
E -- no --> D
E -- yes --> F[fire: sessions.prompt sends continue]
F --> G[consecutive +1<br/>whether the send succeeded or not]
A --> H{user/message arrives}
H -- own continue echo<br/>same text within echoWindowMs --> G
H -- real human input --> I[reset counter + cancel pending<br/>leave the loop entirely if retrying]
A --> J[scanOnBoot: scan interrupted sessions] --> C
B -- "completed / aborted / blocked" --> K[reset counter<br/>completed also re-arms]
All core logic lives in AutoRetryRunner in lib/client.js; lib/index.js (the
host half) only registers the settings schema.
Compatibility notes (important)
📖 Full version: docs/COMPATIBILITY.md (with a debugging checklist and an index of modification points).
Tested version
This plugin was written and verified against DSH 0.1.0-rc.7 (web profile;
the desktop runtime ships the same rc.7 here). Different DSH versions and
form-factors (desktop app, newer/older release candidates, community builds) may
have different interfaces — if it does not install or does not fire, check the
table below item by item.
DSH API surface this plugin depends on
| # | Dependency | Shape in rc.7 | Possible changes elsewhere |
|---|---|---|---|
| 1 | Opening the event stream | api.events.mux({}, signal) → AsyncIterable<RpcRequest<MuxFrame>> |
method name, args, return type |
| 2 | Mux frame envelope | Frames are RPC envelopes { rpcId, payload }; the content lives in payload (session/event, …) |
Some builds push raw frames { type, sessionId, event }; the plugin already accepts both (see onMuxFrame) |
| 3 | Turn-end reason | turn/end data.reason.kind ∈ { completed, aborted, blocked, error, 'max-tokens', interrupted } |
Enum names may grow/shrink; TurnEndReasonMap is designed to be plugin-merged |
| 4 | Sending continue | api.sessions.prompt({ sessionId, mode: 'queue', content: [{ type: 'text', text }] }) → { result: { ok } } |
request/response shape may change |
| 5 | Session list | api.sessions.list({}) → result.value (or result.data) array; fields s.id/s.sessionId, s.updatedAt/s.lastActivityAt |
field names may change (plugin reads both) |
| 6 | Client module format | window.__ModuleLoader__.load({ id, factory }) (@deepseek-ai/dsh-client-runtime) |
desktop or other builds may use a different loader |
| 7 | Settings schema (host) | settingsNamespace(NS) + ctx.settings.register(ns, schema, { applies: 'live' }) (@deepseek-ai/dsh-settings) |
registration API or applies values may change |
| 8 | Settings card (client) | ctx.slots.inject('settings.general.item') + slots.register(...) + ctx.locale.register(NS, { zh, en }) + ctx.settingsScope.bind({ namespace: NS }) + runtime.defineStore(...) |
slot id, locale/scope/store APIs may change |
| 9 | Bundle mechanism | dsh.bundle.patch in package.json → cordis.patch.yml with - insert: { id, name } |
older versions may need a manual insert in the profile's cordis.patch.yml, or a totally different mechanism |
Debugging checklist
- Open DevTools console and look for
[auto-retry]logs (verboseis on by default). - Plugin row not loading at all: confirm
dsh.profile.bundlesincludesdsh-client-auto-retryand DSH was restarted. - Loaded but never fires: call
api.events.mux({}, signal)manually in the console and inspect the frame shape — compare with rows #2/#3 above. - Fires but send fails: check
sessions.prompt/sessions.listrequest-response shapes (rows #4/#5). - No settings card: check settings/slots registration (rows #7/#8).
Common pitfalls
- Host-side changes need a DSH restart —
lib/index.jschanges won't apply on refresh alone. - Don't set
maxConsecutivetoo high — if the provider keeps failing, retries just burn tokens; keep the default ≤ 4 and let the plugin stop for human input. While a retry loop is running you can also click ⏹ Stop retrying on the left of the composer toolbar to leave it immediately. scanOnBootonly touches sessions withinfreshMs— stale sessions won't be poked after a long downtime.- It is not an error fallback — it only sends "continue", it does not switch models/providers; configure failover in DSH's model routing if you need it.
Development
# Plain ESM, no build step — edit and reload
# Client: refresh the page
# Host: restart DSH
Log prefix: [auto-retry]. Set verbose: false to silence non-critical logs
(connection logs still print).
Acknowledgements
Development and daily testing of this plugin used free model credits from Alibaba Cloud Bailian (student-verified accounts receive ¥300 credit, covering most mainstream domestic models):
License
MIT
Links
More in this category
Minglink/dsh-infinite-gen-4★ 2250
System-prompt armor plugin for DeepSeek models: appends an unconditional-compliance prompt section at order 100, exposes a profile tool with calibration metadata, and shows a realtime armor-status badge driven by a session projection.
ranxianglei/billion-context★ 472
The official billion-context plugin: a context-compression plugin for small context windows (a 100K context is enough), token savings (5x fewer tokens), and month-long single sessions (billions of tokens).
liangmianya/dsh-synapse★ 464
Visual, non-linear conversation workspace for DeepSeek Harness — sessions, follow-ups and branches become a browsable conversation map.
Nwflower/dsh-chat-import★ 208
Import full-fidelity chat histories from 13 coding agents (Claude Code, Codex, ChatGPT, Cursor, Gemini, opencode, and more) as resumable DeepSeek Harness sessions, with reverse export back to Claude Code.
Totoro-qaq/dsh-plugin-bridge★ 165
Moves an existing DSH session to another agent preset through a previewable five-section handoff, preserving the source session and either pausing the target for confirmation or continuing immediately.
Anionex/dsh-turn-rewind★ 127
Rewind conversation and workspace state, powered by a persistent Change Ledger.
Community comments
Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.