插件目录 / Knowledge / dsh-turn-rewind
dsh-turn-rewind
已验证 · 实测可装 Anionex
功能简介
基于持久化 Change Ledger 回退 DeepSeek Harness 的对话与代码工作区状态。
推荐 — 实测可用且热门
基于持久化 Change Ledger 回退 DeepSeek Harness 的对话与代码工作区状态。 实测能干净安装、正常启动。119+ stars,社区认可度高,是低风险选择。
「已验证」表示我们的自动化 CI 在干净 profile 里实际执行了 dsh plugin add 并启动成功——仅此而已。功能描述与版本兼容性均为作者声明。这不是安全审计,也不代表对第三方代码的背书。
README
DSH Turn Rewind
Message-anchored project-file recovery for DeepSeek Harness, with an option to restart from the restored request.
Turn Rewind is the user-facing feature, repository, and Profile Bundle name. Change Ledger is the durable restore engine underneath it: the ctx.changeLedger service, on-disk format, and storage path keep that name because they describe the reusable snapshot and recovery layer rather than the Web action alone.
Change Ledger gives a DSH session an explicit safety boundary around workspace mutations:
create restore point
↓
agent / user / external tools modify the worktree
↓
preview exact path-level drift
↓
review a full or selective restore plan
↓
press the final restore button in the rewind dialog
↓
create rescue point → restore → verify
It never commits, stashes, resets, switches branches, edits the Git index, or decides automatically that a change should be reverted.
What's new
Most recent work first:
- File rewind works in ordinary directories (0.3.0) — when the working directory is not a Git repository it is snapshotted as an ordinary directory (
.gitandnode_modulesexcluded by default, plus an optional.dsh-rewindignore), so messages-only rewind is no longer the only option. - The fastest path is now the default (0.3.8) — the new
automode uses Git-native checkpoints in a Git worktree (reusing the repository object database, so committed content is never stored twice) and the plugin's own store for ordinary directories. - Unchanged files are no longer re-read (0.3.5) — each workspace keeps a persistent path identity cache; a measured 20 000-file / 351 MB workspace dropped from 237 s to about 7 s per capture.
- One oversized file no longer discards a whole checkpoint (0.3.3) — it is skipped and reported while the rest is still captured, and a restore never touches a path its checkpoint did not record, so a file that was too large then and small now is never deleted.
- Checkpoint budget 5 s → 60 s, parallel capture (0.3.4) — a 5-second budget in a large directory could only ever report a skip.
- Rewind button and messages-only fixes (0.2.2 / 0.2.3) — adapted to the DSH 0.1.2-alpha client hooks and restored the messages-only mode that the Host rejected.
- Settings card matches the official cards (0.3.6 / 0.3.7) — one collapsed row that expands, consistent with every other plugin card.
Preview
Rewind appears as an icon-only third action under each user message, after its timestamp and native Copy action:

Opening it shows the affected files and offers three choices: restore the files and restart from before that message, restore only the files, or rewind only the messages and leave the files untouched:

