DeepSeek Harness Plugin

Frog755/dsh-client-auto-retry

Stars ★ 3 Downloads (30d) 706 Category Sessions & Messages Added 2026-08-19 npm @frog755/dsh-client-auto-retry

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:

  1. Listens to the session event stream (api.events.mux);
  2. When a turn/end arrives with reason.kind ∈ { error, interrupted, max-tokens }, waits a grace period (default 5s, giving the host time to reconnect/recover);
  3. Auto-sends "继续" (configurable text) to that session;
  4. 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

  1. Open DevTools console and look for [auto-retry] logs (verbose is on by default).
  2. Plugin row not loading at all: confirm dsh.profile.bundles includes dsh-client-auto-retry and DSH was restarted.
  3. Loaded but never fires: call api.events.mux({}, signal) manually in the console and inspect the frame shape — compare with rows #2/#3 above.
  4. Fires but send fails: check sessions.prompt / sessions.list request-response shapes (rows #4/#5).
  5. No settings card: check settings/slots registration (rows #7/#8).

Common pitfalls

  • Host-side changes need a DSH restart — lib/index.js changes won't apply on refresh alone.
  • Don't set maxConsecutive too 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.
  • scanOnBoot only touches sessions within freshMs — 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):

https://university.aliyun.com/course/promotion27-activity?clubTaskBiz=subTask..12810055..10280..&userCode=hbs5sljx

License

MIT

Content from the project README on GitHub ↗

Links

More in this category

View the whole category →

Community comments

Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.