# dsh-auto-update ![License](https://img.shields.io/badge/license-MIT-blue.svg) ![Node](https://img.shields.io/badge/node-%3E%3D20-green?logo=node.js) ![GitHub](https://img.shields.io/badge/GitHub-a1113622001%2Fdsh--auto--update-181717?logo=github) A DeepSeek Harness (cordis) plugin that **makes the harness update itself**. It periodically checks npm for a newer version of `@deepseek-ai/dsh` than the currently installed one; when an update is found it downloads the tarball and stages it; when the harness process exits, a detached helper process runs `npm install -g` to replace the global installation with the new version; and optionally it can relaunch `dsh` with the original startup arguments. By default the web panel shows a small "Check for updates" pill in the bottom-right corner (click to expand the full panel), which displays version status and offers "Check for updates", "Update and restart", and "Cancel staged update". ## How it works 1. **Install discovery** — derives the global install root from `process.argv[1]` (the npm shim launches `…/node_modules/@deepseek-ai/dsh/lib/bin.js`); can also be set explicitly via `config.installRoot`. If the harness is running from source (`pnpm dsh`), auto-update is unavailable and logs a note. 2. **Version check** — calls `npm view @deepseek-ai/dsh@ version --json`, reusing your configured registry / mirror / proxy. Includes a minimal built-in semver comparison (handles prereleases such as `1.2.3-rc.1`). 3. **Staging** — `npm pack` downloads the exact version's tarball into `$DSH_HOME/updates/` and writes `pending-update.json` (with from/to, install root, process pid, whether to relaunch, etc.). This step never touches the running installation. 4. **Applying** — on the harness's `process.on('exit')` it detaches `bin/apply-update.js`: it waits for the old process to actually exit (on Windows a running process locks native modules such as sharp/koffi/node-pty, so installation must happen after exit) → `npm install -g ` → verifies the installed version → records the result → optionally relaunches `dsh `. ## Installation Install the plugin in your profile directory (same as `dsh-session-stats-panel`): ```sh # Local path install example (Windows / Linux / macOS) corepack pnpm --dir "%USERPROFILE%\.dsh\profiles\web" add "C:\path\to\plugins\dsh-auto-update" # Or Linux / macOS: # corepack pnpm --dir "$HOME/.dsh/profiles/web" add "/path/to/plugins/dsh-auto-update" ``` Or, install directly from GitHub using the bundled manifest: ```sh dsh plugin --profile web add github:a1113622001/dsh-auto-update ``` > This plugin is distributed through its GitHub repository only and is **not > published to the npm registry**, so `dsh plugin add dsh-auto-update` (without > the `github:` prefix) fails with a missing-package error. > When installing manually, you must register the [`cordis.patch.yml`](cordis.patch.yml) > bundle at the repo root with the harness so the plugin starts with it. > `dsh plugin add` already carries that bundle, so no manual patching is needed — > the YAML block below is exactly the content of `cordis.patch.yml`. Then append this to `%USERPROFILE%\.dsh\profiles\web\cordis.patch.yml`: ```yaml # dsh-auto-update: lets the harness update itself (check npm → stage → apply on exit) - insert: - id: auto-update name: 'dsh-auto-update' config: distTag: latest checkOnStart: true checkIntervalMinutes: 240 autoStage: true applyOnExit: true relaunch: false ``` After saving, the running harness hot-reloads the host plugin (`cordis.patch.yml` is watched); refresh the page once and the "Check for updates" pill appears in the bottom-right corner (collapsed by default; click to expand the panel). ## Configuration | Field | Default | Description | |---|---|---| | `distTag` | `latest` | npm dist-tag to track (e.g. `latest` / `next`) | | `registry` | `""` | npm registry override; empty uses your configured registry | | `checkOnStart` | `true` | check once at startup | | `checkIntervalMinutes` | `240` | periodic check interval (minutes); `0` disables periodic checks | | `autoStage` | `true` | download and stage automatically once an update is found | | `applyOnExit` | `true` | run the detached apply process when the harness exits | | `relaunch` | `false` | relaunch `dsh` with the original args after applying ("Update and restart" in the panel always relaunches) | | `installRoot` | `""` | explicitly specify the harness install root (used when auto-discovery fails) | | `updateDir` | `""` | updates directory; defaults to `$DSH_HOME/updates` (`C:\Users\\.dsh\updates`) | ## Behavior details - **Safe defaults**: by default it only "stages + applies on exit" — it never kills the process mid-session. For a fully automatic loop set `relaunch` to `true`, or click "Update and restart" in the panel. - **Panel buttons**: `/auto-update` is a loopback-only HTTP channel mounted on the harness web server; the browser calls `status` / `check` / `stage` / `cancel` / `applyRestart` via `POST /auto-update/`. It is a plain `webServer` prefix route, not a `connection.rpc` channel — on dsh ≥ 0.1.5 the latter throws `cannot get property "webServer" without inject` from inside the connection service and the route silently never mounts. - **Manual trigger**: `applyRestart` forces a check first, stages if needed, then writes `relaunch: true` to the manifest; ~0.8s after responding it exits the process, and the exit hook starts the apply process and relaunches. - **Crash tolerance**: if the process is force-killed (no `exit` event), the staged manifest is preserved; on next start the plugin recovers it and re-checks, then applies as usual. - **Update history**: `$DSH_HOME/updates/update-history.jsonl` records the outcome of each apply; `update-result.json` holds the most recent result. ## Verification / manual testing ```sh # 1) Plugin syntax node --check lib/index.js node --check lib/updater.js node --check bin/apply-update.js # 2) Panel channel mounted (harness running; open the UI once to get the cookie) curl -s -X POST http://127.0.0.1:3080/auto-update/status -H "content-type: application/json" -d '{"type":"client-request","rpcId":"t1","method":"status","payload":{}}' # 3) Force a check curl -s -X POST http://127.0.0.1:3080/auto-update/check -H "content-type: application/json" -d '{"type":"client-request","rpcId":"t2","method":"check","payload":{}}' ``` ## Known limitations - Only supports harnesses installed as a **global npm package** (the `dsh` CLI in `%APPDATA%\npm`). Source runs (`pnpm dsh` / a dev checkout) are not updated. - Applying happens **after exit**: if the process never exits gracefully (e.g. a hard kill or power loss), the update is deferred to the next exit. - On Windows, native modules (sharp/koffi/node-pty) can still hold file handles open after the process exits, occasionally causing `EBUSY` during `npm install -g`. The helper waits until the recorded process has truly exited, then retries with backoff, so a single lingering handle does not fail the update. - `npm install -g` requires write access to the npm global directory and cache (normal user permissions are enough). - If the plugin source directory is moved away, the `helper` path recorded in a staged manifest becomes stale; re-checking re-stages a fresh manifest.