DeepSeek Harness plugin

dsh-rewind

In-place conversation rewind in the same session window without forking (Claude Code /rewind semantics): a per-message ↶ button cuts the model context back to any user message, with an optional Claude-Code-style file restore from disk-persisted before-backups.

Jump to install

Source facts

Repository
SiriLee/dsh-rewind
Latest update
Aug 18, 2026
Category
Sessions & Messages
GitHub stars
4

Install

Start with a prompt that asks an agent to read the source. Switch to the command if you want to install it yourself.

Copy this prompt into DSH, Codex, or another agent and ask it to read the page and repository first.

Do not install anything yet. Read this DeepSeek Harness plugin and explain what it does, which files, networks, or credentials it can access, and how to install and remove it.

Plugin page: https://deepseekplugins.org/plugins/SiriLee/dsh-rewind
GitHub: https://github.com/SiriLee/dsh-rewind
Plugin: dsh-rewind
Author: SiriLee
Install command: dsh plugin --profile web add dsh-rewind-plugin

Do not run the install command until I confirm.

Check the source files

Read the README and other files from this plugin directory before installing.

File explorer3 files
README.mdSource · read only

dsh-rewind

[简体中文](README.zh.md)

In-place conversation rewind for DeepSeek Harness: the Claude Code /rewind semantics inside the same session window — cut the model context back to an earlier user message, and optionally restore workspace files from disk-persisted before-backups.

> Status: published to npm (dsh-rewind-plugin, v0.2.3) via GitHub Actions Trusted Publishing + Sigstore provenance. Targets the web profile (dsh --profile web). Interaction mirrors Claude Code's rewind, adapted to dsh's real web UI.

