前言¶
DSH(DeepSeek Harness)團隊里正在用隊友(teammate / 可繼續的 subagent)跑長任務的人,遲早會碰到一個坑:隊友被派發到某個模型上之後,那個模型的額度用完了,隊友就卡死了。你在 GUI 裏切自己的模型、改全局默認模型,它都不受影響;給它發“繼續”,它會在同一條已耗盡的路由上再失敗一次。
本插件做的就是這件事:給隊友會話單獨加一個模型選擇器,讓它在額度耗盡後能換一個還有額度的模型繼續跑,不用註銷重建隊友,也不觸動 Lead 會話和全局默認模型。插件由 wufeng0105 維護,屬模型推理分類,本倉庫當前星標爲 0,發佈在 npm 上的包名是 @wufeng0105/dsh-teammate-model-picker,當前版本 0.3.2,要求 dsh >=0.2.1-rc.5 <0.3.0。
下面介紹這個插件解決的問題、它的實現方式、安裝與用法,以及需要注意的限制。
問題出在哪¶
DSH 的隊友在被派發的那一刻,就把當時 Lead 正在使用的模型寫進了自己的會話描述(durable descriptor)。此後每次喚醒——收到消息、恢復運行、冷恢復——都按這份描述裏的模型路由,不會重新解析當前默認模型。
於是當隊友被釘在一個已耗盡額度的模型上時,現有能力幫不上忙:Client 側官方選擇器對 subagent 會話直接返回空,Host 側通用 Session 路由對 origin: "subagent" 的會話一律返回 session/agent-busy。也就是說,隊友會話的模型座位是空的,你沒有地方可以操作它。
更具體地說:
1、你在 GUI 裏切換自己的模型,隊友不受影響,因爲它讀的是自己那份描述。
2、你給隊友發“繼續”,它沿用同一條已耗盡的路由,結果還是失敗。
3、官方的“切換模型”能力對隊友會話是明確拒絕的,所以不存在“順手點一下”的路徑。
README 提到,拒絕是三層疊加的結果,縫隙在哪裏這個插件的 DESIGN.md 裏有完整論證,這裏不展開。
插件做了什麼¶
它是一個 DSH 插件 bundle,分兩半,對接着上述兩層缺口:
- Host 半邊:把“每個隊友的模型覆蓋”存成插件 Config(
overrides),在隊友 Agent 發佈時(agent/created)爲它安裝與官方installModelSelection等價的三個 waterfall,覆蓋agent/request的最終路由。 - Client 半邊:在 composer 工具行的
conversation.input.right座位註冊一個模型選擇器,觀感對齊官方選擇器(兩級菜單:模型 / 推理等級),並且只在隊友會話裏渲染。
四條硬約束值得單獨列出來,因爲它們決定了插件的邊界:
1、不改任何官方包。
2、不引入新的 session 事件類型。
3、沒有 override 時零介入——派發路由原樣生效。
4、不動全局默認模型。
這四條意味着它沒有侵入 DSH 的內核,渲染的時機也只在隊友會話裏,不會污染普通會話。
安裝¶
插件已經發布到 npm,用包名安裝即可。安裝命令如下:
plugin_manager install_bundle target = @wufeng0105/dsh-teammate-model-picker
這一步做的是註冊表形態的安裝,裝的是獨立副本,之後你改源碼不會影響到它。開發時也可以直接指向本目錄,走 link 形態,改源碼立即生效。兩種形態生效的判定標準一致:返回 application: applied 且 warnings: []。
安裝會寫 profile 的 package.json(依賴、精確版本、dsh.profile.bundles)、pnpm-lock.yaml、pnpm-workspace.yaml,不會動用戶配置層。README 明確標註,裸路徑與 file: 形態在開發機上未複測,生產用請走上面的包名路徑。
卸載命令:
plugin_manager remove_bundle target = @wufeng0105/dsh-teammate-model-picker
這裏有三個層面要分清,別在排障時搞混:
1、安裝/卸載 bundle,改的是 profile 的 package.json 裏 dsh.profile.bundles。
2、bundle 的默認配置在源 cordis.patch.yml 裏。
3、用戶配置(也就是你實際選的模型)在用戶 profile patch 層的 - id: teammate-model-picker 那一行。
所以清空用戶配置不等於卸載 bundle,兩者是分開的。
典型用法¶
裝好之後,換一個隊友的模型就是四步:
1、打開一個隊友會話。
2、composer 工具行右側會出現模型選擇器,顯示當前模型和推理等級。
3、點開 → 「模型」→ 選一個還有額度的模型。
4、給隊友發“繼續”。
改動立即生效,並且寫入用戶 profile patch 層,重啓 Harness 後依然生效。如果之後想讓它回到派發時釘下的那條路由,菜單裏的「恢復派發路由」會刪掉這個隊友的 override。
Config.showPicker 是真正的開關:設爲 false 時選擇器不渲染,默認是 true。也就是說,如果你不想在任何隊友會話裏看到這個座位,可以在配置裏關掉。
怎麼驗證真的生效了¶
靜態部分有一條離線門,秒級跑完:
npm run check
它做的是靜態與契約檢查(身份、依賴方向、樣式 token、打包清單、版本區間、文檔預算)加上離線契約測試(Host fake-DSH、Client vm、負例自檢)。負例的存在是爲了保證每道門都有可能變紅,不是永遠綠燈。
環境檢查分兩步:
node tools/doctor.mjs # 版本區間 / row / 會話日誌格式 / schema 協議自述
node tools/doctor.mjs --contracts # 打印本插件依賴的契約清單
真正的行爲門需要一條真機隊友會話來判定——換模型到底有沒有把路由換過去,離線測不出來:
node tools/session-model-log.mjs <teammateSessionId>
它解壓 session.v4.jsonl.zstd 並打印四類事件:subagent/descriptor(派發時釘下的路由)、model/selection(切換意圖)、request/header(每次請求實際用的路由)、turn/end(含失敗原因)。判定標準和歷史實測記錄在 REPORT.md 裏。
適用場景與注意¶
適合誰:用 DSH 跑多 Agent 協作、把長任務派給隊友、並且依賴具體模型額度的人。當你懷疑隊友因爲封頂或額度問題停擺,又不想重建會話時,這個插件補上了原本空白的座位。
幾個必須知道的限制,README 裏都是明說的:
1、選擇器落在相鄰座位,不是官方那個模型座位。視覺接近,但 tooltip、aria 和鍵盤 Tab 序列與官方不完全同源。
2、插件複製了官方 CSS,類名前綴是 tmp_,只用了主題 token。官方改版樣式時需要同步;token 改名只會讓外觀退化,不會崩。
3、沒有模型白名單,展示的是運行時目錄全量。如果部署裏啓用了子代理模型白名單,兩邊可能不一致。
4、切換在活躍 turn 中生效於下一個 step,與官方 installModelSelection 語義一致,不中斷進行中的請求。
5、對 agent 暴露的操作尚未接線:換模型目前只有 UI 路徑,規範要求的 agent tool 還沒註冊,需要一次運行期 API 查閱才能接上。
另外要說明它的定位:這份實現是本地繞過,不是官方正解。官方修復到位之後,這個插件應該評估退役。具體哪幾條該由官方修,見 REPORT.md 的「上游建議」。
結尾¶
這個插件的價值在於,它把“隊友會話的模型座位”從一個明確拒絕的位置,變成了一個可以操作的位置,且沒有改動任何官方包、沒有改動全局默認模型。如果你正在用 DSH 跑隊友會話並被額度耗盡卡住,安裝一條命令即可,退出也是一條命令。
更多細節:
- 目錄頁:https://www.skillhub.cn/plugins/wufeng0105/dsh-teammate-model-picker
- GitHub:https://github.com/wufeng0105/dsh-teammate-model-picker
- 許可證:MIT