用 dsh-turn-rewind 給 DeepSeek Harness 加上對話與工作區回退

前言

用 DeepSeek Harness(dsh)跑編碼智能體時,常見情況是:某一輪提示寫偏了,Agent 已經改了一堆文件,對話上下文也跟着跑遠。這時只靠 Git 看 diff 不夠——diff 能告訴你現在改了什麼,但不會按「某一條用戶消息發出之前」把工作區和對話一起收回來。dsh 原生的「分支新對話」只分對話、不動項目文件,兩邊很容易對不上。

dsh-turn-rewind 做的就是這件事:在每條用戶消息上掛一個回退入口,按 Turn 把項目文件恢復到發送該消息之前,並可選擇從那一輪重新開對話。本文按社區目錄頁、GitHub 倉庫 README / README.zh.md、package.json 與官方 Harness 倉庫交叉覈對後整理。

DeepSeek Harness 的官方定位是「一切皆插件」,源碼在 deepseek-ai/deepseek-harness。下面介紹的社區插件目錄(deepseek-harness-plugin.com)是獨立站點,與 DeepSeek / 幻方沒有官方從屬關係,不要把它當成官方應用商店。

這是什麼

dsh-turn-rewind 是一款會話與消息類插件,由 Anionex 維護,倉庫爲 Anionex/dsh-turn-rewind。npm 包名是 @anionex/dsh-turn-rewind,當前 package.json 版本爲 0.1.1,許可證 BSD-3-Clause。截至 2026-08-17,GitHub 與目錄頁均顯示 65 stars。

它解決的問題可以概括成一句話:給 DSH Session 加一條圍繞工作區改動的安全邊界——先建恢復點,Agent / 用戶 / 外部程序改完工作樹後,再按路徑預覽差異,由人按下最終按鈕才真正恢復。

倉庫把兩層名字分得很清楚:

  • Turn Rewind:用戶看到的功能名、倉庫名和 Profile Bundle 名。
  • Change Ledger:底層持久恢復引擎。ctx.changeLedger 服務、磁盤格式和存儲路徑繼續用這個名字,因爲它描述的是可複用的快照與恢復層,而不只是 Web 上的回退按鈕。

本倉庫是標準 DSH Profile Bundle:package.json 聲明 dsh.bundle.patchcordis.patch.yml 直接掛載 @anionex/dsh-turn-rewind,不修改 DSH 主倉庫。

核心功能

按用戶消息錨定恢復點

當 Profile 同時提供 DSH Agent 服務時,插件會在每輪第一次 agent/pre-step、Agent 開始處理用戶消息之前,保存一份隱藏的文件狀態。保存失敗只會讓該消息無法回退,不會攔住用戶這一輪。

在 Web Profile 中,每條用戶消息下方會在時間和原生「複製」之後多一個純圖標「回退」,提示是「恢復到發送這條消息之前」。打開後先檢查已保存的文件狀態,給出分頁預覽,再讓你選擇恢復方式。

兩種恢復模式

模式 項目文件 對話
恢復文件並從這裏繼續(默認) 先自動備份當前狀態,再恢復項目文件 創建並打開一個結束於所選消息之前的新 Session,並把該消息原文填回輸入框
只恢復文件 同樣先備份再恢復 當前 Session 保持原位,內容不變

DSH Session 日誌只追加、不改寫,所以「從這裏繼續」會新建 Session,而不是截斷原對話。選中第一條消息時,Host 在同一工作目錄創建空 Session;選中後續消息時,在上一輪已完成的 turn/end 處分叉。原 Session 始終保留。

如果項目文件已經是發送所選消息之前的狀態,Turn Rewind 不會退化爲「只開對話分支」,而是提示無需恢復,並引導使用 dsh 原生的分支新對話。倉庫明確區分兩者:分支新對話只分對話、保持項目文件不變;Turn Rewind 一定恢復項目文件,再由你決定要不要開新對話。

先審閱、後寫入