![npm version](https://www.npmjs.com/package/dsh-rewind-plugin) ![npm license](https://github.com/SiriLee/dsh-rewind/blob/main/LICENSE)

Table of contents

  • [✨ Features](#-features)
  • [📸 Screenshots](#-screenshots)
  • [How it works](#how-it-works)
  • [📦 Install](#-install)
  • [Usage](#usage)
  • [Behavior details & limitations](#behavior-details--limitations)
  • [Not included](#not-included)
  • [Compatibility](#compatibility)
  • [Development](#development)
  • [Publishing](#publishing)
  • [Directory layout](#directory-layout)
  • [License](#license)

✨ Features

FeatureDescription
In-place rewindRewind to any user message from a per-message ↶ button: the target message and everything after it (agent replies, tool calls) are withdrawn from the model context and the rendered transcript — no new session, no window switch
Time-travel semanticsRewinding to a message withdraws that message too; its text is offered back in the composer so you can edit and re-send it
Claude-Code-style file restoreWrite-class edits are backed up before they happen and persisted on disk; "conversation and code" restores files to their pre-edit content and deletes files created after the target
Impact preview"Conversation and code" first shows the exact restore / delete list for confirmation (the option is hidden when there are no tracked changes — like Claude Code's code-restore visibility)
Approval-plugin coexistenceCapture runs at the tools/execute around-dispatch stage, so another plugin's pre-execute approval short-circuit (e.g. dsh-edit-approval) cannot skip the backup, and a denied call never records
Paths resolved by session cwdRelative paths are backed up and restored against the real file using the fs-tools session-cwd rule; the resolved display path is what gets recorded
Restores write real filesRestore goes through plain node:fs directly to the filesystem; symbolic and hard links are skipped with a warning (no clobber through a shared inode)
Survives host restartBackups live on disk under ~/.dsh/rewind-snapshots/<session>/<anchor seq>/, newest 100 message groups per session
Localizedzh / en copy registered into the dsh locale system

📸 Screenshots

<table> <tr> <td align="center"><img src="assets/screenshots/rewind-button.png" width="440" alt="Per-message ↶ rewind button"><br><sub>Per-message ↶ rewind button</sub></td> <td align="center"><img src="assets/screenshots/mode-popover.png" width="440" alt="Mode-selection popover"><br><sub>Mode-selection popover</sub></td> </tr> <tr> <td align="center"><img src="assets/screenshots/impact-list.png" width="440" alt="Impact list"><br><sub>"Conversation and code" impact list</sub></td> <td align="center"><img src="assets/screenshots/guard-hint.png" width="440" alt="Manual /rewind guard hint"><br><sub>Manual /rewind guard hint</sub></td> </tr> </table>

How it works

Two halves work together: the conversation rewind (in-place, same window) and the checkpoint file restore (Claude-Code-style before-backups).

1. Conversation rewind

The plugin appends an empty-content marker assistant/message into the session log whose surfaceOp: { op: 'replace', start, end } replaces every surface node after the target message with the marker:

  • The marker carries sourceEventSeqs covering every shadowed node, and the Session.append surface rules validate the cut (only a contiguous range on the current surface).
  • Because the marker is empty, the harness derives it to null — it never enters the model context and never renders as conversation content. The agent and the user both see the conversation exactly as it was at the target.
  • The append-only log is untouched — the audit trail keeps every withdrawn event; only the model-visible surface is cut, so the next request derives its context from the target onward.

A running turn (LLM thinking / streaming) is force-stopped first (cancel({ kind: 'user' })) and the rewind waits for quiescence; if it can't stop, the rewind is aborted with an error.

2. Checkpoint file restore

The plugin tracks the write-class tools — write, edit, str_replace_editor (mutating commands create / str_replace / insert):

1. Before-capture at tools/execute (the around-dispatch stage): the target file is read and the resolved path + content are held in a pending map. This stage only runs after any pre-execute approval gate let the call through — so an ask short-circuit (dsh-edit-approval) cannot skip the backup, and a denied call never records. 2. Disk commit at tools/post-execute: the before-backup is written under the turn's anchor message seq (~/.dsh/rewind-snapshots/<session>/<anchor seq>/<callId>.json). 3. Restore (/rewind @<seq> both): every backup anchored at or after the target applies — modified files are written back to their earliest captured before-state, files created after the target are deleted, symbolic / hard links are skipped. Writes go through plain node:fs, independent of the fs service. 4. A failed tool body that throws skips tools/post-execute; a tools/result safety net clears the pending capture so nothing leaks in memory.

Backups persist across host restarts, bounded to the newest 100 anchor groups per session.

📦 Install

Published to npm — the registry path is the recommended one. Restart dsh web (--profile web) after installing.

Option A: registry (recommended)

dsh plugin --profile web add dsh-rewind-plugin

Option B: local checkout (authors / contributors)

cd dsh-rewind
npm install      # devDeps come from the npm registry; no harness checkout needed
npm run build    # full build: lib/ (host ESM + client bundle + .d.ts)
dsh plugin --profile web add /path/to/dsh-rewind   # link install

Option C: GitHub (pin a commit for reproducibility)

dsh plugin --profile web add github:SiriLee/dsh-rewind#<commit-sha>

First run fails: pnpm blocks git dependencies from running build scripts. Follow the CLI hint to add an allowBuilds key to the profile's pnpm-workspace.yaml (e.g. $DSH_HOME/profiles/web/pnpm-workspace.yaml), then retry. pnpm then runs the plugin's prepare (full build) and installs it into the profile.

Usage

Rewind via the per-message button

1. Hover any user message you sent — a ↶ rewind button appears in its action row. 2. Click it. The target is that message (step one is done). A small popover opens (step two): - Rewind conversation only — cut the model context back to before the message; workspace files stay untouched. - Rewind conversation and code — same context cut, plus workspace files restored to their state before the message. An impact list (files to restore / delete) is shown first, then you confirm. - The "conversation and code" option is hidden when no tracked file changes exist after the target (matching Claude Code's behavior). 3. The rewind executes as an in-session command; a result message confirms (e.g. "已撤回 seq N 及之后内容;还原 M 个文件"), and the withdrawn message's text is filled back into the composer for editing and re-sending.

Rewinds are time-travel

Rewinding to a message withdraws it and everything after it — the transcript and the agent's context both return to before the message. The command result says so, and the message's text is offered back in the composer.

Manual /rewind is blocked

/rewind exists only as the button's internal channel. Typing /rewind (bare or with arguments) into the composer is intercepted — submitting shows a transient hint pointing at the ↶ button.

Behavior details & limitations

  • Only write-class tools running while the plugin is active are tracked (write / edit / str_replace_editor). Changes made by bash, other tools, or external programs are not backed up and cannot be restored — the same limitation as Claude Code, which also defers such rollbacks to the user's git.
  • If a before-capture read fails (e.g. a permission error), that change is simply not backed up and a both rewind cannot restore it — the plugin logs a warning but does not block the write.
  • File restore/delete writes through the real local filesystem; under sandbox / remote backends path resolution may be restricted.
  • Symbolic links and hard links are not written through (they share the inode with another name; a restore would clobber both) — they are skipped and reported.
  • A rewind can itself be rewound (its marker enters the log), but the file-restore action is not re-backed up.
  • The ↶ button is injected on user messages rendered in the current session view; switch to another session before rewinding it.
  • When no tracked file changes exist after the target, the mode popover offers only "conversation only" (Claude Code hides code-restore options the same way).

Not included

  • Keyboard shortcuts (esc+esc to open the rewind menu) — planned as a follow-up.
  • /compact — provided by the harness.
  • Fork / branch rewind — the harness's built-in "branch in new chat".
  • Whole-tree / git-first snapshots covering bash and external edits — deliberately not implemented, in line with Claude Code's native rewind (which also defers such rollbacks to the user's git).

Compatibility

  • Node.js ^22.19.0 || >=24.0.0.
  • DeepSeek Harness web profile (dsh --profile web); peer @deepseek-ai/* packages are resolved by the harness at runtime.

> [!WARNING] > This project and DeepSeek Harness are both in developer preview. Pin exact > versions in reproducible environments and review the behavior notes above.

Development

npm install            # devDeps from the npm registry
npm run typecheck      # tsc on both compilation surfaces (host + client)
npm test               # vitest: rewind / snapshot / hidden / session-cwd / integration (46 cases)
npm run build          # esbuild: lib/index.js (host ESM) + lib/client.js (loader closure) + .d.ts
node scripts/verify-host.mjs   # boot the BUILT host artifact end-to-end (18 checks)

prepare runs the full build, so git installs and npm pack / npm publish always produce a complete lib/ and the LICENSE.

Maintainers: see [docs/harness-reference.md](docs/harness-reference.md) for the DeepSeek Harness interface reference (subsystem docs + key source index).

Publishing

Releases go out through GitHub Actions Trusted Publishing (OIDC, no stored NPM_TOKEN):

npm version patch && git push origin main --tags   # triggers .github/workflows/publish.yml
  • The workflow verifies the tag matches package.json, runs typecheck + tests +

a full build + artifact verification, publishes with --provenance (Sigstore), and creates a GitHub Release. It is idempotent — an already published version is skipped. CI (.github/workflows/ci.yml) runs the same checks on every push / PR, plus a npm pack --dry-run sanity check that the tarball carries lib/ and LICENSE.

  • One-time npm-side configuration (cannot be done from this repo): open

dsh-rewind-pluginsettings → Trusted Publisher → Add, with Provider GitHub Actions · Organization or user SiriLee · Repository dsh-rewind (the GitHub repo, not the npm name) · Workflow filename publish.yml · Environment empty · Allowed actions npm publish. Once configured, pushes of v<version> tags publish automatically.

Directory layout

src/index.ts            host plugin: /rewind command + checkpoint pipeline (tools/execute|post-execute)
src/rewind.ts           pure planning: target resolution, surface range, candidate listing
src/snapshot.ts         checkpoint store (disk before-backups, restore/preview, bounded prune)
src/session-cwd.ts      session-cwd resolution (fs-tools rule)
src/client/index.ts     client plugin: per-message ↶ button + manual /rewind guard
src/client/popover.ts   mode-selection popover (both-mode impact confirm)
src/client/hidden.ts    withdrawn-span computation (hiddenSeqsOf), pure
src/client/locales.ts   zh / en copy (LocaleNamespaceMap)
src/client/styles.ts    injected styles (dsh design tokens)
scripts/build.mjs       esbuild: lib/index.js (host ESM) + lib/client.js (loader closure) + .d.ts
scripts/verify-host.mjs end-to-end host verification (18 checks)
tests/                  vitest suites (rewind / snapshot / hidden / session-cwd / integration, 46 cases)
docs/harness-reference.md   maintainer docs: DeepSeek Harness interface reference
assets/screenshots/     UI screenshots
cordis.patch.yml        bundle patch (mounts the dual-face plugin row)
package.json            dsh.bundle + dsh.client manifests, optional peerDependencies

License

[MIT](LICENSE)