# dsh-turn-rewind

> Provides per-turn project file rollback for DSH:

## Metadata

- Author: [@Anionex](https://github.com/Anionex)
- Repo: <https://github.com/Anionex/dsh-turn-rewind.git>
- GitHub: [Anionex/dsh-turn-rewind](https://github.com/Anionex/dsh-turn-rewind)
- Stars: 93
- Language: JavaScript
- License: [BSD-3-Clause](https://spdx.org/licenses/BSD-3-Clause.html)
- Topics: `agent-rewind`, `cordis-plugin`, `deepseek-harness`, `dsh`, `dsh-plugin`, `marisa-plugin`, `restore-point`, `turn-rewind`, `workspace-safety`
- Forks: 6
- Open Issues: 4
- Last push: 2026-08-16T17:17:17.000Z
- Added: 2026-08-13T00:00:00.000Z

## Install

```bash
dsh plugin --profile web add github:Anionex/dsh-turn-rewind
```

## Wiki

## One-line Summary
Adds a "Restore to state before sending this message" button to each user message in DeepSeek Harness: clicking shows a list of affected files, confirming precisely restores project files, and optionally opens a new conversation continuing from that message.

## Core Capabilities
- In Web Profile, add an icon-style "Rewind" button after the timestamp and native copy button for each direct user message, with tooltip "Restore to state before sending this message"
- At startup, via `agent/pre-step` hook, automatically capture a project file snapshot before the Agent starts processing each round of user messages, serving as a hidden restore point for that turn; capture failure only prevents that message from being restored, won't block the current user request
- Popup supports two restore modes: default "Restore files and continue from here" (auto-create new session and fill original message text into input box) or "Restore files only" (current conversation remains unchanged)
- Before each execution, first create a rescue snapshot, then do hash verification after execution; failure automatically rolls back via rescue snapshot; when DSH crashes mid-operation, on startup automatically mark unfinished operation logs as "interrupted" and retain corresponding rescue points for manual recovery
- Through `ctx.changeLedger` public service, allow other Cordis plugins to inject and reuse the same set of snapshot, planning, restoration, and reconciliation APIs
- Git operations, HEAD/branch state, and selected files are double-validated between restoration plan generation and execution; any related changes invalidate the plan, no silent overwrites

## Technical Implementation
- **Language**: TypeScript (ESM, strict mode)
- **Key Dependencies**: `@deepseek-ai/cordis` (host plugin framework), `react` + `@deepseek-ai/dsh-client-ui-primitives` (Web UI), `node:fs` + `node:crypto` (snapshots and content-addressed storage), `git` CLI (read repository facts via subprocess)
- **Architecture Pattern**: Standalone DSH Profile Bundle — `package.json#dsh.bundle.patch` points to `cordis.patch.yml`, mounts `turn-rewind` profile layer at host startup; server registers Cordis service via `ctx.provide('changeLedger', ...)`, client registers to runtime + conversation UI via `dsh.client.inject`
- **Entry Files**: `src/index.ts` (server Cordis service registration), `src/rewind-host.ts` (HTTP interface + turn checkpoint coordinator), `src/client/index.tsx` (Web popup and button)

## Use Cases
- User has Agent or external tools modify project files, then realizes the direction is wrong and wants to undo, keeping only the workspace state before a certain message
- Want to revert project state to before a certain conversation turn, while opening another branch to continue discussion, and fully preserve the original conversation
- Team runs long sessions on DSH, concerned about Agent accidentally modifying files, tool execution crashing, or accidentally modifying code themselves, needing ability to precisely revert workspace at any time without losing conversation

## Prerequisites & Compatibility
| Dependency | Minimum Version | Description |
|---|---|---|
| DSH Host (Cordis) | `@deepseek-ai/cordis ^4.0.1` | `package.json` only declares Cordis peer dependency, specific DSH version not specified |
| Node.js | `^22.19.0 || >=24.0.0` | `engines` field requirement |
| `git` command | Any mainstream version | Read repository facts via subprocess like `git ls-files / rev-parse / config` |
| Platform | Cross-platform (macOS / Windows / Linux) | Uses Node `node:fs`, `node:os`, `git` CLI; no platform-specific code |
| Native Modules | None | Only depends on Node built-in modules |

> Package declares `dsh.bundle.patch`, automatically loads as profile layer after installation; this plugin has no native module dependencies.

## Installation

```bash
dsh plugin --profile web add github:Anionex/dsh-turn-rewind
```

## Configuration Options
| Config | Type | Description | Default |
|---|---|---|---|
| `storageDir` | string | Directory for persistent restore points, logs, blobs; must not overlap with managed worktree | `$DSH_HOME/change-ledger/v1`, falls back to `~/.dsh/change-ledger/v1` if `DSH_HOME` not set |
| `maxRestorePoints` | positive integer | Maximum number of user + rescue restore points per workspace | `50` |
| `maxTurnCheckpointsPerSession` | positive integer | Maximum number of auto turn checkpoints per session; only cleans up its own oldest, user/rescue points are never silently deleted | `30` |
| `maxFiles` | positive integer | Maximum files covered by a single restore point, fails directly if exceeded | `20000` |
| `maxFileBytes` | byte count | Maximum allowed size for single file, fails directly if exceeded | `16777216` (16 MiB) |
| `maxSnapshotBytes` | byte count | Total cumulative byte limit for all regular files in a restore point | `536870912` (512 MiB) |
| `planTtlMs` | millisecond | Validity period after popup generates restore plan; expires and becomes invalid | `900000` (15 minutes) |
| `staleLockMs` | millisecond | Threshold for "owner process dead but lock file still exists" determination for locks, exceeded before takeover allowed | `30000` (30 seconds) |

> All capacity and user restore point limits use fail loud strategy, exceeding limits throws error instead of auto-cleanup.  
> Override in profile's patch layer by `id: turn-rewind`, e.g.: `config: { storageDir: "~/.dsh/change-ledger/v1", maxRestorePoints: 50, ... }`.

## FAQ

**Q: Does it need additional configuration after installation?**

A: No. Package already declares `dsh.bundle.patch`, `cordis.patch.yml` mounts directly, takes effect after installation; storage directory has default value, after installation restart profile to see rewind icon under each user message.

**Q: Will restore affect Git's branches, HEAD, index, stash, commit?**

A: No. Security contract explicitly states only restore workspace files, do not modify Git index, branches, HEAD, stash, and commit. Reviewed HEAD/branch changes won't block restore either, restored content will appear as uncommitted modifications of current HEAD.

**Q: Can it restore when other Agents are running in the same project directory?**

A: No, it will be blocked. Restore scans other Agents' workspaces, if any running Session shares the same worktree, this restore fails with `WORKSPACE_IN_USE`; idle Sessions don't block restore.

**Q: Does restore automatically rollback on failure?**

A: Yes. Before each execution, a rescue snapshot is created; execution failure immediately rolls back via rescue snapshot; if rollback also fails, operation status is marked as `recovery-required`, rescue point can be found via `listRecovery` for manual processing.

**Q: Which Git repositories are supported? Which are not?**

A: Only regular Git worktrees supported. Sparse checkout, submodule gitlinks, ignored files, socket/device/FIFO and other special files, extended attributes/ACL/ownership/timestamp/hard-link topology, Git index and repository metadata are not in snapshot scope, creating restore points fails directly when encountering these.

**Q: Can other Cordis plugins call this capability?**

A: Yes. Plugins get `ctx.changeLedger` via `inject: ['changeLedger']`, then can call the complete lifecycle API: `create / list / inspect / planRestore / applyRestore / delete / listRecovery`; full types exported from `@anionex/dsh-turn-rewind/format`, engine itself can be used separately from `@anionex/dsh-turn-rewind/core`.

**Q: DSH crashes mid-operation, what happens to unfinished restore operations?**

A: Automatic reconciliation at startup: mark those non-final operation logs with unheld workspace locks as `interrupted`; if another surviving DSH process holds the workspace lock, won't be misidentified. Rescue points are always retained as regular inspectable restore points until explicitly deleted.

**Q: Does restore rewrite Git history?**

A: No. Repository metadata (index, branches, HEAD, stash, commit) remains unchanged throughout, plugin only reads factual information from commands like `git ls-files / rev-parse` for validation, never initiates any write operations like `commit / stash / reset / checkout / branch`.

## Learning Curve
Beginner — from user perspective, just install and restart profile, no configuration or commands needed; Agent developers need to read Service API only to call `ctx.changeLedger`.

## Known Issues & Limitations
- Does not support sparse checkout worktrees, creating restore points fails directly (sparse paths cannot be defined in snapshots)
- Does not support submodule: when any gitlink is encountered, entire worktree is rejected, requires entering each submodule to create restore points separately
- Does not track ignored files, special file types (socket/device/FIFO), extended attributes/ACL/ownership/timestamp/hard-link topology, restoration of these objects fails rather than silently discarded
- If target path has ignored or unmanaged files occupying space during restore, will directly refuse to overwrite (avoid recursively deleting worktree content)
- Refuses to cross symlink parent directories, also refuses to overwrite non-empty directories
- When any running state Agent exists in same worktree, this restore is blocked by `WORKSPACE_IN_USE`; idle Sessions don't block
- During ongoing Git operations like merge / rebase / cherry-pick / revert / bisect, this restore is blocked
- Default capacity limits (`maxFiles=20000`, `maxFileBytes=16 MiB`, `maxSnapshotBytes=512 MiB`) are conservative, need to actively increase if workspace exceeds limits
- If new conversation creation fails during same turn "Restore files and continue from here", plugin automatically rolls back files from rescue point; but if rollback also fails, need manual processing via `listRecovery`

---

This document is auto-generated by [deepseek-plugin.org](https://deepseek-plugin.org). HTML page: [dsh-turn-rewind](https://deepseek-plugin.org/plugins/Anionex/dsh-turn-rewind)
Wiki generated by AI (model: `MiniMax-M2.5`)