恢復生命週期按倉庫說明是這樣走的:

創建恢復點
        ↓
agent / 用戶 / 外部程序修改工作樹
        ↓
預覽逐路徑變化
        ↓
審閱全部或部分恢復計劃
        ↓
在回退彈窗中按下最終恢復按鈕
        ↓
先建救援點 → 恢復 → 哈希驗證

彈窗預覽會基於當前工作樹生成一份短期、會話綁定的恢復計劃,這一步不改任何文件。真正修改前會再檢查所選路徑和倉庫狀態,並先創建自動備份。預覽之後出現的相關變化會使計劃失效。同一工作樹只要還有正在運行的 Agent(包括當前來源 Session),恢復會被阻止;空閒 Session 不阻止。

插件不會自動 commit、stash、reset、切分支、改 Git index,也不會替你判斷某項改動「應該回滾」。恢復後的內容可能表現爲當前 HEAD 上的未提交修改,因爲 commit、分支、HEAD 和 index 都保持原樣。Git 正在合併、變基等操作時仍會阻止恢復。

Change Ledger 負責什麼

普通 Git 面板可以展示當前 diff,但不擁有完整、持久的恢復生命週期。Change Ledger 獨立負責:

  • 內容尋址的恢復點 manifest
  • Git worktree、HEAD、分支和進行中 Git 操作的狀態圍欄
  • 從審閱到執行之間的 stale plan 檢測
  • 短期確認碼與 DSH 人工批准雙門檻
  • 每次恢復前自動建立救援點
  • 恢復後的內容哈希驗證
  • 恢復失敗後的自動回滾
  • DSH 重啓時對未完成操作日誌進行對賬
  • 可供其他插件依賴的 ctx.changeLedger 公共服務

持久格式見倉庫 docs/FORMAT.md,安全與故障模型見 SECURITY.md

安裝與啓用

社區目錄頁給出的安裝命令如下,在 DeepSeek Harness 終端中運行即可:

dsh plugin add github:Anionex/dsh-turn-rewind

dsh CLI 會從 GitHub 解析插件並安裝到當前配置。如需可復現安裝,目錄頁建議固定 commit 哈希:

dsh plugin add github:Anionex/dsh-turn-rewind#<commit>

<commit> 換成倉庫裏實際的 commit 哈希。目錄頁同時提醒:插件以當前 dsh 進程的權限運行,安裝時可能執行代碼。安裝前請檢查源代碼倉庫和許可證。

倉庫 README 還給出了按 Profile 掛載的寫法。先在檢出的插件目錄構建,再分別加到需要暴露該服務的 Profile:

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

修改 Profile Bundle 後需要重啓對應 DSH 進程。package.json 聲明的 Node 引擎是 ^22.19.0 || >=24.0.0

默認配置可在 Profile 的 patch 層覆蓋,倉庫給出的字段如下:

- id: turn-rewind
  config:
    storageDir: ~/.dsh/change-ledger/v1
    maxRestorePoints: 50
    maxTurnCheckpointsPerSession: 30
    maxFiles: 20000
    maxFileBytes: 16777216
    maxSnapshotBytes: 536870912
    planTtlMs: 900000
    staleLockMs: 30000

未配置時,storageDir 使用 $DSH_HOME/change-ledger/v1,未設置 DSH_HOME 時回退到 ~/.dsh/change-ledger/v1。倉庫要求它不得與被管理的 worktree 重疊。容量與用戶恢復點數量限制都是 fail loud:超限會直接失敗,而不是悄悄丟掉文件。自動 Turn 檢查點有獨立的每會話保留窗口,只清理自己最舊的檢查點;用戶和救援恢復點不會被靜默刪除。

典型用法

