Files
dsh_ai1net_server/.workbuddy/memory/2026-09-下18.md
T
admin c1b5e4d966 chore(工作区): 全量入库 + 补齐 .gitignore(以工作区为准)
- 变更规模:新增 514 / 修改 62 / 重命名 155 / 删除 4(归档重组与文档轮次)
- .gitignore 修:`归档/**/db-cwd归一-备份-*/` —— 原规则写绝对层级(归档/db-cwd归一-…),
  目录搬进 归档/配置与备份/ 后**静默失效**,43 MB 的 DB 备份又变成未跟踪
- .gitignore 补:嵌套 git 内部数据(归档/内嵌git-20261008/、归档/skills-git-旧线-20261007/dotgit-原样移出/)
- .gitignore 补:运行态与部署副本(.workbuddy/collab/、.workbuddy/tools/、.workbuddy/.load-pending、.workbuddy/tmp-*)
- .gitignore 补:备份件(*.bak-*)
- 未跟踪文件从 2190 降到 890(其余为 归档/ 归档件与 .workbuddy/memory/ 知识文件,按口径入库)
2026-10-10 23:13:22 +08:00

30 KiB
Raw Blame History

工作日志 · 2026-09(第 19 片)

⚠️ 本目录日志已按【月】分片(2026-09-15 用户定,单文件 ≤50 KB):同月续片 2026-09.md / 2026-09-下.md / 2026-09-下2.md / 2026-09-下3.md / 2026-09-下4.md / 2026-09-下5.md / 2026-09-下6.md / 2026-09-下7.md / 2026-09-下8.md / 2026-09-下9.md / 2026-09-下10.md / 2026-09-下11.md / 2026-09-下12.md / 2026-09-下13.md / 2026-09-下14.md / 2026-09-下15.md / 2026-09-下16.md / 2026-09-下17.md / 2026-09-下18.md / 2026-09-下19.md / 2026-09-下20.md / 2026-09-下21.md / 2026-09-下22.md / 2026-09-下23.md 写入约定:一律 append 到当月最后一片;该片超 50 KB ⇒ 新建 2026-09-下N.md;⛔ 不要再按日新建 2026-09-DD.md。 覆盖来源:2026-09-13.md、2026-09-14.md 上一片:2026-09-下17.md 下一片:2026-09-下19.md


23:2x–23:5x 用户授权解锁 ⇒ 接手上床,T06 全流程闭环上线(craft-session-T06)

用户两句指令:「另一个会话已经删除了,继续执行完成这个功能开发」+「允许解锁操作」。按 R9 我没有自己删锁,而是用项目自带机制 handoff-guard.sh --release-exec 释放残留锁、再以 craft-session-T06 重新持锁(单 T06)。

