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 一律写「远程服务器」。
9.6 KiB
70 · anysearch 插件与 dsh 0.1.2-rc.1 不兼容 → admin 实例崩溃循环(已止损)
- 日期:2026-09-12
- 触发:用户报障「界面上实例正在启动,计数一直 01 01 01」+「正在定位问题插件(1/2):
@anysearch/anysearch-dsh(66s)」 - 结论一句话:插件在
import阶段就抛错(@deepseek-ai/dsh-llm未导出assertNever)→ dsh 的 plugin tree 加载是全或无 → 整个启动中止、进程退出 → 平台崩溃自愈反复重启(指数退避,5 次/10 min 逼近熔断)。摘掉该 bundle 即恢复;与 provider 配置、权限、网络都无关。 - 状态:✅ 已止损(2026-09-12 15:32 起实例恢复)|⚠️ 待决:AnySearch 这个能力是否保留(见 §八)
- 关联:档案 64(AnySearch 接入 · §9 曾预警「走候选池会出问题」)、档案 65(provider 托管段,未部署)、档案 20(崩溃自愈熔断)、档案 68(候选池启停链路)
一、现象
| 观察 | 用户侧 | 服务侧 |
|---|---|---|
| 实例启动卡住 | 「正在定位问题插件(1/2):@anysearch/anysearch-dsh(66s)」 |
每次重启都在同一插件抛错 |
| 进度计数不动 | 「计数一直 01 01 01」 | 启动在插件加载阶段中止 → 无后续进度可报 |
| 反复重启 | 页面持续停在启动态 | [crash-restart] 退避 1000→2000→4000→8000ms,restartsInWindow 1→4 |
二、根因(journald 原文)
Error: dsh: plugin tree failed to load: failed to apply loader entry include (cordis:include):
failed to import loader entry web-search-anysearch (@anysearch/anysearch-dsh):
The requested module '@deepseek-ai/dsh-llm' does not provide an export named 'assertNever'
...
at .../@deepseek-ai/dsh-tool-web/lib/index.js:5
import { assertNever } from "@deepseek-ai/dsh-llm";
链条:@anysearch/anysearch-dsh → 依赖 @deepseek-ai/[email protected] → 该包 import { assertNever } from "@deepseek-ai/dsh-llm" → 平台侧 @deepseek-ai/dsh-llm(随 dsh 0.1.2-rc.1,官方包零改动)没有这个导出 → ESM 实例化失败 → plugin tree failed to load → 进程 exitCode: 1。
定性:插件与平台的版本兼容性问题(API 面不匹配),不是配置错、不是权限问题、不是网络问题。
三、止损(2026-09-12 15:31–15:32,exec-session-C)
| 步 | 动作 | 证据 |
|---|---|---|
| 1 | 备份 profile 配置 | home/profiles/web/package.json.bak-anysearch-<ts>(cp -a) |
| 2 | 从 dsh.profile.bundles 与 dependencies 摘掉 @anysearch/anysearch-dsh 两处 |
python3 -m json.tool → JSON 合法;两处均无 anysearch 残留 |
| 3 | 等平台自愈下一次拉起(无需人工重启) | 15:31:53 起无新 crash-restart |
| 4 | 验证实例健康 | dsh web: http://127.0.0.1:34197/?token=… 已打印;curl 本地 → HTTP 401(服务在跑、待鉴权 = 正常);scope dsh-114801-f798a574.scope active |
未做(有意):未重启平台服务、未动 guest 实例(4092b965 全程正常)、未删 .credentials.yaml 里的 ANYSEARCH_API_KEY(留着便于换版本后复用)。
踩坑留痕:本轮前两次尝试用 ssh '<内联 script>' 拼复杂引号 → 命令被引号规则击穿(第 3/4 次同类错误)。再次确认技能里的硬性要求:复杂引号一律「本地写文件 → scp → 执行」,勿在 ssh 里内联拼引号。
⚠️ 兼容性 不是 provider 配置问题:
cordis.patch.yml里没有 anysearch 托管段,所以档案 65 的部署救不了这个故障(65 解决的是「provider 指向缺失」,这里是「插件本体 import 失败」)。
四、为什么「自动拉起」救不了它(回答用户问题 1)
能拉起,但不能自愈:
- 平台的崩溃自愈确实在工作 ——
[crash-restart]指数退避(1s→2s→4s→8s,上限 30s)+ 窗口熔断(10 分钟内 5 次 →crash-loop-circuit-open,之后停止自动重启等人工介入)。 - 但重启的前提是「偶发故障」。这里是确定性失败:配置里那个插件每次都会在同一个位置抛错 → 每次拉起都崩 → 一路撞到熔断。
- 所以判据是:「重启后错误是否变化」。错误一模一样 = 配置/兼容性问题,重启无用;错误随机或只出现一次 = 才可能是自愈能救的场景。
五、为什么进度卡在「01」(回答用户问题 2)
- 实例启动分两大段:(a) 平台侧启动 + 探活 → (b) dsh 进程内 plugin tree 加载。用户看到的「正在定位问题插件(1/2)」属于 (b) 的逐个加载阶段。
- dsh 的 plugin tree 加载不做部分成功:任一条 entry
import失败 →plugin tree failed to load→ 整棵树放弃、进程退出。所以第 1 个插件就失败时,永远走不到第 2 个,进度计数自然停在01。 - 「66s」是平台侧探活等待超时(等不到实例 HTTP 就绪)。
六、红线遵守
- R8(中断用户须先知会):本次影响面 = 仅 admin 实例
cce6d1cd,且它当时已在崩溃循环、本就不可用;未重启平台服务、未动 guest 实例。修复后该实例可用性由不可用变为可用。 - R7(禁批量):只改 1 个用户的 1 个配置文件(2 行);改动前
cp -a留回滚点。 - 服务器侧操作锁:已持有
fix-anysearch-crashloop(/opt/dsh/state/.op-lock/),完工释放。 - 未 push 任何仓库;未改平台代码。
七、回滚
P=/var/lib/dshs/users/cce6d1cd-b376-4304-80f0-0e1c58c9ffde/home/profiles/web
cp -a $P/package.json.bak-anysearch-<ts> $P/package.json # 恢复含 anysearch 的版本(会再次崩溃循环,仅在需要复现时用)
停实例:systemctl stop dsh-114801-*.scope(下次访问自动拉起)。
八、⚠️ 待决(需用户定:这是业务目标,不是技术选型)
问题本身是业务边界:AnySearch 这个能力还要不要?两条路:
| 选项 | 做法 | 代价 |
|---|---|---|
| A. 保留能力,换兼容版本 | 找一个与 dsh 0.1.2-rc.1 的 @deepseek-ai/dsh-llm 兼容的 anysearch 版本(或让插件侧不依赖 [email protected]);先在测试用户上验证再投放 |
需要版本调研 + 一轮验证;期间候选池里的 tgz 仍是坏的 |
| B. 下架 | 把 _anysearch_anysearch-dsh.tgz 从候选池下架(门户 #/plugins),平台继续用 DeepSeek 官方搜索(现状本来就够用) |
零风险;放弃 AnySearch 的搜索质量/成本收益 |
在决定之前有一个必须做的动作(建议立刻做):候选池里 _anysearch_anysearch-dsh.tgz 对任何用户都是坏的 —— 谁点启用谁崩实例(business-plugins 候选池是「全员可见」的)。建议先下架该条,再慢慢评估 A/B。
另附:.credentials.yaml 里 ANYSEARCH_API_KEY 是明文。若选 B,建议连它一起删(删 3 行,见档案 64 §8.4);若选 A,保留待复用。
九、✅ 处置决定与执行(2026-09-12 18:46–18:56)
9.1 用户决定:B —— 彻底放弃 AnySearch
(承接 §八 的 A/B 待决;用户 2026-09-12 明确选 B。)
9.2 执行(持文档库全局锁 + 服务器侧操作锁;未重启服务、未中断任何在线用户)
| 步 | 动作 | 证据 |
|---|---|---|
| 1 | 只读核查:候选池条目 / 凭据 / 各用户 profile 残留 | 池内 _anysearch_anysearch-dsh.tgz(11:16);无任何用户 profile 引用 anysearch;/opt/dsh/users/main/.dsh/.credentials.yaml 只匹配到 client-connection / browser-session → ANYSEARCH_API_KEY 已不存在(§8.4 的"删 3 行"因此无需执行) |
| 2 | 下架候选池条目(走平台 admin API,非手工改库) | DELETE /api/plugins/business/%40anysearch%2Fanysearch-dsh → HTTP 200 {"ok":true};audit_log id 175 delete_business_plugin |
| 3 | 复查 | DB business_plugins 无该行;池内 tgz 已移除;残留计数 0 |
| 4 | 清临时 session(R4) | user_agent='poc-curl2' 的临时 session 已清 |
下架姿势说明:用 mksess.cjs 造临时 admin session(600 s)→ curl -X DELETE 打 127.0.0.1:3080 → 用完即删。
刻意不手工改 SQLite —— 走 API 才会同时落到 audit_log,并顺带执行 PKG_NAME_RE 校验与 tgzPath 一致性检查。
9.3 同类排查(顺手做,未扩大范围)
用档案 71 已部署的判据模块(/opt/dshs/lib/web/plugin-compat.js → checkPluginCompat())对池内存量两条做静态体检
(预检只覆盖"新导入",覆盖不到存量 —— 这是本次顺带补上的盲区):
| 条目 | 体检结果 | 处置 |
|---|---|---|
dsh-univer-office |
level = ok(sawPlatformRef=true、无 findings) |
保留(池内现存唯一条目) |
@liustack/modlens |
level = unknown(不声明、也不 import 任何 @deepseek-ai 包 → 静态判不了) |
⚠️ 且 audit 有它的 plugin_incident(与 anysearch 同一批)→ AI 自主决定一并下架(代价不对称:下架可逆、保留=谁点谁崩);tgz 已备份 /opt/dsh/backups/plugin-pool-20260912/_liustack_modlens.tgz.20260912-185302;audit_log id 176 |
9.4 现状与回滚
- 候选池现存 1 条:
dsh-univer-office(已验兼容)。 - 回滚任一:把备份 tgz 经门户「上传」重新导入候选池即可(DB 行随之重建)。
- 平台搜索能力:继续用 DeepSeek 官方搜索 —— 即 §八 选项 B 的预期结果。