Web 界面回退

  1. 確認當前 Profile 已掛上 @anionex/dsh-turn-rewind,並且提供 DSH Agent 服務,這樣每輪開始前纔會留下可用的恢復點。
  2. 在 Web 界面找到某條用戶消息,點擊時間和「複製」後面的回退圖標。
  3. 彈窗會列出受影響文件,可展開「查看全部文件」。文件會按實際結果顯示爲「恢復之前的版本」「找回文件」「移除後來新增的文件」「恢復文件權限」或「恢復之前的文件類型」。
  4. 選擇「恢復文件並從這裏繼續」或「只恢復文件」,審閱後按下最終恢復按鈕。彈窗本身就是確認流程,不再要求重複勾選。
  5. 若選擇「從這裏繼續」,新 Session 創建失敗時,Change Ledger 會自動從備份把文件恢復到操作前的狀態。

Web Profile 提供同源 /turn-rewind 接口:根據所選 user/message 序號找到對應狀態,分頁返回文件預覽,生成短期、會話綁定的恢復授權,並把新對話創建交給 dsh 官方 Host 的 create / fork 生命週期。插件絕不會自動恢復文件。

給其他插件調用 Change Ledger

其他 Cordis 插件可以注入 changeLedger,走同一套結構化 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 是持久恢復點 ID。
}

完整格式類型從 @anionex/dsh-turn-rewind/format 導出;可信集成和測試可以從 @anionex/dsh-turn-rewind/core 使用獨立 Engine。

中途崩潰時,恢復也走同一套 API:用 listRecovery 找到操作的 rescuePointId,用 inspect 審閱該救援點,再對相關路徑走 planRestore / applyRestore。任何路徑寫入前,插件都會先創建救援點和持久 operation journal。如果 dsh 在非終態操作期間退出,下次啓動會把該操作標記爲 interrupted;另一個仍存活的 dsh 進程持有工作樹鎖時,不會誤判其操作。

適用場景與注意事項

適合這類情況:

  • 編碼 Agent 已經改壞工作區,希望回到某條提示發出之前,而不是隻開一條新對話
  • 只想把文件收回去,當前對話先留着對照
  • 需要給其他插件提供可檢查、可回滾的工作區恢復點

0.1 系列只支持普通 Git worktree,範圍內包括:tracked 文件(含創建恢復點時已經缺失的 tracked 路徑)、未被 .gitignore 或 Git 標準 excludes 忽略的 untracked 文件、文本和二進制普通文件、符號鏈接,以及可執行位等可移植權限位。

明確不支持或會直接拒絕的對象包括:sparse checkout、submodule gitlink(應分別進入每個 submodule 建恢復點)、ignored 文件、socket / 設備 / FIFO 等特殊文件、擴展屬性 / ACL / 所有者 / 時間戳 / hard-link 拓撲、Git index 和倉庫元數據、非 Git 目錄。如果 ignored 或其他未受管理的文件佔據了待恢復路徑,插件會拒絕恢復,不會遞歸刪除它。

還需要注意:

  • 插件以當前 dsh 進程權限運行,安裝前應檢查源碼與許可證。
  • 它不沙箱化其他進程,也不能阻止外部程序同時改文件。
  • 它對同一操作系統用戶不提供保密或防篡改保證;狀態文件默認僅所有者可讀寫,但宿主用戶仍被當作可信方。
  • 「從這裏繼續」在創建子 Session 時有 seedLength 圍欄:只有所選 user/message 和對應的精確 turn/start 都低於每一層持久圍欄時,才能複用祖先保存的狀態;子 Session 自己的狀態優先,兄弟分支絕不混用。

小結

智能體改文件是不可逆的操作,除非事先留下可驗證的恢復點。dsh-turn-rewind 把這件事做成 Turn 級的顯式回退:消息上掛入口,Change Ledger 管快照、審閱、救援和失敗回滾,Git 的 commit / 分支 / index 保持不動。當前版本把範圍收在普通 Git worktree,自動恢復被明確排除。

目錄頁:https://deepseek-harness-plugin.com/zh-CN/plugins/dsh-turn-rewind/

GitHub:https://github.com/Anionex/dsh-turn-rewind

羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

小夜