Files
dsh_shenxian/dsh-server-docs/04-调整方案/92-官方推荐插件列表按dsh版本过滤.md
T
admin 5ad755116e chore(docs): 文档库并入代码仓(R4 选 a)+ 索引/台账跟进
1) dsh-server-docs/ 从工作区(原 E:\...\aliyun-dsh-server\dsh-server-docs)**整体并入本仓**,
   保留目录名 ⇒ 仓库内 dsh-server-docs/... 的相对引用天然继续有效;旧目录(含其 .git)已归档到
   工作区 _中间产物_待清理/,未随本提交带入。
2) .gitattributes:新增 `dsh-server-docs/** -text` —— 原文档库是 `* -text` + autocrlf=false,
   必须保持纯 LF,否则会被本仓的 CRLF 规则翻掉。
3) 活引用里的绝对路径已全部改到新位置(docs 的 INDEX / README / scripts / skills + 用户级 skills
   + ~/.workbuddy/settings.json 的 hooks);历史档案(04-调整方案/、archive/)按「只增不改」未动。
   ⚠️ hooks 路径改动需「完全重启会话」才生效(配置是会话启动快照)。
4) 交接单/T08:新增 §16「生产整体切换执行记录」(形态 / 落地动作 / **4 个只有真上线才暴露的真 bug** /
   验收证据 / 回滚命令 / 残留项);台账 T08 行 → 已完成并归档;03-路线图 §二 登记 T08 收尾项。
5) 统一称谓:**「本机」只指跑 WorkBuddy 的开发机**,47 / 106 一律写「远程服务器」。
2026-09-15 18:47:13 +08:00

11 KiB
Raw Blame History

92-官方推荐插件列表按 dsh 版本过滤(2026-09-14 落地)

  • 日期:2026-09-14 | 状态:✅ 已上线(插件 0.3.16 + 平台后端已构建重启)
  • 触发:用户原话 ——「设置选项下插件管理中的官方推荐插件列表 只能显示匹配当前dsh版本 和 超过当前版本的插件(超过的要明确标注)」

TL;DR 官方目录(awesome-dsh-plugin.com/plugins.json)不带 dsh 版本字段 ⇒ 只能逐包去 npm 查声明 (精简 packument 的 peerDependencies / engines,不下载 tarball),再与平台真实版本比。 分类 match / none / newer / older / unknown;默认隐藏 older(只声明了更旧版本), newer(要求更高的 dsh)橙色显著标注并给出「需 ≥ X,当前 Y」。 ⚠️ 两个"静默降级"陷阱(本轮实测踩到,都必须钉住): ① satisfies 必须带 includePrerelease: true —— 平台是 prerelease(0.1.5-rc.1 / 子包 0.1.5-rc.2), 默认语义下 ^0.1.2 甚至 * 都不满足 ⇒ TOP300 会把 dsh-univer-office、dshmarket 这些正在跑的 判成「只兼容更旧」(match 97 / older 139 → 容忍后 match 214 / older 22); ② 伞包 @deepseek-ai/dsh 必须单独解析 —— 它不在自己的 node_modules 里, 而实测的 3 条 newer 全部只写在伞包上(复用它 ⇒ 这 3 条永远判不出来)。 结果:TOP300 里 match 231 / none 66 / newer 2 / unknown 1,无 older;dsh-zotero、dsh-md-notes 被标注为「需 dsh ≥ 0.1.5-rc.2」。


一、需求拆解与判据选择

问题 结论 依据
官方目录有没有现成的 dsh 版本字段? 没有。3632 条只有 name/owner/url/page/category/description/npm/version/stars/downloads/install/added/tarball/screenshots 实抓 plugins.json 统计字段出现次数(version 是插件自身版本)
那版本要求写在哪? 写在各 npm 包的 peerDependencies(少数用 engines.dsh),且分布在几十个 @deepseek-ai/dsh-* 子包上,不是只写在 dsh 伞包 抽样 TOP60 + 随机 40:NONE 26 / 其余全是各种 peer:@deepseek-ai/dsh-*
拿什么跟它比? 平台该包的真实版本(dshScopeDir() 下各包的 package.json),与 plugin-compat.ts 判据 A 同源 §三 的陷阱 ②
成本? 一条 = 1 次精简 packument(accept: application/vnd.npm.install-v1+json,十几 KB)。结果按 <npm>@<版本> 落盘缓存 7 天 首访 5.6s(冷)→ 命中缓存 143ms

