@dsh-external/dsh-plugin-updater
A plugin-update center for DSH: automatically tracks the versions of every plugin in the environment, compares them against the latest releases on the npm registry / GitHub, and presents outdated plugins as cards (UI modeled on the CC Switch version-update component). One click runs 「download → integrity check → replace → hot restart」with automatic rollback on failure; a configurable periodic auto-check raises in-app notifications (top-right bell) when new updates appear.
> Open source: github.com/Alyosha28/dsh-plugin-updater · Topic: dsh-plugin
Feature overview
| Feature | Description |
|---|---|
| Full version tracking | Scans every Cordis loader entry (178+), reads each plugin's installed version from its package.json |
| Latest-version comparison | npm registry first; GitHub-only packages resolve through releases/tags/default branch (auto-falls back to git ls-remote on API rate limits — no quota consumed) |
| One-click update | Download (with progress) → integrity check (npm sha512/sha1; GitHub gzip CRC + name/version re-verification) → backup → replace → hot restart |
| Failure rollback | Full backup before replacement; any failed step restores the old files and rebuilds the previous fiber generation |
| Hot restart | Purge module cache → re-import → rebuild fiber through the registry (production-verified recipe), including client-module rev rescan |
| Periodic auto-check | Default 6 h (configurable 1 h/6 h/12 h/24 h/7 d); new updates are written to in-app notifications (bell badge) |
| Proactive notifications | Floating bell at the top-right: unread-notification + outdated-plugin count badges, popover list, mark-all-read |
| AI tool | dsh_plugin_update (check/status/update): the agent can check and update plugins on your behalf |
| Safety rails | DSH-managed core packages are skipped by default (allowCoreUpdates opts in); source-linked (link/junction) installs are never replaced in place; self-update is refused; mutating API routes are same-origin checked |
UI (modeled on CC Switch)
- Settings → Plugin Updates page: a card grid — each card shows the plugin
icon + name + status badge (yellow 「Update」/ green 「Up to date」/ red 「Check failed」) + current/latest version rows + source tags (npm/GitHub/DSH-managed/source-linked).
- When updates exist, a brand-tinted banner 「N updatable plugin(s) found」
appears at the top, and the toolbar gains an Update all (N) button; each card carries a prominent Update button at the bottom-right.
- While updating: the button becomes an inline progress bar (stage + percent +
detail); on completion the card turns green and a success/failure toast slides in at the top.
- Cards are sorted so actionable updates come first, then blocked updates,
then plugins with problems (check failures / unresolvable), then the rest.
- The bottom of the page holds auto-check settings: interval, notify toggle,
core-package toggle.
Update pipeline (per plugin)
resolveInstalled(profile package.json)
→ resolve latest (npm → GitHub releases/tags → default-branch package.json)
→ download tarball (whole-transfer timeout + progress callbacks)
→ verify: npm = sha512/sha1 integrity; GitHub = gzip CRC + post-extract name/version check
→ extract with system tar (500 MB bound)
→ backup old directory (nested node_modules preserved)
→ replace files (npm install on dependency changes; npm rebuild on lifecycle scripts)
→ re-verify the on-disk version
→ hot restart (purge loadCache → import → rebuild fiber via registry, old generation restored on failure)
→ any failed step: restore backup + rebuild old generation, detail recorded in the task stepArchitecture
src/
├── index.ts host entry: Config schema + HTTP API routes + dsh_plugin_update tool
├── service.ts PluginUpdaterService: check/update/config/notify/timer/hot-reload
├── registry.ts npm/GitHub latest-version resolution + install location + repo derivation
├── updater.ts download/verify/extract/backup/replace/npm subprocesses
├── notify.ts in-app notification store (~/.dsh/storages/dsh-plugin-updater/)
├── semver.ts semver-lite comparison (v prefix/prerelease/build metadata)
├── types.ts shared data contracts
└── client/ Web UI (tsdown → lib/client.js, window.__ModuleLoader__ wrapper)
├── index.ts settings.section registration + locale + bell mount
├── section.tsx CC Switch style update dashboard
├── badge.tsx top-right notification bell (auto-offsets from the memory-evolve bell)
├── api.ts /dsh-plugin-updater/api/* client
└── styles.ts DSW design tokens (--dsw-alias-*), auto light/darkHTTP API (same-origin):
GET /dsh-plugin-updater/api/state full state (check results/tasks/notifications/config)
POST /dsh-plugin-updater/api/check trigger a check
POST /dsh-plugin-updater/api/update start an update task {names:[...]} → {taskId}
POST /dsh-plugin-updater/api/config update config {patch:{...}}
GET /dsh-plugin-updater/api/notifications notification list
POST /dsh-plugin-updater/api/notifications/read mark all readConfiguration
| Field | Default | Description |
|---|---|---|
| checkIntervalMs | 21600000 (6 h) | Auto-check interval (≥60 s) |
| notifyNewUpdates | true | Write an in-app notification when new updates appear |
| allowCoreUpdates | false | Allow updating DSH-managed core packages (dangerous) |
| profileDir | auto-detected | The updatable scope = that profile's node_modules |
| githubToken | empty | GitHub token (env GITHUB_TOKEN also works and is never persisted) |
| fetchTimeoutMs | 30000 | Per-request timeout |
Config persists to <DSH_HOME>/storages/dsh-plugin-updater/config.json; notifications and notified-version records live in the same directory.
Build & install
# Build (Windows-native): junction deps → tsc (host) → tsdown (client)
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\build.ps1
# or bash scripts/build.sh (delegates to build.ps1 under WSL)
# Install into a profile (local DSH)
# Hot-assemble via DSH's dev_install_package (profile package.json + bundles + junction + loader.create)Verified (tested end to end)
- ✅ Full scan of 178 loader entries with no npm/GitHub false positives
(including repos whose tag names don't match their package.json version scheme)
- ✅ End-to-end update: 0.0.0-beta.1 → 0.0.1 (GitHub codeload source;
download/verify/extract/replace/re-verify pipeline)
- ✅ Hot restart of an active plugin (dsh-vision fiber [active] and its tool
intact after the update)
- ✅ Auto-check → in-app notification (bell badge count/popover/mark-all-read)
- ✅ UI button update flow (outdated → progress → up-to-date + toast)
- ✅ Automatic
git ls-remotefallback under GitHub API rate limits;
cross-site requests 403; concurrent updates 409
Known limitations
- Repos without tags/releases fall back to the default branch's package.json
version (the branch is treated as the release channel).
- Updates that change dependencies trigger
npm install --no-savein the
profile directory (best effort; on network failure the step errors and the files are rolled back).
- The updater refuses to update itself (self-destruction guard); update it via
DSH dev tooling or manually.