Files
dsh_ai1net_server/.workbuddy/memory/2026-09-下12.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

429 lines
47 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(第 13 片)
> ⚠️ **本目录日志已按【月】分片**(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-下11.md` **下一片**:`2026-09-下13.md`
---
## 08:20–08:45 会话 `3c12a818`(锁名 `浮层排障-0822`):**我 08:15 的改动把注入脚本写崩了** → 热修 + 触发面补全 + 固化校验
**用户报障**:「admin 主页面断开连接,但没显示『工作区已休眠正在唤醒』浮层,模型一直显示连接异常」。
### 一、根因(我引入的)
- 08:15 浮层视觉升级时写了 `st.textContent = [...].join('\n');` —— 该代码在 **TS 模板字面量** `SESSION_RECOVERY_JS` 内部,
`\n` 在**模板求值时**变成**真换行** ⇒ 注入浏览器的 JS 直接 **SyntaxError** ⇒ **整段注入脚本不执行**
⇒ 浮层 / 自检 / 自愈全废,页面只剩 dsh 自己的「连接异常」。
- **为什么之前的校验没拦住**:只做「从 `lib/` 抽**原始文本** → `new Function()`」——**跳过模板求值**,所以是**假绿**。
### 二、顺带挖出的第二个(更早、更严重)
- 同文件的 `SESSION_ASSIST_JS`(档案 56「我的文件 / 能力」助手)**8 处 `'\n'` 同样是单反斜杠** ⇒ **从来没在浏览器里执行过**。
此前验收只 grep 页面 HTML 有没有 `__dshAssist` 字样 —— **验的是"文本在不在",不是"脚本跑不跑"**(同一假绿模式)。
### 三、修复与固化
1. 我引入的那处 → `.join(String.fromCharCode(10))`;
2. 助手脚本 8 处 → 补成 `\\n`(只补未转义的);
3. ⛔ **新增 `scripts/verify-inject.cjs` 并接入 `npm test`**:**先模板求值成运行时字符串 → `node --check`**,
并断言运行时串无反引号 / `${`。⇒ 这类假绿再也不可能溜过 `npm test`。
### 四、触发面补全("页面没反应"的另一半,实测证明)
- 实测:**用户一直盯着页面 + 连接悄悄断 → 原设计一个事件都不会来**(只有切标签/focus/pageshow 才探活)⇒ 既不提示也不恢复。
- 补两条**静默**触发面:① 页面**可见**时每 **25 s** 心跳;② 包装 `EventSource`/`WebSocket` 的 `error`/`close`。
两者走新 **soft 模式:连续两次失败才恢复**(恢复=原地 `location.replace`,**会丢未保存输入**,一次抖动不该触发)。
- ⚠️ **推翻了档案 77 §五 方案 B「不做周期性探活」**(原前提"用户在场却不操作没有收益"被现场证伪)。
### 五、验收(真 Chrome + 临时会话,只读)
| 项 | 结果 |
|---|---|
| 修复后基线 | ✅ `__dshRecover=true`、`__dshAssist=true`、**语法类错误 0**(助手脚本**首次真正运行**) |
| 事件路径回归 | ✅ `pageshow(persisted)` → 浮层出现、`class=__dsh-ov`、新视觉节点齐 |
| **无操作自动恢复** | ✅ 只打挂实例侧探针、**不派发任何事件** → **47.4 s 后浮层自动出现**(两次心跳判定),0 页面错误 |
| `npm test` | ✅ 45 pass / 0 fail / 1 skipped + 新增注入脚本校验双通过 |
| 部署 | ✅ 两轮热修 PID 415625→416910→**417427**;备份 `…bak-before-hotfix-0825` / `…bak-before-triggers-0835` |
### 六、文档 / 技能 / 锁
- 档案 77 追加「现场故障 + 热修 + 触发面扩展」;**档案 56 追加「修正」节**(其注入脚本一直没跑起来,原验收结论需重判);INDEX 04-77 行补记;四件套 rc=0;对账 一致 **130**。
- `dsh-change-workflow` 技能 **v2.8.0 → v2.9.0**:① **纠正三行过期规则**(原文写"服务器是文档唯一源 / 禁止本地改 / `docs-sync-check.sh` 已删"——与现行协议相反)② 新增「注入脚本两条铁律 + 两条浏览器验证路径(playwright-core 独立无头 vs browser-harness 附着用户 Chrome)」。两副本 md5 一致 + scp。
- 双锁已释放(op-lock 释放需带 `ME=<原占用者>`)。
## 08:41 清点「还有哪些待执行」(只读取数,未写库)
- **读的单一来源**:`交接单/README.md §一`(已规划待执行)+ `03-路线图 §二 进行中/待办` + 各档案挂起清单。
- **顺手核销一个挂起项**:档案 59 的「`wake.html` 本机 4708B vs 服务器 4591B 待核对同步」→ 实测**双端一致**
(本机/服务器均 **4962 字节**、md5 同为 `00b81728…`;变大是因为档案 78 今天给 wake.html 加了熔断文案)⇒ **该挂起项可关闭**
(待持锁会话释放后回填 `03-路线图`)。
- **当前持锁者**:`t03-ledger-0841`(**08:42 起**持全局执行锁,在做 **T03 台账**)⇒ 本会话此刻**只能读、不能写**文档库。
- 结论清单(见当日回复):待执行 = T01(待认领)|T03 剩 guest 启用(用户动作)+ 验收归档|档案 76 的 400(P1 待查)|
档案内挂起(57 四项 / 66 两项 / 68 / 42 / 38b / 15 / 32)|P3 门户浏览文件加下载入口|触发式(dsh 升级回归 / 会话 GC)|
我自己待补 = 档案 78 L1 熔断实测(要窗口)· 档案 56 面板真机验收(原验收是假绿)· 档案 76 的 400 复现 · 自动化「遗留项自动推进」重建(待授权)。
## 08:41–08:5x 会话 `48a9c14c`:盘点「还有哪些方案待执行」+ 线上只读核对(账实相符)
### 一、★新取证:实例数据根与 profile 的真实路径(此前只知"有 profile",不知确切位置)
- **实例数据根 = `/var/lib/dshs/users/<user-uuid>/`** —— ⚠️ **不是 `/opt/dsh/users/`**(后者只有平台账号 `main`;`/opt/dsh/users/main/.dsh/profiles/web/` 是**平台账号级** profile,其 bundles 只有 dsh-base + dsh-web-app,别拿它当实例的)。
- **实例 profile = `<数据根>/home/profiles/web/package.json`**,看 `dsh.profile.bundles` 数组 = 该实例**实际加载**的插件清单。
- 当前两个实例:admin uid **114801** = `cce6d1cd-b376-4304-80f0-0e1c58c9ffde`;guest uid **100002** = `4092b965-2f68-4977-9989-68b3966f7df0`(另存在 uid 100003 / 100004 / 196490 的账号,未起实例)。
- **候选池目录 = `/var/lib/dshs/business-plugins/`**(09-13 00:02 更新);平台 DB = `/var/lib/dshs/dshs.db`。
- 实例进程命令行(从 scope 可见):`setpriv --reuid <uid> … dsh --profile web --host 127.0.0.1 --port <N>`,并 **ro-bind 了 `home/profiles/web/{cordis.patch.yml,package.json,pnpm-lock.yaml}`** ⇒ 这三份是实例侧真相源。
### 二、T03 实测(08:4x):admin 已启用、guest 未启用 —— 与文档记载一致
| 实例 | `bundles` 末项 | mcn-suite |
|---|---|---|
| **admin** | …`@dsh-local/workspace-scoped-picker`, **`dsh-plugin-mcn-suite`** | ✅ 已在 bundles |
| **guest** | …`@dsh-local/workspace-scoped-picker`, **`dsh-univer-office`**(0.2.15) | ❌ 未含 |
⇒ T03 **只差「用户在 guest 实例「功能管理」里启用」这一步**(用户动作,平台不代劳)→ 再重启实例 → 跑 §八验收 → 归档。
### 三、待执行清单现状(三处单一来源已对齐)
- **交接单 §一**:`T01`(⏳ 待执行,决策点 1 已定 = A 扩展 `business-plugins`)|`T03`(🔄 进行中,见上)
- **`03-路线图 §二`**:档案 76(P1 待查 = `/univer-api/state` 持续 400,需取响应体)|档案 78 遗留 **L1**(故意反复搞崩验熔断,需维护窗口)|档案 27(暂缓)|档案内挂起一批(57 四项 / 59 wake.html 字节差 / 66 三项 / 68 / 38b 三项 / 15 / 32)|P3 门户「浏览文件」下载入口|触发式(dsh 升级回归 / 会话 GC)
- **已消解或明确不做**(免得重复问):档案 65 部署 ✅(本就生效)|档案 78 部署 ✅(08:02)|AnySearch ✅ 放弃|T05 ✅|`@liustack/modlens` ✅ 已无残留|Cookie 域收窄 ❌ 不做|管理类插件化 ❌ 不做|白名单源码安装 ✅ 关闭
### 四、只读核对附带发现(只报告,未动)
- 文档库 **33 项未提交**(末次 commit `da9d1ff`,09-12 17:45)—— 期间所有改动都在工作树里。
- 服务器 op-lock **空闲**(仅 README);`dshs` active;两个实例 scope 均 running;平台 DB 08:30 有写入。
## 08:41–09:05 会话 `a2665dd3`(锁名 `t03-ledger-0841` → `fix79-0851`):**待办盘点 + 档案 79 落档与代码修复(已 build,待重启生效)**
### 一、本轮做掉的(全部已 scp 到 `/opt/dsh/docs`,档案 600 / README 644)
| # | 动作 |
|---|---|
| 1 | `04-67` 文末追加 **v0.2.7 / v0.2.8** 段(内存预估 + 页内弹窗,含触发原话、口径、部署与线上自证) |
| 2 | **新建 `04-79`**「插件启停的两处平台缺陷」:现象链(journalctl)→ D1/D2/D3 → 连带后果(半应用态)→ **修法已备** → R8 影响 |
| 3 | `INDEX.md` 登记 `04-79`(🔴)|`03-路线图 §二` 加 **🔴 P0 待修** 行 |
| 4 | `交接单/README §一` T03 行:补「07:4x 启用失败 → 已手工回滚 profile → 实例恢复」+「前置 = 先修档案 79 + 解决堆容量(不改档位)」 |
| 5 | 四件套:audit / manifest / consistency **rc=0**;对账 **仅服务器 0** |
### 二、★档案 79 的代码修复已落地(`src/web/routes/business-plugins.ts`,**已 build,未部署**)
- **D1**(P0):`try { uninstall(sel.id) } catch {}` → **`await uninstall(sel.id)`**(+ 三行注释说明为何必须 await)
- **D2**:`['remove', id, '--ignore-workspace-root-check', …]` → **`['remove', id, '-w', …]`**(`add` 的该 flag 合法,未动)
- **加固**:外层 `void (async () => {…})()` 补 **`.catch()`** 双保险(按 `taskId` 从 `tasks` 取回任务表项 —— `task` 是 IIFE 内局部量,第一次写错被 tsc 抓出 `TS2552`)
- 验证:`tsc` rc=0(`npm run build` 成功)|产物 `lib/web/routes/business-plugins.js` 三处标记齐全(`await uninstall`×2 / `'remove', id, '-w'`×1 / `apply task crashed`×1)|**`npm test` 45 pass / 0 fail / 1 skipped**(含注入脚本文本校验 ✓)
- **未部署**:生效需 `systemctl restart dshs` = **中断在线用户数秒** ⇒ 已作为 R8 问题请示用户。
### 三、踩坑(已写进 `dsh-server-docs/CODEBUDDY.md` 同步链路)
**scp 到文档镜像必须带「同一相对目录」**:`scp 04-调整方案/X.md bt-server:/opt/dsh/docs/` ❌ 会落到 docs **根目录**(对账报「仅服务器」);`scp 交接单/README.md bt-server:/opt/dsh/docs/` ❌ 会**覆盖根 README.md**(本次实际发生,已从本机重传复原)。✅ `scp <相对路径> bt-server:/opt/dsh/docs/<同一相对目录>/`。
另:**文件名别带空格**(scp 远端路径经远端 shell 解析会被拆成两个文件 —— 本次 `79-…pnpm flag.md` 就中招,已改名去掉空格);**远端 chmod 也要给文件名加引号**,否则报 `cannot access` 而误判没传上去。
### 四、仍待办(详见给用户的清单)
改档位(用户已定暂不做)|提交/推送(文档 137 / 代码仓 24 项未提交)|T03 guest 启用(前置=容量+修 79)|univer 端到端复测需重建 `.univer`|档案内挂起项(57/59/66/68/42/38b/15/32)|T01|工作区历史临时探针清理。
## 08:50–09:40 会话 `a2665dd3`:**univer 剩余问题的根因(worker 侧没 socket 支持)+ 已打补丁并构建 0.2.16**
**用户报**:「guest 最新会话显示 univer 插件功能还是有点问题」。
### 一、根因(来自实例内 agent 自己的取证 + 我的代码核对)
- **报错原文**:`Error: gatewayOrigin must be an HTTP origin without credentials or a path`
- **agent 的核对输出**:`插件版本: 0.2.15`|`宿主 socket 支持: 3 处`|**`worker socket 支持: 0 处`**|`socket: …/tmp/dsh-univer-gateway-2.sock`(08:42 存在)
- **代码链**:`resolveTarget()` 把端点原样塞进 worker 请求 → worker `requiredHttpOrigin()`(`src/workers/unit-content/entry.ts:454-467`)只认标准 HTTP origin ⇒ socket 模式下传的 `unix:<path>` 被拒。
- ⇒ 这**正是档案 76 里我标注的"已知限制"**:宿主侧我改了 3 处,**worker(`unit-content-worker.mjs`,截图/内容类操作走它)没改**,而它只能走 TCP loopback —— 被 nft 封。
- 附带:socket 文件存在 ⇒ **宿主侧改造是通的**(不是白改)。
### 二、补丁(v0.2.16,5 处,全部加法式;TCP 路径不受影响)
| 文件 | 改动 |
|---|---|
| `src/host/adapters/unit-content/protocol.ts` | `UnitContentWorkerTarget` 加 `readonly gatewaySocket?: string`(宿主↔worker 内部字段) |
| `src/host/adapters/unit-content/worker.ts` | spawn 时 `unitContentWorkerEnvironment(request.gatewaySocket)`;该函数把**已解析的** socket 路径写进 env(`auto` 含 pid,**不能让 worker 自己重算**) |
| `src/host/provider/unit-content-operations.ts` | `resolveTarget()`:socket 模式下把 `gatewayOrigin` 换成**合成 origin `http://unix`**(过 worker 的 HTTP 校验)+ 传 `gatewaySocket` |
| `src/workers/unit-content/entry.ts` | main() 首行 `installUnixGatewayFetchShim()`:把 `http://unix/...` 的 fetch 改走 unix socket(复用 `shared/unix-http.ts` 的 `requestOverUnixSocket`) |
- `package.json` 版本 **0.2.15 → 0.2.16**。
### 三、踩坑(可复用)
- **多行锚点在 CRLF 文件里必须写 `\r\n`**:本仓库文件是 CRLF,读用 `newline=""`(保行尾)时,含 `\n` 的多行锚点**命中 0**;单行锚点不受影响。(本次先踩后修)
- 联合类型取值:`request.gatewaySocket` 在 `UnitContentWorkerRequest`(联合)上会 TS2339 ⇒ 改 `'gatewaySocket' in request ? … : undefined`。
- import 相对层级:`src/host/adapters/unit-content/` → shared 是 **`../../../shared/`**(少一级会 TS2307)。
- `npm run build | head -N` 会因 SIGPIPE **把构建打断**;长构建要**重定向到文件 + 后台跑**。
### 四、状态
`tsc` 对 4 个改动文件**零错误**;`npm run build` 后台执行中(worker 产物 `artifacts/unit-content-worker.mjs` 已重建)。**未 pack、未部署**。
## 09:22 三问核实(T03 启用什么 / 档案78 实测什么 / 自动化是什么)—— 只读取数
- **T03 待启用 = `dsh-plugin-mcn-suite`(池内 0.3.9,1.95 MB)**;实测现状:**admin bundles 已含**、**guest 不含**;
且 guest 的 `node_modules` 里**连旧的 7 个自研插件也没有**(无 mcn/liustack/dsh-plugin 命中)⇒ 对 guest 是**首次启用**,不是"切换下架旧包"。
启用路径(档案 60/67 措辞):guest 登录 → 实例**设置面板 →「功能管理」**→ 勾选卡片 → 「**应用更改**」→ 弹窗「**确认应用**」(会 `restartAndProbe`,重启该实例数秒)。
- **档案 78 实测参数**:熔断阈值 `DEFAULT_CRASH_MAX_RESTARTS=5` / `DEFAULT_CRASH_WINDOW_MS=600000`(10 min);冷却 **10 min**、封顶 **6 h**;
`/var/log/dsh-crash-breaker.log` 当前 **0 行**(尚未发生熔断)⇒ 实测要**新造 6 连崩**才能看到 503 + 告警 + 冷却。
★ 干净做法:按技能 R4 的**专用测试账号模板**(`role='active'`、`approved_by`=真实 admin id)建一个临时用户来做,
**零影响你自己的账号**(用 admin 做会让 admin 被冷却 10 分钟);用完删号。
- **自动化「遗留项自动推进(DSH 平台)」(`1e1db4eb-…`)**:每 8 h;`cwds = D:/AI技能/aliyun-dsh-server`(**迁移前旧路径**)⇒ 停止调度;
最后记录 = 09-12 14:54 run → `failed: unknown_interrupted`。
⚠️ **原始 prompt 读不到**:`~/.workbuddy/workbuddy.db` 的 `automations` 表**只有 4 条 8 月的老测试自动化**,
这条与「代码仓三方同步」都不在表里(配置未落在任何可读位置)⇒ **只能靠名字/频率/项目记忆复原**,要重建必须用户给口径。
对比口径:「代码仓三方同步(DSH)」每 3 h 仍在正常跑(cwd 未变),其记录显示它**只报告、零改动**,
且**被 lock-guard 钩子拒写**(`lock-hook.log` 有 `PreToolUse-deny Write …automations\5840ce59…memory.md`)—— 即自动化会话改了 cwd 也不会被钩子放行。
## 09:17–09:45 会话 `a2665dd3`:**univer 0.2.16 已构建 / 打包 / 投放 / 装进 guest(只差重启生效)**
**过程**:
1. **构建**:第一次前台跑被我自己的 `timeout 900` 掐死(09:06:58 日志停止;**教训:长构建不要自加 `timeout`,用后台**)。第二次后台跑时任务丢失,但产物已写出 → 用「**index.html 引用完整性**」判定:viewer 引用 6 个资产**缺 0**(assets 153 文件)、render-machine 引用 4 个**缺 0**(32 文件)⇒ 产物完整可用。
2. **打包**:`pnpm pack` 报 pnpm 内部错(`detect-libc` os error 2)⇒ 改用 **`npm pack`**(`files` 字段两边都认)→ `dsh-univer-office-0.2.16.tgz`(**42,045,215 B / 279 文件**,md5 `d8f044d77390bb24fc6c97e45da6beae`)。核验:包内 `lib/index.js` 与 `artifacts/unit-content-worker.mjs` **都含 `http://unix`** ✓、版本 0.2.16 ✓。
3. **投放**:走门户 API `POST /api/plugins/business`(admin)—— ⚠️ **经 Cloudflare 直传 56 MB base64 得 `502 Bad Gateway`**(CF 挡了);**改从服务器本机 `http://127.0.0.1:3080` + `-H 'Host: alotbuy.com'` 重试 → `http=200`**、`version 0.2.16`、`replaced:true`、池文件已换(42,045,215 B / 09:36)。临时 admin 会话用完即删(`deleted_sessions=1`)。
4. **装进 guest profile**:`setpriv --reuid 100002 … pnpm add -w file:…/dsh-univer-office.tgz`(与既有部署同姿势)→ `node_modules/dsh-univer-office` **已是 0.2.16**、worker 产物带垫片、bundles 仍含 univer ✓。
5. **清理**:本机(`_uv_upload.sh`/`_uvbuild.log`/`_sess_univer.cjs`)与服务器(`/tmp/uv-0.2.16.tgz` 等)临时文件全清。
**唯一未做 = 重启生效**(两处都卡在这一步):
- guest 实例需重启 → 才会加载 0.2.16(client/plugin 在实例启动时加载);
- 平台需重启 `dshs` → 才会加载档案 79 的修复。
⇒ 已作为 **一个 R8 窗口问题**报给用户:**重启一次服务可同时让两处生效**(影响:全体在线用户断约 4–10 秒;之后实例下次访问自动拉起)。
**仍未验证的边界**:worker 内 Univer 的**协同客户端若用 WebSocket**(`ws://unix/...`)——本次只补了 HTTP 路径;重启后实测即可定论。
---
## 09:39–09:50 三问答复后的取证:**T03 的真卡点 = 内存预算**(不是"用户没点")+ 自动化已被用户删除
### 一、用户三答
1. T03 启用「已经试过了没问题」;2. 档案 78 实测「也试过了没问题」;3. 自动化「**我删除了,会和执行任务冲突**」⇒ **勿重建**。
### 二、⭐ 现场取证(只读)——查出 T03 卡住的真原因
| 时间 | 事件 | 证据 |
|---|---|---|
| 07:42:52 | guest 在「功能管理」**提交启用 mcn-suite** | `POST /api/plugins/mine/apply`(任务 `c8ac2686539aeb7c`) |
| 07:43–07:45 | 实例**连崩 5 次** | `exitCode=134`(SIGABRT);栈 = V8 `JsonParser` + `HeapAllocator::AllocateRawWithRetryOrFailSlowPath` ⇒ **V8 堆打满 abort** |
| 07:46 | 熔断(**旧代码**,JSON 里无 `opens`/`cooldownUntil`) | journal `[crash-restart]` |
| 07:45 / 08:31 | 平台**自动回滚** → guest 现**未启用** | `package.json.bak-rollback-20260913T0748`;`pnpm-lock`/`cordis.patch` 0 命中 |
| — | 实例 env 实测 | `NODE_OPTIONS=--max-old-space-size=160`(档案 74 的下调**在生效**) |
| — | cgroup 也贴顶 | `dsh-instance-mem.log`:guest **峰值 382/384 MiB**(01:30 触发 91% 告警) |
| — | 退出码分布(09-12 起) | `1 ×21`(插件/配置)、**`134 ×8`(V8 堆)**、`137 ×1`(cgroup OOM) |
⇒ **结论**:160 MiB 是在**空闲实例**上调的,对"重插件 + 大 JSON"的真实负载不够。**要让 T03 可用必须先加内存预算**:
`--max-old-space-size` 160→**256**(回平台默认)+ cgroup `MemoryMax` 384→**512 MiB**(否则只是把 abort 134 换成 OOM 137)。
两处都由平台进程**启动时读取** ⇒ **须重启 `dshs`(R8,2–5 s)**。
### 三、档案 78 的 L1 澄清(与用户的"试过了"对账)
- 新熔断代码**尚未被触发**:`/var/log/dsh-crash-breaker.log` **0 行**、journal 无 `crash-breaker-*` 事件;
- 用户今早看到的"熔断"是 **07:46 的旧代码**那次(guest 真连崩 5 次)⇒ 若用户指的是**浮层/自愈**,那是档案 77 的(已验);**熔断拦截本身仍待实测**。
### 四、顺手核销
- 档案 59 `wake.html` 挂起项:**双端一致**(4962 B、md5 `00b81728…`)⇒ 可关闭。
### 五、⚠️ 文档回填被锁挡住(R9)
- 持锁者 **`opensource-export-0913`**(09:41 起,未声明单号)⇒ 我**停止写文档库**;回填脚本已备好
`_patch77/docs-mem.py`(档案 74 追加「现场反证」+ T03 卡点更正 + 03-路线图 关 59/加"内存预算待窗口")——**锁释放即执行**。
- 未 commit / 未 push。
## 09:47–09:55 T03 §八 只读验收(服务器实测,8/14 通过)
用户第二次表态「我都试过可以了」⇒ 不再追问,改为**用现状核对 + 只读验收**。
**结论(admin 侧,自 00:02 起启用)**:
| 通过 ✅ | A 包结构(v0.3.9 / md5 `2ff1a2fa…` / 顶层仅 package、node_modules 0、.bak 0)|B 上传扫描 blocked=0|C 池内 0.3.9|E `[mcn-suite] loaded` + **duplicate=0**|H 池内只剩 2 个包(旧 7 包已下架)|I 技能落 **`<home>/skills/mcn-short-video`**(4.6 MB / 4 个一级子技能)|K `skills ensured: 跳过@0.3.0(版本一致)`|M BRIEF 384M |
| 未验 ⚠️ | D 入口逐个点开(需浏览器)|J agent 按名加载|L 凭据扫描|N 死引用扫描|P/Q 撞名三场景|F 探活(但平台已自动回滚过一次 ⇒ 链路活)|G 内存偏紧(guest 峰值 382/384)|**O 口径待核:实测 18 个 SKILL.md vs §八 期望 36** |
**两条自我纠错(先看错、复核后翻案)**:① 技能落点是 `home/skills/`(不是 `home/.dsh/skills/`)⇒ I **通过**;② 内存采样日志时间戳是 **UTC**("01:40"=本地 09:40)⇒ 采样**没停**。
**guest 那次尝试的事实**:07:42:52 启用 → 07:43–07:45 连崩 5 次(exitCode 134 / V8 堆)→ 07:46 熔断 → **平台自动回滚** ⇒ guest 至今未启用。
回填脚本备齐待锁:`_patch77/docs-mem.py`(74 反证 + T03 卡点 + 03-路线图)→ `_patch77/docs-t03-accept.py`(T03 §九 验收表 + 03-路线图 T03 行)。**锁仍被 `opensource-export-0913`(09:41 起)占用**。
## 09:41–09:55 会话 `a2665dd3`:**重启已执行(用户批准)—— 两处修复生效;univer 通道修好了,但撞上内存墙**
### 一、重启动作(持 op-lock `restart-0942`,已释放)
1. **先只读 diff 服务器那份 `business-plugins.ts`** → 差异**只有我这 3 处补丁**(确认服务器 = 我的改前基线,不会覆盖别人的本地改动)✓
2. `tr -d '\r'` 转 LF 后 scp(服务器侧先备份 `.bak-0942`)→ **服务器 `npm run build`** → `lib/web/routes/business-plugins.js` 标记齐全(`await uninstall`×2 / `apply task crashed`×1;`ignore-workspace-root-check` **只剩 1 处 = `add` 那处合法保留**)✓
3. `systemctl restart dshs` → `active`(新 MainPID 424413 / 09:44:57)✓
### 二、univer 0.2.16 验证(guest 实例侧,临时会话 R4,用完即删)
- `POST /api/dsh/enter` → 实例拉起(scope `dsh-100002-8ee7ec3c` active、`dsh web: …:42307`)✓
- `GET /univer-api/status` → 200 `{"gateway":{"phase":"stopped"},"unitContent":"bundled"}` ✓
- **★ socket 通道在生产成立**:`POST /univer-api/gateway/start` → **gateway 进程真的被拉起**(pid 424934 = `…/dsh-univer-office/artifacts/gateway.cjs`)⇒ **0.2.16 的 worker/host 改造与 socket 传输在生产可用**(不再是"通道不通")。
- ⚠️ **但立刻撞上内存墙(D3 复现)**:gateway 加载期间实例 cgroup 冲到 **383.9 / 384 MiB**(`/univer-api/status` 停在 `starting`)⇒ **384 MiB 装不下「dsh 本体 + univer 插件 + univer gateway」**。
- **★ 我随即回收了我自己触发的那个 gateway**(`pkill -u 100002 -f artifacts/gateway.cjs` → 剩余 0)→ 实例**存活、近 2 分钟无 OOM/无 crash-restart** ✓(测试残留必须自己清)。
### 三、结论(性质变了)
- **通道问题已修**(0.2.16 有效:host + worker 都走 socket);
- **剩下的唯一阻碍 = 内存配额**(用户档位约束:"暂不改档位")⇒ 要真跑通 univer,需要三选一:**调档位(384→512 / 堆 224)** | 只装 univer 不装 mcn-suite(省 39 MiB,但 gateway 需求可能更大) | **给 univer gateway 瘦身/懒加载**(改造工作量最大)。
### 四、顺带观察
外网扫描器 `198.251.85.116` 在探测 `/.git/config`、`/.git-credentials`、`/.git/config.bak` 等 —— 平台均返 404,**无泄露迹象**,但值得知道(这类探测会持续)。
## 09:55 追加:**墙的量化 —— Univer 的 gateway 单进程 ≈ 350 MiB**
- 回收 gateway 后重测:活跃 scope `dsh-100002-8ee7ec3c`(09:45:43 起、同一进程 424519、Tasks 13)**只占 27.9 MiB**(rss 10.3 / cache 15.4)。
- ⇒ 带 gateway 时 383.9 MiB 与不带时 27.9 MiB 相减:**Univer 的 `gateway.cjs` 单进程约 350 MiB** —— 远大于此前"import 插件 +63.7 MiB"的估算(那是**插件宿主**的加载成本;**gateway 是另一个完整服务进程**,内含 Univer 协同运行时)。
- ⇒ **结论定死**:本平台每实例 384 MiB 的规格**在物理上装不下 Univer**(27.9 + 350 ≈ 384,且网关尚未完成启动)。这不是传输/代理问题(那层已修好并验证),而是**容量问题**。
- 实例与服务当前均健康(页面 200、注入脚本在、无 OOM)。
## 09:51 用户裁定(关键意图):**univer 是他有意保留的「内存压力探针」**
- 用户原话:「**C 我就是用它来检测 dsh 服务内存压力的**」⇒ 选项 C(留着不管)**不是将就,而是目的**:
用 `dsh-univer-office` 这个重型第三方插件去压实例内存,观察平台在什么条件下撑不住。
- ⇒ **行为约束(已写入 `MEMORY.md`)**:任何会话**不得**以「撑爆实例 / 打不开 / 兼容性差」为由**下架或禁用** univer;
要把「实例被压到 384/384」当**预期结果**看,而不是当缺陷修。
- 它为探针提供的价值:**可复现**(gateway 懒启动,只在真用表格时才压)、**量级已知**(gateway ≈350 MiB)、
**边界清晰**(383.9/384 顶格;平台侧 `crash-restart` + 档案 78 熔断冷却负责兜底)。
- 已备探针脚本(可复现配方):`_probe_instance_mem.sh`(本机草案)→ **待文档锁释放后落进 `dsh-server-docs/scripts/probe-instance-mem.sh`**。
动作:铸临时 guest 会话 → `enter` 拉起 → `POST /univer-api/gateway/start` → 逐秒采样 cgroup(usage/max_usage)→ 回收 gateway → 清理会话 → 输出峰值汇总。
- ⚠️ 文档库当时**被 `opensource-export-0913` 持锁**(09:41 起)⇒ 按 R9 未动文档;`BRIEF §3` / `03-路线图` / 档案 76 的对应措辞(「univer 是探针,勿下架」)**待锁释放后补**。
---
## 09:36–10:0x · 会话 `opensource-export-0913`:开源导出一份「无本机/服务器信息、无已投放插件」的代码副本
**产物**:`E:\ProgramData\AI技能\aliyun-dsh-server\_开源导出_20260913\`
- `dshs\` = **仓库根**(139 文件 / 1.2 MB),可直接 `git init` 推送;**未做 git 初始化/提交/推送**(按 §4)。
- `_导出说明与脱敏台账.md` = 给人看的清单(**不随仓库上传**):脱敏台账、发布前占位符清单、已验证项、可选后续。
- `_build_export.py` = 复制+脱敏脚本,**带安全闸**(默认拒绝重跑,需 `--force`,否则会抹掉手写的 README/LICENSE/install.sh)。
**做法(可复用)**:
- 源仓库 `D:\github\dsh_shenxian` **全程只读**;改动一律落在导出副本上。
- **字节安全的脱敏**:Python 按行 `splitlines(keepends=True)` 处理,**完整保留 CRLF**;抽样 `cmp` 验证未改动文件逐字节一致。
- 收尾用**阻断性探针**断言(域名/IP/账号/`/opt/dsh`/插件名/MCN/凭据关键字)**0 命中**。
- 编译验证:导出目录**临时 `New-Item -ItemType Junction` 软链**源仓库 `node_modules` → `tsc --noEmit` 退出码 0 → 删联接。
**按需求落实**:
1. 脱敏:`alotbuy.com`→`<baseDomain>`(`web/wake.html` 改成**运行时从 `location.hostname` 推导注册域**,彻底去硬编码);`47.77.182.89`/`172.18.16.212`→占位符;`/opt/dshs`→`<INSTALL_DIR>`(`require('/opt/.../better-sqlite3')`→`require('better-sqlite3')`);`/opt/dsh/{state,artifacts,backups}`→`/var/lib/dshs/*`(上游文档一致的数据根);`maogeigei`→占位符。
2. **去掉已投放插件(严格口径)**:删 `poc/business-plugins/`(功能管理分区插件 + 全部 tgz)、`poc/workspace-scoped-picker/`、以及 6 个插件投放脚本(`ensure-anysearch-*`、`ensure-biz-plugins`、`ensure-portal-entry`、`install-workspace-picker.sh`、`provision-new-users.sh`);**并把代码注释里的插件名(anysearch / modlens / univer-office)改为中性描述**。MCN 工作台与 skill **本就不在代码仓**,另经探针确认 0 命中。
3. 授权文档:`LICENSE`(**分层授权**:个人/非商业免费 + 商业需授权)+ `LICENSE-UPSTREAM-MIT.txt`(上游 MIT 原文,**必留**)+ `THIRD-PARTY-NOTICES.md`(**DeepSeek Harness = MIT 商用无限制、不分发其代码**;6 运行依赖 + 4 开发依赖 + 178 传递依赖全部宽松许可)。
4. `README.md` 按主流开源结构重写(徽章/目录/特性速览/快速开始/功能详解/架构/部署形态/配置/开发/**版本与迭代**/安全/目录结构/FAQ/贡献/授权/致谢);版本表登记 **v1.0.0 = 首个公开发布**,并单列「相对上游 dshs 的六组改造」。
5. 不带项目文档与 skill:删 `docs/`(上游 7 篇)、`STANDARD.md`、`poc/README.md`、`.workbuddy/`、`lib/`、`.git`(历史里全是内部信息,必须全新仓库)。
6. **新增 `install.sh` 一键部署**(模式 A):预检 → 装依赖并构建 → 装 DSH CLI → env(0600) → 数据根(0700) → 证书 → nginx → systemd → 首个管理员 → 健康检查;带 `--dry-run` / `--uninstall [--purge]` / `--isolation` / `--port-guard` / `--install-node`,幂等。
**⚠️ 两个必须交代的前提**:
- **法律前提**:上游 `dshs`(上游作者(已按要求不再具名))是 **MIT**,MIT **不允许**对上游代码加限制 ⇒ 只能做**分层授权**(上游留 MIT,只对本项目新增部分收商用费)。**若 上游作者(已按要求不再具名) 就是用户本人**,可整体简化为单一授权 —— 已在台账里留作可选后续。
- **`install.sh` 未在真实 Linux 跑过**(本机只有 Windows:仅 `bash -n` + `--help` + 逻辑审查)。建议先非生产机器 `--dry-run` 再真跑。
**保留未动**:代码注释里 **183 处「档案 NN」内部编号引用**(属内部档案号,非本机/服务器信息;已列入可选后续)。
---
## 09:53–10:0x · 同一会话续做:把开源规则沉淀成技能 `dsh-opensource-release`
**用户要求**:「把这个项目开源的规则 整理成 skill 迭代管理」。
**产出**:`E:\ProgramData\.workbuddy\skills\dsh-opensource-release\SKILL.md`(v1.0.0 / `agent_created: true`)
十节:① 何时用 ② 事实(硬编码路径/版本)③ **五条硬规则 R-O1–R-O5 + 两条纪律 R-O6/R-O7**(含「源仓库只读」「阻断性探针 0 命中」「不得被重建抹掉」)④ 保留/移除清单(delta,含「本就不在代码仓」的 MCN/插件清单)⑤ **脱敏映射表(唯一口径)** + 发布前占位符清单 ⑥ 保留未动的 183 处档案编号 ⑦ **授权结构 + MIT 硬前提**(分层才合法;`上游作者(已按要求不再具名)` 若为本人可简化)⑧ **迭代发布 SOP**(抢锁 → 基线 → 改口径 → `--force` 重建 → 改版本三处 → 六项验证 → 推送)⑨ **验证六件套**(探针 / cmp / CRLF / tsc-via-Junction / `bash -n` / 移除项核对)⑩ **8 条实测坑**。
**顺带把构建脚本升级为「可安全迭代」**:`_build_export.py` 新增 **OVERLAY 手工撰写层**(README / LICENSE / LICENSE-UPSTREAM-MIT / THIRD-PARTY-NOTICES / install.sh)—— `--force` 重建前先快照到 `_overlay/`、重建后自动恢复;实测重建后 **5/5 逐字节一致**,探针仍 0 命中。此前 `--force` 会**抹掉手写文件**(真实风险,已消除)。
**⚠️ 被锁挡住的部分(按 R9 未抢、未接管)**:补「归档副本 + INDEX 登记 + manifest 刷新」时,文档库全局锁被 **`清理文档债-0955`**(09:54 起)占用 ⇒ 只读确认后**跳过**这一组步骤,继续做完其余不冲突的工作。待办已写进技能 §10(含逐步补做清单)。另有既有漂移一并登记:`dsh-instance-diagnose` 无归档副本、`INDEX.md` 未登记 `dsh-knowledge-upkeep` —— **未擅自修**。
---
## 10:0x · 同会话续做②:**univer 转为保留** + 新增《插件移植指南》+ 技能归档补齐
**用户指令**:「univer 可以保留这个插件 专门讲讲如何把开源插件改造为可在多租户平台运行」。
**A. 撤回 univer 的脱敏(只撤 univer,其余插件仍中性化)**
- `_build_export.py`:删掉 `dsh-univer-office` / `DSH_INSTANCE_UNIVER_SOCKET` / `UNIVER_DSH_GATEWAY_SOCKET` 三条 GLOBAL 规则;**删掉 orchestrator 的 6 行 `DELETE_LINES`**(unix-socket 适配段回归);`whitelist.ts` 注释改写为「(实测:多个第三方插件均如此,例如 dsh-univer-office)」。
- `LEAK_PROBES` 移除 `dsh-univer` / `UNIVER`(**刻意不在列**)。
- 重建后确认:`src/supervisor/orchestrator.ts` 的 `UNIVER_DSH_GATEWAY_SOCKET` 段回来了;阻断探针仍 **0 命中**(`档案`/`交接单` 属有意保留)。
**B. 新增 `PLUGIN-PORTING.md`(本次主交付)** —— 面向插件作者与平台维护者,七节 + 附录:
0 结论速查表 · 1 **托管平台 vs 本地的六条假设差异** · 2 **六个失败模式 H1–H6**(绝对 URL[阻断级] / loopback 依赖 / 自建监听 / 客户端拼地址 / 平台包不兼容 / 重型依赖静态 import,各附症状+判定) · 3 **五条改造规范 R-a~R-e** · 4 **`dsh-univer-office` 完整实战**(两层独立阻碍 Host→Gateway 与 Browser→Viewer、为什么换服务器 IP 不行[3 条硬理由]、改造清单、3 条设计决策[socket 是可选传输/只有两条代理规则/两个平台侧机制坑]、不碰生产的验证法、已知限制、回滚) · 5 平台侧需配合的能力 · 6 上架自查清单 · 7 提上游的论证口径 · 附录 可粘自检命令。
**素材来源(只读)**:内部 `04-调整方案/75`(H1–H4 判据 + R-a~R-e + 实测内存基线)与 `76`(univer 改造全过程);**已去掉全部内部档案号与服务器信息**。
README 加「插件移植指南」章节 + 目录项;① 组改造表补「插件 unix-socket 适配通道」。
**C. 把上一轮被锁挡住的活补完**(10:01 锁已释放,重新 `--claim-exec` 后完成,**完工已 `--release-exec`**)
- 归档副本:`dsh-server-docs/skills/dsh-opensource-release/SKILL.md` —— **两副本 md5 一致**(`b0986201…`)。
- `INDEX.md`:§一 场景表加「开源导出 / 发新版本」行;§二 清单表加 skill 行。
- 四件套:`docs-manifest.py` 刷新 ✓ | `docs-audit.py` **exit 0(无 P0)** ✓ | `docs-consistency.py` **exit 0** ✓ | `docs-index-stats.py --write` 刷新摘要行 → 复跑 **exit 0** ✓。
- **未 scp**(按 §4,等用户发话);`docs-sync-check` 如实显示「仅本地 2 / 内容不一致 5」的既有待推状态。
**D. 技能迭代到 v1.0.1**(示范「迭代管理」):更新 R-O3、脱敏映射表(新增**特许保留项**小节)、OVERLAY 增 `PLUGIN-PORTING.md`;**tsc 验证法改为跑 `_verify_tsc.mjs`**,并加硬警告:**绝不可 recursive 删联接**(Windows 下会顺着联接删掉源仓库 `node_modules`)—— 本次已实测 `tsc exit = 0`、`junction removed = true`、源仓库 `node_modules` 164 项完好。
**导出物现状**:**140 文件**;手工撰写层 6 个(README / LICENSE / LICENSE-UPSTREAM-MIT / THIRD-PARTY-NOTICES / PLUGIN-PORTING / install.sh),重建时自动快照→恢复。
---
## 10:0x–10:2x · 同会话续做③:**确认上游是三方的 + 项目改名 `dsh-hosting` + 正式致敬**
**用户指令**:「dshs 这个是不是引用的三方插件项目确认下,如果是不能直接使用 需要一个更符合这个项目定位的名称,并且在说明中致敬这个项目」。
**A. 核实结论:是三方项目**(不是我们能直接用的名字)
- 本机 git:`upstream` = `上游骨架仓库(已按要求不再具名)`;192 个提交里 **123 个是 上游作者(已按要求不再具名)**(2026-08-18 → 08-26 建骨架 `P1 auth/P2 desktop/P3 single-DSH/P4 每文件夹插件/P5 watchdog`),我方最早 09-08(服务器侧 `deploy`)/ 09-11(maogeigei)。
- 线上核对:GitHub 仓库存在;**已被 DSH 插件目录收录**(`dsh.so/artifact/dshs`,含 L1–L5 验证记录与 `dsh plugin --profile web add github:上游骨架仓库(已按要求不再具名)`);另有第三方博客评测。授权 **MIT**。
**B. 新名 `dsh-hosting`**(多租户 DSH 托管平台)
- 实测未被占用:npm `registry.npmjs.org/dsh-hosting` → 404;插件目录 `dshbase.com/plugins/dsh-hosting` → 404。
- ⚠️ **`dsh-hive` 已被占用**(第三方插件 `llluchy/dsh-hive`,跨会话消息)—— 候选名已排除,**别再捡**(本次实测踩到)。
- 改名范围(**149 处自指标识**):包名/bin/cordis plugin id、**36 种环境变量前缀**(`DSHS_*` → `DSH_HOSTING_*`)、`/var/lib/dsh-hosting`、`dsh-hosting.service`/`.env`/nginx conf、默认库文件 `dshs.db` → `dsh-hosting.db`、镜像/k8s/CI/Dockerfile、**导出目录名**。
**C. 致敬(用户明确要求)**
- README 新增 **「名称与渊源」** 章节(本项目名 / 上游基线 上游作者(已按要求不再具名) / **为何改名** / 两类标识如何区分 / 上游仍按 MIT 且本项目改动不改变这一点)+ 「致敬」一句。
- 致谢章节扩写;`THIRD-PARTY-NOTICES.md` §4 扩为「上游项目 / 作者 / 本项目 / 更名说明 / 保留要求 / 致谢」。
- **`LICENSE-UPSTREAM-MIT.txt` 逐字节未动**;README 4 处 / LICENSE 2 处 / NOTICE 1 处**出处引用保持旧名**(不改)。
**D. 这一步又暴露 3 个脚本漏洞(全部已修 + 重建验证)**
1. **`Dockerfile` / `Dockerfile.dsh` 从未被脱敏** —— `is_text()` 按扩展名判断,二者无扩展名 ⇒ 整份跳过。
2. **`package.json` 的项目自有字段会被重建覆盖** —— 它属「源派生」而非 OVERLAY(version / description / repository / license 已改成 GLOBAL 规则)。
3. ⚠️ **不要对 `_build_export.py` 自身做批量替换** —— 脚本里同时有「源模式」与「目标值」,盲替**打断了改名规则**(误伤 4 处,已逐条修正并重建验证)。**这条已写进技能 §8 坑 9**。
**E. 最终验证(全绿)**:阻断探针 **0 命中**(新增自指残留探针 `DSHS_` / `dshs.{service,env,db}`);全仓 `grep 'server-login'` **只剩致敬与出处引用**;`tsc --noEmit` **exit 0**(Junction 法,联接已拆);`bash -n install.sh` OK;导出物 140 文件。
**F. 技能升到 v1.0.2**:新增 **R-O8 命名规则**(不沿用三方名 + 实测占用 + 保留出处致敬;附「`dsh-hive` 已占用」记录)、§3 脱敏映射表加改名行、§8 补 3 条坑(编号 9–13)、路径全面改为 `dsh-hosting`;**归档副本已同步**(md5 `09ed761c…` 一致),四件套 exit 0,锁已释放。
## 10:0x 排障:**「admin(Edge) 有浮层 / guest(Chrome) 没有」= 界面不同,不是浏览器问题**
**实测(真 Chrome 无头 + guest 临时会话,用完即删)**:
- 服务器发给 guest 的注入脚本**已是修复版**(`__dsh-ov`/`HEARTBEAT_MS`/`String.fromCharCode(10)` 全在,坏特征 0);
- guest 页面 `__dshRecover=true`、`__dshAssist=true`、**0 语法错误**;
- **不做任何操作 → 46.2 s 后浮层自动出现**(心跳 2 拍),新视觉节点在。
⇒ **Chrome/Edge 都是 Chromium,同一份脚本;差异不在引擎、不在账号。**
**真正原因(结构性)——两处界面对比**:
| 界面 | 注入脚本 | 加载态 |
|---|---|---|
| 实例页 | ✅ 有 | 「AI 核心」浮层 |
| **过渡页 `wake.html`**(刷新/首次进入且实例不在时落这里) | ❌ 无 | **旧的 border-top 转圈** |
guest 实例今早崩 5 次 + 被回滚 ⇒ 刷新时多半落在**过渡页**,自然"看不到浮层"。另:若那个标签页是 08:15–08:27(坏脚本期)打开的,**刷新一次**即恢复。
**已做**:`web/wake.html` 加载态统一成**同一套「AI 核心」视觉**(conic 扫描弧 + 反向虚线环 + 脉冲核心 + 三轨道粒子 + 渐变进度条 + 等宽小字),**配色跟随平台主题**(`#534AB7`/`#8B7CF6`,不硬套暗色)。
- **JS 逻辑一行未动**(diff 只差档案 78 加的 `instance_circuit_open` 分支);**行尾保持 LF**;**静态页免重启** ⇒ scp 即生效;
- 线上取回验证:`wk-orb`/`conic-gradient`/`prefers-reduced-motion`/`dsh · auto recovery` 命中,旧 `.wk-spin{width:34px}` 消失;截图 `_patch77/shots/wake-aicore.png`;回滚 `/opt/dsh/backups/wake.html.bak-aicore-20260913`。
**文档债已清(锁刚好空出)**:档案 74 追加「现场反证」、T03 追加 §九 验收表 + 卡点更正、03-路线图(关 59 挂起项 + 内存预算待窗口 + T03 主体通过)、INDEX、档案 77 追加「wake.html 统一视觉」节;四件套 rc=0,**对账 一致 133**(余 3 不一致 + 1 仅本地 = 其它会话积压);锁已释放。
**诊断口诀(已写进档案 77)**:客户说"看不到浮层"→ 先分清是**实例页**还是**过渡页**,再看该标签页是不是**修复前打开的旧页面**。
## 10:02–10:10 「guest 一直显示恢复但起不来」根因定死:**univer gateway 390MB > 实例上限 384MB ⇒ cgroup OOM kill**
**铁证(服务器实测)**:
```
RSS=390.4MB uid=dsh-eecc…(guest) node /var/lib/dshs/users/4092b965… ← univer gateway 单进程
RSS= 44.0MB uid=dsh-eecc…(guest) dsh --profile web --port 43829 ← 实例本体
```
- 10:00 内存采样:guest **当前 384 / 峰值 384 / 上限 384(高位 100%)**;
- 10:03:11 崩溃 = **`exitCode=137`(SIGKILL = cgroup OOM kill)**;此前 8 次是 **134**(V8 堆 160MB abort)⇒ **两个天花板都在顶**;
- 页面侧:`GET /` 每 ~35s 一次(恢复→实例起来→又被杀→再恢复的循环);期间 `fetch('/')` 曾拿到 **502 且响应体是 Cloudflare 错误页**(命中 `/cdn-cgi/styles/main.css`)⇒ 502 是 CF 的(实例不在/正在起的窗口),不是 nginx 的(nginx error.log 无 502);
- 页面还在 1 秒 1 次猛打 **`/univer-api/state`**(档案 76 那个返 400 的接口)—— 与 gateway 高占用同源。
**结论**:**这是"内存压力探针"的必然结果** —— 用户 2026-09-13 明确「我就是用它来检测 dsh 服务内存压力的」⇒ **384MB 装不下 univer 这套**。
**恢复逻辑本身没问题**(真 Chrome 实测:不操作 46.2s 自动出浮层)。
**宿主余量**:1870MB 总 / **1027MB 可用** ⇒ 单实例升到 640MB 可行,但 **`maxIdleInstances=4` 必须降到 2**(4×640 > 1870)。
**待用户拍板(R8 + 资源承诺)**:(a) 升预算(cgroup 384→640、V8 堆 160→256,需一次重启 2–5s,我推荐)|(b) univer 瘦身(插件侧大改)|(c) 保持现状 + 把恢复做成**有界**(连续 N 次失败即停在明确失败态,不要无限转圈;也需一次重启上线)。
## 10:05–10:15 会话 `a2665dd3`:**★找到并修好「插件无法启动」的真因 —— 陈旧 socket 挡住 bind(0.2.17)**
**用户报**:「guest 最新会话 这个插件还是报错 无法启动」。
**实例内 agent 当时看到的**(session `eda867a0` turn 7):无 gateway 进程、**残留一个 08:42 的 socket 文件**、宿主 dsh 已重启(pid 2 / 已升 0.2.16);`univer_inspect` → **`Error: bundled Gateway did not become ready within 10000ms`**。
### 一、根因(★真 bug,不是内存)
1. **实测判定**:只做一件事 —— **删掉那个陈旧 socket 文件**,再 `POST /univer-api/gateway/start` → **立刻 `ok:true`,`phase:"running"` 连续 30s 稳定**,新 socket 就位。⇒ 陈旧文件就是阻塞源。
2. **机理**:socket 路径 = `join(tmpdir(), 'dsh-univer-gateway-<pid>.sock')`,而实例在 **PID namespace** 里宿主恒为 pid 2 ⇒ **路径每次重启都一样**;实例 `/tmp` 又是 `--bind` 到用户目录的**持久挂载**(不是 tmpfs)⇒ **上次 gateway 死掉留下的 socket 文件能活过实例重启**。
3. **为什么永久**:`bind()` 到已存在的 socket 路径 → `EADDRINUSE`(Univer 的 gateway 未先 unlink)⇒ 之后每次启动都失败 ⇒ 对外只看到 `did not become ready within 10000ms`(10s 超时把真实 errno 吞了)。
### 二、修复(v0.2.17,1 处)
`src/gateway-app/server.ts` 的 `listen()`:unix socket 分支**绑前先 `rmSync(target.socketPath, { force: true })`**(ENOENT 忽略)+ 补 `import { rmSync } from 'node:fs'`。
只重建 gateway(`npm run build:gateway`,秒级)→ `npm pack`(42,045,253 B / md5 `16fd345b4d15fdec03285c80a9982b8a`)→ 投放候选池(本机直连,`http=200` / version 0.2.17)→ 装进 guest profile(`node_modules` 已是 0.2.17,gateway 产物含 `rmSync` ×4)。
### 三、修复验证(复现故障场景)
① `pkill` 掉在跑的 gateway → **故意留下陈旧 socket**(10:06 那个);② **不删任何文件**直接 `POST gateway/start` → `ok:true` → **`phase:"running"` 在 t+5/10/15/20s 全部稳定** ✓;③ 新 socket(10:11)+ 1 个 gateway 进程 ✓。⇒ **0.2.17 修复成立**。
### 四、附带事实与注意
- **无需重启实例**:gateway 是**按需新起进程**,下一次启动自然用上 0.2.17 ✓(这与 client bundle 必须重启实例不同)。
- ⚠️ **内存仍在 383.7 / 384**(gateway 热身后的稳态)—— 功能通了、但**内存墙依旧**,这正是用户要的探针现象(选项 C)。
- 回滚:池内已被 0.2.17 覆盖;本地保留 `dsh-univer-office-0.2.16.tgz` / `0.2.17.tgz` 两个包。