# 工作日志 · 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**(本轮唯一未取证项,救回一个字段名):读服务器 `dsh-llm-pi-ai@0.1.2-rc.1` ⇒ 分区名 `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/..HEAD` 在本机**不可靠**(无 remote-tracking ref)⇒ 用 **`git ls-remote origin refs/heads/`** 与本地 `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`,**`` 是 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\AI技能\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=` ⇒ **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 /lib:/usr/lib64:/lib64 `,否则原样);在**两处 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//cmdline` 确含 `ld-linux-x86-64.so.2 --library-path …`;`/proc//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`(缺浏览器 + 内存不够,与本条无关)。