# dsh-undo-savepoint — Undo/rollback system for DSH [![awesome · DSH plugin](https://awesome-dsh-plugin.com/badge.svg)](https://awesome-dsh-plugin.com) > English | [中文](README.md) | [Changelog](CHANGELOG.en.md) **DSH crash-rescue plugin: undo config & plugin-code changes, secret-safe snapshots, one-click SAFE MODE, plus offline CLI/GUI that work even when DSH won't boot.** An undo/rollback system for [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness): **every plugin install, skin switch or settings change is auto-snapshotted; manual saves whenever you want; one-click undo / redo / restore to any version** — plus offline CLI & GUI tools that still work even when DSH fails to boot. ## Preview | v0.3.4 conversation header: iconized Undo / Redo / Snapshots buttons + auto-snapshot status badge (click the badge to open the panel) | |---| | ![header](docs/webui-header.png) | | WebUI snapshot manager (diff / restore / delete / clean-up / export / import / SAFE MODE) | WebUI Settings — own "Snapshots" section (sensitive mode / plugin whitelist / dir pickers) | |---|---| | ![panel](docs/webui-panel.png) | ![settings](docs/webui-settings-section.png) | | Offline GUI (two-row toolbar + SAFE MODE button; works when DSH is down) | Offline settings dialog (sensitive mode / Browse dirs) | |---|---| | ![gui](docs/gui-main.png) | ![guisettings](docs/gui-settings.png) | | SAFE MODE confirmation (enter / exit) | SAFE MODE status notice | |---|---| | ![confirm](docs/safe-mode-confirm.png) | ![done](docs/safe-mode-done.png) | ## Features | Capability | What it does | |---|---| | **Config + plugin-code rollback** | Snapshots cover config files AND user-plugin code trees — any broken edit is undoable (incl. pure code incidents like the whale-kit `yield*` crash); undo / redo / restore-to-any-version from WebUI, chat or offline CLI | | **Secret redaction + local vault** | `.env` / credentials enter snapshots auto-redacted (structure preserved) — exported ZIPs are safe to share; real values live in a local vault, **local rollbacks restore them fully** | | **One-click SAFE MODE** | When DSH cannot boot at all, temporarily disables every user plugin except the undo system so it always boots; auto-snapshots + config backup on entry, one-click exit | | **Crash attribution** | After an abnormal exit, names the concrete last-known-good snapshot with a one-click rollback button — no guessing | | **Safe cross-machine migration** | Restore preflights missing plugins and warns clearly; snapshots export/import as one-click ZIP (see [docs/migration.en.md](docs/migration.en.md)) | | **Offline emergency kit** | CLI + GUI window + one-click desktop shortcut: undo / restore / SAFE MODE / crash banner / rollback log — everything works when DSH is down | > Basic capabilities (keyboard shortcuts, chat commands, auto-cleanup, dual save modes, configurable options, …) are covered below and in the [Changelog](CHANGELOG.en.md). ## Crash rescue quick reference (pick by scenario) | Scenario | Action | |---|---| | Config/plugin mount broken | Chat / WebUI / CLI: `undo` or `restore -Id ` | | Plugin code broken | Same (snapshots include plugin code trees, one-click restore) | | Last run crashed, unsure what to roll back to | WebUI / GUI banner shows the last-known-good snapshot, one-click rollback | | **DSH will not boot at all** | Desktop "DSH Undo Manager" → **SAFE MODE** button (or CLI `safe-mode -Label on`) → restart DSH, it always boots | | Missing plugins after restore (cross-machine) | Preflight warning in the restore report; install first or use safe mode | | "My config suddenly changed" | CLI `recent` / chat `undo_recent` check the rollback log | | Rollback touched plugins/mounts | Report says "restart DSH for it to take effect" | ## What is snapshotted & where The snapshot captures DSH's 6 config files: `cordis.patch.yml`, `package.json`, `cordis.yml`, `pnpm-workspace.yaml` (under the profile) + `settings.yaml`, `.env` (under `~/.dsh`). | Store | Default path (configurable in settings) | Contents | |---|---|---| | Manual store | `\manual\` | Manual snapshots (never auto-pruned) | | Auto store | `\auto\` | Auto snapshots, boot baselines, undo pre-restore snapshots (auto keeps latest 20) | | Legacy store | `\` root | Old flat layout — still read, auto-migrated on startup | > ⚠️ Snapshots contain copies of `.env` etc. which may include secrets — do not share or push them. ## Multi-profile support (v0.3.3) The plugin detects the active DSH profile from the launch arguments (`dsh --profile mine` / `--profile=mine`; `dsh web` falls back to `web`) and works per profile: - **Config directory**: defaults to `~/.dsh/profiles/` (previously hardcoded to `web` — under any other profile snapshots read the wrong files, the watcher missed changes, and restores wrote to the wrong place); - **Snapshot stores**: default to `//{auto,manual}` (per-profile isolation); if the scoped dir does not exist but the old flat store does, the flat store is used so legacy snapshots are never hidden; - **Provenance**: the manifest records a `profile` field and `undo_list` shows the current profile. Offline CLI/GUI cannot see the launch arguments — set the `DSH_UNDO_PROFILE` environment variable or `profileName` in settings (default `web`). Explicit configuration always wins: `profileDir` / `manualDir` / `autoDir` / `profileName` (config or settings). ## Installation Prerequisites: DSH (`@deepseek-ai/dsh`) and Node.js (≥20). **Option A (recommended, ecosystem standard)** — this plugin declares a `dsh.bundle` manifest, so install it with the official plugin command: ```bat dsh plugin --profile web add github:lire1131/dsh-undo-plugin#master ``` Restart DSH after installing. Snapshot directories and options are configurable in Settings. **Option B (local source / pre-release)** — clone and mount manually: 1. **Clone the repository** into a local plugins directory (an ASCII path is safer), e.g. `D:\dsh\plugins\dsh-undo-savepoint`: ```bat git clone https://github.com/lire1131/dsh-undo-plugin.git D:\dsh\plugins\dsh-undo-savepoint ``` 2. **Create a junction** so DSH's module resolver can find the local package by name `dsh-undo-savepoint` (used by both the host plugin and the WebUI client plugin): ```bat mklink /J "\node_modules\dsh-undo-savepoint" "D:\dsh\plugins\dsh-undo-savepoint" ``` > DSH resolves package names by walking up from its own `node_modules`. The default location is `C:\Users\\node_modules` when npm installed into the user directory; if you run DSH from an npx cache, junction into that cache's `node_modules` instead. Check the path in DSH's startup error output or run `npm root -g`. 3. **Mount it in the profile patch layer**: edit `\profiles\web\cordis.patch.yml` and append: ```yaml - insert: - id: dsh-undo-savepoint name: dsh-undo-savepoint ``` 4. **Activate**: saving hot-reloads the host part; refresh the page to see the header buttons and settings rows; restart DSH for full steady state (legacy flat snapshots migrate automatically). > Dependency note: the host plugin loads `@deepseek-ai/dsh-tools` via `createRequire('/package.json')`. If DSH lives elsewhere, set the environment variable `DSH_ROOT=` — no extra package installation needed. ## Where are the external tools? ("I installed it and cannot find it") The external undo tools (GUI window + CLI) are **not placed on the desktop** — they ship inside the plugin install directory: | Install method | Tool location | |---|---| | Method A: `dsh plugin add` | `C:\Users\\.dsh\profiles\web\node_modules\dsh-undo-savepoint\tools\` | | Method B: clone + junction | your clone `...\dsh-undo-savepoint\tools\` | > ⚠️ The install command references the repo name `dsh-undo-plugin`, but the installed folder is named after the **package name `dsh-undo-savepoint`** — searching by "repo name" will not find it. **One-click desktop shortcut (recommended — open the tools straight from the desktop afterwards):** Double-click `tools\make-desktop-shortcut.bat` (it auto-locates the plugin directory) and a **DSH Undo Manager** icon appears on the desktop; or copy the whole block below into a PowerShell window and press Enter (no need to locate any file first): ```powershell $d = @("$env:USERPROFILE\.dsh\profiles\web\node_modules\dsh-undo-savepoint", "$env:USERPROFILE\.dsh\profiles\node_modules\dsh-undo-savepoint", "$env:USERPROFILE\node_modules\dsh-undo-savepoint") | Where-Object { Test-Path (Join-Path $_ 'tools\dsh-undo-savepoint-gui.bat') } | Select-Object -First 1 if ($d) { $w = New-Object -ComObject WScript.Shell $s = $w.CreateShortcut((Join-Path ([Environment]::GetFolderPath('Desktop')) 'DSH Undo Manager.lnk')) $s.TargetPath = Join-Path $d 'tools\dsh-undo-savepoint-gui.bat' $s.WorkingDirectory = Join-Path $d 'tools' $s.Save() Write-Host "Desktop shortcut created: $($s.FullName)" } else { Write-Host 'Plugin directory not found — install it first: dsh plugin --profile web add github:lire1131/dsh-undo-plugin#master' } ``` **Just want to open the tools folder:** ```powershell explorer "$env:USERPROFILE\.dsh\profiles\web\node_modules\dsh-undo-savepoint\tools" ``` After that, double-click the desktop **DSH Undo Manager** icon to open the external tools (they work even when DSH itself fails to boot). ## Usage - **Undo**: header **Undo** button / `Ctrl+Alt+Z` / tell the AI "undo the last step". - **Redo**: **Redo** button / `Ctrl+Alt+Y` (only when nothing changed since the undo). - **Manual save**: "Save" in the panel / tell the AI "save a snapshot" / CLI `snapshot`. - **Restore to a fixed version**: click "Restore to this" on a row in the panel; or tell the AI "restore to "; or CLI `restore -Id `. - **Delete a snapshot**: "Delete" in the panel; or CLI `remove -Id `. - **Custom shortcuts**: Settings → General → Undo/Redo shortcut (click the box then press a combo; Backspace clears). - **Save options**: Settings → General → Snapshot Settings (auto-save toggle, debounce, keep count, two directories; the 📁 button opens the native folder picker). ### Offline tools (works even when DSH won't boot) > GUI language: the manager window follows the system UI language (zh/en); force it with `DSH_UNDO_LANG=zh|en`. From the repository directory: ```powershell # GUI window (recommended): double-click tools\dsh-undo-savepoint-gui.bat, or: powershell -NoProfile -ExecutionPolicy Bypass -File "tools\dsh-undo-savepoint-gui.ps1" # CLI powershell -NoProfile -ExecutionPolicy Bypass -File "tools\dsh-undo-savepoint.ps1" list powershell -NoProfile -ExecutionPolicy Bypass -File "tools\dsh-undo-savepoint.ps1" snapshot -Label "reason" powershell -NoProfile -ExecutionPolicy Bypass -File "tools\dsh-undo-savepoint.ps1" undo powershell -NoProfile -ExecutionPolicy Bypass -File "tools\dsh-undo-savepoint.ps1" redo powershell -NoProfile -ExecutionPolicy Bypass -File "tools\dsh-undo-savepoint.ps1" restore -Id -Force powershell -NoProfile -ExecutionPolicy Bypass -File "tools\dsh-undo-savepoint.ps1" remove -Id powershell -NoProfile -ExecutionPolicy Bypass -File "tools\dsh-undo-savepoint.ps1" prune -KeepAuto 20 # Safe plugin install (auto snapshots before/after; auto-rollback on failure) powershell -NoProfile -ExecutionPolicy Bypass -File "tools\dsh-plugin.ps1" add ``` Typical rescue scenario: **DSH fails to boot with something like `duplicate loader entry id`** → open "DSH Undo Manager", pick the snapshot from before the change → Restore → restart DSH. No reinstall, no lost sessions. ## REST API (backend of the WebUI) | Endpoint | Description | |---|---| | `GET /api/undo/status` | `{canUndo, canRedo, total}` | | `GET /api/undo/list` | Snapshot list (with location: manual/auto/legacy) | | `GET/POST /api/undo/settings` | Read/write save options (auto-save, debounce, keep count, dirs); POST applies immediately | | `POST /api/undo/undo` | Undo the last change | | `POST /api/undo/redo` | Redo the last undo | | `POST /api/undo/restore` | body `{id}` — restore to a fixed version | | `POST /api/undo/remove` | body `{id}` — delete a snapshot | | `POST /api/undo/snapshot` | body `{reason}` — manual save | | `POST /api/undo/pick-dir` | Open the native folder picker, return the chosen path | ## Design notes - **Undo semantics**: auto snapshots are taken *after* a change, so "restoring the newest snapshot" would be a no-op; real undo restores the newest snapshot whose state **differs** from the current one. When everything matches, a clear "nothing to undo" message is shown instead of pretending. - **Undo can never undo itself**: after restoring `cordis.patch.yml`, the plugin re-ensures its own mount entry automatically. - **Auto-archiving never sabotages undo**: the watcher records content hashes of what a restore wrote, so the restore's own file changes are not auto-snapshotted (which would block redo); real changes are snapshotted as usual. - **Format parity**: the Node plugin and the PowerShell tools share the same snapshot stores and manifest format; compatible with both Windows PowerShell 5.1 and PowerShell 7. ## Development - Dependency resolution: the host plugin loads `@deepseek-ai/dsh-tools` via `createRequire(/package.json)` (override with `DSH_ROOT`), no in-repo dependencies required. - Tests (no DSH needed; run in the repository directory): ```bat node tools\smoke-test.mjs :: 29 logic tests (snapshot/undo/redo/store split/no-change hint) node tools\e2e-watch.mjs :: 6 real-timing regressions (auto-save/undo-no-harm/redo) ```