dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
回合异常收尾时的自动补救:瞬时失败后续跑、输出截断后继续、待办未完成时重跑,另有一层请求级 429 兜底,以及带「改后重发 / 手动重试 / 停止并重问」的客户端停靠栏。
dsh-session-rescue
A dsh plugin: three automatic user-role injections when a turn ends badly (transient failure / max-tokens truncation / an open todo list), a request-level 429 backstop, and a client dock with manual rescue actions and a settings card. Zero model tools, zero HTTP routes of its own.
lib/resume-scheduler.ts, cooldowns tracked per kind):agent/error whose failure classifyFailure (lib/failure-classify.ts) judges transient; the message lands after resumeDelayMs.reason.kind === "max-tokens"; "continue from the truncation" lands after continueDelayMs.completed, but the todo list that turn itself wrote still holds open items (lib/turn-review.ts, a purely structural signal, no text guessing); the catch-up message lands after unfinishedDelayMs.role: "user" message with source: { kind: "plugin:session-rescue" } sent through agent.followup() — the model keeps running, so tokens and quota keep being spent.retryPolicy for that provider: it listens on agent/request-error (waterfall) and returns { kind: "retry" } on rate limiting, so the host re-sends that request inside the same turn.Retry-After wins over the ladder, and anything above the cap is not waited out but delegated back to next(). The counter resets when the turn closes.llm-retry owns the retry (code filtering, provider retry-after, jitter, durable events). The deepseek adapter resolves a policy even when none is configured, so this ladder only ever engages for adapters that resolve none.llm-pi-ai namespace.agent/error, agent/status, agent/request-error, agent/disposed (cleanup). Turn facts (turn/start, turn/end, user/message, tool/call, todo/write) fold incrementally through ctx.sessionProjections; on profiles without that registry, or on shapes the fold cannot vouch for, it falls back to a full session.snapshotEvents() scan with identical verdicts.classifyFailure) is a fixed ladder. Permanent failure codes veto first (CONTEXT_WINDOW_EXCEEDED / AUTH / INVALID_CREDENTIAL / MISSING_CREDENTIAL / INVALID_REQUEST / INVALID_ARGS / NO_ADAPTER / INVALID_MODEL_CONTEXT / INVALID_PREPARED_CALL). The QUOTA code is then split by evidence: HTTP 429, Retry-After or rate-limit wording means transient; balance-exhausted wording means permanent. HTTP 401/403 and billing status 402 are permanent; the explicit transient codes (RATE_LIMIT / SERVER / TIMEOUT / TRANSPORT / EMPTY_RESPONSE), HTTP 408/425/429/5xx and rate-limit wording are transient; PI_AI_ERROR with an unrecognized finish_reason or empty-response wording is transient. Everything unrecognized is permanent — conservative by design.error without a .failure face counts the same as permanent; the classifier unwraps host-wrapped AggregateError before giving up.providerExcludes hits resume only; the last turn/end says aborted or interrupted (you stopped it — nothing is injected, no quota spent); the turn called ask_user_question (no catch-up); the turn was opened by the official Schedule reminder (all three kinds stand down)."goal" turn is decided at fire time from the official goal service, not unconditionally: active + armed (the driver is still driving) → stand down; active + disarmed (the driver gave up — by its own design it never auto-retries) → this plugin takes the round over; paused / blocked / complete are human or lifecycle decisions and are never overridden; an unreadable goal state stands down too.preFireCheck): the agent is gone, the status is not idle, the inbox holds queued messages, or a newer turn already started → the pending injection is silently voided, without counting or entering cooldown.completed turn zeroes resume / continue; unfinished only refills once the todo list closes (otherwise inject → still open → inject again would self-oscillate).dsh plugin --profile web add @jayyuen66/dsh-session-rescueRemove with dsh plugin --profile web remove @jayyuen66/dsh-session-rescue. The package is on the public npm registry, so installation needs no credentials and no scoped-registry line.
require() of an ESM entry work at all, which is why engines.node is declared.@deepseek-ai/dsh entry under peerDependencies (^0.2.1-rc.3, i.e. the 0.2.x line — ^ on a 0. version caps below 0.3.0); the @deepseek-ai/dsh-* peers are checked the same way, and engines.dsh carries the same range.@deepseek-ai/* under dependencies, by contract** (test/publish-manifest.test.ts enforces it). Consumer profiles install with nodeLinker: hoisted and autoInstallPeers: false, so a host package under dependencies is materialized as a real directory next to the copy the running host already loaded — native tool calls then throw while build, typecheck and coverage stay green. The host packages host.js imports for value (@deepseek-ai/dsh-brand, @deepseek-ai/schemastery) are required peers repeated under devDependencies at the same pin, so the bundler externalizes them instead of inlining official function bodies.prepack-rebuilt host.js and client.js, types/, cordis.patch.yml, plus README*, LICENSE, CHANGELOG.md and icon.svg. Artifacts are not committed, and are not minified on purpose — the bare cross-package specifiers must survive for the externalization assertions to mean anything.dist-tags.latest). It usually means the sibling library package @jayyuen66/dsh-plugin-shared is not on the registry yet — this package value-imports its lib/locale, lib/record, lib/errors, lib/card-apply, lib/card-diff and lib/lesson-bus, so a missing one surfaces as ERR_MODULE_NOT_FOUND.If your profile pins @jayyuen66/dsh-plugin-shared with a pnpm overrides entry, that pin must be at least the version this package declares. A lower pin installs a copy without the names imported at runtime, and the host then reports N entries did not activate together with does not provide an export named ... at boot — the package installs fine and never loads, and no per-repository CI run can see it.
dsh --profile web --dump-config shows this package's layer.# == or # == , patched by ; accept both forms.llm-pi-ai providers (name, retry mode, quota coverage). A permanently "unavailable" readout means the layer is mounted but the Remote namespace did not reach the browser.[session-rescue] lines naming the kind and the gate. Counter-check: a turn you stop yourself (aborted) injects nothing, by design — "no injection" there is not a broken install.session_rescue tool for the model to call.sessionRescue, reached through the host /api gateway; authentication, Host / DNS-rebinding / Origin checks and body limits are all the gateway's.lessonLoop bus (facts and passes), sessionProjections (fast turn facts) and the goal service (fire-time takeover verdict). A profile without them keeps rescuing — it just falls back to the full log scan, and goal turns are left alone.dsh.bundle.patch in package.json points at cordis.patch.yml, whose single line is - id: session-rescue with name set to the bare package name — the host scans dsh.client on bare-name entries only. dsh plugin add/remove maintains the assembly layer.timer and settings (inject: ["timer", "settings"]); the read surface is registered through an injected child fiber into the host remote gateway. It does not depend on webServer, so a host without that service (TUI) keeps auto-rescue working and only loses the settings card's data plane.plugins.bundle.config on the plugin page, keyed by the bundle package name; configForms.get() and the settings namespace stay the bare entry id session-rescue. Edits are staged, Save writes them into settings, Revert discards.config:; precedence = card runtime value > line config > schema .default(), and an invalid config fails the plugin load loudly.Kind · Trigger · Default delay / cooldown / quota resume · agent/error, failure classified transient (lib/failure-classify.ts) · 10 s / 120 s / 3 continue · Turn ended max-tokens (output truncated) · 3 s / 60 s / 3 unfinished · Turn ended completed, its own todo list still open (lib/turn-review.ts) · 5 s / 120 s / 2
resumeOnOpenTodos is the catch-up kind's own switch; providerExcludes gates resume only and touches neither of the other kinds nor the request-level 429 retry.chainResumeDelayMs (default 60 s): resumeDelayMs plus failure time always sits inside the 120 s cooldown, so without this exception a failing chain would be silently swallowed.lib/messages.ts and follows the official locale preference (Chinese when that entry is not projected); no session content is interpolated.Namespace session-rescue = the entry id (implicit registration — the package does not call settings.register). The settings form and the line config share one Config schema in the host entry; the .volatile() fields are the card's editable face. Times in ms.
Field · Type / range · Default · Card · Purpose enabled · boolean · true · yes · Master switch; false stops all three injections and the request-level 429 retry, the manual dock stays providerExcludes · string[] · [] · yes · Providers whose resume never auto-fires (e.g. ones with a dedicated retry policy) resumeDelayMs · 1000–300000 · 10000 · yes · Wait after a transient failure resumeCooldownMs · 5000–3600000 · 120000 · yes · Minimum interval between two resumes in one session maxResumes · 0–20 · 3 · yes · Per-session resume quota (consecutive semantics) chainResumeDelayMs · 1000–300000 · 60000 · yes · Re-schedule delay when a resume-opened turn fails again (cooldown bypassed) continueDelayMs · 500–300000 · 3000 · yes · Wait after a max-tokens truncation continueCooldownMs · 5000–3600000 · 60000 · yes · Minimum interval between two continues maxContinues · 0–20 · 3 · yes · Per-session continue quota resumeOnOpenTodos · boolean · true · yes · The catch-up kind's own switch unfinishedDelayMs · 1000–300000 · 5000 · yes · Wait after an open-todo turn unfinishedCooldownMs · 5000–3600000 · 120000 · yes · Minimum interval between two catch-ups maxUnfinished · 0–20 · 2 · yes · Catch-up quota; refills only when the list closes requestRetryMax · 0–20 · 5 · no · Request-level 429 retry cap requestRetryBackoffMs · number[] (≥ 1 entry) · [2000, 5000, 10000, 20000, 30000] · no · Backoff ladder for the request-level retry requestRetryBackoffCapMs · ≥ 1000 · 30000 · no · Longest this plugin will wait; a longer Retry-After delegates back downstream
The three requestRetry* rows are deployment values: not .volatile(), so the card has no row for them — they go on the registration line's config: only (plain values, applied on restart).
Six methods on the official Typert Remote namespace sessionRescue. Parameters go through strict codecs; results use src-json.
Method · Input · Reply state · — · ok plus a per-session snapshot: pending kind, remaining-ms countdown, chained / suspended flags, fire counters, the per-session switch cancel · { sessionId } · Cancels that session's pending injection, suspended ones included toggle · { sessionId } · Flips the per-session switch; switching off also cancels pendings. 409 when the toggle ledger is full with no dead session to evict resume · — · Restores suspended injections after a reconnect; ok plus the restored count retryProviders · — · The official llm-pi-ai providers with retry mode / count / quota coverage; 503 when that namespace is unavailable retryPolicy · { provider, preset } · Writes providers..retryPolicy (presets default / enhanced / always / off) via settings.mutate; a revision conflict answers 409
status is a retained semantic code, not an HTTP status of a route of this package — there are no routes.role: "user" message through agent.followup(), with an id like session-rescue-- and text from the fixed templates. This package keeps no second retry configuration of its own.lessonLoop bus: when present it receives the transient-failure, unclassified-failure, max-tokens and unfinished-turn facts, and one pass once the same provider genuinely finishes a completed turn. An absent or throwing bus only warns; the main path never depends on it.Aspect · Content Nothing written, nothing fetched · The host half uses no node:fs and makes no network calls; the only output surfaces are console.* lines and the injected message In memory, bounded · At most 200 session records in the scheduler, 64 suspended pendings, 64 pending-pass entries and 200 per-session toggle entries — oldest trimmed first, timers released on agent/disposed and on plugin unload Persisted, exactly two places, both through official settings · This namespace session-rescue and the llm-pi-ai retry presets, stored under the dsh data directory and surviving restarts Per-session switch · Process memory only; a restart falls back to the global enabled Language · The injected text follows the official locale preference; Chinese when that entry is not projected The only exit path · Content leaves only towards lesson-loop, and only when it is installed: it receives the failure code / status / message verbatim, the session id and the session's cwd, and lesson-loop alone decides where that is written. Without it nothing is recorded
continue, unfinished and the request-level 429 retry do not consult it.todo/write list that still holds pending or in_progress items. Sessions that never use the todo tool cannot trigger it.dsh-llm-retry mounted leaves a 429 terminal with zero request-level retries.always mode it adopts the decision coming back — this plugin is simply not consulted in those windows.