Files
dsh_ai1net_server/.workbuddy/memory/2026-09-下9.md
T
admin ce8e6ceed9 chore(工作区): 纳入版本控制基线(回收 411 MB 过程产物)
回收 411 MB(470 M → 58.8 M),全部经回收站,可恢复:
- 待清理/(146.2 M,含 relay 分片 128 M 与 42 项过程目录)
- tmp/(32.4 M,按接续棒命名的过程临时区)
- .workbuddy/tmp/(39.5 M)
- 4 份 workbuddy.db 冗余副本(101 M,09-23 事故的坏副本 / 抢救产物)
- tmp/im16/gw/centrifugo 二进制(63.9 M,可重下)+ 缓存残留

入库范围:常驻规则(CODEBUDDY.md / README.md / state.py)、在途接续入口与
接续包、docs/、交付物/、交接单/、归档/、scripts/、.codebuddy/、
.workbuddy/memory/;共 398 件,其中 >60 KB 的 26 件全为文档。

排除(.gitignore):tmp/、待清理/、运行态日志与缓存、*.db 与 DB 备份整目录、
打包二进制(*.tar.gz / *.tgz)、记忆修复前备份。
2026-09-24 07:51:03 +08:00

242 lines
48 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 工作日志 · 2026-09(第 10 片)
> ⚠️ **本目录日志已按【月】分片**(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-12.md
> **上一片**:`2026-09-下8.md` **下一片**:`2026-09-下10.md`
---
## 18:2x 代码仓三方同步(用户:「改为3小时同步一次」)
- 建自动化 **「代码仓三方同步(DSH)」 FREQ=HOURLY;INTERVAL=3**;原「遗留项自动推进」8 小时保留(职责不重叠)。
- 三方原状:Gitea origin/master 停在 **ebe8075**(共同祖先);本机 0e141a4(+2);服务器 b23e386(+3)。
- **关键诊断**:忽略行尾后,business-plugins.ts/whitelist.ts/security-scan.ts 的「1450/638/296 行差异」**全是 CRLF 假差异**(core.autocrlf=true + 无 .gitattributes);真实差异只有 3 文件,且**本机更新**(client.js/package.json=v0.2.5 对比度精修;orchestrator.ts 含类型+防御)。
- 处置:update-ref 建回滚点 backup-pre-sync-1815(0e141a4) → 备份 4 个本机独有文件 → reset --hard srv-incoming(b23e386) → 回填 → commit **da3e0f9** → push(ebe8075..da3e0f9,**FF,无需 force**)。
- 结果:本机 = Gitea = da3e0f9;服务器 b23e386(落后 1,**未动其工作树**)。
- **两个坑(已写进自动化 prompt)**:① 本仓库 git diff 会把行尾差异算成整文件改写 → 一致性判定用 `git hash-object` / 加 `--ignore-cr-at-eol`;② `git branch <名> <commit>` 本机静默不落地 → 改 `git update-ref`。
## 18:3x 两项方案变更落地(持锁 plan-session-1827,已释放)
- **① AnySearch**:用户拍板「三方插件一律 admin 导入候选池 + 用户自助启用,**不铺**」→ 落 3 处口径:档案 64 §8.3 追加修正块(第 2 条直铺命令作废)、03-路线图 档案64 行、BRIEF §3 行。执行口径改以 **档案 65 §7.3** 为准。
- **② 子代理 approval**:源码级取证(**dsh-subagent/lib/types/child-agent.js**)—— 子代理沙箱**只继承「父会话显式 override」**(注释原文:never deployment defaults or one-shot grants)+ approval **硬编码钉死 never**(语义 = 需要审批的操作**自动拒绝**)。→ 平台的 DSH_PERMISSION_MODE 走"部署默认值"通道,恰被官方排除 ⇒ 子代理永远无 shell。
· 用户判据「不扩大用户访问边界」**成立**(子代理同实例/同 uid/同 cgroup/同 bwrap)。
· 但**平台层无干净解法**:env 无效(被官方排除)/profile patch 无效(改的仍是默认值通道,approval 是硬编码常量)/改会话种子已被档案 45 否决/改官方包违 R2。
· 已在 **档案 42 文末追加「修正(2026-09-12)」**小节 + 03-路线图对应行改为"已取证"。
- 校验:docs-audit / docs-manifest / docs-consistency 三件套 rc=0。
- **下一步**:实例内新会话派子代理跑 bash 实测 —— 可用则本项自动闭环。
## 18:3x 子代理 shell **闭环**(用户追问:用户自己设权限是不是就支持了)
- **答案:支持**。源码复核 `overrideOf`(dsh-sandbox-policy 与 dsh-user-approval)→ 读的是**会话日志里最后一条 sandbox/mode 事件**、**不与部署默认值比较** → 只要用户在会话内切过一次档位,子代理即继承 danger-full-access(append source: delegation)→ 该模式**无待审批操作** → 钉死的 never 不再构成阻碍 ⇒ **子代理可用 shell**。
- ⇒ **平台侧零改动**,走官方机制;边界不扩大(继承的是用户自己选的档位)。
- 操作步骤:新开会话 → `/permission workspace-write` 再 `/permission danger-full-access`(**先切走再切回**,确保日志写事件)→ 派子代理跑 bash 验证。残留不确定:设置 UI 选到同一档位时是否写事件。
- 已更新:档案 42「追加结论」+ 03-路线图 对应行改为「已闭环」。校验 audit/manifest/consistency rc=0。
## 18:3x 档案 42 ③ **实测通过并关闭**
- 用户实测:实例会话中派子代理执行 `bash -c 'echo subagent-ok'` → **原样返回 subagent-ok** ⇒ **子代理 shell 可用**。
- 结论:**平台侧零改动**、无需放权评估(不扩大用户访问边界)→ 档案 42 ③ 关闭。
- 已更新:档案 42 追加「实测结果」段 + 03-路线图 对应行改为「已闭环并实测通过」。校验三件套 rc=0。
## 18:3x 待改清单(**因锁被占而暂缓**:session-brief-refresh-1835 自 18:32 持全局锁,按纪律停手)
- [ ] **业务技能投放口径**:用户已讲过(T03 §决策点 4 原话「业务技能能否打包到插件中一起安装和使用,**不要分开管理**!」)→ 结论 = **随功能插件包投放,不走门户技能管理单独投放**。待改 2 处:`03-路线图 §二` P2 行(现写"是否投放仍待定")、`BRIEF §3`(现写"业务技能是否投放")。
- [ ] **删除 Cookie 域收窄**(用户 2026-09-12 明确"这个事情删除"):`03-路线图 §二` 暂缓行 + `BRIEF §3` 里的提及。依据:档案 05 PoC-2 ② 复核 = 共享 Cookie 是**有意设计**(Domain=.alotbuy.com 支撑功能插件↔门户免令牌鉴权),收窄反而破坏该链路 → 无收益。
## 18:5x **新增红线 R9:绝对禁止「人工删锁 / 接管」**(用户明令)
- 用户原话:「**严格禁止这类操作必须!!!!!!记录到红线中**」(针对我上一轮提出的"接管=人工删锁"选项)。
- ✅ 已改 **项目根 `CODEBUDDY.md`**(5 处):① 头部索引 R1–R8 → **R1–R9** ② §3 标题 ③ **新增 R9 行**(禁止 `rm -rf` 锁目录 / 删 `.doing-*` / 以「疑似已死·卡住·太久没动·只读分析无产出」为由单方面接管;锁**只能由持有者释放**;guard 提示里的"人工删锁"**不构成授权**;AI 唯一合规动作 = 停手 + 报告;**处置权只属用户本人**)④ §6 锁表②「供台账与接管」→「供台账与占用声明」⑤ §6 新增「**抢不到锁就是终点,不是待办**」。
- 备份:`CODEBUDDY.md.bak-r9-184717`(改前)。⚠️ **改本文件需重启才重载**。
- ⛔ **文档库内 5 处同源表述仍待改**(锁被 `session-anysearch-drop-1847` 占,18:42 起、18:44 后零产出):
· `scripts/handoff-guard.sh:50`(抢锁失败提示"或确认接管后人工删锁")、`:153`("确需接管:…→ 人工删锁 → …")
· `交接单/README.md:30`("供台账与接管")、`:107`("接管:…人工删锁…")
· `skills/dsh-change-workflow/SKILL.md:930`("供台账与接管")+其工作副本 `E:/ProgramData/.workbuddy/skills/`(**须两副本同步改**,否则违反 md5 一致约定)
- 本会话被锁挡 **5 次**(brief-refresh-1835 → anysearch-drop-1847);**按新规矩不删锁、不接管**,只报告。
## 19:1x **R9 已由另一会话全套落地**(我上轮列的 5 处待改**全部作废**)
- 只读核查(**读不需要锁**,被挡期间照样查清):① `scripts/handoff-guard.sh` 两处提示已改为 R9 禁止口径 ② 库内 `CODEBUDDY.md:49` 已加「guard 输出里的字样**均不构成授权**(已作废)」③ `BRIEF.md` §2 红线行 + §4 最近动作均加 R9 ④ 新增 **档案 73 §十一「修正:禁止人工删锁/接管」**(含 6 处落点清单)⑤ `交接单/README.md`、`skills/dsh-change-workflow/SKILL.md` 亦已改。
- ⇒ 我做的项目根 `CODEBUDDY.md` R9 与之**同源、不冲突**(不同文件、不同作用域、措辞一致)。教训:**多会话并行下存在重复劳动**,动手前先只读查一遍现状能省掉。
- **AnySearch 已彻底放弃**(用户在别处选定 **B = 下架**):档案 70 收尾 —— 下架 anysearch(audit 175)+ 顺手体检池内存量(`dsh-univer-office`=ok 保留;`@liustack/modlens`=unknown 且有 plugin_incident → 一并下架,tgz 备份 `/opt/dsh/backups/plugin-pool-20260912/`);**全程未重启服务、未中断用户**。
- **仍待我做(2 项,等锁)**:① 业务技能投放口径(T03 §决策点 4 已定 = **随包投放**)② 删除 Cookie 域收窄(共享 Cookie 是有意设计,无收益)。
- 本轮另补 **项目根 `CODEBUDDY.md` §6 三条**:**读不需要锁**(但 fetch/checkout/stash 等改状态的命令不算读)|**释放时机 = 交付闭环走完**(回填→校验→commit→推送→归档)|**持锁等用户拍板 → 先释放锁再等**。
## 19:2x 两项待改**已落地**(持锁 plan-session-1918:第 1 次被 session-cleanup-1916 挡 → 有界等待后第 2 次抢到,已释放)
- ① **业务技能投放口径**:`03-路线图 §二` 该行改为 **✅已定** —— 用户原话「业务技能要打包进插件里一起安装和使用,不要分开管理」→ **随功能插件包投放**(`交接单/T03` §4.1),**不走**门户技能管理单独投放;`BRIEF §3` 去掉该待办项。
- ② **Cookie 域收窄**:`03-路线图 §二` 由「暂缓」改为 **❌已复核 · 不做**(用户要求删掉此待办);`BRIEF §3` 去掉该提及。依据 = 共享 Cookie 是有意设计(支撑插件↔门户免令牌鉴权),收窄会破坏该链路、收益为零(档案 05 PoC-2 ②)。
- 校验:docs-audit / docs-manifest / docs-consistency **三件套 rc=0**。⚠️ **未 commit**(未获授权)—— 闭环按新 §6 规则只走到「校验」,commit/推送待用户发话。
- ⚠️ 顺带发现:**`BRIEF §3` 那条「档案 64+65 · AnySearch 能力 …待用户定 A/B」已过时**(用户已在别处选 B = 下架,档案 70 收尾 18:56 已记录)→ 待下次持锁一并修。
## 19:2x BRIEF §4 一致性修正 + T03 执行条件核实(持锁 plan-session-1920,已释放)
- **只读先查的价值再次验证**:BRIEF §3 那条 AnySearch「待定 A/B」**已被别的会话修掉** → 我上轮承诺的"下次持锁一并修"其实无需做。实改只剩 1 处:BRIEF §4「档案 70 …(已止损,**能力去留待定**)」→「(已止损;**能力已按用户决定下架**)」,与同段开头「AnySearch 已彻底放弃」自洽。三件套 rc=0。
- **T03 执行条件核实**:① 本机产物在 D:/dshworkspace/plugin_package/dist/dsh-plugin-mcn-suite-0.3.0.tgz(1.90 MB / 11:15)② **guest 实例正在运行**(scope dsh-100002-299e7f95,uid 100002 ↔ 4092b965…)→ 「guest 启用」会**重启它**(断约 1 分钟)③ **dataRoot = /var/lib/dshs**(/opt/dsh/artifacts/ 里那批 business-plugins-0.2.x.tgz 是**平台自身**的功能插件包,**不是**候选池)。
- ⇒ T03 剩余步骤(上传候选池 → guest 启用 → 启动实例 → 验收 → 归档)**涉及重启 guest 实例**(R8)→ 已请示用户,**未擅自执行**。
## 20:2x T03 执行前只读核查(按 R8 自主判时机,**未动生产**)
- **判时机**(用户要求「规则你都知道,别问」→ 自主判):guest 的 ws/MCN短视频创作 mtime = **20:06**、sessions = **20:02**(距核查 13–17 分钟)→ **活跃中** → 按 **R8「能避开活跃时段就避开」** 暂缓「启用 + 重启」。
- **核实 3 条事实**:① 候选池 `/var/lib/dshs/business-plugins/` **只有 `dsh-univer-office`**(39.9 MB)⇒ T03 §七 防线②「下架旧 7 包」**无需做**(那 7 包**从未上架**)② **guest 的 bundles 不含任何旧包** ⇒ §七 头号风险(新旧并存 → duplicate loader entry id 崩实例)**不成立**,切换顺序可简化 ③ dataRoot = `/var/lib/dshs`,库 = `dshs.db`。
- **⚠️ 新发现的不一致**:guest bundles 里仍启用 **`@liustack/modlens`** —— 该包今天已从候选池下架(档案 70 收尾),但 profile 仍启用它(包文件尚在 node_modules,**暂不致命**)→ 属「**已下架却仍启用**」态 ⇒ **与 T03 共用同一次重启窗口摘除**(避免两次中断用户)。
- 落档:`交接单/T03` 追加「只读核查结论」段(含时机判据);`03-路线图 §二` 新增该待办行。三件套 **rc=0**。
- 计划:等 guest 空闲(判据 = 最近 30 分钟无活动)→ **一次窗口做完**「上传新包 + guest 启用 + 摘 modlens + 重启 + §八验收」,避免多次中断。
## 20:2x guest 最新两会话排查(只读,未改任何文件)
- 对象:`8cdf723e`(09-11 21:58 建,11 turn,最后事件 20:13)与 `eda867a0`(09-12 20:02 建,1 turn/45 工具,20:11 结束)。两会话档位均 `danger-full-access`/`never` ✓(档案 55 的 P1 未复发)。
- **P0 根因(实锤)= 实例 OOM**:`20:11:10 kernel: oom-kill:constraint=CONSTRAINT_MEMCG, oom_memcg=/system.slice/dsh-100002-299e7f95.scope, task=node,pid=355329,uid=100002` → 平台 `crash-restart` `exitCode 137` → 20:12:11 stable。**两个会话的工具调用在同一时刻(20:11:05 / 20:11:09)被中断**,用户 20:12:12 问「是报错了吗」。
- **当前仍在临界**:`MemoryCurrent=400175104 / MemoryMax=402653184` = **99.4%**(新实例才跑 10 分钟);`dsh-instance-mem.log` 显示 uid 100002 峰值**长期贴顶 382/384 MiB**(02:35Z 起);近 3 天该实例 crash-restart ≥7 次(09-11 23:56 连崩 5 次 exitCode 1;09-12 00:19 / 01:28 / 09:37 / 20:11)。
- `NODE_OPTIONS=--max-old-space-size=256` **已正确注入**(档案 58 修复在位)→ 说明 256 MiB V8 堆 + 实例其余开销**仍撑不住 384 MiB cgroup 上限**。
- **P1 = `dsh-univer-office` 与本平台 loopback 封锁天然不兼容**:nft `table ip dsh_egress` 有 `meta skuid 100000-199999 ip daddr {47.77.182.89, 127.0.0.0/8, 172.17.0.1, 172.18.16.212} tcp ... reject with tcp reset`(计数 159)。插件需「bundled Gateway」监听 `127.0.0.1:9080+` 走 loopback HTTP → 实例内 curl=000 / python=Connection refused / node fetch failed(RST 而非超时)。
- 后果:`univer_new` 报 `bundled Gateway did not become ready within 10000ms`×3;`抖音爆款视频榜_2026-09-12.univer` **全盘 find 无此文件**(从未落盘);前端 `/univer-api/state` 从 20:08:08 起持续 **400**,已 153 次且**仍在刷**。
- ⇒ 档案 71 静态预检判 `ok` 没错,但**检测不出运行时架构冲突**(预检盲区)。
- ⇒ agent 反复起 gateway(9081/9082/9099 多进程)**可能正是 20:11 OOM 的推手**。
- 附带:DNS 类失败 2 次(`ENOTFOUND lg.racknerd.com`、`api.bgpview.io`),外部站点侧,非平台问题。
- 方法沉淀:取证路径 = `/var/lib/dshs/users/<uid>/home/sessions/--var-lib-...-ws-<工作区>--/<session-id>/session.jsonl.zstd`(**不是**档案 55 写的 `users/<uid>/home/sessions`,uid `4092b965…`=guest、`cce6d1cd…`=admin)。
- ⚠️ **工具缺陷(未修,待授权)**:`dsh-server-docs/scripts/sess-list-presets.mjs` **首行注释缺 `/**` 起始符** → `node` 直接 `SyntaxError: Unexpected token '*'`,该脚本当前不可用。**按 R7 未擅自修改文档库**。
- ⚠️ 与 T03 计划的关联:20:2x 那条定的「等 guest 空闲再重启」窗口,**guest 此刻仍活跃且刚 OOM 崩过** → 内存贴顶是重启窗口的额外风险项。
## 20:3x guest 内存归因 + 隔离复现实验(只读 + /tmp 隔离,未碰生产)
- **内存构成实测**(cgroup v1:`/sys/fs/cgroup/memory/system.slice/dsh-100002-af3f3fa9.scope/`):
- `usage_in_bytes` 382 MiB / `max_usage_in_bytes` **384 MiB = limit**(曾精确打满)
- `memory.stat`:**rss 363.7 MiB(95.4%)+ cache 仅 17.8 MiB** ⇒ **几乎全不可回收,内核 OOM killer 无路可退**
- `rss_huge 82 MiB`(THP 放大);smaps:Anonymous 372404 kB、Pss_Anon 363.7 MiB、`[heap]` 段 63.9 MB
- **内存非恒定贴顶,而是 316~384 MiB 波动**(清理后实测降到 316)→ **波峰正好在死亡线**
- **插件账本(隔离 cgroup 实测,纯只读 import)**:node 空跑 56 MiB → **+`dsh-univer-office` = 121 MiB(该插件 +65 MiB)** → +libsql 125 MiB。⚠️ univer 磁盘 **168 MB**(含 `exchange-node-binding-linux-x64-gnu` 29 MB、`engine-formula-rust-binding` 10 MB 等原生绑定);`node_modules` 总览还有 puppeteer-core/chromium-bidi/libsql/zod×2。
- **对照**:`dsh-instance-mem.log`(时间戳为 **UTC**!)显示 admin(uid 114801,**未装 univer**)峰值 **233 MiB** vs guest(uid 100002)峰值 **382 MiB** → 差 149 MiB,其中 65 MiB = univer 加载实测值。
- **三条隔离实验(`systemd-run -p MemoryMax=402653184`,跑完即回收,全程未碰生产)**:
1. **插件账本**:同上,未撞墙(125 MiB)。
2. **JS 堆压力 + `--max-old-space-size=256`** → `FATAL ERROR: Reached heap limit`(Mark-Compact 254 MB),systemd `code=dumped/status=ABRT` ⇒ **死亡路径 A = V8 堆限自崩**。
3. **纯堆外分配(Buffer)** → `kernel: oom-kill:constraint=CONSTRAINT_MEMCG, task=node` + `code=killed/status=9/KILL` ⇒ **死亡路径 B = 内核 OOM SIGKILL**,**与 20:11 真实事故记录逐字同构**。
- **结论**:不是单一"坏东西",而是**配额本身零余量** —— V8 老生代上限 256 MiB(占配额 2/3)+ 插件 65 MiB + dsh 本体/堆外 ~50 MiB + 页缓存 18 MiB ≈ 386 MiB > **384 MiB 上限**。**两条死亡路径都实测复现**,且**都不产生可读日志**(一条 ABRT、一条 SIGKILL)。
- **宿主容量**:物理内存仅 **1.83 GB**(MemTotal 1915896 kB),available 924 MiB,swap 1 GB 已用 194 MB → 每实例 384 MiB ×2 + orchestrator ≈ 用满。**这是容量问题,不是 bug**。
- 生产实例全程 `active`,未重启、未中断用户;实验 unit 无残留,`/tmp/memsim` 与本地 `_tmp_memsim` 已清理。
## 20:4x 内存治理方案调研(**只读**,未改任何配置)
- **关键发现①:平台已预留 env 覆盖点,改 V8 堆限零代码改动**
`src/supervisor/orchestrator.ts:402` → `NODE_OPTIONS: process.env.DSH_INSTANCE_NODE_OPTIONS ?? '--max-old-space-size=256'`
⇒ 运维只需在 **`/etc/dshs.env`**(service 的 `EnvironmentFile=`)加一行 `DSH_INSTANCE_NODE_OPTIONS=...`;**改 .env 只需 `systemctl restart`(不必 daemon-reload)**。
⚠️ 副作用:该 env 被实例内**所有 node 子进程继承**(用户自己跑 node 也被限),非 node 任务(python/ffmpeg)不受影响。
- **关键发现②:档案 58 的设计假设与实测严重脱节**(源码注释自述,这是定靶心的依据)
- 设 256 时的依据:「dsh 实测老生代仅 **27~30 MiB**,三倍余量足够」→ 现在实测被会话数据填到 **250 MiB**,**低估近 9 倍**
- 配额依据:「384 − 145 = **239 MiB 留给用户任务**」→ 实测 dsh 自身吃到 **363.7 MiB**,**留给用户任务只剩 ~20 MiB**(用户跑 python/ffmpeg 立刻爆)
- 另有 `NODE_COMPILE_CACHE=<home>/.node-compile-cache`(削 code range 88 MiB 冷启动开销)
- 注释亦载明:dsh 是 223 包 / 717 JS 文件 / 21.2 MB 源码,私有内存里 **88 MiB 是 [anon](V8 code range)**
- **配额硬编码在代码里**:`orchestrator.ts:625` `-p MemoryMax=384M`;改它要走「改码 → npm run build → drain + 重启」(注释里给了回滚口径)。
- **方案结论(待用户拍板,本轮未执行)**:① 压 `DSH_INSTANCE_NODE_OPTIONS=--max-old-space-size=160`(零代码,省 ~90 MiB)② guest 在「功能管理」卸 `dsh-univer-office`(省 65 MiB + 消除 `/univer-api/state` 无限 400)③ 配额 384→512 **暂不动**(宿主 1.87 GB,2 实例即 1.02 GB,风险大于收益)④ 加内存阈值告警(现状贴顶是静默的)。**①②合计可把峰值从 383 压到 ~230 MiB**。长期解法 = 升宿主内存(花钱,须用户决策)。
- 两个动作都要重启/重载,**会断在线实例约 4 秒**(T05 实测口径)→ 按 R8 已向用户报明,等窗口。
## 21:0x 用户拍板「先做 1、4」→ **已落地并实测验证**
- **锁**:全局执行锁 + 服务器侧操作锁(`instance-mem-tune`);完工**反序释放**(先 op-lock 后 exec-lock)✓
- **改动 1(零代码)**:`/etc/dshs.env` 追加 `DSH_INSTANCE_NODE_OPTIONS=--max-old-space-size=160` → `systemctl restart dshs`。**走的是平台预留覆盖点**(`orchestrator.ts:402`),未碰官方 dsh。
- 实测:orchestrator 进程 env 已读到;**实例 `NODE_OPTIONS=--max-old-space-size=160`**、`v8 heap_size_limit=163M` ✓
- **效果:实例 `rss` 363.7 → 195.5 MiB(−46%)**,cgroup 总量 382 → 274 MiB(余量 ~110)
- 验证方式(R4 合规):自建临时 session 直插(**不走登录、不踢用户**)调 `POST /api/dsh/enter` 拉起实例 → 验证 → `[cleanup] 已删除 1 条`
- **改动 2**:`instance-mem-sample.cjs` 加**阈值告警**(85% × 连续 3 次 → `WARN` 前缀 + `⚠` 标注;未达连续则标 `(高位 N%,x/3)`)+ **`tooSoon` 保护**(<60s 的重复触发只保持计数,防手动连跑虚高);cron 每小时 → **每 10 分钟**(否则 3 小时才告警,抓不到分钟级 OOM)
- **档**:新增 `04-调整方案/74-实例内存治理-V8堆限下调与阈值告警.md`;`INDEX.md` 登记 04-74;三件套(audit/manifest/consistency)**rc=0**。**未 scp 到 /opt/dsh/docs、未 commit**(按 §4 等用户发话)
- ⚠️ **部署陷阱(已写入项目根 `CODEBUDDY.md §7` 第 5 条)**:本机 `D:\github\dsh_shenxian` 的 `core.autocrlf=true` ⇒ **工作树 CRLF**,服务器是 **LF**;直接 scp 会把 CRLF 带进生产 → 必须 `tr -d '\r'` 先转,**且只转本次要传的那一个文件**。另注:`grep -c $'\r'` 在 git bash 里会误报(`od -c` 才准)
- **技能**:新增 `dsh-instance-diagnose`(三层定位 / cgroup 三件套 / 两条死亡路径 / 隔离复现 / 6 条踩坑)
## 21:0x 追测:**「谁占了内存」的重要纠正**(会话数据 vs 加载成本)
- **整个会话(2546 事件、11 turn)的全部字符串只有 0.9 MB**(最大单串 40820 字符,说明 dsh 对工具输出有截断)⇒ **对话内容根本不是内存来源**,"减会话长度/web_fetch"几乎无用
- **空载实例(零会话,仅 enter 后)smaps 拆解 = 285 MiB**:匿名 214.0(`V8 堆/匿名数据` 155.9 + `[heap]` 58.1 + JIT 2.2)+ 文件映射 69.1(其中 **node 本体 59.8**)+ 其他
- ⇒ **占用主体 = 「dsh 本体 + 148 官方插件 + 业务插件」的加载**(模块对象与源码),**不是操作**。配 `--max-old-space-size=160` 后 V8 按"许可"预留到接近上限,故匿名映射仍 ~214 MiB
- ⚠️ **两个实验设计坑(下次别踩)**:① `systemd-run` 起服务**不继承调用者 env** → 必须 `--setenv=NODE_OPTIONS=...`,否则堆限根本没生效(用 `v8.getHeapStatistics().heap_size_limit` 自证)② **同质字符串会被引擎优化**(`'x'.repeat(N)+i` 走 cons string 惰性拼接,不复制前缀)→ 实测"投喂 23 GB 只涨 140 MB"的假数据。**结论:内存类模拟实验不可靠,优先用真实数据统计**
## 21:1x 插件内存成本逐个实测(**推翻"装得多就占得多"**)
- 方法:隔离 cgroup(`MemoryMax=600M`)+ `node --expose-gc`,逐个 `import` guest 的插件测增量(**`.cjs` 不支持顶层 await → 必须 `.mjs`**)
- **结果(rss 增量)**:`dsh-univer-office` **+65.1 MiB** | `libsql` +7.8 | **平台自研 ×3(`@dsh-local/portal-entry`、`workspace-scoped-picker`、`business-plugins`)= 0.0 MiB** | `puppeteer-core` **0.0 MiB**
- ⇒ **成本由"装了什么"决定,不是"装了几个"**:一个重型插件 ≈ 无穷多个轻量插件。**平台自研 3 个合计 0 MiB(写法是轻的)**,是正面样板
- **`puppeteer-core` = 0 MiB 是"懒加载有效"的实证**(只 import 主入口、不启动浏览器)
- **univer 为什么重**:`lib/index.js` = **单文件 bundle 5400+ 行 / 6 MB**,**顶层静态 import** 拉起重型依赖(连 `@puppeteer/browsers` 都在,一个表格插件带浏览器依赖),全文件**仅 2 处动态 import** ⇒ 优化空间在「改懒加载」,但它是第三方的(要 fork 或提上游)
- **guest 的 `bundles` 实际清单**(`profiles/web/package.json`):`@deepseek-ai/dsh-base`(148 官方插件基座)+ `dsh-web-app` + 3 个 `@dsh-local/*` + `dsh-univer-office`。注:`@liustack/modlens` **只在 node_modules 残留、不在 bundles**(已禁用)
- ⚠️ 插件的 `pnpm remove`(禁用)**必须重启实例**才真释放 —— Node 模块缓存不卸载
- 已把上述实测数据与两条实验坑补进技能 `dsh-instance-diagnose`
## 21:3x 用户报「继续检查 guest 最新对话还是有问题」→ 已定位
- **用户的问题 = univer 打不开**:会话 `eda867a0`(21:28 最后更新)里,用户 21:24「再试试呢」→ agent 重试 `univer_new` **又失败**(`bundled Gateway did not become ready within 10000ms`);21:27「如何打开 univer」→ agent 深挖源码后转向用 python 产出 xlsx+docx(21:25 已生成 `reports/抖音爆款视频表_2026-09-12.xlsx` + `日报.docx` + 4 图表)
- **★ 独立验证出的根因 = 架构级不兼容(配置不可修)**,两条**独立**阻碍:
1. **Host→Gateway**:实例内 node 连不上自己起的 `127.0.0.1:9080`(nft `dsh_egress` 对 `skuid 100000-199999 → 127.0.0.0/8` 是 `reject with tcp reset`)
2. **Browser→Viewer**:源码**硬编码** `gateway = http://127.0.0.1:${port}`、`viewerUrl = ${gateway}/?file=...`,client 拿它当 **iframe src** ⇒ 浏览器去**用户自己电脑**的 127.0.0.1 找 Viewer ⇒ **即使解封 loopback 也打不开**
- ⇒ 该插件的 Viewer **假设「浏览器与实例同机」(本地部署)**,与托管多租户平台根本不兼容 → **治理结论:候选池下架 / 用户禁用**(顺带省 65 MiB)
- **内存现状(跑会话后)**:cgroup **370 / 384 MiB**(余量仅 14);smaps 对比空载:`V8 堆/匿名 155.9 → 275.5`(+120)、文件映射 69.1 → 110.1 ⇒ **只要跑了活,V8 堆就涨到"许可上限"附近**。**但全程无崩溃、无 OOM** ✓ —— 堆限 160 的防崩作用已验证
- 服务级 20:53 后**零 crash-restart** ✓
- 技能 `dsh-instance-diagnose` 已更新:① `NODE_OPTIONS` 更正为 **160**(含档案 74 与覆盖方法)② 新增「**已知与本平台不兼容的插件**:`dsh-univer-office`」整节(含两条阻碍的机理与验证命令、替代路径)
- 排查用会话副本已清理;**全轮只读,未改生产**
## 21:5x 用户拍板「把托管友好性作为插件评估与改造的一个点」→ **已立档(档案 75)**
- **新增档案 `04-调整方案/75-插件评估新增托管友好性与资源成本两个维度.md`**(📋 只立标准,**未改预检脚本**)+ `INDEX.md` 登记;三件套 **rc=0**;**未 commit、未 scp docs**
- **维度 A 托管友好性(H1–H4)**:核心判据一句话 = **「插件的一切对外交互,是否都能走平台已有的那一条入口」**(浏览器侧只用相对路径;实例侧不依赖 loopback 网络服务)
- **H2 = 阻断级**:**client bundle 里出现绝对 URL(`http://` 字面量)= 会交给浏览器 = 托管平台必失效且平台侧无法补救**(univer 死因)
- H1 硬编码 loopback / H3 自建监听=警告;H4 交付 URL 由谁生成为人工研判
- 检测复用档案 71 的 `walkJs` 静态扫描框架,**不另起一套**
- **维度 B 资源成本**:加载 `rss` 增量,**>30 MiB 标注 / >60 MiB 强提示**(univer 实测 +65.1;自研 ×3 = 0.0 是正面基线)
- **改造规范 R-a~R-e**(自研/改造插件必守):① 对外入口唯一(挂 Host 的 webServer,同域相对路径)② **内部通信走 IPC(unix socket / stdio),不用 TCP loopback** ③ 不新增监听端口 ④ 重型依赖懒加载 ⑤ 交付浏览器的地址一律由 Host 生成且为相对路径
- **与既有档案关系**:是**档案 71 的新增维度**(沿用其分级语义);与 70(预检盲区致事故)同属系统性补盲;与 74 的资源成本同源
- 技能 `dsh-instance-diagnose` 的「已知不兼容插件」节已加**指针**指向档案 75(不复制内容,保持单一来源)
- **落地(待排期)**:预检脚本加 H1/H2 静态扫描 + H2 命中判 `blocked` + 静态体积提示 → **属生产代码变更,须另立交接单**(规划与执行分离);T03 的 MCN suite 起按 R-a~R-e 验收
## 22:0x 用户问「现在可以改造这个插件了吗」→ 可行性已验证(**三个门槛全过**)
- **门槛① 源码可得** ✅:已 clone 上游到 `D:\github\dsh-univer-office-src`(**浅克隆**,395 文件 / **216 个 ts** / 15 MB,含 `src/`、`packages/`、`scripts/`、4 个 tsconfig、`test/`)。仓库 `github.com/dream-num/dsh-univer-office`(走代理 10800)。⚠️ 候选池里的 tgz **只有产物无源码**(`files` 只发 lib/artifacts/docs/skills,构建脚本都没发布)→ **改造必须回到上游**
- **门槛② 依赖可装** ✅(**原本最大不确定项**):`.npmrc` 把 `@univer-cli`/`@univerjs`/`@univerjs-pro` 三个 scope 全指向**私有 registry `https://insider-npm-registry.univer.work/`**,且仓库**未配 authToken**;实测该 registry **公开可读**(包元数据 HTTP 200,返回正常 npm 元数据)→ **无需 token 即可装**
- **门槛③ 改动点定位** ✅(TypeScript 源码,模块化清晰:`src/{gateway-app,host,client,viewer-app,viewer-support,render-machine,workers,shared}`)
| 类 | 位置 | 要点 |
|---|---|---|
| ① | `src/gateway-app/server.ts:11` / `:136` | `const LOOPBACK_HOST = '127.0.0.1'` + `server.listen(port, LOOPBACK_HOST, …)` → 需支持 unix socket |
| ② | `src/gateway-app/main.ts:16-17`、`src/host/processes/gateway/gateway-process.ts:29`、`protocol.ts:20` | origin 构造 `http://` / `ws://127.0.0.1:${port}` → 需可配置 |
| ③ | `src/host/provider/gateway-univer-service.ts:538` | `viewerUrl: ${gateway}/?file=…` → 需改相对路径 |
| ④ | (**新写**) | **Host 侧新增反向代理**(`/univer-api/viewer/*` → gateway),**含 WebSocket upgrade 转发** ← **真正的工作量在这** |
- **工作量诚实评估**:①②③ 合计不到 10 行;**④「代理层含 WS」是新功能**(且要穿 nginx → orchestrator 两跳)。构建链重:`build` = lib+worker+gateway+render+viewer **五个产物**,150+ devDependencies(vite 8 / ts 7 / tailwind 4 / insiders 快照依赖)
- **已启动**:`pnpm install --frozen-lockfile`(`prepare` 会自动触发 build)→ **这是决定改造成立与否的最后 gate**(构建不出来 = 改造不成立)。本机环境:node v22.22.2 ✓ / pnpm 11.21.0(源要求 11.23.0)/ D 盘余 1.2 T ✓
- **待定(要用户拍板)**:若继续,须先定「**fork 放哪 / 谁维护 / 上游更新怎么合**」——这是长期承诺
## 22:1x 用户说「开整」→ **构建验证通过,改造已开工**
- **✅ 构建 gate 通过(决定性)**:`pnpm install --frozen-lockfile` 成功,**`prepare` 自动跑完 `build`**(5 个产物)——`✓ built in 10.16s`、`built ...\artifacts\viewer`、`Done in 14m 45.1s`。只有 `INEFFECTIVE_DYNAMIC_IMPORT` 良性警告(i18n locale chunk 优化提示)。**⇒ 改造成立**
- 产物:`artifacts/{gateway.cjs 57M, unit-content-worker.mjs 30M, viewer/, render-machine/}` + `lib/{index.js 5M, client.js 1.1M}`
- **我的改动已验证编译进产物**:`gateway.cjs` 里 `UNIVER_COLLAB_GATEWAY_SOCKET` ×1、`socketPath` ×12
- 增量构建快(rollup 10 秒级),首跑 14m45s 主要在装依赖
- **已改 3 个文件(gateway 侧监听层,均向后兼容)**:`src/gateway-app/server.ts`(`LOOPBACK_HOST` 常量 → 新增 `socketPath`/`host` 选项,`listen()` 支持 unix socket)、`main.ts`、`gateway-entry.ts`(真入口,读 `UNIVER_COLLAB_GATEWAY_SOCKET`)
- **★ 关键发现①(简化方案的钥匙)**:gateway 全部 API 在 **`/uf/<base64url(univerfile)>`** 前缀下(`main.ts` 自带端点清单),WS 是 `/uf/<enc>/universer-api/comb/connect`;**Viewer 加载后按绝对路径请求 `/uf/*`** ⇒ 代理规则两条即可:`/uf/**` + `/univer-api/viewer/**`
- **★ 关键发现②(坑)**:**Node 内置 `fetch` 不支持 unix socket**(无 `socketPath`)⇒ 健康检查 `protocol.ts:4` 与 `GatewayClient` 都要改。**但改造面比预想小**:host 侧 fetch 仅 **5 处**,且**已有集中封装 `src/host/adapters/gateway/client.ts`(89 行,只用 `response.json()/ok/status` 三个能力)** ⇒ 在 `GatewayClient` 内加一条 unix 分支(`http.request({socketPath,…})` 包最小 Response-like)即可覆盖大部分
- **剩余改动清单**:④ `launcher.ts` 传 socket env | ⑤ `gateway-process.ts:29` origin 构造 | ⑥ `protocol.ts:2,20` 健康检查 | ⑦ `gateway-univer-service.ts:538` viewerUrl → 相对路径 | ⑧ `adapters/gateway/client.ts` 加 unix 分支 | ⑨ `host/webServer/router.ts` **新增代理层**(`/uf/**` + viewer)
- 代码在 `D:\github\dsh-univer-office-src`(**未打包、未碰生产**)
## 22:2x 改造阶段 1 代码完成(①~⑨),构建通过
- **新增 4 个模块**:`shared/gateway-socket.ts`(socket 路径解析 + **`unix:<path>` 端点标识** `unixOrigin`/`parseUnixOrigin`)、`shared/unix-http.ts`(**HTTP over unix socket 最小客户端**,只覆盖 ok/status/text,**零新依赖**)、`shared/viewer-paths.ts`(浏览器侧路径常量,避免 service→webServer 依赖)、`host/webServer/viewer-proxy.ts`(**流式反向代理**,去掉 hop-by-hop 头)
- **改动**:launcher(传 `UNIVER_COLLAB_GATEWAY_SOCKET`)|gateway-process(端点按传输打标)|protocol(健康检查 + probeGateway 支持 socket)|**adapters/gateway/client(加 unix 分支,一处覆盖绝大部分调用)**|router(viewer 代理分支)|plugin(`/uf` 注册)|gateway-univer-service(**viewerUrl 与 base 均改相对路径**)
- **★ 关键设计决策(易踩)**:dsh 的 webServer 是**前缀匹配但无「最长优先」** ⇒ `/univer-api` 会吃掉 `/univer-api/viewer/...` ⇒ **viewer 代理必须放进现有 router 的 dispatch 内**,单独注册会被 404 截胡;`/uf` 与之不冲突,可独立注册
- **★ 踩坑**:只 grep `viewerUrl:` 会漏 —— 另有 `const base = ${gateway}/?file=` 派生 `openUrl`/`worktreeUrl`/`mergeUrl`(**全是交给浏览器的 URL**)。改完产物内旧拼接残留 = **0**
- **构建**:`✓ built in 13.69s` rc=0;产物含 `createGatewayViewerProxy`×3、`parseUnixOrigin`×4、`VIEWER_PAGE_PATH`×3、`UNIVER_DSH_GATEWAY_SOCKET`×1
- **剩余**:① 3 处散点 fetch(`render-source-operations.ts:147`、`resource-operations.ts:86`、worker 侧 1 处)socket 模式下也要改 ② **WS 代理(阶段 2)**:dsh `webServer.registerUpgrade` 是**精确路径**匹配,而 gateway 的 WS 在 `/uf/<enc>/univeriser-api/comb/connect`(含动态段)⇒ **需按文件动态注册**(`ctx.effect` 可增删)③ 自带 `test:host`/`test:integration` 冒烟 ④ 打包 → 候选池 → guest 启用 → 端到端验收
## 22:4x ★ socket 传输在**真实 Linux** 验证通过(改造核心假设成立)
- **方法(不碰生产)**:构建产物(`lib` + `artifacts` + `test`,36 MB 压缩)传到服务器 `/tmp/uv-verify`,用 **guest 实例已装的 node_modules 做只读符号链接**补原生依赖(`libsql` 等)
- **结果(unix socket 模式)**:`[dsh-univer-gateway] listening on unix:/tmp/uv-verify/gw.sock`;`GET /` → **200**,且 `<title>Univer</title>`=true、`<div id="app"></div>`=true(**这正是原先失败的健康检查判据,在 socket 上成立**);`GET /uf/AAAA` → 400(路由可达)。**TCP 对照同样通过**
- ⚠️ **Windows 限制(本机测不了 socket 的原因)**:本机 **AF_UNIX 返回 EACCES** —— 用**纯 `node:net`** 也复现 ⇒ **平台限制,与改动无关**;`UNIVER_DSH_GATEWAY_SOCKET` 链路本身通(gateway 收到路径并尝试监听)
- **TCP 回归**:`test/integration-smoke.mjs` 在 Win 与 Linux **均 OK**(18 项:new/status/Unit/import/export/**screenshot**/print-pdf/resources/worktree lifecycle…)⇒ **改动未破坏原功能**
- **清理**:服务器 `/tmp/uv-verify`、本地临时文件已删;**生产完好**(guest 实例 `active`、依赖正常)
- **⏳ 待拍板(R8)**:端到端验证必须**部署到 guest 实例**(启停 = 重启实例、断在线)—— 这是唯一需要用户点头的一步
- **仍待做**:① WS 代理(阶段 2,按文件动态注册 `registerUpgrade`)② 边缘功能 socket 支持(截图资产、worker snapshot)
## 23:1x 改造**全部完成**(代码 + 验证 + 打包),仅剩部署验收待拍板
- **WS 代理(阶段 2)完成**:`createGatewayUpgradeProxy`(转发 upgrade:重建 101 响应头 + 双向 pipe + 失败降级)|`viewerSocketPaths(fileKey)`(两条:`/uf/<enc>/universer-api/comb/connect`、`/uf/<enc>/events`)
- **注册机制**:dsh `registerUpgrade` 是**精确路径**且 gateway 的 WS 路径含动态段 ⇒ **按文件懒注册**(router `/univer-api/state` → `onViewerFileOpened` 回调 → plugin 内 Map 去重 + `ctx.effect` 统一 dispose)
- **统一传输入口** `shared/gateway-request.ts`:`requestGateway(endpoint, path, init)` 按 endpoint 前缀选 socket/TCP ⇒ `client.ts` 与 `render-source-operations.ts`(截图资产)均改用它,**消除重复分支**;`unix-http.ts` 扩展 `headers.get`/`json()`/`arrayBuffer()`/`signal`
- **未做(已标注为已知限制)**:worker 侧(`unit-content-worker`,协同/快照类)仍走 TCP —— 其内部 12 个 URL 用标准 `fetch` 拼接,改造面大且属边缘;**导出 xlsx 走 Host 的 `GatewayClient`,不受影响**
- **验证全绿**:构建 rc=0 | 回归 `integration-smoke` **OK**(18 项功能)| **Linux socket 复验通过**(`GET /`→200 且 Univer 标记 true;TCP 对照同)| 版本升 **0.2.15**、`pnpm pack` 产物 `/d/tmp/dsh-univer-office-0.2.15.tgz`(41 MB)
- **清理**:服务器 `/tmp/uv-verify` 等 + 本地临时文件已删;**生产完好**(guest 依赖正常)
- **⏳ 唯一待拍板**:**部署到 guest 实例做端到端验收**(R8:启停 = 重启实例、断在线)
## 23:2x 用户「确认执行」→ **已部署到 guest,但端到端仍差一步**
- **✅ 投放成功**:`POST /api/plugins/business`(admin,base64 传 tgz)→ `version 0.2.15`、`replaced:true`、**`compat.level=ok`(223 平台包全匹配)**、**无 P0 命中**(`trustedOverride:false`);仅 `network-egress` 警告(来自 README/assets 里的 URL 字符串,非阻断)
- **⚠️ 发现平台缺陷(重要)**:guest 侧「禁用→启用」**失败**(`安装失败,请重试或联系管理员`)。根因:**`business-plugins.ts` 的 `pnpm add` 缺 `-w`** ⇒ `ERR_PNPM_ADDING_TO_ROOT`(profile 内有 `pnpm-workspace.yaml`,必须显式 `--workspace-root`)。且该路由 `runPnpmAs` 用 `stdio:'pipe'` **吞掉 pnpm 的 stderr** ⇒ journal 里查不到真实原因。**手动 `pnpm add -w` 成功**(5.5s,装到 0.2.15,属主正确 100002)
- **✅ 平台侧配置完成**:`orchestrator.ts` 的 baseEnv 增加透传(`DSH_INSTANCE_UNIVER_SOCKET` → 实例内 `UNIVER_DSH_GATEWAY_SOCKET`,**不设则保持原 TCP 行为**);`npm run build` 通过;`/etc/dshs.env` 加 `DSH_INSTANCE_UNIVER_SOCKET=auto`;重启服务后 orchestrator 已读到;**实例 env 实测注入成功**(`UNIVER_DSH_GATEWAY_SOCKET=auto`)
- **✅ 组件级验证全过**:实例内 `os.tmpdir()=/tmp` → socket 路径 `/tmp/dsh-univer-gateway-<pid>.sock`(**103 字节,未超 108 上限**);**在实例命名空间内(`nsenter`)用插件真实产物跑 gateway → `listening on unix:/tmp/…sock`,socket 文件生成** ⇒ **改造在实例内确实有效**
- **⚠️ 端到端仍未通过**:用户浏览器 **23:24:10 请求 `/univer-api/state` → 23:24:18 返回 400(8.2s)**;实例当前**无 gateway 子进程**、`/tmp` 下**无 .sock**。400(非 500)指向**校验类错误码**(`INVALID_*` / `SESSION_SCOPE_*`),8.2s 说明确曾尝试启动
- **⏳ 下一步**:请用户在实例里**再触发一次 univer 操作**(23:24 那次实例刚重建 ~23:24:04,插件加载可能未就绪);若仍 400,开 `UNIVER_DSH_GATEWAY_DEBUG=1` 取 gateway stderr
- **另注**:同期间发现 **admin 实例 crash-loop**(`dsh-plugin-mcn-suite` 缺 `xlsx` 依赖,`crash-loop-circuit-open`)—— **与本次改造无关**,属 T03 遗留问题
## 20:4x T03 推进到「上传前一刻」—— **按纪律停手**(线上有别的会话在动)
- **窗口条件已满足**:guest 实例 `systemctl is-active dsh-100002-299e7f95.scope` = **inactive**(已停)+ 最后活动 20:06(41 分钟前)⇒ **零中断窗口** ✓
- **T03 执行路径已完全摸清**(本轮收获,下次直接可用):
· **上传** = `POST /api/plugins/business`(admin),body = `{filename, file:<base64 tgz>, trust?:{confirmed,reason}}`;cookie 名 **`sid`**;端口 **3080**;`UPLOAD_BODY_LIMIT = 180 MB` ✓(1.9 MB 包绰绰有余);P0 命中且无 trust → 409 fail-closed。
· **启用** = `POST /api/plugins/mine/apply`(**用户**自己发)⇒ 按刚固化的规则「用户在实例里自己启用」,**这一步不该我代劳**。
· **摘 modlens** = 改 guest `profile/web/package.json` 的 bundles 列表(零中断,因实例已停)。
- **⛔ 停手原因**:`bash scripts/op-lock.sh status` 显示 **🔴 `instance-mem-tune`**(占用者 `mem-opt-2046`,20:47 起)→ 线上有会话正在动生产 ⇒ 按纪律停手;已 `release t03-upload-pool` 撤掉我刚占的位。
- **⚠️ 发现 op-lock 的设计缺陷**:它**按「操作名」占位**(锁根下一个操作一个子目录)⇒ 两个会话占**不同名**的操作**都会成功** ⇒ **它拦不住「并行动生产」**(不像全局执行锁那样真正互斥)。本次即「占位成功但线上已有人」—— 只能靠人主动看 `status`,机制本身不设防。建议改为「单锁位 + 占用者字段」,或 `claim` 时发现已有任意占位即默认拒绝(`--force` 才可覆盖)。**待下次改文档时补进 `交接单/README §六`。**
## 21:0x T03 上传候选池 —— **被 T05 兼容性预检拦住(HTTP 409 `compat_incompatible`)**
- 条件全满足(op-lock 空、guest `inactive`、59 分钟无活动)→ 占 op-lock → scp tgz(md5 `f98a3303…` 与单子一致 ✓)→ `mksess.cjs` 造 admin 会话 → curl `POST /api/plugins/business`。
- **判据(服务端回显)**:`{"kind":"dep-range","pkg":"@deepseek-ai/dsh-client-ui-sidebar","detail":"平台为 0.1.2-rc.1,插件要求 ^0.1.0-rc.5(不满足)"}`。
- **原因(semver 的 prerelease 规则,不是误报)**:带 prerelease 的版本只能被「**同 major.minor.patch 且带 prerelease**」的比较器匹配 → `0.1.2-rc.1` 的 tuple (0,1,2) 在 `^0.1.0-rc.5`(= `>=0.1.0-rc.5 <0.2.0`)内**没有对应比较器** → 判不满足。**插件声明过严**(作者意图「≥0.1.0-rc.5」,但 `^` 写在 prerelease 上不等价)。
- **根因(时间差)**:**T05 兼容性预检 16:35 才上线**,而 T03 的包是 **11:15 打的** → T03 单子没预料到这道新关卡。
- **副作用 = 零**:候选池**未变**(池内仍只 `dsh-univer-office @0.2.14`);临时 admin session **已删**(`deleted sessions=1` ✓);服务器与本机临时文件已清;op-lock **已释放**(无人占用 ✓)。
- **待定处置**:(a) 改插件 `package.json` 依赖声明放宽到能匹配 `0.1.2-rc.1` → 重打包 → 重传(**推荐**,不掩盖问题);(b) 人工确认实际兼容后上传带 `trust:{confirmed:true,reason}` 放行(快,但掩盖声明问题)。**下一步:先只读验证「插件实际用了 sidebar 的哪些 API / 平台 0.1.2-rc.1 是否提供」再定。**
- 经验:`op-lock.sh release` **必须带同一个 `ME=`**,否则被按 R9 拒绝(本次踩 1 次,属**正确**保护)。
## 22:2x T03 **安装失败 → 根因定位 + 修复 + 重传完成**(用户报障「安装失败」)
- **现象**:用户在「功能管理」启用 mcn-suite → 失败。取证:apply 请求 200 → 实例被拉起(22:12:57 `dsh web: http://…`)→ 但 `node_modules` 无包、bundles 无、日志无 mcn 痕迹、profile mtime 被改 ⇒ **平台 install 阶段失败并回滚**。
- **复现**(mksess + POST apply + 轮询 task):`#failed | 失败 ERR: 安装失败,请重试或联系管理员` —— **阶段停在「正在安装 1 个插件…」,不是探活阶段**;⚠️ **平台把真实错误泛化吞掉了**(违反 A10「失败面要留证据」)。
- **★真实根因**(手动跑平台同姿势 pnpm 才逼出来):
`ERR_PNPM_NO_MATCHING_VERSION No matching version found for @deepseek-ai/dsh-client-runtime@>=0.1.2-rc.1 <0.2.0-0`
⇒ **这些 `@deepseek-ai/dsh-client-*` 不在公开 npm registry 上**(平台用的是本地那份 0.1.2-rc.1),而 **pnpm 会去 registry 解析 peer 依赖** → 解析不到 → 装不上。**这与「预检拒收」是两层不同问题**:改范围只过了预检,装的时候照样卡。
- **修法**:`package.json` 增加 **`peerDependenciesMeta`:4 个 peer 全标 `optional: true`**(准确表达「由平台提供、不走 npm」);版本 **0.3.1 → 0.3.2**;`npm pack` 重打包(1,951,946 B)。
- **验证**:以 admin 身份手动 `pnpm add` 新包 → ✅ **成功**(`+ dsh-plugin-mcn-suite 0.3.2`,2.9s)→ 随后清理残留(`pnpm remove` 报 CANNOT_REMOVE_MISSING(package.json 已恢复、无该依赖)→ `rm -rf` 兜底)→ bundles 回到平台 5 个 ✓
- **上传**:HTTP 200、**`replaced: true`**、`compat.level=ok`;候选池 = `dsh-plugin-mcn-suite @0.3.2` + `dsh-univer-office @0.2.14`。
- **现场**:服务器与本机诊断脚本/临时包全部清理;admin profile 已复核一致(node_modules 无残留、deps 正常);op-lock 已释放。
- **⚠️ 建议修平台缺陷**:apply 失败时应回显 pnpm 的真实 stderr(现在吞成"安装失败,请重试或联系管理员",导致必须手动复现才能定位)。
- **待办**:请用户再试一次启用(0.3.2 已就绪)。另:用户新提「功能管理列表改卡片、一行 2 个」→ 新需求,待做。
## 22:3x 「功能管理」插件列表 → **卡片形式(一行 2 个)v0.2.6**(用户要求「一起改了吧」)
- **改动**(`poc/business-plugins/lib/client.js`,4 处纯渲染层):① 新增**内层网格容器** `display:grid; gridTemplateColumns:repeat(2, minmax(0,1fr)); gap:10px`(**单独包一层**,不直接改外层 `wrap` —— 它同时装标题/搜索/按钮/提示等纵向块;插件项收集进 `cards[]` 再统一 push)② 每项由「行」改「卡片」:`padding 12px 14px`、`borderRadius 12px`、`background: var(--dsw-alias-bg-layer-1,#fff)`、**常显 1px 边框**(原非错误项为 transparent)③ hover 改按规范 §4.5:`border-color: brand-primary + box-shadow 0 4px 12px rgba(47,111,237,.15)`(原为换背景色)④ 卡片内 `alignItems: flex-start`(复选框/徽章顶部对齐,长说明不撑歪行高)。
- **规范依据**:06-工作台UI规范 §4.5(卡片 r12 + hover 主色扩散影)、§2.4、§2.3;颜色仍走 `--dsw-*`(section 嵌在 dsh 面板内)。
- **验证**:`node --check` 语法 OK(556 行);版本 0.2.5 → **0.2.6**;`npm pack` → `dsh-local-business-plugins-0.2.6.tgz`(产物名带 `dsh-local-` 前缀,**部署脚本要的是 `business-plugins-*.tgz`** → scp 时改名)。
- **部署**:`/opt/dsh/artifacts/business-plugins-0.2.6.tgz` → `node scripts/ensure-biz-plugins.cjs --all`(**未加 `--restart`**,不主动打断在线实例)→ admin/guest 均确认 **0.2.6**、包内含改动 ✓;op-lock 已释放。
- **生效条件**:client bundle 在**实例启动时**加载 ⇒ **用户需重启自己的实例**才看得到。
- **未做**:浏览器渲染验证(本机无 Chromium)→ 待用户目视验收。
- 落档:档案 67 追加「v0.2.6 卡片化」小节。