Files
dsh_shenxian/dsh-server-docs/04-调整方案/88-内置dsh安装路径探测-修静默失效P1.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

8.7 KiB
Raw Blame History

88 · 内置 dsh 安装路径按序探测 —— 修「厂家目录 / 平台包目录在 /usr/lib 布局下静默失效」(P1)

  • 日期:2026-09-14
  • 状态:✅ 已修复并验证(源仓 cbaf3ad = 主修;1d72e8f = 降级留痕 + 消漂移)
  • 触发:开源导出会话提 P1 交接单(dsh-laijing-github\_交原仓会话_模型目录安装路径缺陷_20260914.md),复核成立后经用户「确认修改」落地
  • 落点:新文件 src/web/dsh-install.ts、src/web/model-catalog.ts、src/web/plugin-compat.ts、poc/workspace-scoped-picker/lib/index.js(+README)、README.md、scripts/verify-dsh-install.mjs、插件 business-plugins 0.3.13 / workspace-scoped-picker 0.1.5

TL;DR|平台有三处把「内置 dsh 的安装目录」写死成 /usr/local/lib/node_modules/@deepseek-ai/dsh。而 npm root -g 的落点随发行版变(发行版包管理器装的 Node 常见 /usr/lib/node_modules)⇒ 在那种机器上:厂家目录读成空(模型设置只剩内置 + 自定义)、插件兼容性预检的安全网整体失效(platformPkgCount()=0)、目录选择器 import 即抛。三处失败都不报错(前两处静默降级),所以在本机(/usr/local,恰好等于写死值)怎么也测不出来。修法 = 新增 dsh-install.ts 按序探测(env → dsh 可执行文件解软链 → 常见全局根 → npm root -g → 历史默认值),并把降级改为可观测。


一、症状与影响面

面向 影响
本平台(bt-server 等 /usr/local 布局) 零影响 —— 写死值恰好等于真实落点(实测 platformPkgCount=223、厂家目录 39)
install.sh 部署的机器(如测试服 test106,/usr/lib) ① 兼容性预检安全网失效 —— 不再拦 semver / 导出符号不兼容的插件(档案 71 白做);② 目录选择器不可用(workspace-scoped-picker import 抛错);③ 厂家目录空 ⇒ 新增厂家只剩「内置 DeepSeek + 自定义」;④ 逃生口未暴露 ⇒ 使用者无从自救
开源发布 发布阻断项:以「这功能好像没实现」的形式变成 issue,无任何报错可循

⚠️ 同一现象、两个根因:用户当天抱怨的「模型厂商选择怎么这么少、国内一家都没有」——在导出部署上正是本缺陷的症状;在我们服务器上则是另一个根因(档案 87 第一版真没接官方目录)。只修一个是不够的。

二、三处写死位置(第三处是交接单漏的)

# 位置 失效表现 是否静默
1 src/web/model-catalog.ts:30 厂家目录读成空 ⇒ GET /api/me/model-providers → {"providers":[]} 静默(catch 返回 [])
2 src/web/plugin-compat.ts:36 platformPkgCount() = 0 ⇒ 预检退化成「平台包目录不可读」 静默(catch 返回 0)
3 poc/workspace-scoped-picker/lib/index.js:34-35(DEFAULT_SEAM_CANDIDATES,README 同处引用) 本模块 import 抛错 ⇒ 实例内目录选择器不可用 响亮(有 DSH_SEAM_DIRECTORY_PICKER 逃生口、抛错列已试路径)

