Back to home@JJXjustin

dsh-session-rewind

DSH session and file rewind plugin (shadow git repo)

Stars
0
Language
JavaScript
Created
Aug 31, 2026
Updated
Aug 31, 2026
GitHub repo

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:zlibzstdCompressSync,Node 22.2+ 才有;较老版本自动降级为明文 JSONL,功能不受影响)

  • Git ≥ 2.x(文件快照依赖;影子仓库基于 git 实现)

    Git 下载链接(任选其一):

  • 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.jsondsh.profile.bundles 数组包含 dsh-session-rewind

说明dsh-session-rewind 是纯 JS 插件(lib/*.js 手写、无 build 步骤),无需 npm install/build,link 装配后刷新页面即可生效。若改了 package.jsondsh.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
}
字段默认说明
autoCheckpointfalseagent/pre-step(每步/模型请求前)打点,默认关——打点统一为「每轮一次」
autoCheckpointPerTurntrue每轮对话结束打 1 个版本点(唯一的默认打点来源;autoEveryNTurns>0 时改按 N 轮取模)
checkpointOnToolfalse每个顶层工具调用前打一版(标签「工具:」),显式开启才生效(旧名 checkpointOnTools: true 仍兼容)
autoEveryNTurns0每 N 轮打点(0 = 用 autoCheckpointPerTurn 的每轮语义)
gitAnchortrue文件快照默认开:每个检查点同时在影子仓库建 git 提交(commit message = dsh-rewind:<ckptId>:<label>),还原时把工作区受管范围完整恢复到该快照
shadowDirnull影子仓库根目录(显式指定则不用 rewindDir/shadow-repos);默认 <工作区>/.dsh-rewind/shadow-repos/<sha1(cwd)16>
rewindPathsnull快照白名单(数组),默认整个工作区;配置后只快照这些路径
rewindExcludenull额外排除路径(追加到内置排除 .git/node_modules/.dsh-rewind/.agent-teams/_rollback/日志等之后)

聊天 UI(client 端)

输入卡片上方有一个常驻的 「🕐 还原检查点」 入口(注册在 conversation.input.dock 插槽,与待办/队列/goal 行并存):

  1. 点击展开版本点列表(时间 / 标签 / seq / 轮数 / git 快照标志)——只显示当前轮次以前的轮次(当前轮内的检查点在轮次结束后才会出现);面板打开期间每 4 秒自动轮询刷新,新版本点无需刷新页面即可看到
  2. 点某一版本点 → 预览将丢弃什么(N 条事件 / M 轮 / K 个工具调用 + 文件恢复提示「文件将完整恢复到检查点快照(git xxx):已跟踪文件回退、未提交改动丢弃、未跟踪的新文件移入归档备份」)
  3. 确认还原会话日志 + 工作区文件一起回退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.jsondsh.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插件状态:配置 / 冻结会话 / 挂起请求

回退流程(模型侧标准操作):

  1. 调用 rewind_list 查看版本点 → 选定目标
  2. 调用 session_rewind 获取预览(丢弃 N 条事件 / M 轮 / K 个工具调用)
  3. 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.jsonrewindDir 可显式覆盖
  • 无 cwd 的会话兜底 <DSH 进程启动目录>/.dsh-rewindrewind_statusfallbackRootDir 展示)
  • 工作区零侵入:用户在项目里看不到任何 .git/.gitignore,快照全部落在 .dsh-rewind/shadow-repos/
  • 历史遗留数据(早期版本写入 ~/.dsh-rewind / ~/.dsh/dsh-session-rewind)可用 node tools/migrate-data.mjs 合并进工作区(按 id/seq 去重、保留最新 200 点,幂等可重跑)

设计要点

  • 工具注册走「永不 throw」双兜底(harness.defineTool/registerToolctx.tools.register → 降级不注册),任何失败不影响 DSH 启动
  • 插件 inject: ['tools', 'webServer']sessions / sessionPersistence / sessionProjectionCache 全部 ctx.get 可选获取,缺哪个功能降级到哪
  • client bundle 为 window.__ModuleLoader__.load 格式(DSH client-modules 标准),不依赖 __DSH_MODULES__package.jsondsh.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 主题/皮肤