做完的事(全部有实机证据):

  1. 取证官方 schema(本轮唯一未取证项,救回一个字段名):读服务器 [email protected] ⇒ 分区名 llm-pi-ai、凭据字段 apiKeyEnv、endpoint baseURL、协议字段是 api(不是 protocol)、ref 名须匹配 ^[A-Za-z_][A-Za-z0-9_]*$、provider/maxRetries 会直接抛错;并确认该插件在 dsh-base 的 bundle 列表里(真的会被加载)。
  2. 又挖到一条权威出处:官方 dsh-client-ui-settings/README.md:97「Non-loopback pages get no durable settings」⇒ 坐实「平台页必然报错」;并辨清与 03-路线图 决策 3 的冲突:那是 host 侧 trust fence(proxy 伪装 Host ⇒ 放行),这是浏览器侧持久化(真实域名 ⇒ unavailable)——两层都成立,85 §九 只验证了前者。
  3. DB 层:迁移 V6(credential_vault.route/base_url/api/models + users.shared_model_enabled)+ 各后端补 5 项;重定义 getEnabledCredentialKeyRef(老实现无 ORDER BY,互斥一删就任取一条 ⇒ keySourceOf/resolveApiKey 会飘)。
  4. 新落地层 src/web/model-landing.ts(纯文本、可测)+ 13 组回归;回归当场抓出真 bug:providers: 被写重复(第一版按"只看顶层行"找链,而 providers: 本就是缩进 2 的子键)。含一次性交接(认领老实现写下的 DEEPSEEK_API_KEY,否则"关共享即生效"不成立)。
  5. 接口 4 条 /api/me/* + 前端「设置 → 模型设置」(插件 0.3.11,settings.section id=model-settings order 100 全角色)。
  6. 出厂:代码 tar 管道(LF 化)→ 服务器 build 干净 → 重启;V6 实测落库;插件 0.3.11 铺发两实例(bundles=6);角色补丁 --force --restart(先留回滚点 /opt/dsh/backups/patch-20260913-234511/,跑完 diff 验证)。
  7. 验收四件事全绿:① 两账号 patch 均有 models 禁用行;② HTTP 实测新增/开关/删除 + 4 类非法入参被拒(400/409) + 前端 harness 全绿;③ 关共享 → 实例 .credentials.yaml 里 DEEPSEEK_API_KEY 消失、进程 env 恒 0 条,开回来又出现且与自定义厂家并存;④ settings.yaml 落地官方 schema 原文、实例启动成功无报错、删除后整块消失。
  8. 顺手修两颗雷:① ensure-role-profile-patch.cjs 的 --force 整文件覆盖会抹掉 admin 的 disable-hmr + workspace-scoped-picker 两个平台块 ⇒ 新增 stripManagedBlock() 只替换自己那段(实机 diff 证明另两块完整保留);② verify-mem-model.mjs 因档案 86 重构而长期失败的陈旧断言。
  9. 收口:档案 87 + 单 T06 归档 + 交接单/README.md §一 + INDEX.md(加 87 行 + §四) + 四件套(audit rc=0 / 一致性 ✓ / 对账 148 一致,仅剩别人的 1 个在途)+ 文档镜像按 SOP scp(只推自己的)。

暴露的流程问题(已上报,未代改):① 档案 82–86 尚未登记进 INDEX.md §二;② 04-调整方案/.lock-78/85/86/87 空占号目录堆积(87 已清);③ ⚠️ 别拿会话自述当线上事实 —— 上一会话称"未构建未部署",实际角色补丁已被它刷到线上。这次"先看服务器再动手"正是靠这一点救回两颗雷。


23:59 用户指令「同步项目到仓库」⇒ 两仓已提交并推送

  • 代码仓 eb50ca2 → 918f1d3(master,已 push;ls-remote 核对远端 = 本地)。 20 个文件。⚠️ 不可避免地把档案 86 的代码一并带上(admin-user-ops.ts / dsh.ts / server.ts 的 register / client.js 的改名段 / portal.html)—— 它已上线并端到端验证,且与我的改动同处一个文件、按文件粒度无法拆分;不带它仓库 tsc 直接失败(缺模块)。已在提交说明里明确披露。
  • 文档库 bc3a176 → bbccf35(main,已 push;同样 ls-remote 核对)。只 add 自己那 5 个(04/87、archive/T06、INDEX.md、交接单/README.md、docs-manifest.json)外加 04/86 —— 因为 87 与 INDEX 都引用 86,不带它会让仓库出现悬空引用(该档案另一 lane 已完成)。
  • 未纳入(属别人在途,一个没动):04/76、skills/{dsh-change-workflow,dsh-decision-method,dsh-feature-first,dsh-opensource-release}/SKILL.md、skills/dsh-plugin-diagnose/。
  • 📌 判据经验:git log origin/<branch>..HEAD 在本机不可靠(无 remote-tracking ref)⇒ 用 git ls-remote origin refs/heads/<branch> 与本地 rev-parse 对比才是权威判据。

09-14 00:0x–00:3x 用户实测反馈驱动补做:接官方厂家目录 + 页面按官方结构重做

用户原话:「新增模型条目怎么看不懂呢,是按照 dsh 自带的模型设置交互开发的吗,而且模型厂商选择怎么这么少、国内的一家都没有」→「页面就按照 dsh 模型设置的页面(使用项目 ui 规范),增加 admin 设置的模型管理就行」。

根因(我认下的设计做窄):第一版只支持「内置 DeepSeek + 手填一个 OpenAI 兼容网关」,把"选厂家"这件事整个漏了 —— 而官方 pi-ai 包里自带 38 个厂家(国内 11 家:蚂蚁 / 通义千问×3 / 小米×2 / 月之暗面×2 / 智谱×2 / MiniMax)。

官方依据(决定性):dsh-llm-pi-ai 的 config.d.ts 原文 —— "A route key is not required to name an installed pi-ai provider. When it does, that provider's endpoint, protocol, display name, and model catalog are the profile's defaults" ⇒ 目录厂家只需 apiKeyEnv,端点/协议/模型全免填。

做了四层:① 新文件 src/web/model-catalog.ts(只读官方 data/*.json,10 分钟缓存,不导入 pi-ai 运行时;中文名走显示表,选取范围以目录为准);② GET /api/me/model-providers(38 家 + cn 分组 + modelCount;排除 deepseek —— 平台已有内置入口);③ POST /api/me/keys 新增 provider(只收 apiKey);④ 前端按官方页结构重写(提供方行 + 新增先选厂家 + 中国大陆/国际分组 + 保留强化 admin 的「平台共享模型」)。

两个判据坑(都写进代码注释了):

  1. refForEntry 必须用「没有 route」判内置 —— 目录厂家也没有 baseUrl,沿用第一版的「baseUrl 为空」会把目录厂家的 key 写进 DEEPSEEK_API_KEY。
  2. 验收桩只收 children + placeholder,<optgroup label> 是 prop ⇒ 「中国大陆 / 国际」断言会假红;已扩桩(optgroup 的 label 也算用户可见文案)。

真环境验收全绿:目录端点 38 家 / 国内 11 家;加 moonshotai-cn 只给 key → settings.yaml 只写 apiKeyEnv(无 baseURL/api/models,正是官方语义)、.credentials.yaml 出现 MOONSHOTAI_CN_API_KEY 且与 DEEPSEEK_API_KEY 并存;非法厂家 400;删除后整块消失;本地 verify-platform-admin-section(+8 条新断言)与 verify-model-landing 全绿;zh/en 词典 282/282。

出厂与归档:插件 0.3.12(两实例 bundles=6)|后端 build + restart|档案 87 追加 §九 补做|代码仓 918f1d3→022b1f7、文档库 bbccf35→730249a(均已 push,远端哈希已核)|临时候话 / 测试条目 / 临时脚本全部清理(0 残留)。


09-14 00:20 用户「都推上去」⇒ 把别人的 lane 在途也代为提交,文档镜像首次双端全绿

背景:上一轮我报告"我的改动已全同步、只剩 6 项别人的在途",并问要不要一并提交 → 用户答「都推上去」。

做法(保住可归因性):按 lane 分两笔,提交说明里明确写"另一 lane 的会话沉淀,由用户 2026-09-14 拍板代为提交(原 lane 未提交)" —— 这样历史里能看出是谁写的、为什么由别人提交。

  • 69ce162 docs(76):univer 档追加第二轮(gateway「写入假错误」→ 0.2.25)与第三轮(AI 生成 Office 文件做成实例内能力 → 0.2.26)。
  • d1d1f11 docs(skills):4 份技能副本(decision-method 扩到 U1–U22 / feature-first 新增 §5.1 结论骨架 / change-workflow + opensource-release 增补)+ 新技能 dsh-plugin-diagnose v1.0.0(业务插件静默失败诊断:三层归属 + inject 差集 / glibc 直测 / 产物插探针三把尺子,09-13 univer 四轮排查沉淀)+ manifest 刷新。

顺手把镜像也补齐(这是"双端全绿"的关键):/opt/dsh/docs 一直挂着 1 个"内容不一致"(skills/dsh-opensource-release/SKILL.md)—— 把 6 个文件 + manifest 一并 scp 过去、mkdir -p 新技能目录、按既有约定 chmod 600。 ⇒ docs-sync-check.sh:149 一致 / 0 不一致 / 0 仅本地 / 0 仅服务器 → 双端一致 ✅(本库首次);docs-consistency.py ✓;两仓工作树均已清空,远端哈希与本地一致。

口径记录:这次代提交是用户一次性授权,不是常设规则 —— 下次仍按红线默认不动别人的在途;要动先问。 仍留的尾巴:技能是三处(本机 ~/.workbuddy/skills ← 文档库 ← 镜像),本次只对齐了后两处,本机工作副本未核;INDEX.md §二 里 skills/dsh-plugin-diagnose 这条未登记(同理 档案 82–86 也未登记)。

2026-09-14 工作日志

04:2x 复核「开源导出会话」发来的 P1 交接单:模型目录 / 平台包目录安装路径写死

来源:E:\ProgramData\AIProject\dsh-laijing-github\_交原仓会话_模型目录安装路径缺陷_20260914.md (提出方 = 开源导出会话;受理方标注为本会话;它没动源仓、没动锁,按 R9 停手)

复核方法(不照抄结论,全部自己取证)

  1. 先查全仓有几处 —— ⚠️ 第一次我 grep -v node_modules 把要查的行本身也滤掉了(那些行就含 node_modules 字样),得出"0 处"的错误结论;改用 ripgrep(尊重 .gitignore)后真相浮现。
  2. 用平台自己的编译产物在 /usr/lib 布局的测试服(test106 = 106.54.21.172,OpenCloudOS+宝塔)上真跑,而不是只读代码或采信对方贴的输出。
  3. 补扫 lib/node_modules 类写死,确认没有第四处。

结论:问题确实存在,而且比单子描述的多一处

# 位置 单子 我的复核
1 src/web/model-catalog.ts:30 有(L30) ✅ 有。catalogDir() 指向 /usr/local/lib/node_modules/... ⇒ 该布局下 exists=false ⇒ listCatalogProviders() 返回 0 家(真实位置有 39 个 json)
2 src/web/plugin-compat.ts:**36** 有(写作 L35) ✅ 有(行号差一行)。platformPkgCount() 实测 0 ⇒ 预检退化为「平台包目录不可读」
3 poc/workspace-scoped-picker/lib/index.js:34-35(DEFAULT_SEAM_CANDIDATES)+ README:36 漏了 🆕 同样写死同一路径。表现不同:响亮失败(有 DSH_SEAM_DIRECTORY_PICKER 逃生口、抛错列已试路径)——已实测复现(把该 lib 传到测试服 import ⇒ 抛「无法解析官方 seam 基类」,两候选都不存在、真实位置在 /usr/lib 且存在)。后果更重:它是平台级 bundle,一挂 = 实例内目录选择器不可用。test106 上尚无用户 profile ⇒ 尚未触发,但用户首登建 profile 后即触发
  • 逃生口未暴露 ✅ 成立:DSH_PACKAGE_DIR / DSH_COMPAT_ROOT / PI_AI_DATA_DIR 在 install.sh / env 样例 / README 里 0 命中(只出现在交接单自身与导出物的 memory)。
  • 修复思路的基础成立 ✅:readlink -f /usr/bin/dsh → /usr/lib/node_modules/@deepseek-ai/dsh/lib/bin.js ⇒ 靠 DSH_USERS_PLATFORM_DSH_BIN 解软链拿包根这条路可用。
  • 全仓 rg "lib/node_modules"(排除 node_modules/lib/**)0 命中 ⇒ 除这三处无其它同类写死。

状态

  • 只检查、未改码(用户指令是「检查是否有这些问题」)。源仓 HEAD 仍 022b1f7、工作树干净。
  • ⚠️ 若按单子的 §3 只改两处 ⇒ 会漏掉第三处。建议 dsh-install.ts 再导出一个 dshSeamCandidate()(或把 seam 候选纳入同一套探测)。
  • 本机与测试服 /tmp 探针已清(测试服全程只读,只在 /tmp 放过脚本且已删)。

04:3x–05:0x 用户「确认修改」⇒ 落地修复(源仓 cbaf3ad,已 push)

落地内容

文件 改动
src/web/dsh-install.ts(新) 全平台唯一入口:dshPackageRoot/dshScopeDir/piAiDataDir/resetInstallPathsCache;按序探测 = env → 平台配置的 dsh 可执行文件解软链反推包根(最可靠)→ 常见全局根 → npm root -g → 历史默认值;只依赖 node 内建
src/web/model-catalog.ts catalogDir() → piAiDataDir()
src/web/plugin-compat.ts 删 PLATFORM_DSH_ROOT/SCOPE_DIR 常量,三处改用 dshScopeDir()
poc/workspace-scoped-picker/lib/index.js + README.md 补第 3 处:seam 候选改为按序探测包根(插件跑在实例进程内拿不到平台代码 ⇒ 自带一份,两处注释互相指认)
scripts/verify-dsh-install.mjs(新)+ package.json 15 项断言(env 覆盖 / 解软链 / 包名不误认 / 派生量 / 缓存语义 / 本机实况),接入 npm run verify

我改了导出会话修复件的两处(关键)

  1. env 名:它用 DSH_USERS_PLATFORM_DSH_BIN(导出侧名),源仓是 DSHS_DSH_BIN(config.ts:239)⇒ 照抄会让"最可靠那一路"在源仓侧永不生效。改为两侧都认(DSHS_* 优先、DSH_USERS_PLATFORM_* 兼容)。 📌 顺带查明:我们服务器根本没设 DSHS_DSH_BIN(/etc/dshs.env 无)⇒ 我们走"常见全局根"那一路(/usr/local 命中);test106 走 env 那一路(/usr/lib 命中)。两条路都得留。
  2. 补第 3 处(见上)。

验证(全实测)

  • 本机:tsc --noEmit 0 错误;新回归 exit 0;既有 5 个 verify 脚本全绿;单测 16 pass / 0 fail。
  • 服务器 bt-server 回归:platformPkgCount = 223、厂家目录 = 39(未回退、无破坏);已 build + restart。
  • 测试服 test106(原故障机,/usr/lib)三种姿势全部解析正确:① 无 env → /usr/lib/node_modules/@deepseek-ai/dsh(scope 240 项、pi-ai 数据 39);② DSHS_DSH_BIN=/usr/bin/dsh;③ DSH_USERS_PLATFORM_DSH_BIN=/usr/bin/dsh。全程只读(只在 /tmp 放脚本且已删,未动 /root/dsh-users-platform)。
  • 回执已写:dsh-laijing-github/_回复源仓会话_安装路径缺陷已修复_20260914.md(含"导出会话待做:重建导出物 + 复跑探针 + 把 3 个 env 写进 install.sh/README")。

🔎 两条方法论教训(差点骗了我自己)

  1. grep -v node_modules 会把要查的行本身也滤掉(这些行就含 node_modules 字样)—— 我第一次据此得出"源仓 0 处"的假结论,改用 ripgrep 才看到真相。⇒ 排依赖目录要用 --exclude-dir/.gitignore,不要用行过滤。
  2. rg 在本机 PATH 里可能不存在:rg ... 2>/dev/null 失败会静默返回空、看起来像"0 命中"。⇒ 结论性地断言"扫干净了"之前,先确认工具真的跑了。

04:4x 复盘(用户问「有什么影响 / 为什么上一轮没发现」)

影响面(分层)

面向 影响
本平台(我们两台服务器) 零影响 —— npm root -g = /usr/local/lib/node_modules(npm 默认 prefix)恰好等于写死值 ⇒ 一直正常(实测 platformPkgCount=223、厂家目录 39)
install.sh 部署的机器(如 test106,/usr/lib) ① 厂家目录读空 ⇒ /api/me/model-providers 返回 [] ⇒ 新增厂家只剩「内置 DeepSeek」+「自定义」;② platformPkgCount()=0 ⇒ 兼容性预检的安全网整体失效(不再拦 semver/导出符号不兼容的插件,T05 白做);③ workspace-scoped-picker import 即抛 ⇒ 目录选择器不可用(目前未触发,一有用户 profile 就触发);④ 逃生口未写进 install.sh/README ⇒ 使用者无从自救
开源发布 属发布阻断项:第一个用发行版 Node 的部署者就会踩,且无任何报错,会以「这功能好像没实现」的形式变成 issue

⚠️ 关键巧合:用户 09-14 00:1x 抱怨的「厂家选择怎么这么少、国内一家都没有」,在导出部署上就是本缺陷的症状;在我们服务器上是另一个根因(我第一版真没接目录)。同一现象、两个根因 —— 我上一轮只修了自己那个,没意识到另一种部署上还有第二个。

为什么没发现(根因链)

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

防再犯(可执行的)

  1. 凡「读别人安装位置/版本」的代码 ⇒ 路径必须走解析层,且至少一条单测用假根(已落地:scripts/verify-dsh-install.mjs,15 项)。
  2. 静默 catch 必须留痕(本次未做,待办):listCatalogProviders 与 platformPkgCount 的降级路径应至少 console.warn 一次、或把"目录不可读"透出到 /api/dsh/status —— 否则"功能没生效"不可观测,这次就是靠人肉比对才发现。
  3. 验收判据要显式包含"非本机布局"(一句 env 注入即可)。
  4. 看到注释里出现「本部署事实 / 目前是 / 暂时」⇒ 当场转成待验证项。

04:45–05:1x 用户「一次性做完 不要留尾巴」⇒ 收尾全部清掉

尾巴 处置
降级静默(P1 能潜伏的根因) ✅ 已改为可观测:model-catalog 读不到 ⇒ console.warn(每进程一次)+ 新增 catalogDiagnostics(),并把 {dir,readable,count} 透出到 /api/me/model-providers 的 catalog 字段;plugin-compat 的 platformPkgCount()=0 同样告警一次;插件 0.3.13 前端分开提示「不可读(含路径)」与「目录为空」
picker 源码 ≠ 线上 ✅ 升 0.1.5 重打包铺发(两实例实装 0.1.5 且含新代码)——顺带做了最要紧的回归:在 /usr/local 下它的 lib 仍 import OK(没把本来能用的改坏)
逃生口未暴露(源仓侧) ✅ 源仓 README.md 配置表补 DSHS_PACKAGE_DIR / DSHS_COMPAT_ROOT / DSHS_PI_AI_DATA_DIR 三行(含"写死会静默失效"的警示)
无回归网 ✅ verify-dsh-install.mjs 扩到 6 组(新增**「读不到 ≠ 目录为空」的区分** + 告警只发一次);verify-platform-admin-section.mjs 加 2 条(目录不可读/为空文案不同),词典 284/284
台账 ✅ 新 档案 88(含「为什么三轮都没发现」的根因复盘)+ T07 归档 + INDEX.md(§二 88 行 + §四 T07)+ 交接单/README.md §一 + 四件套(audit rc=0 / 一致性 ✓)+ 镜像 scp
提交 ✅ 代码仓 cbaf3ad→1d72e8f、文档库 d1d1f11→704cd86(均已 push,远端哈希已核)

本机/线上状态:两实例 business-plugins 0.3.13 + workspace-scoped-picker 0.1.5;后端已 build + restart;回归 platformPkgCount 223 / 厂家目录 39(端点回 38)/ picker import OK。

唯一剩的、不在我 lane 的:① 导出物侧(install.sh / env 样例 / 导出 README)补 3 个 env + 重建导出物 —— 已在回执里交给开源导出会话;② INDEX.md §二 中 档案 82–86 未登记 与 skills/dsh-plugin-diagnose 未登记(别人的 lane);③ 我刚才发现 skills/dsh-opensource-release/SKILL.md 又被其 owner 改了(对账报 1 处不一致)⇒ 按规矩未碰。

08:0x–08:2x · guest 新会话(DSH 官方推荐插件 Top20)+ P1(state 400)结清

新会话 session-9217f396(今早 07:57):用户问「dsh 官方推荐实用插件前 20」⇒ AI 产出两份交付:DSH插件精选清单.xlsx(下载用)+ 尝试 univer_import 失败(GLIBC 2.35)⇒ 改用 Facade API 原生重建 DSH插件精选清单.univer(4 sheet、逐格回读、数字格式生效、标记 ready)。 用户原话问它:「不能在浏览器会话页面中查看吗 univer插件没有这个功能吗」。

我的复核(服务端链路全通):

  • GET /univer-api/state?file=…&sessionId=…(按客户端真实形状 encodeURIComponent)⇒ 200:gatewayRunning:true、viewerUrl 就绪、worktree wt-mu0h2qv0-pa2aq3 状态 ready、含 1 个 sheet 单元。
  • GET /univer-api/viewer/?file=<fileKey> ⇒ 200(37 KB viewer HTML)。 ⇒ .univer 现在能在会话里在线看;xlsx 不能(要导入 = 原生绑定 = glibc 挡)。

✅ P1 结清(档案 76 §七):挂了整天的「/univer-api/state 生产 400」按真实形状复测 = 200。并给出三类 400 分辨表:① 插件信封 {ok:false, code:INVALID_REQUEST}(缺 sessionId);② 插件信封 INVALID_FILE_PATH / SESSION_SCOPE_UNAVAILABLE(文件不存在 / 旧会话失效 ← 09-13 那批 400 最可能是这两类);③ ⚠️ 代理层 Fastify 默认形状(error: Bad Request / message: Client Error / statusCode 400)—— 我手工 curl 未编码中文路径踩到,一度误判成「插件恒 400」。 教训铁律:curl 带中文/空格路径必须 -G --data-urlencode;见到 Fastify 默认错误形状,先怀疑「请求没到插件」。

仍存在的两条(宿主环境,不是插件缺陷):

  1. xlsx/docx 导入导出 —— exchange-node-binding 无 JS 回退、无 env 覆盖 ⇒ 要么换 glibc ≥ 2.35 基底;要么走数据级替代(实例里已有 SheetJS:xlsx 读出→灌进 .univer,反向同理;掉样式/公式保真度)。后者我能做(与 office-file-generation 同一思路的镜像)。
  2. 截图 / PDF / compile_svg —— 宿主上没有任何浏览器(已 find 全盘确认);⚠️ 且装了也会撞内存:headless Chromium 要 +200~400 MB,而 guest 配额 544 MiB(univer 网关已占 ~350 MB)⇒ 一跑大概率 cgroup OOM(137)。结论:不建议装,真要就得先提配额。

未做到:浏览器侧未能亲验(agent-browser 未安装,需 ~500 MB Chromium)⇒ 若刷新后仍看不到卡片/dock,需让 guest 里的 AI 跑一次 client 探针,或授权我装 agent-browser。

08:3x–08:5x · ⭐ 重大突破:不用换基底、不用容器,就能让 univer 原生绑定可用

起因:用户问「为什么不升级 glibc」→ 我给出代价/收益对比后,用户说「按照你的方案试试」⇒ 我先做我承诺的零风险干跑验证(只取一份 glibc 2.35 rootfs,不启 dockerd、不碰防火墙),结果比预期好得多。

实验链(全部实测):

  1. 宿主现状再确认:Alibaba Cloud Linux 3 / glibc 2.32 / kernel 5.10;docker 已装但守护没跑、无镜像;平台 supervisor 里有 k8s-spawner.js(容器形态在架构上留了口子)。
  2. objdump -T 实测:两个 univer 绑定都要 GLIBC_2.33/2.34/2.35 的版本化符号;libsql 只要 2.18(所以它一直好用)。
  3. 从 Ubuntu 22.04 取 libc6(注意 deb 里是 data.tar.zst,Python 3.12 的 tarfile 不认 ⇒ 用宿上的 zstd 解)⇒ 得到 glibc 2.35 的 20 个 so + ld.so。
  4. ❌ 第一次只给 libc/libm/ld + 宿主 /lib64 ⇒ GLIBC_PRIVATE 冲突(libpthread.so.0: undefined symbol __libc_siglongjmp)—— 因为我混进了宿主 2.32 的 libpthread(2.35 里它已并入 libc)。必须整套 2.35 一起给。
  5. ✅ 给全 20 个 so 后:node -v 正常,require 两个绑定全部成功(exchangeExportSnapshot/exchangeImportToSnapshot、rustNumfmtFormat/...)。
  6. ✅ 端到端:gateway + worker 都跑在 2.35 上 ⇒ univer_import 一个 xlsx 成功(列宽 44/176/88、单元格文本、SUM 公式全进来了)—— 正是昨天失败的那一步。
  7. ✅ 运行时已就位:/usr/local/dsh-runtime/glibc-2.35/(5.0 MB,20 so + ld.so + COPYRIGHT-libc6.txt);process.report.getReport().header.glibcVersionRuntime 自证 = 2.35。放 /usr/local 是为了让 bwrap 沙箱内可见(/usr 只读挂载)。

⇒ 结论:不需要换发行版基底,也不需要 docker/容器 —— 只要给 worker / gateway 这两个进程挂自带 glibc 2.35 即可(它们是独立进程,混 libc 的经典危险不适用)。 顺带收益:公式引擎也能切回 Rust(我的垫片本就是"绑定能用就用 Rust"⇒ 自动恢复)。

待接线(下一步):① fork 侧把两处 spawn(worker.ts / processes/gateway/launcher.ts)改成经 ld.so --library-path 启动,env 开关(如 UNIVER_DSH_GLIBC_2.35_DIR)控制、可回退;② 平台侧把该 env 给实例(/etc/dshs.env 或 supervisor env,实例继承平台进程环境);③ 重建 0.2.28 → 投放 → 全回归(导入/导出/截图除外/网关/worker + 档案 76 的 socket 链路)。 许可证:glibc 是 LGPL ⇒ 已随附 COPYRIGHT-libc6.txt;对外开源导出时不要把这份运行时打进导出物(它只在服务器上)。

08:3x–09:0x · ✅ 接线完成并投放 0.2.28(原生 Office 导入导出已启用)

  • 插件侧改动(最小、可回退):新增 src/host/processes/glibc-runtime.ts(nodeLaunch():有运行时则 ld.so --library-path <rt>/lib:/usr/lib64:/lib64 <node> <entry>,否则原样);在两处 spawn 接线 —— host/adapters/unit-content/worker.ts、host/processes/gateway/launcher.ts。目录自探测(默认 /usr/local/dsh-runtime/glibc-2.35 + UNIVER_DSH_GLIBC_RUNTIME_DIR 可覆盖/置空关闭)⇒ 平台侧零改动(不改 env、不重启 dshs)。
  • ⚠️ 本机踩坑:launcher.ts 是 CRLF 行尾,用 \n 模式替换会静默匹配不上(worker.ts 是 LF)⇒ 改本机 TS 前先 cat -A 看一眼行尾。
  • 验证(真机证据):POST /univer-api/gateway/start → {"ok":true,"reused":false};/proc/<pid>/cmdline 确含 ld-linux-x86-64.so.2 --library-path …;/proc/<pid>/maps 里 libc/libdl/libm/libpthread 全部来自 /usr/local/dsh-runtime/glibc-2.35/lib/;/univer-api/status = gateway running;实装 0.2.28、实例 active。
  • 顺带搞清一条:/univer-api/state 需要活会话(实例重启后 SessionStore 内存态未水化 ⇒ SESSION_SCOPE_UNAVAILABLE)⇒ 这就是 09-13 那批 400 的第二种可能;自检请用 POST /univer-api/gateway/start(不需要会话)。
  • 落档:档案 76 §八(实验链 / 落地 / 真机验证 / 遗留注意)已写入并同步;四件套 rc=0。
  • 仍未解:截图 / PDF / compile_svg(缺浏览器 + 内存不够,与本条无关)。