按轮次为工作区拍 git 检查点:撤销某一轮的改动,或回到某一轮结束时的状态;执行前展示差异预览,回退本身还能再撤销。
安装
# npm 包(预构建)
dsh plugin --profile web add dsh-checkpoints
# GitHub 源码(首次需按提示配置 allowBuilds 构建授权后重试)
dsh plugin --profile web add github:tower666/dsh-checkpoints
装任何插件都等于在你的机器上跑第三方代码,权限和你本人一样大——能读你的文件、用你的凭据、访问网络,工具审批管不到它。GitHub 来源的插件还会在安装时执行构建脚本——pnpm 默认拦截,所以安装可能停在 ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED 或 ERR_PNPM_IGNORED_BUILDS;dsh 会打印出需要添加的确切键名,把它加进该 profile 的 pnpm-workspace.yaml 的 allowBuilds 下,重跑一次即可装上。放行构建本身就是一次信任判断:请只安装可信来源,并尽量锁定 commit(github:owner/repo#sha)。
README
该插件的 README 只有英文版本。
Persistent, per-turn workspace checkpoints with a previewed, approved, and undoable revert — the "Cursor checkpoints" behaviour for the DeepSeek Harness Web UI.
The shipped @deepseek-ai/dsh-workspace-changes plugin records what a turn
changed and shows its diff, but its snapshots live in the Session's temporary
directory and disappear when the Session or the Host ends, and it offers no
revert. This bundle adds both: a shadow git repository that survives
restarts, and a revert that can be applied to one file or to a whole turn.
Install
The package is a bundle: installing it also selects it in the profile, and a running Host loads it after one restart.
From npm (recommended)
dsh plugin --profile web add dsh-checkpoints
Or install dsh-checkpoints by name: ask the agent — the Creator preset's
plugin_manager tool takes the package name as install_bundle's target —
or paste the name into Plugins → Add. The package page is
https://www.npmjs.com/package/dsh-checkpoints.
From git
git+https://github.com/tower666/dsh-checkpoints.git#v1.1.0
Same entry points as above, with that spec as the target. Use it when you want
an exact source tree without the registry: a tag keeps the instruction
readable, and a commit (#<40-hex sha>) makes the target immutable.
From a local checkout
Point install_bundle at this directory's absolute path.
Upgrading
There is no upgrade path yet: remove the bundle, then install the newer version,
tag, or commit. Checkpoints under $DSH_HOME/workspace-checkpoints survive
both, so the conversation history keeps working.
What it does
Records a snapshot of the workspace at
turn/startand again atturn/endinto a shadow repository under the Harness home, and persists the turn's changed-file list in a small catalog next to it.Serves that history to the Web page: one row under each completed turn, plus the
/api/checkpoints.*routes.Offers two actions per turn, both behind one dialog that lists the files, previews each one's unified diff, and writes only the files you tick — all of them or just one:
- Undo this turn's changes — puts back the files this turn changed, at their content from the start of the turn; files the turn did not touch are left alone.
- Revert to this turn — makes the workspace look like the end of that turn: everything up to it is kept, every change any later turn made is dropped. Hidden on the newest turn, where nothing follows it yet.
The overwritten state is snapshotted first, so either action can itself be undone.
Exposes the same operation to the model as the
workspace_checkpointtool, which asks the DSH approval answerers before it writes anything.
Mount
- name: 'dsh-checkpoints'
config:
maxFileBytes: 2097152
| Field | Default | Meaning |
|---|---|---|
storeDir |
$DSH_HOME/workspace-checkpoints |
Where the shadow repositories and the catalog live. |
timeoutMs |
30000 |
Milliseconds one git command may run. |
outputMaxBytes |
8388608 |
Bytes of git output retained per command. |
maxFileBytes |
2097152 |
Largest snapshot blob the revert will write or diff; a bigger one is reported as unsupported: oversized. |
maxDiffBytes |
524288 |
Bytes of unified-diff text one preview may carry. |
maxTurnsPerSession |
300 |
Turns the catalog keeps per Session before the oldest are forgotten. |
planTtlMs |
600000 |
How long an unapplied preview stays valid. |
The bundle deliberately declares no Config schema: a bundle installed from
a workspace directory resolves its imports from that directory, so it cannot
depend on the schema library the Host ships. Invalid values fall back to these
defaults instead of failing activation.
Safety model
- The user's repository is never written. Every snapshot command runs with
GIT_DIRat the shadow repository and its own persistent index, and the user's object store is attached only as a read-onlyGIT_ALTERNATE_OBJECT_DIRECTORIESentry. No ref, index entry, object, or commit is created there;test/integration-git.mjshashes the whole.gittree before and after to prove it. core.autocrlf=falseandcore.safecrlf=falsewith an emptyGIT_CONFIG_GLOBALkeep stored and restored bytes exact, whatever the user's global git configuration says.- A working directory outside any repository still gets checkpoints: the shadow repository is self-contained and needs no user repository at all.
- A revert is two-phase.
/api/checkpoints.planreads only; the returnedplanIdis single-use, expires afterplanTtlMs, and/api/checkpoints.restorerefuses it when any planned file changed while the preview was open. Every path is resolved against the recorded workspace root and rejected when it is absolute or escapes it. - The state a revert overwrites is snapshotted first, and
/api/checkpoints.undorestores it. - The agent tool routes the same write through
ctx.approval.request(), so the session's approval policy decides it and'unavailable'fails closed. The approval service requires an open turn; the Web route is therefore gated by its own explicit preview-and-confirm handshake instead, which the user performs in the dialog. - Snapshot blobs larger than
maxFileBytesare listed but never written; the dialog marks them "not revertable".
Routes
All are exact routes on ctx.connection.fetch, below /api, so they sit inside
Connection's authentication fence and answer no-cache JSON.
| Route | Method | Purpose |
|---|---|---|
/api/checkpoints.turns?sessionId= |
GET | Every recorded turn of one Session. |
/api/checkpoints.plan |
POST | Prepare a revert preview ({sessionId, turn, side?, paths?}): side: 'start' (default) undoes that turn's own files, side: 'end' restores the workspace to the end of that turn. |
/api/checkpoints.diff?planId=&path= |
GET | One file's unified diff for that preview. |
/api/checkpoints.restore |
POST | Apply a preview ({planId}). |
/api/checkpoints.undo |
POST | Undo an applied revert ({undoId}). |
The turn row
Each completed turn of the conversation carries one row with one action, chosen by the turn's position:
newest turn ⟲ 3 files changed in this turn [Undo this turn’s changes…]
earlier turns ⟲ 3 files changed in this turn [Revert to this turn…]
| Turn | Action | Baseline | File list |
|---|---|---|---|
| newest | Undo this turn's changes | the start of this turn | the files this turn changed |
| earlier | Revert to this turn | the end of this turn | every path that differs between the end of this turn and now |
The newest turn has nothing after it, so "go back to here" would be a no-op; it offers the undo instead. Every earlier turn is a point to return to: reverting to it keeps that turn's own work and removes the later turns' additions and edits.
One consequence worth knowing: undoing a single historical turn while keeping the later ones is not offered by the row. The row answers "what do I do at this position", and at a historical position the useful answer is "return here".
Either dialog lists the files with their line counts, previews each file's unified diff on demand, offers select-all/select-none, and writes only the ticked files. A file the baseline does not hold is marked "will be deleted", an oversized snapshot blob "not revertable". After applying, the dialog offers to undo the action itself.
Store layout
$DSH_HOME/workspace-checkpoints/
catalog.json # session → turn → {beforeTree, afterTree, files}
empty-gitconfig # the empty global config every git call uses
repos/<sha1(workspace)>/ # one shadow repository per workspace
.git/ # its own objects, index untouched by the user
index # the persistent shadow index
tmp/ # scratch indexes for restores and previews
Tree objects are content addressed, so unchanged content costs nothing after the first snapshot, and committed blobs are read from the user's repository through the alternate instead of being copied.
Develop
node test/smoke.mjs # recorder + service: preview, restore, undo, persistence
node test/integration-git.mjs # a real repository stays byte-identical
node test/routes.mjs # the five /api route handlers
node test/client.mjs # the browser half's registration and render tree
test/live-check.mjs <sessionId> <storeDir> <workspace> <file> replays the
service from a fresh process against a store the running Host wrote.
Each suite builds its fixtures under .scratch/ in the directory it is run
from, so delete that directory afterwards; nothing else outside test/ is
written.
Verification
Every suite replaces the Host subprocess capability with a child-process
adapter and file-backed stdio, so the suites also run inside the DSH file
sandbox, where piped child stdio is refused.
tsc --checkJs over the package is clean:
node <dsh-install>/node_modules/typescript/bin/tsc --noEmit --allowJs --checkJs \
--noImplicitAny false --target es2022 --module nodenext --moduleResolution nodenext \
--skipLibCheck --lib es2022,dom \
--typeRoots <dsh-install>/node_modules/@deepseek-ai/dsh/node_modules/@types --types node \
index.js client.js lib/*.js
链接
同类插件
zhu1090093659/dsh-web#packages/dsh-git-graph★ 9959
输入框上方提供 Git 分支选择器,并把分支泳道与提交历史画成图谱,沿着时间线找到任意变更。作者已将其退役并改用官方 workspace-changes 插件;已发布的 npm 包仍可安装,但不再开发。
Akimiya-z/codex-guard#dsh★ 132
在 DeepSeek Harness 内做提交前的 Pull Request 卫生检查:扫描当前改动中的 TODO 残留、硬编码密钥与非规范提交信息。
Cerbur/clutch-dsh#clutch-dsh-worktree★ 38
为 DSH Web UI 增加按 Git Worktree 组织 Session 的视角,同时继续由 DSH 管理原始 Project 和 Session 数据。
xbzbing/dsh-git-panel★ 33
DSH Web GUI 里的 IDE 风格 Git 面板:常驻标签页含三栏总览(分支/标签列表、可搜索的提交历史图、含变更文件树与提交信息的提交详情)与变更记录页(统计条、带逐文件暂存/取消/丢弃的未提交列表、含 amend 的提交框);并排差异带懒加载 highlight.js 语法高亮、块间上下文按需展开与词级行内高亮,图片文件以变更前/后双图对照;另在输入框提供 zsh 风格分支标记。所有 git 命令以 argv 数组经宿主 subprocess 执行、cwd 锁定仓库根。
DamonKoy/dsh-web-ui#dsh-git-graph★ 30
dsh web GUI 会话头部栏的 Git 分支选择器与提交图。
PerryLink/dsh-github★ 28
官方级 GitHub CI 集成:composite action.yml、轮询 PR 评审机器人(幂等行内评论 + status-check 门禁)以及 PR/issue 工具,所有写入走人工审批门。
社区评论
评论公开保存在 GitHub Discussions。加载评论会连接 GitHub 和 Giscus;发表内容需要 GitHub 账号。