全仓复核:除这三处外无第四处(rg "lib/node_modules" 排除 node_modules/lib/**;Dockerfile 那两处是删镜像里的 npm,无关)。

三、为什么上一轮(以及更早两轮)没发现

  1. 失败被设计成静默,还被写成"特性":listCatalogProviders() 的 catch 注释原文是「目录不存在 ⇒ 返回空数组,不抛错:调用方据此降级」⇒ 失败等价于"这个功能没做"。
  2. 只在一个布局上验收:写死值在我们 /usr/local 恰好是对的 ⇒ 端到端验收(38 家全绿)只证明了「在这台机器上对」,判据里没有第二种布局。
  3. 前端验收用桩掩盖了后端:verify-platform-admin-section.mjs 给 /api/me/model-providers 打了 fixture ⇒ 前端断言全绿,与后端能否读到目录无关。
  4. 同一个未经检验的假设被复制了三轮:picker(档案 18 v3,最早)→ plugin-compat.ts(档案 71)→ model-catalog.ts(档案 87)。每轮都在同一种布局上验收通过 ⇒ 「同环境反复通过」掩盖了「跨环境从未验证」。
  5. 预警信号被当成注释而不是待验证项:plugin-compat.ts 原注释写着「平台内置包根目录(本部署事实;env 可覆盖…)」——"本部署事实"就是作者自知这是假设的痕迹。
  6. 成本极低的一步没做:当时只要在验收里加一句 DSHS_DSH_BIN=/nonexistent 或换个 root,立刻暴露 ⇒ 不是"发现不了",是有能力发现却漏了。

四、修复

src/web/dsh-install.ts(只依赖 node 内建 ⇒ 可单独编译、单独在目标机验证):

顺序 手段
1 env DSH_PACKAGE_DIR / DSHS_PACKAGE_DIR / DSH_COMPAT_ROOT / DSHS_COMPAT_ROOT(无条件优先,也是测试注入点)
2 平台配置的 dsh 可执行文件(DSHS_DSH_BIN,同时接受导出侧 DSH_USERS_PLATFORM_DSH_BIN)→ realpath 后向上找含 name=@deepseek-ai/dsh 的 package.json —— 最可靠一路
3 常见全局根:/usr/local/lib/node_modules、/usr/lib/node_modules
4 npm root -g(5s 超时,仅前面全落空时跑一次)
5 全落空 ⇒ 历史默认值(保持旧行为,由上层按「目录不可读」降级)

判定用 package.json.name 而非"目录存在",避免撞同名空目录;结果缓存(resetInstallPathsCache() 供测试)。

⚠️ env 名两侧都要认(交接件只写了导出侧名):源仓是 DSHS_DSH_BIN(src/config.ts:239,本仓前缀 DSHS_),导出侧才是 DSH_USERS_PLATFORM_DSH_BIN。照抄会让「最可靠那一路」在源仓侧永不生效。 📌 补充事实:我们服务器没设 DSHS_DSH_BIN(/etc/dshs.env 无)⇒ 我们走「常见全局根」(/usr/local 命中);test106 走 env 那一路(/usr/lib 命中)。两条路都必须留。

三处调用点改走该模块;picker 因跑在实例进程内拿不到平台代码,自带一份同思路实现(两处注释互相指认,改一侧要同步另一侧)。

五、验证(全部实测)

项 结果
本机 tsc --noEmit 0 错误
npm run verify 全套(新增 + 5 个既有验收脚本) 全绿
单测 16 pass / 0 fail
服务器 bt-server 回归 platformPkgCount=223、厂家目录 39(端点回 38)、诊断 readable:true
测试服 test106(/usr/lib = 原故障机) 「无 env / DSHS_DSH_BIN / DSH_USERS_PLATFORM_DSH_BIN」三种姿势全部解析到 /usr/lib/node_modules/@deepseek-ai/dsh(scope 240 项、pi-ai 数据 39)。全程只读(只在 /tmp 放脚本且已删)
picker 回归(最要紧:别把本来能用的弄坏) 在 /usr/local 下已装 0.1.5 的 lib import OK
漂移消除 两实例 workspace-scoped-picker 实装 0.1.5 且含新代码;business-plugins 0.3.13

六、降级留痕(本轮补的第二刀)

原问题能潜伏,根因是降级不可观测。现改为:

  • model-catalog:读不到 ⇒ console.warn(每进程一次,不刷日志)+ 新增 catalogDiagnostics(),并把 {dir, readable, count} 透出到 GET /api/me/model-providers 的 catalog 字段;
  • plugin-compat:platformPkgCount()=0 ⇒ 同样告警一次(原有 note 已在预检结果里可见);
  • 插件 0.3.13:目录不可读(含目录路径)与目录为空两种文案分开如实提示,不再静默给空列表。

⇒ 「功能没做」与「做了但读不到目录」从此可区分。断言见 verify-dsh-install.mjs 第 6 组(含「读不到 ≠ 目录为空」的区分)与 verify-platform-admin-section.mjs 的 2 条新断言。

七、防再犯

  1. 凡「读别人安装位置 / 版本」的代码 ⇒ 路径必须走解析层,且至少一条单测用假根。
  2. 静默 catch 必须留痕(告警或透出字段)。
  3. 验收判据要显式包含"非本机布局"(一句 env 注入即可)。
  4. 注释里出现「本部署事实 / 目前是 / 暂时」⇒ 当场转成待验证项。

八、仍未做(归开源导出会话)

把三个 env(DSHS_PACKAGE_DIR / DSHS_COMPAT_ROOT / DSHS_PI_AI_DATA_DIR)写进导出物的 install.sh + env 样例 + README。源仓没有 install.sh/deploy/(只在导出物里),故本档只在源仓 README 的配置表里登记了它们;回执见 dsh-laijing-github\_回复源仓会话_安装路径缺陷已修复_20260914.md。