前言¶
用 DSH 時,一段對話的所屬項目有時候需要在事後調整:這段工作是在項目 A 裏開始的,後來發現它其實屬於項目 B;或者想把某段對話從「腳手架」挪到正式項目下。官方 API 只支持同一 workspace 內的排序(insertSessionBefore 要求會話已記賬),而會話的 JSONL header 是隻讀的,改不了 cwd。
dsh-session-mover 解決的就是這件事。它是 railgun20001 維護的一個客戶端插件,把側欄裏按項目分組顯示的會話拖到另一個項目標題上即可改歸屬。它明確不是文件遷移——會話的 JSONL 日誌、附件始終留在原項目目錄,插件只改 workspace 歸屬賬本,並把讀取側投影到新項目。
下面介紹它的能力、安裝方式和使用邊界。
定位與核心機制¶
插件把「會話屬於哪個項目」記在兩處:每個 workspace 記錄裏的 sessionIds(持久化賬本),以及註冊表內存索引 sessionPaths。它以賬本爲權威:
- 移動時先讓內存索引指向目標目錄,再改寫兩邊 workspace 的記錄;
- 啓動時把賬本里的歸屬重新灌回索引,讓移動跨重啓保持;
- 每次寫官方賬本前再修一次索引(寫前修復),避免被官方剪枝剔除。
安裝與啓用¶
npm 包名、倉庫名與插件內部 id 都是 dsh-session-mover。
從 npm 安裝:
dsh plugin --profile web add dsh-session-mover
從源碼安裝:
git clone https://github.com/railgun20001/dsh-session-mover.git
dsh plugin --profile web add "link:C:/work/dsh-session-mover"
也可以在 DSH 的「插件」頁點「添加插件」選擇本地目錄。宿主半(index.js)隨 profile 熱加載,客戶端半(client.js)需要刷新一次頁面(Ctrl+R)。
典型用法¶
- 側欄按項目分組顯示會話(默認視圖);
- 按住任意一段對話,往另一個項目的標題上拖;
- 目標項目亮起描邊,鬆手即完成移動。
拖動落點有幾種差異:拖到目標項目的某個會話行上會插到那個會話前面;拖到項目標題上則排到最前。拖到側欄列表上下邊緣時列表自動滾動,靠近邊緣滾得越快,長列表跨分組不用先手動翻頁。拖回原項目或拖到同一分組內,仍是官方的手動排序,插件不干預。
移動成功後:
- 對話立即出現在目標項目下,原項目不再列出它;
- 若該對話已經加載,工作目錄一併切到目標項目目錄,後續工具調用在新目錄執行;
- 若尚未加載,歸屬已生效但工作目錄保持原樣,控制台日誌會說明;
- 從移動過的會話分叉出來的新對話也歸到目標項目,跟隨父會話對齊賬本、索引與工作目錄。
插件不顯示提示條,移動結果寫進瀏覽器控制台(console.info / console.warn)。
受管會話與自檢¶
被移動過、header.cwd 還停在原目錄的會話稱爲受管會話,插件逐條記在移動日誌裏:
$DSH_HOME/dsh-session-mover/journal-v1.json
DSH_HOME 未設置時是 ~/.dsh。日誌只存「會話 id → 賬本目錄 / 原目錄 / 時間 / 來源」,不含對話內容。查看受管清單:
__DSH_SESSION_MOVER__.managed()
// 或:GET /dsh-session-mover/api/managed
宿主每次啓動會打印一行自檢摘要:
[dsh-session-mover] 自檢:受管會話 3 個(目標目錄缺失 0,原目錄已不存在 1)
目標目錄缺失 意味着文件樹與工具調用會報 ENOENT;原目錄已不存在 意味着會話文件可能已隨舊文件夾消失。兩類情況都會逐條 warn。
卸載與禁用前必須拖回¶
這是本插件最重要的使用約定:歸屬賬本被改過,而「會話屬於哪個項目」的成員過濾仍按 header.cwd 判斷。插件在場時會補回這層差異,插件缺席時(卸載、禁用、加載失敗、升級不兼容),移動過的會話會從所有項目裏消失,落到「未分組」。這不是數據丟失:重新啓用插件或把會話再拖回任意項目即可恢復。非 web/webServer 的 profile(例如純 CLI)不加載本插件,看到的始終是原項目歸屬。
要回到原項目,直接把會話拖回去即可,插件不提供額外的「還原」命令。
寫入路徑的兩道護欄¶
插件不滿足官方「header.cwd 必須等於 workspace 目錄」這條不變式,因此官方有兩處寫路徑會踩到移動過的會話:
- 寫前修復:官方
WorkspaceEntity.mutate()按內存索引sessionPaths剪枝,不在該目錄的會話會被從賬本里刪掉並落盤。護欄在寫之前按移動日誌修好索引,避免改名、排序、刪除、新建會話這類無關操作靜默剔除受管會話。 - attach 接管:官方
attachSession()要求header.cwd等於目標項目目錄,否則拋錯。護欄在失敗後按賬本接管,只有能證明會話確實屬於本項目(賬本指向它,或它是本項目某個會話的分叉)時才繞過校驗,其餘情況原樣拋錯。
讀取側的兩條接縫¶
歸屬還有第三個讀者:任何按會話 cwd 推導目錄的界面。插件不改寫會話文件,而是補兩塊接縫:
- 宿主側:用 typert 的 effect-scoped 覆蓋點接管
workspaceFileScope查找參數,讓文件系統接口按賬本而不是 header 解析工作區根。 - 客戶端側:宿主多開一個只讀的
/api/ownership,客戶端半在會話列表快照的寫入處把cwd覆蓋成賬本目錄,移動完成後立即重放快照,不必等頁面刷新。
唯一寫入會話日誌的地方是「工作目錄切換」:會話已加載時插件調用官方的 workingDirectory.set(),追加一條 working-directory/change 事件;未加載時跳過,不寫任何東西。
已知邊界¶
- 不是文件遷移:會話 JSONL、附件、崩潰恢復目錄仍在原項目目錄。刪除或改名舊項目文件夾、按目錄備份、換機遷移都會漏掉已「移動」的會話。
- 讀取側只覆蓋兩處:
workspaceFileScope與客戶端會話列表快照。ACP/IDE 會話列表、CLI、第三方插件、導出與備份仍按header.cwd看。 - 歷史消息裏的舊絕對路徑:在新項目的 workspaceRoot 下會變成越界(
workspace-file/outside-workspace)。 - 目標目錄消失時:官方
workingDirectory.ensure()會把工作目錄拉回舊項目,自檢只告警,不自動修復。 - 與「真遷移」類插件互斥:如
dsh-workspace-mover會搬文件並改寫 header,兩者同裝必然互相打架。
適用場景¶
適合把會話按項目分組管理、且願意接受「歸屬與文件位置分離」這層模型的 DSH 用戶。運行時以當前 dsh 進程權限執行,安裝前應檢查插件源碼與許可證(MIT)。插件對 WorkspaceEntity 內部結構做運行時探測,結構不符時移動會失敗並給出說明,不寫壞數據;讀取側與護欄全部按「失敗就降級」處理。
源碼內附測試矩陣(test/core-test.mjs、scroll-test.mjs、view-test.mjs、client-test.mjs、fork-test.mjs、guard-test.mjs、managed-test.mjs、workdir-test.mjs),真機驗收矩陣見 docs/acceptance.md。
小結¶
對於「會話開錯了項目分組」這個場景,dsh-session-mover 以最小改動解決了歸屬問題:拖動即改賬本、不搬文件、可拖回還原。代價是會話文件仍在原目錄,且卸載或禁用前必須先把移動過的會話拖回。
- 目錄頁:https://www.skillhub.cn/plugins/railgun20001/dsh-session-mover
- GitHub:https://github.com/railgun20001/dsh-session-mover