Version management for dsh: inspect the installed version against every npm channel or version line, install or downgrade in one click, stream the install log live (rollback-snapshot copy progress, npm extraction progress, elapsed time), roll back instantly from local snapshots, and automate the cycle with a policy engine (silent auto-update, execution windows, daily checks). Never stops npm mid-install — a wedged run is only stopped after an hour, and a half-committed global tree is detected and repaired from the pre-install snapshot automatically. Confirms before installing a found update, auto-restarts the host after install, and falls back to a mirror when the primary registry is unreachable.
Install
# from npm (prebuilt)
dsh plugin --profile web add dsh-version-update
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:SuCriss/dsh-version-update
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
English | 中文
The "Version Update" settings menu for the DeepSeek Harness Web GUI — fully rewritten for v1.0. Beyond inspecting and installing any published @deepseek-ai/dsh version, this generation turns updating into a manageable version policy: silent auto-update, execution windows, a daily scheduled check, dist-tag and version-line tracking, and offline rollback from local snapshots — no npm, no network, and a rollback can itself be rolled back.
Features
Version management (kept, enhanced)
- A top-level "Version Update" settings section showing the installed version and install directory.
- Lists npm dist-tag channels (
latest/next) plus every published version; install or downgrade any of them in one click (downgrade wording everywhere the direction matters). - The confirm card reads the target version's GitHub release notes (
dsh-v*tags first) with a link to the full text; failures degrade silently and never block an install. - One click runs
npm install -g @deepseek-ai/dsh@<exact version>in the background. Only exact versions are accepted and npm is spawned without a shell, so registry text never reaches a command line. - A progress bar, not an endless log. The panel shows the phase (preparing / snapshot / download / extract / done), a percentage or the bytes seen so far, elapsed time and how long it has been stalled. A phase with no denominator is reported as unmeasurable and shows bytes only — no percentage is invented, and a phase with no signal at all never claims to be stalled. npm's own output moves behind "Show detailed log", collapsed by default.
- The install source is a choice, and a slow one is replaceable. The confirm card picks Auto / Official / Taobao mirror.
autouses whichever registry actually served the version read (a mirror that answered is remembered). A run that stops moving says so — but npm is never killed mid-flight (stopping reify leaves a half-committed tree); once it settles, "Retry with the Taobao mirror" re-runs the same version, which costs only the download. - A failed install does not damage the version you were running. A snapshot is mandatory before npm may touch the tree (
requireSnapshot, on by default: if the snapshot cannot be written, npm is never spawned), the failure path repairs from it, and the repair also checks that the launcherlib/bin.jsis back. Rolling back needs neither npm nor the network. - After a successful install nothing restarts and no dialog opens: a "Restart now" button appears on the panel, and only that click hands the port over. The handoff is three steps (payload file → detached relaunch helper waits for port release → replacement starts with identical argv) → the page's watchdog reloads once the replacement answers.
Snapshot rollback (new)
- Every install first snapshots the current version to
~/.dsh-version-update/snapshots/<version>/; a failed snapshot is logged, never blocking. - Rolling back = copying a snapshot over the installation: no npm, no network. Restore renames the live tree aside first and moves it back if the copy fails midway. The cost is a full tree copy: 26,513 files / 551 MB measured ~88 s, so the panel names the version being restored and stretches that one request's timeout from 15 s to 10 minutes — otherwise a success still in progress gets reported as a failure.
- The version being replaced is adopted as a snapshot (a same-volume rename, never a copy), so a rollback can itself be rolled back; if that version already holds an intact snapshot the adoption is skipped rather than duplicating it.
- The "Snapshots & rollback" card lists every usable snapshot with one-click restore through the same confirm + restart flow.
- Snapshots are pruned automatically (5 retained by default); damaged entries are removed first and marked unusable in the list.
- Optional
recoverOnFailedRestart: when a restarted host never becomes reachable within 60 seconds, the relaunch helper restores the previous version from its snapshot — again without npm or network.
Policy engine (new)
Policy persists at ~/.dsh-version-update/policy.json and hot-applies from the panel:
| Field | Values | Meaning |
|---|---|---|
mode |
off / notify / auto |
On discovery: display only / highlight / install silently |
track |
{kind:'tag', tag} / {kind:'line', range} / {kind:'pin'} |
Follow a dist-tag (custom tags welcome) / follow a ^x.y.z or ~x.y.z line (stable only) / pin |
window |
null or {start,end} (HH:MM) |
Execution window for auto; midnight wrap supported (22:00–06:00), equal bounds mean all day; findings outside the window park until it opens |
checkAt |
null or HH:MM |
Daily scheduled check; null disables the daily timer |
The scheduler is three boring timers over pure decisions (resolveTarget / inWindow), so the whole policy is exhaustively testable. An automatic check has three triggers: 60 s after the host boots (auto mode only, once per boot), the configured daily checkAt moment, and opening the settings panel. The boot check is what makes auto reliable — checkAt names a single moment, and a machine that is off at that moment misses the whole day, whereas the boot check guarantees one check per start. Discoveries update the panel's status line; auto mode parks out-of-window findings instead of ever installing outside the window.
- Pending auto-install management: the panel shows the waiting target version and when it was queued, with a cancel action that disarms its window wake / busy retry. It does not stop an install already running or change the policy or daily check; later automatic checks may schedule an update again.
Removed
- The v0.x agent-announcement machinery (
announceToAgent, prompt injection, pending notices) is gone entirely — this plugin is now a purely user-facing panel facility that injects nothing into the model's context.
Why a restart, not just a reload
npm install -g (and snapshot restore) overwrite exactly the package directory the running dsh web serves frontend assets from:
- Open pages hold
/assets/index-<hash>.jsURLs that no longer exist on disk; the SPA fallback answers HTML and browser module parsing fails. - The bundle watcher triggers hot-swaps that tear down theme tokens and possibly the React renderer itself.
The host therefore records the booted running version against the on-disk installed; they differ exactly while a completed task awaits a restart. needsRestart is deliberately wider than stale: a finished task proves this process executes superseded code even when versions cannot be compared. The restart overlay is bare DOM + literal colors (theme via the prefers-color-scheme media query) so it stays legible after such a teardown.
Composition
Three halves in one package:
- Host half (
lib/, exports.) mounts the loopback-only route family:GET /check— read-only: local facts + registry channels/versions + task view + ambient (last check verdict, next scheduled run, parked target, recent activity); degrades topublishedErrorwhen the registry is unreachable. It refreshes what the panel displays and never installs or parks anything — those two are the explicit paths belowPOST /check/run,POST /check/auto— explicit checks that differ in exactly one way:/check/runobserves (the panel's check button, the refresh after a cancellation), while/check/autolets the policy act and may install silently (called once when the panel opens). The latter has to be a POST: a GET that can replace the whole installation tree is reachable by a prefetch, a second tab, or any process that walks the loopback ports. All three refreshlastCheckPOST /update—{version, source?}starts one install (trigger always recorded as manual;sourceaccepts only the identifiersauto/official/mirrorand never a URL — registry addresses stay in host config, anything else is a 400)GET /status— task view (running/stale/needsRestart/restartable) + ambientPOST /restart— three-step handoff, called by the panel's "Restart now" buttonGET /notes?version=— GitHub release notes (mounted when enabled and a repo is known)POST /pending/cancel— cancel waiting automatic work and its wake / retry timer, keeping the daily check; returns{result:{cancelled:true}}. Loopback-only and POST-only (405 otherwise); omitted in bare route compositions without a cancellation operation.GET|POST /policy— read / patch the policy; every rejected field is named in a 400GET /snapshots,POST /restore,POST /snapshots/delete— list, restore, and discard one snapshot; a restore contends for the same machine-wide lock an install holds, so a busy tree in this host or another answers 409. Deleting takes that lock too without ever writing the tree (a failed install looks for exactly that directory), answers with the surviving list, and arms on the first click in the panel to delete on the second.
- Browser half (
lib/client.js, exports./client): dictionaries, the settings page (status / policy form / versions / install progress bar and source picker / collapsible log / snapshots / history / installation-tree health), nav glyph marker, restart watchdog. The install card exists only while there is a task, and the log is rendered only once asked for. - Detached relaunch helper (
lib/relaunch.js): waits for pid exit + port release, starts the replacement verbatim; optionally stays alive to snapshot-recover an unreachable replacement.
Install
dsh plugin --profile web add dsh-version-update
or from source:
dsh plugin --profile web add github:SuCriss/dsh-version-update
Restart dsh web once so the host half mounts; until then the panel says so explicitly instead of showing a mysterious HTTP status.
Configuration (cordis entry config)
registry(defaulthttps://registry.npmjs.org) — absolute http(s) URL used for BOTH reads and installs. When that URL fails at the network layer the read falls through to a built-in mirror and the host remembers it: the install then goes to the registry the versions were actually read from, not back to the address that just timed out.mirrorRegistry(defaulthttps://registry.npmmirror.com) — the fallback source address. Used by the panel's "Taobao mirror" choice and by "Retry with the Taobao mirror";autois unaffected.requireSnapshot(default true) — an install must have written its snapshot before npm may run. With it on, a failed snapshot aborts the run outright (FatalPreparationError) and npm is never spawned, because an install with no rollback point is exactly the kind that damages a working version. Turn it off for the old behaviour: log the failure and install anyway.allowRestart(default true) — false removes the restart route.releaseNotes(default true).snapshotKeep(default 5, clamped 1–10).snapshotMaxBytes(default 0 = unlimited) — total snapshot payload-byte quota, excluding metadata and symlink targets. After successful creation, damaged/count-expired snapshots are pruned first, then the oldest usable snapshots are removed until the store fits. The just-created snapshot always survives, even if it alone exceeds the quota. Legacy snapshots without stored sizes are measured on demand.recoverOnFailedRestart(default false).dataDir(default empty =~/.dsh-version-update) — relocates policy/history/snapshots for portable setups.
Runtime behavior (mode, tracking, window, schedule) lives in the policy file edited from the panel, not entry config.
Management and diagnostics additions
Activity timeline: retains the latest 200 install outcomes, snapshot restore and tree repair events in memory; refreshed on checks, settlement, or demand. Loopback-only
GET /api/dsh-version-update/operations?since=Nreturns{result:{events,cursor}}after that monotonic sequence. The log resets on host reload; installs record settlements only, never invented starts.Install preflight: the install confirmation card uses loopback-only
GET /api/dsh-version-update/preflightto check npm, install-parent writes, free disk space and snapshot storage (dataDiraware). Failures become advisory warnings, unknown space isnull; no npm execution or network calls.New snapshots show payload size and support confirmed deletion. Deletion is refused while an install is running — the snapshot taken moments ago is the runner's own way out of a failure — and the discard itself only renames the directory, so the panel never waits on the unlink.
New snapshots record file paths, sizes and symlink targets, and validation comes in two tiers: listing and pruning read metadata only (
meta.jsonis self-consistent and the version it declares matches the directory name), while the two unrecoverable paths — restoring, and reusing an existing snapshot — do the full inventory comparison, refusing on any missing entry or changed size. This is not content hashing. Legacy snapshots remain metadata-validated, explicitly labelled, and may have unknown size.Why not one tier everywhere: a 26,513-file inventory measures 1.6 s, and the list is read on every panel check — running that synchronously parks the entire host event loop. A snapshot only reaches a version name by being renamed after its copy completes, so torn copies live under
.tmp-*and never appear in the list; metadata-level validation is sufficient there.The check button uses
POST /check/runto record a policy-aware verdict. Manual checks never install or create parked automatic jobs, even in auto mode. The only reads that let the policy act arePOST /check/auto(once, when the panel opens) and the scheduler's own timers — it used to ride onGET /check, which gave a read endpoint an install side effect, i.e. exactly backwards.Restart diagnostics read the fixed state-directory
restart.logon demand, limited to a 16 KiB / 100-line tail with common credential patterns redacted. Review local paths and other details before sharing.Missing npm and EACCES/EPERM failures offer terminal, prefix, cache-permission and file-lock guidance without automatic elevation.
New loopback-only endpoints under
/api/dsh-version-update:POST /snapshots/delete({version}),POST /check/run,POST /check/auto,GET /restart/diagnostics.Snapshot deletion now renames to a
.trash-*tombstone and unlinks in the background. A recursive delete of a 200 MB snapshot measures in the seconds on Windows — long enough to freeze the whole host and to outlive the panel's 15 s request timeout, which is exactly why the button looked dead. The version leaves the list immediately and the bytes are reclaimed off the event loop;.tmp-*/.trash-*directories left by a killed process are swept at host start.
Development
npm test # node:test — 242 cases across protocol/domain/routes/composition/browser controller/relaunch helper
npm run typecheck # tsc --checkJs strict — type safety without a build step
The suite deliberately covers the contracts most likely to rot: agreement between the browser semver mirror and the host ranking, per-field fallback in policy normalization, snapshot metadata validation and prune ordering, process-wide single-slot exclusivity across fiber reloads, the countdown/watchdog chain under mocked clocks, machine-lock ownership (a release may only remove the record it still holds), the invariant that a parked auto update always has a next wake armed, the relaunch helper's two independent budgets and its dialable probe address, and the rule that a degraded read may never be presented as a conclusion.
This round's contracts were chosen the same way — by asking which step degrades quietly: the progress model may not invent numbers (a phase with no denominator must be indeterminate, a phase with no signal may not report itself stalled), source accepts identifiers only and answers 400 otherwise (no URL may enter from the browser), npm must not be spawned even once when the snapshot cannot be written, and a readable manifest with a missing launcher must still be repaired from the snapshot.
The fix round added contracts on the same principle: the byte-level check may not creep back onto the hot path (corrupt a snapshot's bytes and the list still calls it usable, while a restore must refuse it and leave the live tree untouched), a restore must leave a way back (the replaced version is adopted as a snapshot so a rollback can be rolled back; an existing intact snapshot is reused rather than duplicated; the adoption's pruning may not evict the restore's own target), a snapshot only the byte check can reject may not block a repair (candidates are tried newest-first, and a successful repair must not fold the failed candidates into its health verdict), and a refused operation may not be swallowed (an error under status: 'ready' has to reach the panel too).
This round is about which direction a read may act in: a GET may not carry an install side effect (GET /check touches neither the auto decision nor the parked set), the one read that lets the policy act must be a POST (POST /check/auto), only the panel's own mount is allowed to act (it calls check({ act: true }), and the render is asserted to hit /check/auto), and the refresh after a rollback may only observe (otherwise, under mode: 'auto', a restore reinstalls itself within seconds). Each of these was verified by reverting the fix and watching the assertion fail — none of them is a vacuous assertion.
Links
More in this category
yjh051108/dsh-routing-suite★ 6999
One repository, three parts: a runtime injector for DSH plugin packages (inject, hot-reload, unload, promote a dev staging tool to the front, route self-heal, plus a settings-page plugin manager that lists, unloads and drags folders in to internalize), a task-aware reasoning-mode router agent preset (router-standard / router-spec / router-react), and a graded two-level task protocol whose six tools (commit_star, lock_stage, revise_do, edit_plan, mark_task, redteam_verdict) pin task state to disk. The injector implementation ships in-tree, so the install carries its own behaviour rather than a dependency list.
strukto-ai/mirage#dsh★ 3674
Swaps the filesystem and bash providers for a mirage virtual workspace: file tools and shell commands run over mounted resources (RAM, S3, Redis, Slack, Gmail, Notion, Postgres) instead of the host disk, with per-mount read/write/exec modes, per-command sandbox routing (monty, pyodide, quickjs in process; docker, e2b, daytona remote), and installed CLIs (git, gh, slack, linear, ntn, gws, or one you register) as head words in the virtual terminal.
hust-open-atom-club/oh-dsh★ 323
Community distribution: TUI, desktop, and Web UI as one bundle with layered installation.
weijiafu14/pi2dsh★ 212
Pi Host ABI compatibility engine: after one install, unmodified Pi extensions from npm mount as native DSH plugins with `dsh plugin add <pi-package>`. Verified end to end on stock DSH with pi-mcp-adapter (full MCP manager: OAuth, resources, prompts, MCP Apps, elicitation, sampling), @tintinweb/pi-subagents, pi-code, pi-hermes-memory and pi-background-tasks; `pi2dsh inspect` reports a package's compatibility before installing.
Fishquito7/dsh-skill-mcp-panel★ 178
Manages DSH skills and MCP servers from the web settings: skill cards with hot enable/disable, workspace scopes, groups, batch migration and drag-and-drop import, plus stdio/HTTP MCP CRUD with connection tests, secret redaction and the unified dsh-panel CLI.
lire1131/dsh-undo-savepoint★ 175
Undo/redo & rollback system for DSH: every config change is auto-snapshotted; undo/redo/restore to any version from the WebUI or the offline CLI/GUI tools (works even when DSH fails to boot).
Community comments
Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.