2026-09-24 07:51:03 +08:00
|
|
|
|
# 工作日志 · 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`:开源导出一份「无本机/服务器信息、无已投放插件」的代码副本
|
|
|
|
|
|
|
2026-10-10 23:13:22 +08:00
|
|
|
|
**产物**:`E:\ProgramData\AIProject\ai1net-dsh-server\_开源导出_20260913\`
|
2026-09-24 07:51:03 +08:00
|
|
|
|
- `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` 两个包。
|
|
|
|
|
|
|
|
|
|
|
|
|