前言¶
DSH 的模型選擇器、設置頁、數據目錄,都由 profile 裏掛上的 bundle 決定。插件裝了,不等於掛上了。dsh.profile.bundles 裏有它、node_modules 裏文件齊全、日誌裏卻一行不提——這種情況排查起來很麻煩,因爲故障不報任何錯誤。
下面介紹的 @local/dsh-plugin-guard(維護者 wodaimnd)就是爲這一類問題做的工具。它把 dsh-compat-vet 的審查修復層和 dsh-forge 的裝前體檢合成一個插件,補上了兩者都沒有的能力:判斷 bundle 裝載行的 name 字段能不能被 loader 解析,以及在不弄壞別人包的前提下修好它。
聲明的依賴範圍(見後面「安裝與啓用」)是本地 link: 安裝,功能全部在本插件進程內完成。
這是什麼¶
一個 DSH 插件,帶側欄面板和 6 個 guard_* 模型工具。分類爲 admin-security。倉庫地址在 GitHub,包名爲 @local/dsh-plugin-guard,版本 0.1.1,private: true。
它回答的問題分三類:
- 裝了爲什麼沒反應:哪些 bundle 解析失敗、peer 不滿足、裝了沒啓用、tool id 衝突。
- 改之前能不能先看清楚改什麼:逐條列出動作、目標文件、改前改後內容、是否可逆、是否需要重啓,由你勾選後才執行。
- 裝之前這個包寫得對不對:對第三方包做靜態體檢。
核心能力¶
| 能力 | 回答什麼問題 | 什麼時候用 |
|---|---|---|
guard_audit 體檢 |
哪些插件裝上了卻掛不上 | 面板不見了、設置頁少了東西、升級 dsh 後功能消失 |
guard_plan → guard_apply 計劃 + 授權 |
要改哪幾個字節、可不可逆、要不要重啓 | 準備修,但不想讓工具擅自動別人的包 |
guard_heal 自愈 |
把 name 解析不了的裝載行就地修好 | 想徹底不用管 |
guard_check_bundle 裝前體檢 |
準備裝的包寫得對不對 | 自己寫插件、或裝第三方包之前 |
guard_undo 撤銷 |
修完發現不對 | 按 journal 逐字節寫回 |
六個工具(guard_audit、guard_plan、guard_apply、guard_heal、guard_check_bundle、guard_undo)都在 tools: true 時註冊,關掉配置 tools 就退化爲純面板。
它補的那一格:裝載行 name 解析¶
舊 dsh-compat-vet 查不出這一類故障。典型症狀是:插件裝在 profile 裏,dsh.profile.bundles 裏有它,node_modules 裏文件齊全,但設置頁沒有它的項、模型選擇器沒有它的分組、數據目錄從未創建,日誌裏一行不提。
原因在於它自帶的 cordis.patch.yml 裏裝載行寫了裸包名,而包本身是 scoped 安裝:
- insert:
- id: our-free-model
name: 'dsh-our-free-model' # ← 真名是 @ebony-vinyl/dsh-our-free-model
config: {}
loader 拿這一行的 name 走 Node ESM 解析。dsh-app-boot 的 HostResolvedRootInclude.import 裏,裸名按 dsh 安裝目錄解析,scoped 名和相對路徑按 profile 目錄解析。裸名在兩邊都不存在,於是 MODULE_NOT_FOUND,宿主半身和瀏覽器半身一起掛不上。
爲什麼不能往 profile 層的 cordis.patch.yml 追加三行解決:dsh-app-boot/lib/index.js 的 applyEntryPatches 裏,name 不是可覆蓋字段,只用來斷言「你改的是你以爲的那一行」,不匹配時 continue 跳過。所以補丁必須落到 bundle 自己的 patch 文件上。
因此自愈分兩級:
一級(默認):就地改字節。 只替換那一行 name 的對應片段,其它字節、註釋、引號風格全不動:
- name: 'dsh-our-free-model'
+ name: '@ebony-vinyl/dsh-our-free-model'
二級(包目錄只讀時):不碰 bundle 字節,改用 profile 層補丁。 停掉壞行再加一條好的:
- id: our-free-model
disabled: true
- insert:
- id: our-free-model--guard
name: '@ebony-vinyl/dsh-our-free-model'
config: {}
buildMap(insert) 保證後一條 patch 能引用前一條插入的行,兩段必須按順序放。
授權閘門:plan / apply / undo¶
在部分環境裏,DSH 原生的確認對話不會彈出(例如被改成 decide() => 'allowed-once')。這時「點一下就改別人包」的按鈕沒有護欄。本插件把確認建在協議層,不在界面上做彈窗:
plan(只讀)→ planId + 每條:動作 / 目標文件 / 改前改後 / 可逆性 / 是否需重啓
│
├─ 用戶在面板上逐條勾選 ← 這就是授權
▼
apply(planId, approved[]) → 校驗 planId 有效(未過期、未執行)
→ 校驗指紋未變
→ 只有 approved 裏的條目執行
→ 任一條不滿足:拒絕,錯誤碼 stale-approval
▼
複檢 → 本次批准的 issue 是否還在?不在了纔算成功
▼
└→ 不在就自動回滾本次改動的全部字節
指紋由以下內容計算 sha256:報告的 checkedAt 與 summary、package.json 字節、cordis.patch.yml 字節、每條目標文件的字節哈希、已裝版本。報告證明「當時判過」,字節哈希證明「現在改的還是同一個東西」。有人在兩個時點之間手動改過 patch,或其它安裝動作重寫了 node_modules,校驗都會失敗並返回 stale-approval。
計劃有效期默認 15 分鐘(planTtlMs: 900000),超時後 apply 同樣返回 stale-approval。複覈不通過會自動回滾本次改動的全部字節;需要手動回滾時用 guard_undo(journalId),按 journal 從備份逐字節寫回。
與 dsh-compat-vet / dsh-forge 的關係¶
本插件合併了兩個舊插件的能力,並修掉了兩個誤報。
| 舊插件 | 保留 | 新增 / 修改 |
|---|---|---|
dsh-compat-vet |
三層複檢、backupProfile/restoreBackup、explainErrorCode、迴環與同源校驗 |
新增 patch-bundle-row / activate 動作;新增 plan/apply/undo 協議;修掉內置包誤報 |
dsh-forge |
裝前靜態體檢全部判據、賬本與 diff | 改名爲 auditBundle;工具名換成 guard_* 前綴 |
@local/market-verdict |
字面量口徑的 tool/service 衝突掃描 | 衝突按包對聚合,避免同名 tool 攤成幾十條淹沒真問題 |
誤報一:內置包被報成未解析。 @deepseek-ai/dsh-base、@deepseek-ai/dsh-web-app 在 app.asar 裏,createRequire 解析不到但確實掛得上。舊 compat-vet 只按 createRequire 判定,每次都產生兩條噪音。正解是讀安裝體的 package.json dependencies(app-boot 自己就這麼做的);爲此插件內置了最小 asar 讀實現 lib/asar.js,只讀 header 索引,不寫、失敗返回 undefined。
誤報二:peer 判定口徑。 dsh-app-boot/lib/index.js 的 unmetRequirements 有三條例外,必須一起照抄:
- 聲明瞭
engines.dsh就只看它,peerDependencies整段忽略; - peer 名只看
@deepseek-ai/dsh與@deepseek-ai/dsh-*前綴(@deepseek-ai/cordis、@deepseek-ai/schemastery不參與判定); - range 對着
runtimeVersion求值,而不是對着 peer 包自己的版本(workspace:^/~/*視爲當前運行時)。
按直覺寫判定會得出「插件聲明的 cordis 版本與運行時衝突」的假結論,而 cordis 根本不在判定範圍內。
配置¶
配置寫在 cordis.patch.yml 的 config 裏:
| 鍵 | 默認 | 說明 |
|---|---|---|
tools |
true |
註冊 6 個 guard_* 模型工具,關掉後退化爲純面板(0 token) |
pageUi |
true |
掛 /api/dsh-plugin-guard 與側欄面板 |
warnOnStartup |
true |
啓動時把阻斷問題打到 stderr |
selfHeal |
true |
啓動自愈;關掉後退化爲純只讀體檢 |
selfHealDryRun |
false |
true 時只報告打算改什麼,不寫盤 |
syntaxCheck |
true |
裝前體檢是否跑 node --check |
ledgerDir |
.plugin-guard |
狀態目錄(計劃 / journal / 自愈賬本),相對工作區根 |
planTtlMs |
900000 |
計劃有效期,15 分鐘 |
maxScanBundles |
64 |
最多掃多少個 bundle 的 patch |
promptSection |
true |
往系統提示詞寫一段「有插件掛不上」的提醒;無問題時不佔上下文 |
常用組合 selfHeal: false + tools: true:只報告、不自動改盤,所有改動都要面板上勾選。
安裝與啓用¶
README 裏的安裝方式是本地 link: 安裝,需要先準備好插件目錄,再改 profile 的 package.json:
$p = "C:\Users\Shinelon\.dsh\profiles\desktop"
# ① 依賴(link: 安裝,改動即時生效)
# package.json → dependencies 里加:
# "@local/dsh-plugin-guard": "link:G:/AI PGRM/DSH/plugins/plugin-guard"
pnpm install --dir $p
# ② 啓用(bundles 末尾加一項)
# package.json → dsh.profile.bundles 里加 "@local/dsh-plugin-guard"
# ③ 重啓 DSH Desktop
與 dsh-compat-vet、dsh-forge 並存安全,工具名用 guard_* 前綴不會撞 id;但功能重疊,建議啓用本插件後停用那兩個,避免同一件事有兩個入口。
回滾:
$p = "C:\Users\Shinelon\.dsh\profiles\desktop"
# ① 停用:從 dsh.profile.bundles 刪掉 "@local/dsh-plugin-guard"
# ② 撤銷:guard_undo(journalId) 或面板「記錄」頁的「撤銷」按鈕
# ③ 徹底移除依賴:從 dependencies 刪掉那行,pnpm install --dir $p
# ④ 清狀態目錄:Remove-Item "$p\..\..\plugin-guard" -Recurse -Force
有一點需要注意:自愈改過的 bundle patch 會在 pnpm install 之後被重寫回原樣,因爲 node_modules 是 pnpm 的產物。這不是問題——下次啓動自愈會再跑一遍(冪等)。「徹底不用管」成立的前提是本插件保持啓用;停用之後那些包的 patch 會退回上游寫法,故障隨之回來。
典型用法¶
- 插件「裝了沒反應」:先跑
guard_audit,它會區分無效裝載、bundle 解析失敗、peer 不滿足、裝了沒啓用、tool id 衝突。 - 確認要修:跑
guard_plan,拿到逐條改動清單;在面板上只勾選同意的那些,再guard_apply(planId, approved[])。任何一條校驗不通過都會返回stale-approval並拒絕執行。 - 不想手動管:保持默認
selfHeal: true啓動自愈,或在面板上跑guard_heal。 - 想自己開發插件或裝第三方包前:跑
guard_check_bundle,它會檢查 manifest、patch、語法、client 包裝器、locale、slot、副作用聲明、skill 預算等項。 - 想先只看看不寫盤:把
selfHealDryRun設爲true,或用面板上的 dry-run 查看自愈計劃。
驗證¶
README 提供了三套測試命令:
cd "G:\AI PGRM\DSH\plugins\plugin-guard"
# ① 協議正確性(合成 fixture)
node test/e2e.mjs
# ② 真機集成(真實 profile + 真實 asar)
node test/integration.mjs
# ③ 瀏覽器半協議形狀
node test/client-contract.mjs
README 中記錄的期望結果:e2e.mjs 25/25,integration.mjs 16/16,client-contract.mjs 25/25。其中 e2e.mjs 有兩個容易踩的坑:故障包目錄必須是 scoped 佈局,否則裸名恰好可解析、復現不出故障;修復只改 patch 行、不改 bundles 條目,否則 bundle 級仍然解析不了。
適用場景與注意事項¶
適合這幾類人:在 DSH 裏維護多個插件、遇到過「裝了沒反應」排查成本高的人;自己寫插件或裝第三方包之前想做一輪靜態檢查的人;不想每次手工改 bundle patch 的人。
幾點要留意:
- 插件以當前 DSH 進程的權限運行,會註冊 6 個模型工具和麪板接口,寫文件只落在兩類位置——bundle 的
cordis.patch.yml(僅授權或自愈時,帶字節備份)和你自己工作區下的.plugin-guard狀態目錄。安裝前應先讀一遍源碼與倉庫聲明,確認沒有問題再啓用。 - 裝前體檢的判據是靜態的,不是運行時模擬。
- 自愈依賴本插件保持啓用;停用後上遊寫法會迴歸。
- 這些事在 README 裏標註的 Python/Node 驗收腳本是開發者側的,普通使用者不需要跑。
已知邊界¶
README 自己列的誠實聲明:
- tool id 掃描只認字面量(
defineTool({ name: 'x' })/.register({ name: 'x' })),拼接出來的動態名字不在視野內; !!js表達式的 name 靜態判不了,標爲expression並跳過;- 相對路徑的 name 只查
package.json存在性,不模擬 loader 錨點解析完整鏈; - 多包 tool 衝突時只提供「隔離其中一個」的選項,不替你決定留哪個;
- 升級 / 卸載(README 因截斷未盡列,完整列表見 GitHub 原文)。
結尾¶
@local/dsh-plugin-guard 的價值在於把「插件明明裝了卻掛不上」這類無日誌故障的排查和修復收斂到一個入口,並且把改動前的計劃、授權、回滾都留在協議層,而不是靠界面彈窗。如果你想在裝插件前先做一輪靜態檢查,或想讓自愈在啓動時順手修好裝載行,這個插件可以直接用。
目錄頁:https://www.skillhub.cn/plugins/wodaimnd/dsh-plugin-guard
GitHub:https://github.com/wodaimnd/dsh-plugin-guard