@local/dsh-plugin-guard:DSH 插件運維臺介紹

前言

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。

它回答的問題分三類:

  1. 裝了爲什麼沒反應:哪些 bundle 解析失敗、peer 不滿足、裝了沒啓用、tool id 衝突。
  2. 改之前能不能先看清楚改什麼:逐條列出動作、目標文件、改前改後內容、是否可逆、是否需要重啓,由你勾選後才執行。
  3. 裝之前這個包寫得對不對:對第三方包做靜態體檢。

核心能力

能力 回答什麼問題 什麼時候用
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 有三條例外,必須一起照抄:

  1. 聲明瞭 engines.dsh 就只看它,peerDependencies 整段忽略;
  2. peer 名只看 @deepseek-ai/dsh 與 @deepseek-ai/dsh-* 前綴(@deepseek-ai/cordis、@deepseek-ai/schemastery 不參與判定);
  3. 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 會退回上游寫法,故障隨之回來。

典型用法

  1. 插件「裝了沒反應」:先跑 guard_audit,它會區分無效裝載、bundle 解析失敗、peer 不滿足、裝了沒啓用、tool id 衝突。
  2. 確認要修:跑 guard_plan,拿到逐條改動清單;在面板上只勾選同意的那些,再 guard_apply(planId, approved[])。任何一條校驗不通過都會返回 stale-approval 並拒絕執行。
  3. 不想手動管:保持默認 selfHeal: true 啓動自愈,或在面板上跑 guard_heal。
  4. 想自己開發插件或裝第三方包前:跑 guard_check_bundle,它會檢查 manifest、patch、語法、client 包裝器、locale、slot、副作用聲明、skill 預算等項。
  5. 想先只看看不寫盤:把 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

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

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

小夜