Why it has a Change Ledger engine
A diff button can show current changes, but it does not own a durable restore lifecycle. Change Ledger owns:
- content-addressed restore-point manifests;
- Git worktree, HEAD, branch, and in-progress-operation fences;
- stale-plan detection between review and mutation;
- exact two-step confirmation plus DSH human approval;
- automatic pre-restore rescue points;
- post-restore hash verification;
- rollback after a failed restore;
- startup reconciliation of interrupted restore journals;
- a public
ctx.changeLedgerservice that other plugins can consume.
The durable format is documented in docs/FORMAT.md. The security and failure model is documented in SECURITY.md.
Safety contract
- Explicit only: nothing is restored automatically — every restore starts from the user pressing the final button in the Web dialog, or from an explicit call through the service API.
- Read before write: the dialog preview generates an expiring, session-bound plan from the current tree and changes no files.
- Human gate: the dialog's reviewed impact plus the final restore button is the human decision; direct mutation requests without a live session-bound plan pair fail closed.
- Rescue before mutation: every restore captures the current eligible tree as a durable rescue point before changing a path.
- No silent omission: unsupported submodules, sparse checkouts, oversized files, aggregate limits, and unsupported file types fail point creation.
- No path escape: every durable path is canonical and workspace-relative; restore refuses symlink parents and non-empty directory replacement.
- No stale overwrite: selected paths and the reviewed HEAD/branch/operation fence are checked again at apply time. Any relevant post-review change invalidates the plan.
- No Git control-plane mutation: the index, branch, HEAD, stash, and commits remain untouched.
Scope
Two workspace kinds are supported and selected from the Session's own directory:
Normal Git worktree
- tracked files, including currently missing tracked paths;
- untracked files not excluded by
.gitignoreor other standard Git excludes; - regular files, binary or text;
- symbolic links;
- executable and other portable permission bits.
Ordinary directory (the Session directory is not a Git repository)
- every regular file and symbolic link below it; links are captured as links and never followed;
.gitandnode_modulesare excluded by default;- an optional
.dsh-rewindignorein the directory root adds.gitignore-style rules; the built-in exclusions are applied last and cannot be re-included; - snapshot content is stored in the plugin's own content-addressed storage instead of the Git object database;
- running
git initinside the directory changes the workspace mode, so earlier restore points stop applying (WORKSPACE_MODE_CHANGED) and a new message must create a new one.
The following are rejected or deliberately outside the snapshot:
- sparse checkouts;
- submodule gitlinks (create a restore point inside each submodule instead);
- ignored files and files excluded by
.dsh-rewindignore; - special files, sockets, devices, and named pipes;
- extended attributes, ACLs, ownership, timestamps, and hard-link topology;
- the Git index and repository metadata.
If an ignored or otherwise unmanaged file occupies a path that restoration would replace, the restore fails rather than deleting it.
Install
Build the checked-out plugin, then add it to each DSH profile that should expose the service:
pnpm install --frozen-lockfile
pnpm run check
dsh plugin --profile web add @anionex/dsh-turn-rewind
dsh plugin --profile headless add @anionex/dsh-turn-rewind
dsh --profile web --dump-config | grep turn-rewind
Restart a running profile after changing its bundle list.
The package is a DSH Profile Bundle. package.json declares dsh.bundle.patch, and cordis.patch.yml mounts @anionex/dsh-turn-rewind without a DSH core patch.
When the profile also provides the DSH Agent service, the plugin captures a hidden checkpoint in the first agent/pre-step waterfall before the Agent processes the opening user message. Capture failures are reported but do not reject the user's turn; the corresponding message simply has no usable rewind point. In Web profiles, the same-origin /turn-rewind endpoint resolves the selected user/message sequence, exposes a paged file preview, mints a short-lived session-bound restore plan, and delegates child creation to DSH's official Host create/fork lifecycle. It never restores files automatically.
User flow
In the Web profile, each direct user message gains a compact, icon-only Rewind action after its timestamp and native Copy control. The tooltip reads “Return to before sending this message.” Opening Rewind checks the saved file state, shows a concise preview with a “view all files” action, and offers three modes:
| Mode | Code | Conversation |
|---|---|---|
| Restore files and restart (default when files changed) | Restores the project files after automatically backing up their current state. | Creates and opens a Session ending before the selected message, then puts that message's text back in the composer. |
| Restore files only | Restores the project files after automatically backing up their current state. | Leaves the current Session open and unchanged. |
| Rewind messages only (default when no checkpoint or no file changes) | Leaves the project files exactly as they are. | Creates and opens a Session ending before the selected message, then puts that message's text back in the composer. |
The dialog itself is the confirmation: there is no duplicate checkbox. It describes each file as restoring an earlier version, finding a deleted file, removing a later-added file, or restoring permissions/type. Rewind messages only is always available — including when the checkpoint for that message is missing, was skipped (for example after disabling automatic checkpoints), or the project files already match the saved state — because it never touches the worktree. It is not blocked by other running Agents in the same worktree.
Before mutation, Turn Rewind rechecks the selected files and repository state, then creates an automatic backup. Changes made after preview invalidate the operation. Any running Agent using the same worktree, including the source Session, blocks restoration; idle Sessions do not block. A reviewed HEAD or branch difference does not block restoration: commits, refs, branch, and index remain unchanged, so restored content may appear as ordinary uncommitted changes against the current HEAD. An in-progress Git operation still blocks. If child creation fails after “restore and restart,” Change Ledger automatically restores the pre-operation files from the backup.
DSH Session logs are append-only, so “restart” creates a new Session instead of truncating the original. For the first message, the Host creates a blank Session in the same working directory; for later messages, it forks at the previous completed turn/end. A child may reuse an ancestor's prompt checkpoint only while both the selected user/message and its exact turn/start remain inside every durable seedLength fence. Direct child checkpoints take priority and sibling checkpoints never mix. Turn Rewind always treats the two dimensions independently: the two restore modes change project files (optionally followed by a new conversation), while Rewind messages only reuses the same fork lifecycle without touching files. The original Session is always retained.
Configuration
Runtime-tunable options are editable in the DSH web settings page under Plugins → Turn Rewind (turn-rewind settings namespace). Changes apply live to the next capture, restore, or deletion; they persist in the host's settings.yaml and override the profile patch values below. storageDir is deliberately not editable there: the storage root must not move while the engine holds locks and journals, so it stays a patch-layer field.
Override the composition base (and storageDir) in the profile patch layer:
- id: turn-rewind
config:
storageDir: ~/.dsh/change-ledger/v1
maxRestorePoints: 50
maxTurnCheckpointsPerSession: 30
maxFiles: 20000
maxFileBytes: 16777216
maxSnapshotBytes: 536870912
planTtlMs: 900000
staleLockMs: 30000
turnCheckpointMode: legacy # off | git-native | legacy; "off" stops creating file checkpoints
turnCheckpointTimeoutMs: 5000
turnCheckpointMaxNewBytes: 33554432
turnCheckpointTrust: fast # fast | strict
A file above `maxFileBytes`, or one whose type is unsupported, is skipped and recorded on the restore point instead of discarding the whole checkpoint; the dialog names it, and a restore never touches a skipped path. Reaching `maxFiles` or `maxSnapshotBytes` keeps the captured part and says so in the dialog.
Setting turnCheckpointMode: off (in the patch or in the settings card) stops automatic file checkpoints; every turn records a durable skip instead, and the rewind dialog still offers Rewind messages only for those messages.
All size and user-point retention limits fail loudly. Automatic turn checkpoints have a separate per-session retention window and prune only their own oldest checkpoints; user and rescue restore points are never silently pruned. When omitted, storageDir resolves to $DSH_HOME/change-ledger/v1 and falls back to ~/.dsh/change-ledger/v1; it must not overlap the managed worktree.
Checkpoint management
The same settings card includes a storage manager backed by the same-origin /turn-rewind/manage endpoint. It lists every workspace this storage root has ever tracked — including projects whose directory no longer exists — grouped with checkpoint counts, approximate sizes, and pending-recovery badges. Per checkpoint, per workspace, or globally (Clear all), it deletes unprotected restore points and garbage-collects unreferenced blobs. Restore points still referenced by an incomplete recovery journal, a running restore, or an unfinished Git-native publish are retained and reported. Git-native (v2) checkpoints live in the repository's Git object database, so their disk space is reclaimed by normal Git garbage collection; the displayed sizes are logical values.
Recovery
Before writing any path, a restore creates a rescue point and a durable operation journal. If DSH stops with a non-terminal journal, the next plugin startup marks it interrupted unless another live DSH process still owns that workspace lock.
Recovery uses the public ctx.changeLedger service API: listRecovery finds the operation's rescuePointId, inspect reviews that rescue point, then planRestore/applyRestore handle the affected paths. Rescue points remain ordinary, inspectable restore points until explicitly deleted.
Public service
Other Cordis plugins can inject changeLedger and call the same lifecycle through the structured service API:
export const inject = ['changeLedger']
export async function apply(ctx: Context) {
const point = await ctx.changeLedger.create({
cwd: '/absolute/git/worktree',
sessionId: 'session-id',
label: 'before refactor',
})
// point.id is a durable restore-point id.
}
The complete exported types are available from @anionex/dsh-turn-rewind/format; the engine is available from @anionex/dsh-turn-rewind/core for non-Cordis tests and trusted integrations.
Development
pnpm install --frozen-lockfile
pnpm run check
The test suite creates real temporary Git repositories and covers full/selective restore, stale plans, ignored-path collision refusal, HEAD drift, rescue rollback, crash reconciliation, active-lock preservation, durable-state integrity, symlinks, size limits, sparse checkouts, submodules, deletion, and blob garbage collection.
About
DSH Turn Rewind is maintained by anionex. If you would like to follow my future work, follow me on X or GitHub.
License
BSD-3-Clause. See LICENSE.
安装
装一次目录插件,之后本站所有插件都能让 DeepSeek Harness 自动找、自动装:
dsh plugin add dshbase-catalog 然后对 agent 说「帮我装 dsh-turn-rewind」,它会在目录里找到并自动安装。文档:dshbase-catalog · 已验证场景包。
该插件是 GitHub 源码(未发 npm)——直接从仓库装:
Web profile:
dsh plugin --profile web add github:Anionex/dsh-turn-rewind Headless(CLI)profile:
dsh plugin --profile headless add github:Anionex/dsh-turn-rewind 实测报告
验证通过:从 GitHub 源码完成 L1 安装 + L2 加载 + L3 运行(dsh 0.1.0-rc.6)。
使用场景
通过持久变更账本回滚对话和工作区状态,干净地撤销一步错棋。
适合谁
大胆试错、想要聊天和文件都有可靠撤销的人。
二次开发建议
变更账本是扩展点——加更细粒度的快照,或对文件/消息选择性回滚。