分类规则(src/web/plugin-dsh-compat.ts):

kind 含义 展示
match 声明范围(prerelease 容忍)覆盖当前版本 绿色「匹配」
none 没声明任何 @deepseek-ai/* 依赖 灰色「未声明」(不等于不兼容)
newer 有范围要求比平台更高(minVersion(range) > 平台版本) 橙色「需更高版本 ≥ X」 ← 用户点名要标注的一类
older 不满足且不要求更高(只允许更旧的) 默认隐藏,灰色「仅兼容更旧」
unknown 取不到元数据 / semver 不可用 / 范围不可解析 灰色「未验证」,照常显示

判定顺序:先判 newer(只要有一条要求更高就归 newer)—— 它比"只兼容更旧"更重要。

二、为什么默认过滤 + 为什么留逃生口

  • 用户要求「只能显示匹配 + 超过」⇒ 默认隐藏 older。
  • 但隐藏必须可见(本库反复强调的"别静默"):说明行会写「已按 dsh {cur} 过滤:本次候选中隐藏 {n} 个仅兼容更旧版本的插件」,并给出勾选框「含仅兼容更旧版本的」(= 后端 dshCompat=all,只标注不过滤)。
  • unknown 不隐藏:一次网络抖动 ≠ 不兼容。首访冷缓存时可能出现若干「未验证」,说明行同时给出「兼容判定仍在后台预热」提示。

三、两个"静默降级"陷阱(实测数据)

平台:伞包 0.1.5-rc.1 / 子包 0.1.5-rc.2(240 个 @deepseek-ai/* 包)

TOP300 抽样:
  严格语义(默认)        match 97 | none 60 | older 139 | newer 3 | unknown 1
  ① prerelease 容忍       match 214 | none 60 | older  22 | newer 3 | unknown 1
  ② 再把伞包单独解析后    newer 3 条全部认出(否则全是 unknown)
  • ① 的代价:默认语义下 older 139 里包含 dsh-univer-office(我们正在跑)、dshmarket(插件市场本身)、 dsh-context、dsh-cost-meter… ⇒ 照着默认语义过滤会把半张列表连能用的一起藏掉。 根因:semver 默认不允许 prerelease 命中不含 prerelease 的范围(0.1.5-rc.2 ∉ ^0.1.2,∉ *)。
  • ② 的代价:plugin-compat.ts 的 platformPkgDir() = <dsh 包>/node_modules/@deepseek-ai/<短名>, 而伞包 dsh 不在自己的 node_modules 里 ⇒ 它对伞包恒返回 null,被当作"平台没这个包,不算不兼容"。 于是只写在伞包上的那 3 条(dsh-zotero ^0.1.5-rc.2、dsh-any-background >=0.1.5-rc.2、dsh-md-notes >=0.1.5-rc.2 <0.1.6) 全部判不出来,而它们正是用户要的那一类。

⚠️ 不要顺手把 plugin-compat.ts 也"统一"成 prerelease 容忍:那里用默认语义是有意的 (注释原文:「与 pnpm 的实际安装判定一致」)—— 它是上传/导入的闸门,语义不同是正确的。 因此本功能另起模块 src/web/plugin-dsh-compat.ts,零改动 plugin-compat.ts。

📌 顺带发现(未改,属别人 lane):plugin-compat.ts 判不到伞包 ⇒ 声明 @deepseek-ai/dsh: ^0.1.6 的插件在上传闸门处会被放行。是否收紧要另立单(改它就是改既有闸门行为)。

四、改了什么

层 文件 内容
新模块 src/web/plugin-dsh-compat.ts(199 行) 平台版本表(含伞包)、semver 软载入(minVersion + includePrerelease)、platformDeclsOf()、judgeDshCompat()
路由 src/web/routes/whitelist.ts GET /api/plugins/whitelist 新增 dshCompat 参数(默认过滤,=all 只标注);响应新增 dshVersion / hiddenByDsh / countAll / compatFiltered,plugins[] 新增 dshCompat / dshRequires;判定落盘缓存 whitelist-cache/npm-dsh-compat.json(TTL 7 天,结构版本号 COMPAT_VERSION);后台预热(进程内一次,并发 6);请求内限并发 12 + 6s 预算,超预算判 unknown 并照常显示;判定窗口 360(> 页面上限 300,过滤后仍能填满)
前端 poc/business-plugins/lib/client.js(0.3.16) 表格新增「dsh 版本」列(+1 列 ⇒ paemptyRow 6→7);wlCompat() 徽章(newer 用 pending 橙色);工具条新增「含仅兼容更旧版本的」勾选;说明行给出过滤说明 + 隐藏计数 + 预热提示;pa.pl.hintOfficial 文案补充过滤规则
校验 scripts/verify-dsh-compat.mjs(新) 17 条回归锚点:prerelease 语义、伞包解析、判定顺序、只隐藏 older、hiddenByDsh/dshVersion 存在、落盘缓存、预热、前端徽章/逃生口/说明行
校验 scripts/verify-models-dict.mjs 引用键扫描从 ms.* 扩到全词典(pa.* 一样会漏)+ 新增 package.json 合法性断言(本轮真把 JSON 写坏过一次)
校验 package.json npm run verify 链追加上述两个脚本

五、验证

层 手段 结果
L1 类型/语法 本机 npm run typecheck + node --check ✅
L2 判据锚点 verify-dsh-compat.mjs 17 项 + verify-models-dict.mjs(全词典) ✅ 全绿
L3 算法预演 上服务器用真平台版本表跑 dry-run,对比两种语义分布 ✅ §三 的两组数字即此产出
L4 服务器构建 scp 源码(LF)→ bash scripts/ci.sh ⚠️ typecheck ✅、build ✅;单测 1 项失败但与本次无关(见 §六)
L5 接口真调 临时 session(直插 DB、用完即删)→ GET /api/plugins/whitelist ✅ 13 项断言全绿:默认响应不含 older、dshVersion=0.1.5-rc.1、hiddenByDsh=30、newer 2 条带 dshRequires;dshCompat=all 时 older 23 条出现
L6 上线实证 产物 sha1 比对 + 两 profile 装到 0.3.16 + 运行中实例启动时刻晚于包落地 ✅ manifest 里 wlCompat/pa.pl.compatCol 均在

铺发:后端 = tar(LF)→ /opt/dshs → npm run build → systemctl restart dshs(重启后 portal 200); 插件 = npm pack → /opt/dsh/artifacts/business-plugins-0.3.16.tgz → ensure-biz-plugins.cjs --all --restart(admin bundles=7 / guest bundles=6)。

六、⚠️ 与本单无关但必须上报:ci.sh 里 1 项单测长期红

test/db.test.mjs 的 「sqlite: concurrent setCredentialKey keeps exactly one enabled」 失败: 断言 exactly one key enabled,实际 5 !== 1。 根因 = 档案 87 的有意行为变更:口径①「条目各自开关、可同时启用」已删掉互斥 ⇒ 该测试的期望值过时,不是本次改动引起(本次只碰 whitelist.ts / 新模块 / 插件客户端,均不进 db.test.mjs 的依赖图)。 📌 建议:由 档案 87 的 lane 把该断言改为「允许并存的 N 把 key 均可启用」,或直接改造为按新口径断言。本单未动(跨 lane)。

七、回滚

  • 插件:删 /opt/dsh/artifacts/business-plugins-0.3.16.tgz → 再跑 ensure-biz-plugins.cjs --all --restart(会自动选回 0.3.15)。
  • 后端:备份在 /opt/dsh/backups/pre-92-20260914-204352/(src/web/routes/whitelist.ts + package.json) ⇒ cp 回去 + npm run build + systemctl restart dshs。 原文件 sha256:whitelist.ts 2fb4bf79…、package.json 2bc1f453…。
  • 新增文件 src/web/plugin-dsh-compat.ts 与缓存目录 whitelist-cache/npm-dsh-compat.json 可直接删(无依赖)。
  • 无数据迁移、无 DB 变更。

八、遗留

  • ⚠️ 代码侧与文档库本轮仍未提交(工作树留痕)。
  • 「older 是否该彻底隐藏」是产品判断:当前是「默认隐藏 + 勾选可看」。若用户希望永久不给看,去掉勾选框即可。
  • 判定窗口 360 / 页面 300 / 预算 6s 都是可调常量(在 whitelist.ts 顶部成组),若日后官方目录暴涨需要重新标定。
  • 未做「按需重新判定单个插件」(现在只能等 TTL 过期或删缓存文件)。