dsh-session-rewind
DSH session and file rewind plugin (shadow git repo)
- Stars
- 0
- Language
- JavaScript
- Created
- Aug 31, 2026
- Updated
- Aug 31, 2026
Introduction
dsh-session-rewind
DSH 会话回退插件:给会话事件日志打「版本点」,可把会话回退到任意版本点(归档原日志 → 重建日志文件 → 刷新投影缓存),并可选 git 锚点实现文件级回退。
- 纯 host + client 插件,不改动 DSH 官方源码文件
- 版本点不写入会话日志本身(不污染 append-only 日志),存于
<会话工作区>/.dsh-rewind/<session>/独立目录 - 回退前强制确认(两阶段工具),回退失败自动全量回滚,绝不留下半截状态
- 文件快照采用影子仓库:
.git放在.dsh-rewind/shadow-repos/,绝不侵入用户项目目录(不git init、不写.gitignore);快照范围受 pathspec 白名单约束(默认排除.git/node_modules/.dsh-rewind/.agent-teams/_rollback/日志等,可配置)
前置要求
-
Node.js ≥ 20(zstd 写回需要
node:zlib的zstdCompressSync,Node 22.2+ 才有;较老版本自动降级为明文 JSONL,功能不受影响) -
Git ≥ 2.x(文件快照依赖;影子仓库基于 git 实现)
Git 下载链接(任选其一):
- Git for Windows:https://git-scm.com/download/win
- 官方镜像:https://mirrors.tuna.tsinghua.edu.cn/git-for-windows/(国内更快)
- 安装时确保勾选 "Add to PATH"(
git --version能输出即成功)
-
DeepSeek Harness(DSH web 环境,插件在其上运行)
安装
1. 获取插件
从 GitHub 克隆本仓库到你的机器(任选其一):
# HTTPS(推荐,需要先配置 git)
git clone https://github.com/JJXjustin/dsh-session-rewind.git
# 或 SSH(配置了 SSH key 后)
git clone git@github.com:JJXjustin/dsh-session-rewind.git
克隆后进入目录:cd dsh-session-rewind
2. 装配到 DSH
插件通过 DSH 的 profile 装配(link 目录方式,免 build)。在你的 DSH profile 目录(通常是 ~/.dsh/profiles/web,Windows 为 C:\Users\<你>\.dsh\profiles\web):
cd C:\Users\<你>\.dsh\profiles\web
pnpm add -w link:<你克隆到的绝对路径>\dsh-session-rewind
确认 package.json 的 dsh.profile.bundles 数组包含 dsh-session-rewind。
说明:
dsh-session-rewind是纯 JS 插件(lib/*.js手写、无 build 步骤),无需npm install/build,link 装配后刷新页面即可生效。若改了package.json的dsh.client声明、或 client bundle 生效异常,重启 DSH 让 client-modules 重新扫描。
3. 卸载
删掉 package.json 里的 dependencies + bundles 两处引用 + node_modules\dsh-session-rewind junction 即可。
配置(可选)
~/.dsh/dsh-session-rewind.json:
{
"autoCheckpoint": false,
"autoCheckpointPerTurn": true,
"checkpointOnTool": false,
"autoEveryNTurns": 0,
"gitAnchor": true,
"shadowDir": null,
"rewindPaths": null,
"rewindExclude": null
}
| 字段 | 默认 | 说明 |
|---|---|---|
autoCheckpoint | false | agent/pre-step(每步/模型请求前)打点,默认关——打点统一为「每轮一次」 |
autoCheckpointPerTurn | true | 每轮对话结束打 1 个版本点(唯一的默认打点来源;autoEveryNTurns>0 时改按 N 轮取模) |
checkpointOnTool | false | 每个顶层工具调用前打一版(标签「工具:」),显式开启才生效(旧名 checkpointOnTools: true 仍兼容) |
autoEveryNTurns | 0 | 每 N 轮打点(0 = 用 autoCheckpointPerTurn 的每轮语义) |
gitAnchor | true | 文件快照默认开:每个检查点同时在影子仓库建 git 提交(commit message = dsh-rewind:<ckptId>:<label>),还原时把工作区受管范围完整恢复到该快照 |
shadowDir | null | 影子仓库根目录(显式指定则不用 rewindDir/shadow-repos);默认 <工作区>/.dsh-rewind/shadow-repos/<sha1(cwd)16> |
rewindPaths | null | 快照白名单(数组),默认整个工作区;配置后只快照这些路径 |
rewindExclude | null | 额外排除路径(追加到内置排除 .git/node_modules/.dsh-rewind/.agent-teams/_rollback/日志等之后) |
聊天 UI(client 端)
输入卡片上方有一个常驻的 「🕐 还原检查点」 入口(注册在 conversation.input.dock 插槽,与待办/队列/goal 行并存):
- 点击展开版本点列表(时间 / 标签 / seq / 轮数 / git 快照标志)——只显示当前轮次以前的轮次(当前轮内的检查点在轮次结束后才会出现);面板打开期间每 4 秒自动轮询刷新,新版本点无需刷新页面即可看到
- 点某一版本点 → 预览将丢弃什么(N 条事件 / M 轮 / K 个工具调用 + 文件恢复提示「文件将完整恢复到检查点快照(git xxx):已跟踪文件回退、未提交改动丢弃、未跟踪的新文件移入归档备份」)
- 确认还原 → 会话日志 + 工作区文件一起回退:
clean -fd+checkout -f <commit> -- <paths>(影子仓库,reset 不支持 pathspec,故用组合拳)把受管范围恢复到检查点快照;快照后新增的已跟踪文件也一并移除(移到影子仓库restore-backups/备份,不是硬删);成功显示摘要后页面自动重载;失败显示错误(归档保留可恢复)
撤销:回退后未发新消息时,面板顶部显示 「↩ 撤销回退」(两次点击确认)——会话日志从归档恢复 + 文件恢复到回退前状态(git reset --hard gitBefore + 未跟踪文件从备份移回),均无需重启。
⚠ 无文件快照的检查点:列表里标红「⚠ 无文件快照」的检查点是 git 快照机制启用之前打的旧点(或打点时 git 快照失败)——还原它们只回退会话、不会回退文件(当时没有拍过快照,技术上限无法补)。git 快照启用后打的所有检查点默认带
git xxx绿色标记,还原即可同步回退文件。
数据经 host 侧 /api/rewind/{list,preview,confirm,undo,undoable,status}(loopback-only)流转,client 不直接读文件。UI 全部包在 React error boundary 内,崩溃只降级该入口,不影响对话面板。
关于「回退后继续对话」:回退 live 会话时会就地热重置内存副本(磁盘+内存+文件三者一致),无需重启即可继续;热重置失败才回落到冻结兜底。
位点说明:最初按 Cline 对齐选用
conversation.chat.turnTail(每轮末尾),但该 chain 已被dsh-client-ui-deliverables/dsh-better-sidebar的产出文件行占用(chain 只渲染一个 entry,有文件时必赢)——改为conversation.input.dock(list 型,多 ID 共存,输入卡上方独立行)。
注意:client bundle 改动只需刷新页面即生效(bundle 从
/plugins/实时服务);若改package.json的dsh.client声明则需要重启 DSH 让 client-modules 重新扫描(包声明缓存不热更新)。
工具
| 工具 | 作用 |
|---|---|
rewind_checkpoint { label?, withFiles? } | 手动打版本点;文件快照(影子仓库 git)默认随打点创建,需工作区有 git(影子仓库自动 init,不侵入项目) |
rewind_list { sessionId? } | 列版本点与回退记录(时间/标签/seq/轮数/工具数/git 锚点) |
session_rewind { sessionId?, checkpointId?, seq? } | 预览回退:展示将丢弃的事件/轮数/工具数统计并挂起待确认请求(不执行任何修改) |
session_rewind_confirm | 确认并执行挂起的回退(磁盘截断 + live 会话热重置,可直接继续对话) |
rewind_undo { sessionId? } | 撤销最近一次回退(仅当回退后未发新消息);从归档恢复 + 热重置 |
rewind_restore { checkpointId?, commit? } | P1 文件级回退:clean -fd + checkout -f <commit> -- <paths> 到影子仓库锚点 |
rewind_status | 插件状态:配置 / 冻结会话 / 挂起请求 |
回退流程(模型侧标准操作):
- 调用
rewind_list查看版本点 → 选定目标 - 调用
session_rewind获取预览(丢弃 N 条事件 / M 轮 / K 个工具调用) - 用
ask_user_question向用户展示预览并请求确认;用户明确同意后才调用session_rewind_confirm;拒绝则放弃(pending 10 分钟过期)
回退语义
seq是独占边界:回退到 seq V = 保留events[0..V),V 及之后的事件被截断- 只允许切在轮边界(不能截断在 turn 中间,与 DSH fork 同规则)
- 执行顺序:先归档原日志(
~/.dsh/dsh-session-rewind/archive/<session>-<time>.jsonl,人读)→ 两步原子替换日志文件(zstd 保持 header 独立帧)→ 刷新投影缓存(coldSnapshot)→ 记录回退摘要 - 投影刷新失败 → 自动从归档全量回滚,日志恢复原状,不抛半截
- 归档文件与
checkpoints.json里的rewinds记录构成可审计副本,原日志绝不静默删除
live 会话:热截断(无需重启)
回退正在运行的会话时,磁盘截断后同步热重置其内存副本(日志数组、surface 折叠、派生消息缓存、header/context 折叠、持久化写游标、投影单元)——与磁盘状态一致后直接继续对话,新事件 seq 从版本点连续追加,无需重启 DSH:
- 回退未在运行的会话 → 立即完全生效(UI/历史读取走磁盘)
- 回退正在运行的会话 → 磁盘截断 + 内存热重置,当场可继续对话
- 热重置任一步失败 → 自动回落旧的 frozen 兜底(
agent/pre-step拦截防 seq 冲突,重启后从磁盘恢复)——日志安全始终优先
撤销回退(rewind undo)
回退后只要还没发出新消息,可以一键撤销、完整恢复到回退前状态:
- 面板顶部出现 「↩ 撤销回退」(两次点击确认);或模型工具
rewind_undo - 从归档恢复完整日志到磁盘 + 热重置内存(同样无需重启),并刷新投影缓存
- 回退后已产生新消息则拒绝撤销(会话已从回退点走出新轨迹,旧轨迹仍在归档永久保留)
数据目录
默认写入「会话自己的工作区」 <会话工作区>/.dsh-rewind/(cwd = 会话 header.cwd;例如本机工作区为 D:\AI_WORK\DeepSeek Work):
<工作区>/.dsh-rewind/
├── archive/ # 回退归档(原始 JSONL 文本)+ untracked/restore-backups 备份
│ ├── <session>-<timestamp>.jsonl
│ └── untracked-<timestamp>/ # 还原时移除的未跟踪文件(撤销可移回)
├── shadow-repos/<sha1(cwd)16>/ # 影子 git 仓库(文件快照,.git 不侵入项目)
│ ├── .git/ # 快照对象库 + commit(message=dsh-rewind:<ckptId>:<label>)
│ ├── exclude # 影子仓库自己的排除规则(core.excludesFile)
│ └── restore-backups/<ts>/ # 还原时移除的快照后新增跟踪文件(撤销可移回)
├── <session-id>/ # 每会话版本点簿
│ └── <session-id>.json # { checkpoints: [...], rewinds: [...] }
- 不用
~/.dsh(用户明确要求不落 C 盘);~/.dsh/dsh-session-rewind.json的rewindDir可显式覆盖 - 无 cwd 的会话兜底
<DSH 进程启动目录>/.dsh-rewind(rewind_status的fallbackRootDir展示) - 工作区零侵入:用户在项目里看不到任何
.git/.gitignore,快照全部落在.dsh-rewind/shadow-repos/下 - 历史遗留数据(早期版本写入
~/.dsh-rewind/~/.dsh/dsh-session-rewind)可用node tools/migrate-data.mjs合并进工作区(按 id/seq 去重、保留最新 200 点,幂等可重跑)
设计要点
- 工具注册走「永不 throw」双兜底(
harness.defineTool/registerTool→ctx.tools.register→ 降级不注册),任何失败不影响 DSH 启动 - 插件
inject: ['tools', 'webServer'];sessions/sessionPersistence/sessionProjectionCache全部ctx.get可选获取,缺哪个功能降级到哪 - client bundle 为
window.__ModuleLoader__.load格式(DSH client-modules 标准),不依赖__DSH_MODULES__;package.json的dsh.client.inject使用 better-sidebar 0.14.0 验证过的注入组合 - 日志重建不依赖
@deepseek-ai/*运行时包(chunk-rows 展开/打包为自实现最小版本,写回采用不打包布局,官方读取路径布局无关),无 peer 依赖安装坑 - zstd 写回与官方一致:header 单独一帧 + 事件帧,checksum 开启
单元测试
cd C:\Users\asus\.dsh\plugin-src\dsh-session-rewind
node --test test/core.test.mjs
覆盖:chunk-rows 展开、seq 连续性校验、编码往返、轮边界校验、统计/标签、bookkeeping 去重、回退事务(截断+归档+记录)、边界失败零写入、投影失败全量回滚、zstd 分帧、恶意 session id 路径转义。
明确不做(Out of Scope)
- 前进/redo(回退即截断)
- 跨会话回退
- 修改 DSH 官方源码(
dsh-*npm 包) - UI 主题/皮肤