@linxin666/dsh-doctor
English | 中文
Transactional rescue mode for DeepSeek Harness profiles: a Doctor
Supervisor plus a transparent Doctor Launcher keep an isolated rescue capsule
ready, detect boot failures, process crashes, heartbeat timeouts, Web failures
and browser white screens, and restore the profile through snapshots,
deterministic repairs, isolated health gates and atomic promote or rollback.
The package ships enabled by default: fresh installs and Web UI version
updates boot with rescue mode active, while an explicit off choice in the
Doctor card is preserved. It can be toggled from its Doctor card in
Settings → Plugin configuration → Web UI plugins. It does not modify a DSH
installation.
What it does
- The Doctor Host Plugin runs inside every protected DSH host: it exposes the
loopback recovery API, reports heartbeat and launch-phase facts to the
Supervisor, and collects browser failure reports.
- The Doctor Web Console (the family plugin card inside Settings → Plugin
configuration → Web UI plugins) shows the system phase, protected profiles,
incidents and the client failure probe, records Web UI plugins that were
enabled but never started, and offers diagnose, repair, rollback, pause and
resume actions alongside the enable switch plus a Service and capsule card:
one-click install, restart-upgrade and uninstall.
- The Send to Harness window composes a troubleshooting prompt from the newest
recorded failure (summary plus error stack) and queues it into the current
DSH session as a new turn, so the user's agent can diagnose and fix it in
place; the prompt is editable and copyable before sending. Failed-plugin rows
also carry one-click Copy error and Disable and restart actions (disable
writes the profile patch enabled row through the plugin-manager channel and
takes effect after the host restart).
- The Doctor Supervisor runs as a per-user background service. It classifies
exits into user stops, task completion and real failures, applies the
crash-loop circuit breaker, and owns rescue scheduling.
- The Doctor Launcher detects the legacy aggregate package before starting DSH and automatically migrates
@linxin666/dsh-web-ui-all to @linxin666/dsh-web-all when autoMigrate is enabled (default true) and the target package is available; migration goes through the official dsh plugin CLI with manifest/lockfile backups and a --dump-config gate.
- The Doctor Launcher relays
dsh arguments verbatim to the real DSH
executable, forwards stdin, stdout, stderr and signals, records startup
intent and exit facts, and only then reports an incident.
- The Rescue Capsule provisions a pinned DSH runtime, a pinned Doctor package
and an isolated
DSH_HOME at a machine-local home, so a broken user overlay
or profile patch can never block the recovery console.
Profile package.json and cordis.patch.yml are only touched through the official
dsh plugin command and the documented profile-layer conventions.
Components
| Part | Runs when | Responsibility |
|---|
| Doctor Host Plugin | inside every protected host | settings surface, loopback API, heartbeat and client failure reports |
| Doctor Web Console | in the DSH Web GUI | enable flow, status, incidents, diagnose and repair actions |
| Doctor Supervisor | as a host-bounded child process | lifecycle monitoring, classification, circuit breaker, rescue scheduling |
| Doctor Launcher | at every dsh invocation | transparent relay of argv, signals and exit facts |
| Rescue Capsule | machine-local isolated home | pinned runtime, isolated home, offline diagnostics and repair tooling |
Install
From npm (family first)
dsh plugin --profile web add @linxin666/dsh-web-all@latest
As a standalone bundle
dsh plugin --profile web add @linxin666/dsh-doctor@latest
From the repository (development)
git clone https://github.com/zhu1090093659/dsh-web.git
cd dsh-web
pnpm install
pnpm -r build
dsh plugin --profile web add link:$(pwd)/packages/dsh-doctor
Restart dsh web, open Settings → Plugin configuration → Web UI plugins, and
expand the Doctor card to confirm rescue mode is on (it is by default). The package
also ships the dsh-doctor CLI for the Supervisor, the Launcher, provisioning
and the legacy-service cleanup.
Enable
When rescue mode is enabled, the host mounts /api/doctor/*, persists the effective protection policy, and reconciles the Supervisor, package version, install path, and rescue capsule in the background without blocking Web startup. The Supervisor runs as a bounded child of the host that spawned it — it answers on the same local socket, carries a parent-liveness watch so it can never outlive its host into a background daemon, and no OS-level service (LaunchAgent, systemd unit, scheduled task) is registered anymore; the first ensure on a machine that still carries one removes it. Disabling stops heartbeats and pauses automatic Supervisor intervention while retaining the capsule. An explicit uninstall writes a suppression marker, so later host starts never respawn the Supervisor; Install now clears that marker. The console button remains available as a manual retry and repair entry point.
Update
After an update, restart dsh web so the host half loads the new code: the
reconciler detects a Supervisor answering with an older version, asks it to
shut down over IPC, and respawns the current one as its own child; the
capsule is refreshed when its pinned version differs. When the user changes
a provider or its keys, the capsule credential fingerprint detects the drift
and the same reconcile re-mirrors the new configuration. The reconcile is
idempotent and safe to repeat; dsh-doctor service-uninstall remains
available as the manual removal of a pre-child OS service registration.
CLI
The dsh-doctor binary exposes the operational commands:
| Command | Meaning |
|---|
dsh-doctor supervisor | run the Supervisor in the foreground |
dsh-doctor launch [dsh args...] | relay one dsh invocation under supervision |
dsh-doctor migrate [profile] | run the deterministic legacy aggregate migration directly |
dsh-doctor status | print the Supervisor snapshot as JSON |
dsh-doctor provision [profile] [--no-credentials] | provision or refresh the rescue capsule (mirrors provider config and credentials with 0600; pinned to the current package version by default; DSH_DOCTOR_PACKAGE, --no-credentials and DSH_DOCTOR_CREDENTIALS=off adjust it) |
dsh-doctor snapshot [profile] | capture one profile snapshot |
dsh-doctor diagnose [profile] | diagnose and plan one profile without writing |
dsh-doctor repair [profile] --allow-live | run the staged repair transaction (gated promote) |
dsh-doctor rollback <txnId> | restore a promoted transaction from quarantine |
dsh-doctor service-uninstall | remove a legacy OS service registration left by an older doctor version |
Exit codes: 0 ok, 1 repaired and verified, 2 attention needed, 3 blocked
(lock, offline or missing secret).
Config
The host settings namespace is doctor:
| Key | Default | Meaning |
|---|
enabled | true | master switch; mounts routes and reconciles deployment when enabled, pauses without uninstalling when disabled |
fullProtection | true | managed protection: heartbeat, incident recording and circuit breaking; off is observation mode |
autoRepair | false | promote after isolated gates; off keeps a staged candidate pending explicit confirmation. Also gates the boot self-heal below |
autoMigrate | true | migrates the legacy aggregate before startup; only the known dsh-web-ui-all -> dsh-web-all mapping is active |
heartbeatIntervalMs | 5000 | host heartbeat cadence |
Environment:
| Variable | Meaning |
|---|
DSH_DOCTOR_HOME | doctor root (default ~/.dsh-doctor; overridable) |
DSH_DOCTOR_REAL_DSH | absolute path of the real dsh executable |
DSH_DOCTOR_PACKAGE | package spec used to install the rescue Doctor |
DSH_DOCTOR_PACKAGE_DIR | local checkout to link during development |
DSH_DOCTOR_CREDENTIALS | when off, credential files are not mirrored into the rescue capsule (mirrored by default) |
DSH_DOCTOR_ENDPOINT | Supervisor endpoint injected by the launcher |
DSH_DOCTOR_TOKEN | one-run Supervisor token injected by the launcher |
DSH_DOCTOR_RUN_ID | one-run launch identity injected by the launcher |
Health and recovery
| Failure | Detection | Default action |
|---|
| boot failure | launcher exit before the ready phase, structured stderr | retry once, then open rescue |
| plugin init failure | non-zero config phase exit | retry once, then open rescue |
| runtime crash | signal or non-zero exit after startup | one restart, then circuit breaker |
| heartbeat loss | no heartbeat within the window | process and HTTP probes, then rescue |
| Web failure | repeated loopback HTTP failures | rescue on spare port when host is alive |
| browser white screen | client probe and error boundary | client-local recovery first; incident only with evidence |
| user Ctrl+C | launcher signal | normal stop, no incident |
| headless business failure | healthy app with non-zero exit | report only |
The circuit breaker suspends automatic retries after repeated failures within
the window and quarantines the profile for explicit user confirmation.
Boot self-heal (plugin-level quarantine)
When a profile fails to boot twice within the failure window and autoRepair
is on, the Supervisor attributes the failure from the captured stderr trace
(the host names the failing loader entry in its boot errors) and, when exactly
one of the profile's own patch rows is implicated, appends a bare
- id: <rowId> + disabled: true override to the profile cordis.patch.yml
— the same row-merge mechanism the loader uses when it persists a
self-disposing plugin. The next dsh web boots without the broken plugin and
every other plugin mounts. Failures that cannot be attributed to a single row
(file errors, unparseable patches, host-level faults) only annotate the
incident; the writer refuses to disable rows it cannot prove broken, never
touches a crash after startup, and never edits a patch file that fails to
parse (the D-040 quarantine lane owns those). Every heal lands in the journal
and the incident evidence, so a disabled plugin is always one
cordis.patch.yml read away from being re-enabled by hand.
Repair model
Every repair is a transaction: snapshot the live profile, stage a candidate
environment, apply only deterministic rule-based operations, run isolated
dump-config and Web health gates against the candidate, promote with the
original quarantined, verify in place, and roll back byte-exactly on failure.
The repair engine never guesses: ambiguous cases generate a candidate and wait
for confirmation, and no action installs an unverified latest or executes
untrusted shell commands. Repair and rollback journals are append-only and
recoverable across crashes.
Security model
- Everything runs as the current user; no root or admin elevation.
- The Supervisor listens only on a local Unix socket (named pipe on Windows);
requests carry a per-install bearer token stored with mode 0600.
- The Web API is loopback-only and never hands the browser the token; rejected requests receive HTTP 403 with
{ ok: false, error: "forbidden: loopback-only" }.
- The launcher and Supervisor never run a shell; DSH argv is relayed verbatim.
- No secrets are written to state, logs or incident records; snapshots redact
credentials and the redacted tier can never restore them.
- The rescue capsule binds only to loopback and never reads the profile home
overlay except during explicit inspection.
- The rescue capsule mirrors the user profile settings and credential files
(settings.yaml / .credentials.yaml / .env and peers, mode 0600, canonical
names only, never backup variants); the manifest records file names and a
content fingerprint only and never holds the secrets themselves; uninstall
removes the mirror per the recorded list.
- Writes are confined to
DSH_DOCTOR_HOME and the package-owned files;
profile mutations happen only through the official dsh plugin command.
- One-click install, upgrade and uninstall only invoke this package's CLI with
argument arrays and never enable a shell; no OS service is registered, so
there is no launchd/systemd/schtasks surface at all.
Known limitations
- A profile started by invoking the real
dsh executable by absolute path
bypasses the launcher; protection covers launcher-started runs, and
bypassed hosts are reported as partially managed.
- Machine-level damage (an unloadable Node binary, an unwritable home, a dead
volume) cannot be repaired automatically; the console shows CLI recovery
instructions instead.
- Snapshots stay machine-local by default; cross-machine restore requires
exported artifacts and a separate credentials vault.
- Windows support is best-effort for junctions, PowerShell 5.1 Unicode and
per-user scheduled-task registration; several internals assume POSIX file
semantics.
Telemetry
The browser half sends one anonymous install heartbeat per UTC day to dsh-market.com: a random localStorage id plus this package's name, nothing else. The server stores only a salted hash of that id, never IP addresses, and exposes aggregate counts only. See docs/telemetry.md for the full contract.