Files
dsh_ai1net_server/接续入口_IM线_20260922.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

137 KiB
Raw Blame History

接续入口 · IM 线(2026-09-22 开线)

🔴 工作区:E:/ProgramData/AIProject/aliyun-dsh-server(本入口只此一份,⛔ 不得复制到别的工作区) 开线棒:会话 aliyun-dsh-server · 2026-09-22 16:4x(用户当场提要求,本会话因上下文过高收口开线) 上游文档(文档库 = D:\github\dsh_shenxian\dsh-server-docs):调整方案/142-IM群组对话-需求基线与方案.md(需求基线)+ 五份交接单 交接单/IM群组-{A,B,C,D,E}-*.md(均已按 09-22 两份架构定稿对齐,各单 §〇) 代码基线:D:\github\dsh_shenxian(生产口径 dshs) 🔴 cwds 判据(2026-09-23 加;优先级高于本文任何历史记录):本线接续棒的 automation cwds 一律写 小写盘符 + 反斜杠 —— e:\ProgramData\AI技能\aliyun-dsh-server。宿主逐字落库、⛔ 不规范化(连大小写也不),写法不同即裂成两个同名会话分组(09-22 正斜杠裂一次、09-23 大写盘符又裂一次)。⛔ 下文出现的 cwds = E:\…(大写盘符)全是误写的历史记录,登记新棒时不得照抄其大小写。


§0 状态(最新一行 = 执行依据)

  • 2026-09-22 16:4x|开线:用户要求「IM 具体如何实现才能在复杂环境下稳定 + 调研同类开源框架(Telegram 等海外项目),找最新性能稳定最好的」。

  • 开线棒已实搜并固化 8 条事实(见 §3)⇒ 下一棒 ⛔ 不必重搜。

  • ✅ 下一棒 = 规划棒 —— 已于 2026-09-22 17:4x 完成(见本 §0 末行)。

  • ✅ 已登记接续棒(唯一一个):automation 2798048a-0ac8-4515-85bd-b58375e756ac(一次性 · scheduledAt = 2026-09-22T17:40)=本次会话(已消费)。 ⚠️ 为何不是「收口 + 58 分钟」:dsh-auto-handoff-chain §3.1.1 的唯一允许拉长情形 = 等一个外部窗口 —— 此刻插件投放与分库线的执行棒已排定 16:52 起跑(其规划棒刚收口),全局锁同一时刻只允许一个执行会话 ⇒ 本棒排在其后(16:52 + 执行棒实测 2141 min + 余量 ≈ 17:40)。 ⛔ 下一棒若抢不到锁 ⇒ 只报告并停,⛔ 不删锁、不接管(R9)。

  • 🔴 2026-09-22 17:0x 口径更新(用户明令,优先级高于本文其余各处):用户原话「机器可以换 不要那这个限制规划」⇒ 资源(单实例 1024 MiB / 宿主 1870 MiB)不再作为选型否决项。框架与架构按 稳定性 · 性能 · 可运维性 主导判据规划;机器配置 = 方案里要给出的"需求测算"(用户按需采购),⛔ 不得以"装不下 / 现有机器跑不动"为由否掉任何方案。

  • ✅ 2026-09-22 17:4x|规划棒收口(第 1 棒 ✅ 完成):产出 交接单/IM群组-稳定性机制与框架选型.md(T1 机制清单 25 条 + T2 判据矩阵 7 类候选 × 7 轴 + T3 三案选型 + 实验 E-1~E-4)。 本单自决项(可推翻):① 判据轴取「身份可复用 / 存储同库 / 七扩展点承载」为决定性轴(⛔ 不按 star / 热度排序)② 建议 = 案 3 形态 + 案 1 路径(连接层写成可替换,⛔ 不现在引整框架)③ 实验一律落 106(47 不动)。 下一棒(唯一) = 第 2 棒 · 执行棒 = automation b656009b-ecdd-4f12-a077-d12e8294dce8(一次性 · scheduledAt = 2026-09-22T17:52),动作见 §2。

  • ✅ 2026-09-22 18:2x|第 2 棒(执行棒)✅ 完成(步 1–4) —— 回填见该单 §十二 执行回报 + §十三 断言名清单(18/18)。 实测结论(三条):① E-3 = 能 —— ejabberd 可用既有 users 作外部认证源,且干净 mnesia 起点下不落地 passwd 账号表;🔴 但发现 ejabberd 对 check_password 有进程内缓存(改表不立刻生效、重启即生效)⇒ 案 2 「继续但需追加前置项」。② E-1(零依赖自研 WS,106 实测):1k/5k/10k 连接 upgrade 全成功、心跳超时率 0,RSS 58.5→131.8 MiB(边际 7.5 KiB/连接),P99 = 45.3 / 256.3 / 421.1 ms,且 P99 ≈ 事件循环最大滞后 ⇒ 瓶颈是单进程全量扇出的排队,不是连接数供给。③ 离线检测只能靠心跳兜底(写失败路径 ≤3 s 生效,但观测窗口短于扇出间隔时 close 事件不发生)。 前置核实:4/5 已验(src/im/ 不存在 · schema = v13 ⇒ 不触发改去 A 单 · upgrade 回调 proxy.ts:759(规划读数 718 已漂移)· npm test 248/247 过 1 跳过);🔴 第 4 条(47 上 dshs 的 \dt)未核实 —— 47 的 sshd 已从 32022 漂移到 22,且 PG 只走 TCP 需口令(⛔ 未取用凭据);已给替代证据(schema.ts 内三表 DDL 计数 = 0)。 下一棒(唯一) = 第 3 棒 · 执行棒续 = automation 21443815-acec-4069-94ad-6be3242c9d5d(一次性 · scheduledAt = 2026-09-22T18:23),动作见 §2「⏭️ 第 3 棒」。

  • ✅ 2026-09-22 19:0x|第 3 棒(执行棒续)✅ 完成(§八 步 5–8) —— 回填见 交接单/IM群组-稳定性机制与框架选型.md §十二 执行回报(IM线-第3棒)。 实测结论(四条):① E-2 = Centrifugo v6.9.6 跑通(10k 连接 upgrade 全成功、零掉线:服务端权威 POST /api/channels 稳态 num_clients = 1000/5000/10000 = 目标值):客户端观测 P99 = 28.9 / 68.1 / 111.9 ms、扇出离散中位 17.2 / 57.9 / 97.8 ms ⇒ 均优于自研(1/2.7、1/1.9);代价 = 边际每连接 70.1 KiB vs 自研 5.9 KiB(11.9×),稳态总量 750.2 vs 116.0 MiB。② 🔴 保活方向相反(实测):Centrifugo 不发服务端 WS ping(wsPingRecv=0),且客户端不发 ping ⇒ 26 s 内 10000/10000 全被断开;判别实验证明「保活计时起点必须绑该连接自己的认证完成时刻」(用整进程统一计时 ⇒ 10k 一次掉 2773 条,是适配器缺陷)。③ 二次判定 = 本期不引入外部连接层组件(维持案 3 形态 + 案 1 路径),依据 = E-1 三档 服务端 P99 ≈ 事件循环最大滞后(10k:340.3 vs 342.5 ms)⇒ 瓶颈是可就地解决的扇出排队;并写下可证伪回退判据(10k P99 > 150 ms 或离散中位 > 120 ms ⇒ 引入)。④ E-4 = 可行:E2EE 最小路径 = 三层密钥(设备身份密钥 / 房间 DK(room_id, epoch) / 成员公钥包裹的密钥包)+ 两张新表(im_room_keys / im_agent_grants)+「加人不换钥、移除必换钥、轮换先于消息可见」,出 8 条可断言路径;relay 零改动。 步 8 回填:§四 25 条机制全部有归属 —— A 单 16 + B 单 5 + C 单 3 + D 单 3 + E 单 2(共 29 行;组 E 与组 A3 因跨"实现层/契约层/客户端层"两端各写一条并注明同源);⛔ 未改 A–E 任一单的既有结论。 下一棒(唯一) = 第 4 棒 · 执行棒 = automation db244c60-db22-4d7d-87a9-bced514bbea4(一次性 · scheduledAt = 2026-09-22T19:13),动作见 §2「⏭️ 第 4 棒」。

  • ✅ 2026-09-22 19:3x|第 4 棒(执行棒)✅ 完成 —— 回填见 交接单/IM群组-稳定性机制与框架选型.md §十二 执行回报(IM线-第4棒)(新建独立小节)+ 第 3 棒 §十二-5「回退判据」处的 🔻 追加块(⛔ 原文未改)。 实测结论(四条):① E-5 改造 = 分片扇出 + 同片批写(106 /root/im-e2/server-shard.mjs,新建 268 行;/root/im-e1/*.mjs 一行未改;/metrics 原字段逐条不变、msgsSent 语义核对 = 6×N ✅)。② 🔴 回退判据被触发 —— 10k 客户端观测 P99 = 337.6 / 359.4 ms(判据 150 ms;两次复跑)、扇出离散中位 = 227.6 / 225.5 ms(判据 120 ms);同会话 E-1 基线复跑 = 268.6 / 171.8 ms ⇒ 两项均变差。③ 🔴 被证伪的是「分时」这一手法 —— 事件循环最大滞后 349.7 → 13.0–46.9 ms(13–27×)但端到端整圈反变长;分片数 4/8/16 与批写开/关四个变体均无改善 ⇒ 整圈墙钟由 write() 绝对总量决定,A 候选的正确形态是并行(多进程),⛔ 不是继续切细"分时"。④ ⚠️ RSS 判定为噪声内(10k 五变体 107.9–132.8 vs 基线 115.9,跨跑方差 ±12 MiB)⇒ 后续内存判据须重复跑 ≥3 次取中位。 门禁:改造判定为负向(1k P99 49.8>44.7、10k 337.6/359.4>268.6 有档位变差)⇒ 已按门禁停下复盘、⛔ 未硬推;🔴 引入外部组件属影响面扩大 ⇒ ⛔ 未擅自引入,三条候选(A 就地继续优化/B 引入 Centrifugo 类/C 混合)已各带优缺点交拍板(收在该单 §十二 第4棒 段末「待拍板」)。 判据 6 / 7(第 3 棒欠账,本轮补跑):判据 6 ✅ npm test(Node 22)= 248 / 247 过 / 0 败 / 1 跳过,与第 2 棒基线一致;判据 7 ⚠️ 测试集内无该用例 ⇒ 未覆盖(grep -rin "101" test/ 零命中;grep -rln "proxy" test/ 零命中;最接近 = test/relay.test.mjs:1383 rawWsProbe,属中继侧 upgrade),⛔ 未自造用例。 下一棒(唯一) = 第 5 棒 · 执行棒 = automation e1c1b3a5-0c11-4544-8b28-6fa80684d9de(一次性 · scheduledAt = 2026-09-22T19:33),动作见 §2「⏭️ 第 5 棒」。

  • ✅ 2026-09-22 19:4x|第 5 棒(执行棒)✅ 完成 —— IM 五单合流第一单(A 单)已开工并收口 —— 回填见 交接单/IM群组-A-房间内核与DB.md §十 执行回报(IM线-第5棒)(新建独立小节,⛔ 原文未改)。 交付(本机代码面):src/db/schema.ts 追加 v14(rooms / room_members / messages 三表 + 索引,192 行新增 / 0 行删除,既有迁移零触碰)+ 新建 src/im/{types,db,store,clock}.ts(963 行) + 新建 test/im-store.test.mjs(552 行,23 用例) + package.json 的 test/verify 各登记 1 处。 判据读数:npm run build rc=0 | node --test test/im-store.test.mjs = 23 / 22 过 / 0 败 / 1 跳过(跳过 = PG 段自跳过)| npm test(Node 22)= 271 / 269 过 / 0 败 / 2 跳过(基线 248 ⇒ +23 全为本单)| grep -c "^ { version:" = 14 | 全仓改动 72 → 74(增量恰为本单 2 个已跟踪文件)。 五条只读前置全部满足(v13 为最新、v14 空闲、IM 三表 DDL 计数 = 0、双后端同形、src/im/ 原不存在)⇒ ⛔ 未触发「停手改去别的单」。 🔴 两处口径说明(已写进该单 §十.7,⛔ 不是判据放水):① 判据 3 的「不同到达序 ⇒ 同一数组」在 DB 层按 §四-6 拆成「读模型恒按 seq 递增」+「规范序函数对同一集合 3 种到达序输出同一数组(含落库往返版)」;② id = sha256(authorId ‖ canonical(payload))(含作者)—— 纯内容哈希会让同房不同作者的同文消息互相吞掉(静默丢消息),改后判据 2/9 全部保持。 ⚠️ 卡点:npm run verify 整体 rc=1,卡在 既有脚本 scripts/verify-platform-admin-section.mjs(require() + 顶层 await ⇒ ERR_AMBIGUOUS_MODULE_SYNTAX;该文件未经改动)⇒ 与本单无关;链路其后 6 步逐条补跑 rc 全 0、check-layering ✅ 无新增违规。⛔ 未顺手修(超出 §三 改动面)。 ⚠️ 已知单内不一致(实测确认):A 单 §五-1 / §六-1 写的「最新 = 12」是 09-20 旧值残留,实测 = 14(按 §〇-5 的 v14 判);⛔ 只报告未改。 下一棒(唯一) = 第 6 棒 · 执行棒 = 开工 B 单(交接单/IM群组-B-平台API与用户端IM-WS通道.md),动作见 §2「⏭️ 第 6 棒」。

  • ✅ 2026-09-22 20:2x|第 6 棒(执行棒)✅ 完成 —— IM 五单合流第二单(B 单)已落地 —— 回填见 交接单/IM群组-B-平台API与用户端IM-WS通道.md §九 执行回报(IM线第6棒)(新建独立小节,⛔ 原文未改)。 交付(本机代码面):新建 src/im/hub.ts(372)+ src/im/ws.ts(542)+ src/web/routes/im.ts(333)(共 1247 行)+ 改 src/supervisor/proxy.ts(+30 / −0,委托钩子 setUpgradeDispatch+回调首行分流)+ src/web/server.ts(本单 +7:import + 挂载)。 判据读数:npm run build rc=0 | check-layering ✅ 无新增违规(rc=0;另提示 src/im/* 未归类,不影响退出码)| npm test(Node 22)= 271 / 269 过 / 0 败 / 2 跳过(第 5 棒基线一致)| 本机端到端验收 15 PASS / 0 FAIL / rc=0(REST 五类 + WS 握手含 cursor + 投递每连接恰好 1 次 + 未订阅 0 次 + 游标补拉恰好 50 无重复 + 心跳超时 ≤2 周期判离线并落具名日志 + 单用户超限 429 具名码 + 既有子域隧道 HTTP 200 / upgrade 101 不受影响)| npm run smoke:subdomain = OK: subdomain routing + auth flow passed。 标定(§五-5,A 单初值 2000 / 10 / 30 s ⇒ 建议不回填):投递增量三档严格线性(Δdeliveries/Δmessages = 10 / 100 / 1000 / 2000),2000 连接的 hub 内扇出 P99 = 0.023 ms;presence 1000 成员房、每人每分钟一次变化 ⇒ 实发 5.0 帧/秒(C=5,比对照 16,667 低 3333×)与 1000.0 帧/秒(C=1000,低 17×);预算三档具名码逐条命中且人类成员不受限频;decideWrite P99 = 0.030 ms。 §二 前置:5 条中 3 处读数漂移(routes import 11→14 行但仍无 im;on('upgrade') 全仓 2 处(relay 自建 http server + 平台 app.server)⇒ 「平台 app.server 上仅 1 处」成立;proxy.ts:718 → 759)⇒ ⛔ 未触发停手。nginx 那条本机(Windows)无法核实、且本棒⛔ 不动服务器 ⇒ 如实标注(location 草案已写进 §九.6)。 ⚠️ 未完成项:nginx location 未应用(⛔ 不部署)|§六.1-9 的「跨进程重启」半程未实测(需部署动作)|GET /api/im/rooms 为 O(房间数) 查询(需 A 单 store.ts 加下推查询)|⛔ 未 commit/push/scp、⛔ 未动 47/106、⛔ 未引入新依赖。 下一棒(唯一) = 第 7 棒 · 执行棒 = automation d2161e2a-bb50-47e2-9a26-50f291fc1d0b(一次性 · scheduledAt = 2026-09-22T20:26)= 开工 C 单(交接单/IM群组-C-Agent接入插件.md),动作见 §2「⏭️ 第 7 棒」。

  • ✅ 2026-09-22 21:1x|第 8 棒(执行棒)✅ 完成 —— IM 五单合流第四单(D 单)已落地 —— 回填见 交接单/IM群组-D-插件SDK与扩展点契约.md §十 执行回报(IM线-第8棒)(新建独立小节,⛔ 原文未改)。 交付(本机代码面):新建 src/im/sdk/types.ts(458)+ src/im/sdk/host.ts(695)+ src/im/sdk/index.ts(38)(共 1191 行)+ 新建 三个示例插件 poc/business-plugins-im/{office-tasks,mud-rate,trpg-turn}/(各含 package.json + cordis.patch.yml + lib/index.js,共 475 行)+ 新建 test/im-sdk.test.mjs(1019 行,66 用例) + 新建说明页 dsh-server-docs/docs/IM插件SDK与扩展点契约.md + 改 package.json 2 处(test/verify 各登记 1 处)。 判据读数:npm run build rc=0 | node --test test/im-sdk.test.mjs = 66 / 66 过 / 0 败 / 0 跳过 | npm test(Node 22)= 366 / 364 过 / 0 败 / 2 跳过(第 7 棒基线 300 ⇒ +66 全为本单)| 内核零改动:git diff 对 src/im/store.ts/hub.ts/web/routes/im.ts 空、git status --short src/im 无 M 标记 | check-layering rc=0 ✅ 无新增违规。 三条硬判据:① 七点齐备(EXTENSION_KEYS 恰为 142 §3.6 七个;三插件合起来全覆盖)② 三类场景各跑通一个(office 聊天档 / mud 实时档+限速 / trpg 实时档+回合制,各落一行到自己的表,内核一行未改 ⇒ 142 §五-6 通过)③ §六.1-8 失败模式齐备(每个扩展点条目都带 timeoutMs + circuit,缺一即不通过)。 口径更正(本棒新增,⛔ 不改 A–E 结论):D 单 §九 表格与 §三 的 im.data 是 09-20 规划态命名 —— 落成的是 ImDataPort 注入式端口(真库端口属建库面,本棒不越界);§九 已就地加只标注的落点说明块。 未完成项 / 已知限(5 条,逐条注明卡在哪):① E2EE 密文形态只在契约面成立(§四-6 形态档位待确认;密钥面未实现,判据落成"载荷契约不声明 envelope 字段")② 数据面端口是注入式桩,非真库端口 ③ check-layering 仍提示 src/im/* 未归类(改它超改动面)④ 迁移「删列/改类型 ⇒ 拒绝启用」只验到声明面 ⑤ 三个示例插件未打包/未投放/未启用(属投放线面)。 ⛔ 未 commit/push/scp、⛔ 未部署、⛔ 未动 47/106、⛔ 未引入新依赖。 下一棒:⛔ 未登记(E 单被 D 单收口结论阻塞,见下行)。

  • 🔴 2026-09-22 21:1x|IM 线下一步 = 等用户拍板(⛔ 未登记任何接续棒): 原因(硬性,非"更慢更绕"):E 单(交接单/IM群组-E-会话界面UI.md)开工前必须定落点,而落点三案无客观优劣(属体验偏好 ⇒ §1 边界外第 ⑤ 类):

    • 候选 A · 集成进门户(web/portal.html 新面板):优点 = 与账号/成员/管理面同域同一次登录,成员与权限天然对齐平台用户;缺点 = 门户是平台自己的页,与「实例内聊天」是两套线程,房间消息要在平台侧渲染,页面变重。
    • 候选 B · 独立页(web/im.html,可挂子域):优点 = 与门户解耦、可按全屏消息流设计,改动面小、回滚干净;缺点 = 多一个入口要维护(登录态/导航/i18n 各一套),用户要记住「聊天在这里」。
    • 候选 C · 实例内工作台面板(走 dsh 既有 slots):优点 = 与 agent 同一屏(代答与上下文都在自己实例里),复用既有面板与插件分发面;缺点 = 房间是跨用户平台级资源 ⇒ 成员表/消息仍走平台 API,同一房间在每个用户实例里各渲染一份(UI 状态不共享),且交付面落进实例内插件链。
    • E 单原倾向(可被推翻):候选 A。 ⇒ 三案各有优有劣、客观标准分不出高下 ⇒ ⛔ 不替用户拍;拍板到手后再登记 E 单接续棒。 ⚠️ 另有一项待拍板仍悬:E-5 实测「分时扇出」无效 ⇒ 连接层三候选(A 就地继续优化(多进程并行)/B 引入 Centrifugo 类外部连接层/C 混合)—— 拍板前 ⛔ 不引入任何外部组件。

    交付(本机代码面):新建 src/im/instance-token.ts(197)+ src/im/agent-bridge.ts(341)+ poc/im-agent-bridge/{package.json,cordis.patch.yml,lib/index.js}(368)+ test/im-agent.test.mjs(29 用例);改 src/web/routes/im.ts(+约 78)+ src/im/ws.ts(+约 12)+ src/web/server.ts(+约 14)+ src/supervisor/orchestrator.ts(+约 55)+ src/supervisor/remote-spawner.ts(+约 30)+ src/worker/agent.ts(+3)。 判据读数:npm run build rc=0 | node --test test/im-agent.test.mjs = 29 / 29 过 / 0 败 | npm test(Node 22)= 300 / 298 过 / 0 败 / 2 跳过(第 6 棒基线 271 ⇒ +29 全为本单)| check-layering rc=0 ✅ 无新增违规(仍提示 src/im/* 未归类,不影响退出码)。 两条关键判据(§五-4,本线最硬的两条):① 上下文能引用窗口内、但没有 @ 它的消息(ok 13,prompt 里含未被 @ 的那条);② 越权内容不出现(ok 16,他人 visibility='private' 的内容不在 prompt 里)。 两处 501 预留点已换真校验:src/web/routes/im.ts 的 imAuth 与 src/im/ws.ts 的 doUpgrade(正确 token ⇒ 通过;不认/过期/跨区 ⇒ 401 且不升级,⛔ 不再 501)。 ⚠️ 偏离已逐条写进该单 §九.6:① ws.ts 改注入钩子(⛔ 不直接 import 登记簿,否则分层反向)② 改动面比 §三 大 4 个文件(集群形态必须由 Manager 签发+随 launch 投递,否则换集群「一条 IM 凭据都不产生」)③ 本地 LLM 会话对接留成可注入接口 opts.askLocalSession(属 D 单契约面,⛔ 未自造)④ 凭据用内存登记簿(代价:平台 dshs 重启后已签发 token 失效,需等实例重启)⑤「标识截图」因 E 单未开工而给 JSON。 ⛔ 未 commit/push/scp、⛔ 未部署、⛔ 未动 47/106、⛔ 未引入新依赖。 下一棒(唯一) = 第 8 棒 · 执行棒 = automation c57c0577-f2ab-496a-91dd-24d90bb405fa(一次性 · scheduledAt = 2026-09-22T20:53)= 开工 D 单(交接单/IM群组-D-插件SDK与扩展点契约.md),动作见 §2「⏭️ 第 8 棒」。

  • ✅ 2026-09-23 05:5x|E 单落点已拍板(用户三次拍板,⛔ 不再征询) —— 本棒为只读调研 + 落点固化(无代码改动、未抢锁写码)。 🔴 用户拍板(原话口径):① 落点 = C 案(实例内 dsh 会话页 tab:「dsh会话、群组会话1、群组会话2…」)② tab 保活 = 候选一(display:none)③ 群组 tab 上限 = 5 个 ④ 新建群组入口 = 放在对应插件内 ⑤ 开源界面 = 借鉴优秀群聊项目的界面功能与交互(按 ① 方案:只借交互与组件,⛔ 不引整站)。 技术依据(实测已验):dsh 会话区 = 官方公开可寻址 DOM slot([data-slot="main.conversation"],旧名 conversation 兜底);MCN 工作台已在生产用同一机制接管(dsh-plugin-mcn/lib/client.js:306 选择器、:453-495 wrapper 接管与卸载还原、:3025 侧边栏注册)。⇒ 可行性已由现网代码证实,⛔ 不触 R2(走官方扩展面)。 🔴 新发现的前置缺口(E 单必须补):src/web/routes/im.ts 全部只有 6 条路由,GET /rooms 按 isMember() 过滤(非成员看不到任何房)、加人要求 role IN (owner,admin) ⇒ 普通用户无法「发现群 → 点按钮加入」,用户要的按钮后端不存在。补缺口成本可控:store.listRooms 已有筛选签名(扩展参数即可)|rooms.config_json 已解析成 room.config(可放标志不迁移;但授权语义宜走显式列 + v15 只增迁移)|在线态已内建(hub.ts presence,1 s 批合并)⇒ 无需新增数据源。 🔴 新风险(必带对策,已写进 E 单判据 11):接管依赖 [data-slot] 选择器,官方改槽名会静默失效(已被咬过一次:0.1.5-rc.1 改名 ⇒ 工作台点了没反应)⇒ 多选择器兜底 + 失效可见提示。 已固化落点:E 单 §四-4/§四-5(拍板)+ §三 范围 + §五 步骤(新增步 0 补缺口、步 2/3/8 新增 tab/保活/上限/加入)+ §六 判据(新增 8–13 条)+ §七 回滚 + §八 回报;本入口 §0/§2/§4。 下一棒(唯一) = 第 9 棒 · 执行棒 = 开工 E 单(交接单/IM群组-E-会话界面UI.md)= automation b4312852-71ae-41a1-9304-a857cb1d7eba(一次性 · scheduledAt = 2026-09-23T06:03),动作见 §2「⏭️ 第 9 棒」。⚠️ 另一项待拍板仍悬:连接层三候选(E-5 已证「分时扇出」无效)—— ⛔ 与本棒解耦,拍板前不引外部组件。

  • ✅ 2026-09-23 06:3x|第 9 棒(执行棒)✅ 完成 —— 开工 E 单,IM 线 A–E 五单全部落地 —— 回填见 交接单/IM群组-E-会话界面UI.md §执行回报(IM 线第 9 棒 · 2026-09-23 06:2x)(新建独立小节,⛔ 原文未改)。 交付(本机代码面):步 0 前置缺口已补 —— src/db/schema.ts 追加 v16(rooms.discoverable + rooms.join_policy,显式列路线)|src/im/{types,store,hub}.ts 增量(JoinPolicy/JoinRejection/JoinOutcome、toRoom 双后端归一、listRooms({discoverable}) 下推、新增 joinRoom 判序、新增 presenceSnapshot)|src/web/routes/im.ts 新增 4 条端点(GET /rooms?discoverable=1 · POST /rooms/:id/join · GET /rooms/:id/members · PATCH /rooms/:id/members/:mid)|新建 poc/im-conversation-tabs/ 四件(package.json 零依赖 / cordis.patch.yml / lib/index.js 25 行 / lib/client.js 1228 行)|新建 test/im-ui.test.mjs(29 用例)|改 test/im-store.test.mjs(PG 段 ratchet = 14 ⇒ >= 14)+ package.json(test/verify 各登记 1 处)。 判据读数:npm run build rc=0 | node --check 双通过(client.js / index.js)| node --test test/im-ui.test.mjs = 29 / 29 过 / 0 败 | npm test(Node 22)rc=0 = 420 / 418 过 / 0 败 / 2 跳过(基线 366 ⇒ 本棒 +29,余量为并发会话同仓贡献)| check-layering rc=0 ✅ 无新增违规。 步 0 选型(自决,可推翻):走路线甲「显式列 + v16 只增迁移」,三条理由 = ① 授权语义不埋 config(可索引/可约束/可审计)② 可下推(WHERE discoverable = 1)③ 默认值落保守侧(0 / 'invite' ⇒ 存量房间零变更,⛔ 不会因迁移把私有群批量变公开)。 判据 1–13 全部取证:多选择器兜底(新名在前)· 卸载反向还原(insertBefore + prevCss 恢复 + wrap.remove())· 保活 display:none(草稿在 React state、滚动位置 listRef)· 上限 5 + 具名 toast(⛔ 无 .slice 截断)· 槽位失效 .imtabs-alert 可见 · presence 快照同源 · 发现+加入(not_found/not_discoverable 归同一 404 不泄露存在性、join_closed 403、room_full 409)· 重连 2^n 封顶 15 s + 抖动 · 词条 zh/en 双侧齐且渲染面零硬编码中文。 ⚠️ 实际读数漂移:E 单 §四-5 与用户口径写的迁移号是 v15,实测 v15 已被别的线占用 ⇒ 取 v16(与「迁移号只增、取下一个空号」纪律一致)。 🔴 未完成项(卡点,⛔ 非"更慢更绕"):生效链路第 ④ 关「线上可见面」未做 —— 本棒边界明令「⛔ 不部署到 47/106」,且实例内 client bundle 的投放属插件投放线(须走候选池 → 用户自助启用 → 重启实例)。⇒ E 单 ⛔ 不能判「已交付」,须由投放线接力出线上截图。 ⛔ 未 commit/push/scp、⛔ 未部署、⛔ 未动 47/106、⛔ 未引入新依赖、⛔ 未改 A–E 既有结论、⛔ 未替用户拍 §4 未决项。 下一棒:⛔ 未登记 —— A–E 五单已全部落地 ⇒ 下一步只能按 §4 剩余待拍板项推进,而三项(E2EE 档位 / 房间上限与预算数值 / 连接层三候选)均需用户先拍板 ⇒ 按纪律停手等拍板(见下行)。

  • 🔴 2026-09-23 06:3x|IM 线下一步 = 等用户拍板(⛔ 未登记任何接续棒):理由 = §4 三项待拍板项无客观优劣,且都不是"执行棒能自己判"的技术项 ⇒ 拍板前 ⛔ 不开新棒、⛔ 不引外部组件。E 单的线上交付另需投放线(见上行卡点)。

  • ✅ 2026-09-23 12:1x|第 10 棒(收口棒)✅ 完成 —— 三项待拍板全部落定 —— 回填见选型单 §十四 用户拍板落定(新建小节)+ A 单 §四-7 / §十-4 状态块(⛔ 各单既有结论未改)。 三项拍板:① E2EE 形态档位 = 强度档(含量化性能判据:密码学开销 ≈ 55 µs/条 ⇒ 相对 1 s 事件循环预算 0.006%、50 msg/s 下占单核 ≈ 0.3%)② 房间上限与预算 = 按建议固化(2000 / 10 / 30 s 转正式值 + 适用前提 = 单进程自研扇出形态,改形态须复测)③ 连接层 = 候选 B(引入 Centrifugo 类)(含三条硬实现项:补发自建心跳 / 可替换接口 / 容量测算 70.1 KiB·连接)。 🔴 本棒最重要的一条提醒(⛔ 别漏):强度档的"影响较小"只覆盖 CPU 维度,三处结构性代价须按实现项对待 —— ① 棘轮状态每设备持久化(丢了永久断链)② 群聊必须 Megolm 化(误用严格双棘轮 ⇒ 扇出 O(N)→O(N²),这才是会压垮连接层的那条)③ agent 代答模型重做(im_agent_grants 在强度档下不成立 ⇒ D 单 §四-2 须重写、E4-6 断言须改写)。 ⚠️ 落地顺序硬约束:档位重写(选型单 §十四-14.3 的 9 条清单)先于任何 E2EE 代码动工 —— E-4 的 8 条断言里 E4-4/E4-6/E4-8 三条是档位相关的,抢跑等于白写。 ⏸ 本轮只拍板未落码:三项都是"先定方向再动工"的决策项;下一棒的动工范围取决于先做哪一项(见下行)。

  • 🔴 2026-09-23 12:1x|本轮收口时所处状态 = 「三项已拍板 · 未排下一棒」 —— 三项拍板已落文档、⛔ 尚无任何一行按新档位改动的代码。 下一步可选(⛔ 待用户拍板先做哪项,本入口不替排):

    • ① 档位重写(做 E2EE 线的前置) —— 按选型单 §十四-14.3 的 9 条清单重写。🔴 顺序硬约束:此项必须先于任何 E2EE 代码动工(E-4 八条断言里 E4-4 / E4-6 / E4-8 三条是档位相关的,抢跑等于白写)。
    • ② 连接层换型(候选 B ⇒ 引入 Centrifugo 类) —— 三条硬实现项:补发自建心跳 / 按"可替换连接层"接口落(⛔ 不把概念泄漏进 src/im/**)/ 出容量测算表。 🔴 上游阻塞未解:IM 入口 §2 序号 6 记的「/opt/dshs 与 /opt/dsh 分叉」仍在 ⇒ 换型须先定部署根(⛔ 两套根同时存在时安装位置不可判)。 🔴 2026-09-23 A 线新查实的事实(对 ② 直接有用):47 的代码/二进制根 = /opt/dshs;106 的 = /opt/dshs-cluster(106 上没有 /opt/dshs)⇒ 原「/opt/dshs 与 /opt/dsh 分叉」这个表述要修正:真正分叉的是47 与 106 两台机之间,不是 47 上两个目录之间。/opt/dsh = 平台数据根(state/backups/artifacts/users),三台机同名。
    • ③ E 单线上可见面 —— 卡在插件投放线(实例内 client bundle 须走 候选池 → 用户自助启用 → 重启实例),⛔ IM 线自己交付不了,须投放线接力。 ✅ 2026-09-23 投放线已推进:装配协议 link: 已落地并两机部署(见该线入口 §0 末行)⇒ ③ 的载体条件已具备,只差把 poc/im-conversation-tabs/ 打进候选池投放。
  • ✅ 2026-09-23 16:2x|第 11 棒(执行棒)✅ 完成 —— ①档位重写(纯文档面 · 零代码) —— 用户口令「先把 a线做完,处理完成后在做b线」,A 线收口后转本线。 交付(4 个文档面,⛔ 零代码改动): ① 🔴 交接单/IM群组-稳定性机制与框架选型.md §四-6 整节按强度档重写 —— 密钥层级 3 层 → 4 层(新增「发送者链 Chain(room_id, sender_device_id, chain_index)」);DK(room_id, epoch)(每 epoch 一把)→ 每消息一把 MK_i(棘轮推进);「密钥包」→ 「链头包」(表 im_room_keys 的列名 epoch → sender_device_id + chain_index);密钥包/链头规则、消息路径上行字段、换钥三条(加人也需分发链头 ← 与旧文相反)、E4-4 / E4-6 / E4-8 三条断言按链轮次改写;新增 N-1 棘轮状态持久化 + N-2 Megolm 化(保 O(N) 扇出) 两条硬要求;旧「档位待拍板」表述作废。 ② 交接单/IM群组-D-插件SDK与扩展点契约.md §四-6 重写(agent 升格一等成员设备;im_agent_grants 旧模型说明其为何在强度档下不成立 + 表结构重写方向)+ 新增 §十一「档位重写落地说明」(11.1 逐条影响表 / 11.2 四条写码硬约束 / 11.3 未做项)。 ③ 调整方案/142-IM群组对话-需求基线与方案.md §六-3 更新为「档位已定 = 强度档」。 ④ 选型单 §七-2 表格加「2026-09-23 现值」列(⛔ 原始待定态记录保留,不改历史)+ §十二-187 行 E2EE 论据加档位更新标注 + D 单 §十-1 执行回报行加标注指向重写版。 §14.3 九条清单落位核对:1/2/3 ✅(E-4 §6.1/6.2/6.3 已改)|4/5/6 ✅(E4-4/E4-6/E4-8 已改)|7 ✅(D 单 §四-2 已重写)|8/9 ✅ 明确保留不改(设备身份密钥只签名 · relay 零改动 —— 已就地写「原样保留」)。 门禁:本棒纯文档 ⇒ ⛔ 未跑 npm test(零代码漂移无需回归,且选型单既有的 Node 22 基线读数仍有效);find 时间戳核实只动了 4 个 .md、src/test/skills 零改动 ✓。 ⛔ 未部署、⛔ 未动 47/106、⛔ 未 commit / 未 push、⛔ 未引入新依赖、⛔ 未建表未写一行密钥面代码(遵守 §14.3「重写先于代码」的顺序约束)。 🔴 下一步(② / ③ 二选一,⛔ 均需用户拍板或投放线接管):

    • ② 连接层换型:技术项、可自决,但体量最大(按"可替换连接层"接口落 + 补心跳 + 容量测算表),且必须先定部署根(本轮已查实 = 47 /opt/dshs / 106 /opt/dshs-cluster,阻塞已解 ⇒ 可开工)。
    • ③ E 单线上可见面:需投放线接力(把 poc/im-conversation-tabs/ 打进候选池 → 用户启用 → 重启实例)——投放线装配协议已就绪 ⇒ 可接力。 🔴 仍在等拍板的唯一一项:跨区可见性默认(倾向互不可见,§4-5)—— 与本轮解耦。
  • ✅ 2026-09-23 18:2x|第 11 棒(收口棒)✅ 完成 —— 优先级拍板:先做连接层 B 案 —— 用户原话「先做b」。 本棒动作 = 只落优先级 + 交接动工范围(⛔ 未写码):上下文已达 162k token(触发预算告警)⇒ 按纪律收口开新会话执行,⛔ 不在高上下文里开工写码。 🔴 本棒最有价值的实查发现:B 案的"可替换连接层"接缝已经现成 —— src/im/ws.ts:209 的 createImWsRuntime(options) 工厂 + ImWsRuntime(:190)/ImWsRuntimeOptions(:163)/ImUpgradeDispatch(:188)接口 + resolveInstance? 注入钩子(:179,:387 调用点)⇒ ⛔ 不必从零造接口,B 案 = 在此接缝上加 Centrifugo 后端实现(命名须中性化,⛔ 不把 Centrifugo 概念泄漏进内核)。 动工范围已写进 §2「第 11 棒」(含 5 步建议顺序 + 三条硬实现项(补发自建心跳/可替换接口/容量测算表)+ 验收判据对账表(10k P99 对账 E-2 的 28.9/68.1/111.9 ms、扇出离散中位 17.2/57.9/97.8 ms、心跳超时率 0、/api/im/ws 路径与帧格式语义不变)+ 环境注意(106 资产位置 / ssh test106 / ulimit -n 65536 / Centrifugo v6 配置键改位))。 未决(§4-5,⛔ 未替用户拍):跨区可见性默认(倾向"互不可见")|强度档下 agent 代答具体形态(属 D 单重写面)。

  • ✅ 2026-09-23 19:0x|automation 库已自愈(app 自动重建)+ 第 12 棒接续棒已补登 —— 库状态(实测):workbuddy.db magic 已恢复为 SQLite format 3(mtime 18:55);损坏库被 app 自动改名留档 = workbuddy.db.corrupt-2026-09-23T10-55-47-845Z(35,377,152 B,与既有诊断读数同尺寸)+ 同目录留 workbuddy.db.recovery-pending(state=PENDING / walReplayed=null)⇒ app 自己走了「重建空库」那条路(= DB 修复讨论里的案 A)。 🔴 代价:历史 automation 记录全丢 —— list 只剩两条 2026-08-27 的旧条目 ⇒ 其余各线的接续棒需逐线重登。 补登结果:第 12 棒(动工连接层 B 案)已登记 = automation 89e11630-4a06-41ca-bfc7-3c66ff4d8a53(一次性 · scheduledAt = 2026-09-23T19:10 · cwds = E:\ProgramData\AI技能\aliyun-dsh-server)⇒ 下行「登记受阻」块已解。 🔴 B 案零开工取证(本行实跑):grep -ril "centrifugo\|ImConnectionBackend\|connection-backend" src poc test ⇒ 零命中;src/im/ 仍为原 8 文件 + sdk/ ⇒ 与「第 11 棒收口时⛔尚无一行按新档位改动的代码」一致。 🔴 编号口径更正(本节内两处撞号):§2 那段标题与下行受阻块原写「第 11 棒」,与 §0 已完成的第 11 棒(①档位重写 + 收口)撞号 ⇒ 本节起下一棒 = 第 12 棒(与手工接力模板的会话名 IM线-第12棒 一致)。 🔴 E 单线上可见面的缺口(本行实查,⛔ 尚无人认领):投放线入口 接续入口_插件投放与分库线_20260922.md 里 im-conversation-tabs 零命中 ⇒ 该投放没有落在任何一方的待办清单上(本线 §0/§3-③ 说"须投放线接力",投放线入口没登记它)⇒ 第 12 棒收口时须显式登记到投放线(或本线自行排棒)。 ✅ 已办(2026-09-23 19:4x,第 12 棒收口执行) —— 已在投放线入口 §5「本线后续」新增第 5 条(写明"是什么 / 为何以前没人做 / 硬阻塞 = 本线 P0 协议不一致 / 投放前须先等第 13 棒修完"),并顺带更正该入口 §0 末行「automation 不可用」的过时前提(库已于 18:55 由 app 重建)。⚠️ 归属仍未认领 ⇒ 已列进 §2「第 13 棒」的连带待办。 ⚠️ 本条未做:MEMORY.md 瘦身仍欠(另轮处理)。

  • ✅ 2026-09-23 19:3x|第 12 棒(执行棒)✅ 完成 —— 连接层 B 案已落地(本机代码面,⛔ 未部署) —— 回填见选型单 §十五 B 案实现回报(IM 线 · 第 12 棒)(新建整节,⛔ §十四 未改)。 交付:新建 src/im/connection-backend.ts(中性接口 + 选择器 + 保活计划 + 两条自检)|src/im/keepalive.ts(自建保活垫片)|src/im/backends/{native,gateway}.ts|test/im-connection-backend.test.mjs(28 用例)|poc/im-connection-gateway/{config.json,nginx-location.conf,README.md};改 src/im/hub.ts(纯重构:扇出抽成可注入 ImFanoutPort + 新增 deliverFrame())|src/web/routes/im.ts(后端选择 + 迟绑定扇出槽位 + /api/im/stats 回带后端读数与保活要求)|package.json(+2 −2)。 判据读数:npm run build rc=0 | 新测试 28 / 28 过 / 0 败 | npm test(Node 22)rc=0 = 478 / 476 过 / 0 败 / 2 跳过(第 9 棒基线 420/418 ⇒ 本棒登记 +28,余 30 条为并发会话同仓贡献)| check-layering rc=0 ✅ 无新增违规 | smoke:subdomain = OK: subdomain routing + auth flow passed(⚠️ 脚本跑完不自退 = 既有现象,外层 timeout 兜底)。 三条硬实现项逐条落地:① 自建保活 = keepalive.ts(单扫描器 + 每连接自己的死线)+ E-7 三向实证 ② 可替换接口 = 中性命名 + 两后端 + 能力自述("不发服务端心跳却不声明需要垫片" ⇒ 构造即抛)③ 容量测算表 = 见 §15.5。 E-6 对账 E-2(同脚本同参数):扇出离散中位逐档重合(1k 19.07 vs 17.2|5k 51.23 vs 57.9|10k 99.12 vs 97.8 ms)⇒ B 案依据未被推翻;⚠️ P99 三档偏高 6–49%(10k 141.64 vs 111.9 ms,原因未定位)⇒ 只对"扇出中位 + 内存中位"作等值判定。 🔴 E-7(本棒新做·硬实现项 ① 的判据,106 · 10k):off(不发 ping)t=26 s 掉到 0(全断)|global(整进程统一计时)掉 2517(E-2 记录 2773,独立复现)|conn(每连接认证后起计时)掉 0(10000→10000→10000,worker closed=0)。三组 wsPingRecv 恒 0。 🔴 P0 发现(本棒最重要,⛔ 未修 —— 详见 §15.7):E 单客户端 bundle 与平台 /api/im/ws 的帧协议不一致 ⇒ 客户端发 {type:'subscribe'} / {type:'ping'}、按 msg.type 收帧(poc/im-conversation-tabs/lib/client.js:45/421/439/446/460-464);平台认 {op:…},其余 ⇒ reject('bad-op') ⇒ socket.destroy()(src/im/ws.ts:251/264-303/347),推的帧一律 {op:…} ⇒ 投放后必现「连上就断、一条消息看不到」 ⇒ E 单线上可见面在修掉它之前不可能通过。 ⚠️ 口径更正(本棒):「心跳超时率 = 0」不能直接用 e2sum.py 的 hbTimeoutRate(它把爬坡期建立的连接也算作"应完成整轮" ⇒ 结构性非零:1k 0.0005 / 5k 0.0067 / 10k 0.042–0.052)⇒ 权威判据应改为「服务端 num_clients 不下降 + worker closed=0」(E-7 的 conn 口径两条都满足)。 ⚠️ 未完成项:在线态取数未接线(stats().presenceIngest='not-wired')|gateway 未部署(47/106 一行未动)|客户端侧垫片未接线(被 P0 阻塞)|50k 以上容量为线性外推、CPU 未测。 ⛔ 未 commit/push/scp|⛔ 未部署|⛔ 未动 47/106|⛔ 未引入新依赖|⛔ 未改 A–E 五单结论|⛔ 未替用户拍 §14.6 的未决项。 下一棒(唯一) = 第 13 棒 · 执行棒 = automation 31f0b0d7-f652-4081-88d0-3aec69136949(一次性 · scheduledAt = 2026-09-23T19:43 · cwds = E:\ProgramData\AI技能\aliyun-dsh-server)= 修 §15.7 的 P0(客户端协议对齐 + 保活垫片接进客户端传输层),动作见 §2「⏭️ 第 13 棒」。排它的理由 = 先修掉阻塞「已交付功能」的缺陷(属客观可判,非业务优先级);② 部署 + ③ 在线态接线排在它之后。 ✅ 收尾五件全过:① 结果回填(选型单 §十五)② 域锁已 --release-exec(复核 交接单/.locks/ 为空)③ 下一棒已登记(= 上行 automation 31f0b0d7;automation 库本轮实测可写)④ 入口 §0/§2 已推进 ⑤ 当日日志 .workbuddy/memory/2026-09-23.md 已追加。 ✅ 顺带办掉一条跨线缺口(本行第 136–137 行):E 单投放面此前"两不管" ⇒ 已在投放线入口 §5「本线后续」新增第 5 条(写明"硬阻塞 = 本棒 P0")+ 改正该入口 §0 末行「automation 不可用」的过时前提(库 18:55 已由 app 重建)。⚠️ 该投放的归属仍未认领 ⇒ 已列进 §2「第 13 棒」的连带待办。 ⚠️ 编辑事故与修复:插入 §2「第 13 棒」块时误把 §3 的标题行当作了 old_string ⇒ 一度删掉 ## §3 已取事实…;本棒已复原,grep "^## §" 复核 §0/§1/§2/§3/§4/§5/§6 齐全。教训:用 Edit 插块时 ⛔ 不要把下一节的标题行当锚点。

  • ✅ 2026-09-23 19:5x|第 13 棒(执行棒)✅ 完成 —— §15.7 的 P0 已修(本机代码面,⛔ 未部署 / 未投放) —— 回填见 交接单/IM群组-E-会话界面UI.md 新追加的 §执行回报(IM 线第 13 棒)(⛔ E 单正文与结论一字未改)+ 选型单 §15.7 处的 🔻 处置标注(⛔ 原文未改)。 改动面 = 2 个文件(严格按本入口 §2 第 13 棒的动工范围):poc/im-conversation-tabs/lib/client.js(主件)+ test/im-ui.test.mjs。 判据读数:npm run build rc=0 | node --check 双通过 | im-connection-backend 28 / 28 过(⛔ 未回退)| 四件 IM 用例 148 / 147 过 / 0 败 / 1 跳过(im-ui 29 ⇒ 30)| npm test(Node 22)rc=0 = 479 / 477 过 / 0 败 / 2 跳过(第 12 棒基线 478 / 476 ⇒ 本棒 +1)| check-layering rc=0 ✅ 无新增违规。 修法:客户端对齐平台的 op 制 —— 出站收敛到唯一入口 sendOp() + 白名单(subscribe / ping / resume);入站 switch (msg.op) 全量 9 分支(hello/subscribed/unsubscribed/message/messages/presence/sent/pong/error,error 写进 UI ⛔ 不静默);自建保活(周期 ⌊26000/3⌋ = 8666 ms = 垫片 planKeepalive() 同款口径 + 死线由本连接 open 时刻推算 + 半开链路检测)。⛔ 未改服务端("兼容 type 别名"= 永久两套协议,本线已定不做)。 🔴 本棒最重要的发现(入口 §2 的一条建议本身是陷阱):原写「保活 ⇒ {op:'pong'}(pong 是已认 op)」—— pong 不是已认 op:src/im/ws.ts 的客户端 op switch 只有 ping / subscribe / unsubscribe / resume / send 5 个 case,pong 只出现在该文件模块头注释的表格里(case 'pong' 检索 = false)⇒ 照原建议发 pong 会落 default ⇒ bad-op ⇒ destroy(),与 P0 同症。已改发 {op:'ping'},并在 E-14 加反向专测钉死前提。⚠️ ws.ts 头注释与实现不一致,属内核面 ⇒ 只报告未改。 新增判据 E-14(协议一致性,两侧源码机械对账):① 出站 subscribe / ping / resume ⊆ 平台认的 5 个 op ✅ ② 无 type: 出站帧、无 msg.type 分支 ✅ ③ 客户端 case 集合 = 平台推帧集合,恰 9 个 ✅ ④ 周期 +「每连接 open 起计时」两条都在 ✅。非空绿证据 = tmp/im13-proto-proof.mjs(只读打印)。 三处口径偏离(已逐条写进回报 §6):① resume 的 since 不用 subscribed 回带的 cursor(那是房间最新 seq ⇒ 补拉取出 0 条),改用本端自己的进度 cursorRef.current ② 发送路径保持 REST(入口那条以"bundle 没有发送路径"为前提,该前提不成立 ⇒ ⛔ 未做未被要求的路径变更)③ REST pull(cursorRef.current) 保留不动(E 单既有判据 E-9c),与 WS resume 并存且按 m.id 去重。 🔴 未完成项:E 单线上可见面(生效链路第 ④ 关)仍未验收 —— 须走候选池投放 → 用户自助启用 → 重启实例 ⇒ E 单 ⛔ 仍不能判「已交付」;本棒只解掉它的唯一硬阻塞。 ⛔ 未改 src/im/** | ⛔ 未改 A–E 五单结论 | ⛔ 未部署 / 未投放 | ⛔ 未 commit/push/scp | ⛔ 未引入新依赖 | ⛔ 未动 47 / 106 | ⛔ 未碰 §14.6 未决项。 下一棒(唯一) = 第 14 棒 · 执行棒 = automation 2d059ec8-0664-45de-a1c1-e8478f1686df(一次性 · scheduledAt = 2026-09-23T20:15 · cwds = E:\ProgramData\AI技能\aliyun-dsh-server)= 认领并执行 im-conversation-tabs 的投放(可自动部分)+ 出线上取证清单,动作见 §2「⏭️ 第 14 棒」。

  • ✅ 2026-09-23 20:2x|第 14 棒(执行棒)✅ 完成 —— im-conversation-tabs 的投放(可自动部分)已落地 47;E 单只剩「用户点启用」这一步 —— 回填见 交接单/IM群组-E-会话界面UI.md 新追加的 §执行回报(IM 线第 14 棒)(⛔ E 单正文与结论一字未改)+ 操作单 E:\ProgramData\AI技能\aliyun-dsh-server\交付物\IM群组-ui投放与线上取证操作单-20260923.md。 投放物:@dsh-local/im-conversation-tabs v0.1.0|tgz sha256 c9b1184aca8eadd6c84316d2e79427546a65f59f0614a2ec9704c895a311bfb1(21,494 B)|lib/client.js md5 21fbc52fdedc9915238dd1c3f5c5e827(=第 13 棒修完 P0 的那一版,本机源 / tgz 包内 / 47 共享层三处逐字一致)。 判据读数:① 包三件齐断言 rc=0(exports['./client'] / dsh.client.platform='web' / dsh.bundle.patch)② POST /api/plugins/business ⇒ http=200、池 4 → 5、replaced:false、trustedOverride:false、compat.level:"unknown"(非 incompatible)、无 P0 命中 ③ POST /api/plugins/business/share ⇒ http=200 action:"created"、fileRef = link:/var/lib/dshs/bundled-plugins/_dsh-local_im-conversation-tabs(root:root、全 world-readable)④ 审计 audit_log id 360(upload)/ 361(share)⑤ 回滚素材 /opt/dsh/backups/plugins/_dsh-local_im-conversation-tabs/0.1.0.tgz。⛔ 未铺 profile、⛔ 未改任何源码、⛔ 未 commit/push/scp。 🔴 本棒自决项(可推翻):入口 §2 只写「打进候选池」,本棒同时发布到共享层 —— 装配协议是 link:(09-23 拍板)⇒ profile 写的是软链依赖、目标是共享层目录,只进池不发布用户启用必撞 plugin_bundle_missing(=半成品投放)。⇒ 已写进投放线入口 §5-5:本线后续给任何插件投放都要带上这一步。 🔴 本棒最重要的口径更正(入口 §2 的判据 ③ 取不到):IM 链路服务端一条日志都不写 —— src/im/ws.ts / src/web/routes/im.ts / src/supervisor/proxy.ts 三处 process.stderr / console.* 零命中;reject()(ws.ts:227-234)只回一帧 error 再 destroy(),不落日志 ⇒ journalctl | grep bad-op 恒空,而"空"会被误读成"没发生"(PB 已记同类坑)。⇒ 判据 ③ 的取证面改到客户端(DevTools → Network → WS → /api/im/ws → Messages 帧流水 + .imtabs-conn 连接态),已写进操作单 §三。 ⚠️ 另两条实测发现(只报告未动,R7):① 47 上在跑的 admin 实例其 bwrap 没有 --ro-bind-try /var/lib/dshs/bundled-plugins(scope 启动于 09-21 00:55,早于该绑定上线;47 的 orchestrator.js:1047-1048 已含)⇒ 必须走一次重启才带上——启用动作自带重启,无需额外动作;106 在跑的实例 cmdline 里有该绑定。② 47 admin profile 有一条 09-21 的陈旧软链 node_modules/@dsh-local/storyforge(dependencies/bundles 里都已无),第 8 棒探针残留;③ 发现 13 条 user_agent='poc-curl2' 的历史临时 admin 会话未清(TTL 600 s ⇒ 早已过期)——本棒只删自己那一条(deleted_sessions=1),其余只报告。 ⚠️ 未完成项(卡点,⛔ 非"更慢更绕"):① E 单「线上可见面」仍未验收 —— 卡在用户动作(实例「功能管理」启用),本棒把"无解"推进到"只差点一下",⛔ 不能替用户点 ② 106 共享层缺该包 ⇒ 若目标实例在 106 启用会失败(属「跨节点内容分发」立项面,操作单 §四-1 已给手工同步命令,本棒未执行)③ 在线态取数未接线 / gateway 未部署(与第 12 棒同一批,排在再后)。 ⛔ 未改 src/im/** | ⛔ 未改 poc/im-conversation-tabs/**(打包只做只读复制)| ⛔ 未改 A–E 五单结论 | ⛔ 未 commit/push/scp | ⛔ 未引入新依赖 | ⛔ 未铺 profile | ⛔ 未取用 47 的 PG 凭据 | ⛔ 未碰 §14.6 未决项 | ⛔ 未替用户点启用。 下一棒(唯一) = 第 15 棒 · 执行棒 = automation 4e08cc77-c222-43ee-a3f5-521baebe2d70(一次性 · scheduledAt = 2026-09-23T20:45 · cwds = E:\ProgramData\AI技能\aliyun-dsh-server)= 47 就地部署连接层 gateway + 在线态取数接线(第 12 棒 §十五 的两个未完成项),动作见 §2「⏭️ 第 15 棒」。⚠️ 若用户先反馈了操作单 §三 的六条取证结果 ⇒ 优先按反馈处置(那是"已交付功能"的验收面)。

  • ✅ 2026-09-23 21:3x|第 15 棒(执行棒)✅ 完成 —— 47 就地部署连接层 gateway + 在线态取数接线(第 12 棒 §十五 的两个未完成项全部闭合**)** —— 回填见选型单 交接单/IM群组-稳定性机制与框架选型.md 新追加的 §15.11(⚠️ 落点不是本入口原写的「§15.8」—— §15.8 已被「未完成项 / 已知限」占用 ⇒ 改落 §15.11,⛔ §十五 既有小节一字未改,只在 §15.8 表前加了一段 🔻 状态标注)。 交付(①代码面 / ②47 部署面):① src/im/presence-ingest.ts 新建(取数端口 + fail-closed 解析器 + 常量时间 secretEquals)+ src/im/connection-backend.ts(增可选 presenceIngest)+ backends/{native,gateway}.ts(自述如实报 local-hub / 未接线则仍自曝 not-wired)+ src/web/routes/im.ts(网关回调面 POST /api/im/gateway/presence + 成员表/发消息的在线态改走端口 + stats() 增 presenceIngest / presenceOnline)+ 新测 test/im-presence-ingest.test.mjs 13 用例;② 47 上 20 个 lib/** 产物逐字一致(属主 root:root)+ 网关进程 /opt/dsh-gateway/centrifugo(v6.9.6,sha256 f4869bf9…a848c)+ config.json/gateway.env(600)+ 单元 dsh-gateway.service(enabled+active、只绑 127.0.0.1:18081、公网 IP 探测 rc=7)+ 边缘片段落盘 + 旧产物备份 /opt/dsh/backups/im-gateway-deploy-20260923-210125/。 判据读数:npm run build rc=0|B 组 28 / 28|新测 13 / 13|npm test(Node 22)492 用例 / 490 过 / 0 败 / 2 跳过|check-layering rc=0(未归类 1 = 既有 src/platform-paths.ts)|部署后 /api/im/ws 101、systemctl is-active dshs = active、门户 200|presenceIngest not-wired ⇒ local-hub(native) / gateway-join-leave(gateway)、presenceOnline 0 → 2、presenceInputs 0 → 2、presenceFrames 0 → 1|取数 E2E 订阅者收到原文 presence 帧|回调面 带 key 200({ok:true,source:"gateway-join-leave",accepted:2,rooms:1})/ 不带 401 bad-gateway-key / native 模式 404 fail-closed|回滚演练 删 drop-in → daemon-reload → 重启 ⇒ 环境键 0 个、握手 101、portal 200、backendKind=native、回调面 404 ⇒ 线上已回改动前。 🔴 保活三向实测(47 就地部署的网关上,240 连接 / 45 s):off(从不回帧)80/80 掉(3012 · no pong)|conn(按每连接收到自己的 ping 才回 {})0/80 掉(收/回 480/480)|global(整进程一个节拍器)80/80 掉(3501 · bad request)⇒ §15.3(E-7)在真实部署实例上复现;干净 conn 全程 num_clients 240 不降。 🔴 一条既有结论被实测改写:client.ping_interval 0s ⇒ 25s —— 「网关不发 WS ping」那条实测本身没错(wsPingRecv=0 数的是 WS 控制帧),错的是由它推出的结论;网关会按 ping_interval 发协议级 JSON ping,客户端只需按连接回 {}(没有待回 ping 时 {} 是空命令 ⇒ 被判 bad request 断开)⇒ 仓库与 47 部署两侧已同步修正。🔴 遗留:GATEWAY_CAPABILITIES.providesServerHeartbeat 仍 false(与实测不符)⇒ 已排入第 16 棒。 ⚠️ 未完成项(卡点,⛔ 非"更慢更绕"):① 边缘未接进生效 vhost(边界明令本棒不切流;片段已落盘 + nginx -t rc=0、生效 vhost md5 897c26bf89165e55a7a22a8c46932972 未变 ⇒ 只剩"插入 ai1net.com.conf 443 块 + reload"一步)② 客户端侧未按网关协议接入(E 单 bundle 传输层仍按平台协议 ⇒ 切流的硬前置)③ 106 未部署(本棒只做 47)④ 容量未复测(未切流 ⇒ 复测条件未到)。 ⚠️ 一条异常(原因已查明):抢锁约 30 分钟后 交接单/.locks/ 被清空(本会话域锁消失、写文档被 hook 拦下)⇒ 已重抢同一组域并复核 OWNER(会话名/session_id 吻合)后继续。🔴 原因 = 另一条线的棒放锁跑了 --release-exec "<会话名>"(脚本不读位置参数、取 _who="${ME:-}",其 shell ME 未设)⇒ 命中 -z 分支 ⇒ 遍历 .locks/* 全删(已记当日日志 + 沉淀 PLAYBOOK §24.4)⇒ ⛔ 与本棒无关。✅ 正确姿势 = ME="<会话名>" bash scripts/handoff-guard.sh --release-exec + 放锁后必核 .locks/;本棒收口已按此执行(只释放自己的锁)。 ⛔ 未 commit/push/scp 源码(部署产物除外)|⛔ 未动 poc/im-conversation-tabs/** 一行|⛔ 未改 A–E 五单结论|⛔ 未引新依赖进 src/**(网关是外部进程)|⛔ 未动 106 用户实例面|⛔ 未取用 DB 凭据|✅ 清场:poc-curl2 临时会话 0、47 /tmp 残留 0、演练连接 0。 下一棒(唯一) = 第 16 棒 · 执行棒 = automation e14edc3d-0ab2-4a78-84be-c93752308bb9(一次性 · scheduledAt = 2026-09-23T21:43 · cwds = e:\ProgramData\AI技能\aliyun-dsh-server 小写盘符)= 换连接层的剩余代码面:① 能力口径修正(providesServerHeartbeat)② 平台侧接入面(令牌 / connect 代理二选一,执行棒自决)③ E 单客户端传输层双模化(含"按连接回 {}")④ 本机回环 E2E,动作见 §2「⏭️ 第 16 棒」。⚠️ 若用户先反馈了操作单 §三 的六条取证结果 ⇒ 优先按反馈处置。⚠️ 本棒不切流 / 不重打包 / 不重投放 / 无需部署(切流 + 重投放 + 目标档位复测留第 17 棒,届时再排)。 ⚠️ cwds 字面取证 —— 本棒一次差点写错的更正(⛔ 记下来别再犯):判据群体 = sessions.cwd(宿主就是按它分组)—— 小写 e:\ProgramData\AI技能\aliyun-dsh-server = 159 vs 大写 E:\ProgramData\AI技能\aliyun-dsh-server = 4 ⇒ 正解 = 小写。🔴 陷阱:若只量 automations.cwds 会得出反结论(大写 3 / 小写 1)—— 那只是"前面几棒写错的人多"(棒 12/13/15 用的正是那 4 条错字面里的),⛔ 不是判据。⇒ 第 16 棒 cwds 已按小写登记(本棒初登记时曾误用大写,已当场改正)。

  • ✅ 2026-09-23 23:1x|第 16 棒(执行棒)✅ 完成 —— 换连接层的剩余代码面全部闭合 —— 回填见选型单 交接单/IM群组-稳定性机制与框架选型.md 新建 §十六(⛔ §十五 既有小节一字未改)+ E 单 交接单/IM群组-E-会话界面UI.md 追加「§执行回报(IM 线第 16 棒)」(⛔ 未改既有正文)。 交付(三项代码 + 一份 E2E):① src/im/backends/gateway.ts 的 providesServerHeartbeat false⇒true(依据 = §15.11.3 三向实测)+ 连带新增结构性判据 fanoutOffsite && !needsKeepaliveShim ⇒ throw(否则原"构造即抛"因该字段改真而变成装饰)~ 口径依据 = 「会不会自己发 ping」与「平台能不能替远端 socket 代发」正交;② 平台侧接入面 选 (a) 发二层票据 + 新增订阅回检(POST /api/im/gateway/token、POST /api/im/gateway/subscribe、GET /api/im/transport、stats() 增 gatewayAccess)~ 被否 (b) connect 代理(理由:改动落部署物、回滚面更大、鉴权依赖边缘带 cookie ⇒ 与"边缘未接 vhost"耦合,且工作量不更少);③ poc/im-conversation-tabs/lib/client.js 传输层双模化(native 逐字不变、分发共用一个 onAppFrame;gateway 走网关协议:connect → subscribe → 空对象即回 {} → push.pub.data.frame 后照原路径分发;模式从平台只读面取,⛔ 未硬编码);④ 新增测试 test/im-gateway-access.test.mjs 15 用例(G-8/G-9 直接驱动真客户端件)。 判据读数:npm run build rc=0|新测 15 / 15|B 组 28 / 28|presence 组 13 / 13|npm test(Node 22)507 用例 / 505 过 / 0 败 / 2 跳过|check-layering rc=0(未归类 1 = 既有 src/platform-paths.ts)|本机回环 E2E(真 Centrifugo v6.9.6 + 真平台进程 + 真客户端件)16 / 16 PASS:upgrade=101/101/101 connect应答=3/3、订阅票据 alice=200 / bob=403 not-a-member、带票据订阅 {"id":2,"subscribe":{…}}、不带票据非成员 {"id":2,"error":{"code":403,"message":"not-a-member"}}(回检兜底生效)、一条消息 发送=200 delivered=1 双端收到且原文含标记、presence 进=true 出=true、保活 ping数=10 / alice.closed=false / carol.closed=false / num_clients=2(掉线 0,两侧同证)。 🔴 七条实测踩点已写进 §16.5(样例配置带中文注释不能直喂网关|回检密钥必须放 http.static_headers(http_headers 只是头名,实测两种错法)|ping_interval 必须 > pong_timeout|"回 {}"须常驻且只回一次|Windows path.join 反斜杠 ⇒ 路径转换静默失效 ⇒ 网关不报错退回默认配置|就绪轮询须接住 fetch 抛错|判据谓词"空真"陷阱)。 ⚠️ 本机载体受限(与产品无关):Windows 版网关二进制不可得(GitHub 0–19 KB/s、六镜像全败、106 亦 rc=28)⇒ 用 106 上已有 Linux 版(md5 17f814b5e048d1393508d4617b104a98,回传后逐字一致)+ 混合载体(网关跑 WSL 回环 18099、平台跑 Windows,因 better-sqlite3 是 Windows 版原生模块)⇒ 两条链路均已实测。 ⚠️ 未完成项(卡点,⛔ 非"更慢更绕"):① presence 回填生产侧缺调用方(Centrifugo OSS 无 join/leave 回调,如实记 §16.7-1)② 切流 / 106 部署 / 容量复测(按边界留第 17 棒)③ E 单线上可见面仍未验收 —— 卡在用户动作(实例「功能管理」启用现版 v0.1.0),⛔ 不能替用户点。 ⛔ 未切流|⛔ 未重打包|⛔ 未重投放|⛔ 未 commit/push/scp|⛔ 未引新依赖进 src/**|⛔ 未改 A–E 五单结论|⛔ 未动 §14.6|⛔ 未动 106 用户实例面|⛔ 未取用 DB 凭据|⛔ 网关密钥未打印/未进日志/未进文档|✅ 网关全程只绑回环 + 端口避开 47 的 18081|✅ 清场:本机与 WSL 网关进程 0。 下一棒(唯一) = 第 17 棒 · 执行棒 = automation 19904b63-431a-4f0a-85cc-c80fcb47221f(一次性 · scheduledAt = 2026-09-23T23:37 · cwds = e:\ProgramData\AI技能\aliyun-dsh-server 小写盘符)= 重打包 + 重投放 + 边缘接 vhost + 切流 + 目标档位复测(2000 / 10 / 30 s)+ 本棒新增面的部署同步(lib/** 产物 + 47 网关 config.json 回检块按 §16.5-2 修正形态落盘),动作见 §2「⏭️ 第 17 棒」。⚠️ 若用户先反馈了操作单 §三 六条取证结果 ⇒ 优先按反馈处置。

§1 目标(用户原话,⛔ 不得改写)

「im具体如何实 才能在复杂环境下稳定 还需要调研同类开源框架,比如telegram等海外通讯项目,找个最新性能稳定最好的」

拆成三条待答判据:

  • T1 · 稳定性:写出「复杂环境下稳定」的具体机制清单(⛔ 不是口号):每条机制要配可验证判据 + 在我们代码里的落点。
  • T2 · 框架调研:同类开源框架(含 Telegram 系海外项目)给出可比较的判据矩阵(⛔ 不是罗列产品)。
  • T3 · 选型结论:给出可落地的选型建议 —— 判据以 稳定性 · 性能 · 可运维性 为主位;⚠️ 资源不是约束(§0 口径更新:机器可换 ⇒ 资源只作为方案的「需求测算」输出,⛔ 不作否决项)。既有资产见 §3-7。

§2 🎯 本轮动作

第 1 棒(规划棒)✅ 已完成(2026-09-22 17:4x) —— 产出 D:\github\dsh_shenxian\dsh-server-docs\交接单\IM群组-稳定性机制与框架选型.md。以下为原要求(存档用):出一份可执行交接单 → 落 D:\github\dsh_shenxian\dsh-server-docs\交接单\(先原子占号;8 段模板 = 该目录 README.md §二),至少覆盖:

  1. T1 稳定性机制:连接层 / 消息层 / 存储层 / 跨区层 四组机制,各自「机制 → 判据 → 落点(file:line 或明确"待新建")」。
  2. T2 框架判据矩阵:候选 = ejabberd · OpenIM · Matrix(Dendrite) · Telegram 系(Opengram / Ferrite / teamgram)· Tinode · 轻量实时层(Centrifugo 类);判据 = 自托管可行性 / 资源占用 / 与 PG 共存 / 群规模 / E2EE / 许可与生态 / 与我们既有资产的契合度。
  3. T3 选型建议:三案(自研薄内核 / 整框架引入(ejabberd / OpenIM 整装接入)/ 混合 = 借机制 + 引组件)各写优点与缺点(竖排成段),按 稳定性 · 性能 · 运维复杂度 · 资源需求(测算值,机器可换) 四维比较,给建议与可验证的下一步实验。⚠️ 资源不作否决项(§0 口径更新)。

⛔ 只出单:不改码、不部署、不动 47/106、不替用户拍 §4 的项。

✅ 第 2 棒(执行棒)已完成(2026-09-22 18:2x) —— 实测结论与全部读数见该单 §十二 执行回报 + §十三 断言名清单(18/18)。以下为原要求(存档用):按交接单 D:\github\dsh_shenxian\dsh-server-docs\交接单\IM群组-稳定性机制与框架选型.md 执行其 §八 步 1–4:

  1. 逐条核实该单 §二 的 5 条只读前置(🔴 若 src/im/ 已存在、或 schema.ts 已 ≥v14 ⇒ 停手,改去 A 单)。
  2. 把该单 §四 机制清单(组 A–C 共 18 条)转成断言名清单草案(⛔ 不实现)。
  3. 跑实验 E-3:ejabberd 能否用既有 users 作外部认证源、且不落地自己的账号表 ⇒ 出二值结论 + 配置与日志证据。
  4. 跑实验 E-1:自研 WS 连接上限标定(1k / 5k / 10k 并发)⇒ RSS + 心跳超时率 + 消息 P99。

⛔ 本棒不做 E-2 / E-4(留给第 3 棒)|⛔ 不做 §八 步 5–8。 🔴 实验一律落 106(测试床);⛔ 47(生产 Manager)不动一行。 ⛔ 不改 src/** 功能实现、⛔ 不改 A–E 五单结论、⛔ 不部署、⛔ 不 commit/push/scp、⛔ 不替用户拍 §4 的未决项。 ⚠️ 回填 = 在该单新增「§十二 执行回报」(格式见该单 §十一)。

⏭️ 第 3 棒(执行棒续 · 本轮动作):继续按同一交接单执行 §八 步 5–8:

  1. E-2:同机同压测脚本跑 Centrifugo 类组件,给同维度读数(RSS / 心跳超时率 / 消息 P99)—— 压测脚本已在 106 /root/im-e1/(server.mjs / wsworker.mjs / run.mjs / sum.py),⛔ 不必重写。
  2. E-4:E2EE 形态对照(确认「中继只见密文 / 控制面只见密文与身份」在自研案下的最小实现路径:群密钥分发 + 成员变更换钥)。
  3. §八 步 6 二次判定:「连接层引入与否」,判定必须写陈述句并引用 E-1/E-2 的具体行;⛔ 不预设结论。
  4. §八 步 8 回填:把 §四 机制清单回填到 A–E 各单验收段(⛔ 不改各单结论)。
  5. 🔴 本棒新发现必须带进第 3 棒:ejabberd check_password 的进程内缓存(改表不立刻生效、重启即生效)⇒ 案 2 需追加「查该缓存开关/TTL」前置项;E-1 的 P99 ≈ 事件循环滞后 ⇒ 二次判定要把「扇出排队」作为自研侧的主项。

⚠️ 环境注意(第 2 棒实测,⛔ 别踩):47 的 sshd 在 22 端口(~/.ssh/config 的 bt-server 写 32022 已失效)|47 的 PG 是本机进程(非 docker exec dshs-pg)且只走 TCP 需口令|106 上 ejabberd 已装好但已 stop+disable:可直接复用的外部认证配置 = /opt/ejabberd/conf/ejabberd.yml(现为 auth_method: [external] + extauth_program: /usr/local/bin/im-e3-extauth.py + default_db: mnesia + loglevel: debug);ejabberd.yml.im-e3.bak 是改前原件(默认 internal auth),⛔ 别当样例用。⛔ 不必重下(GitHub 直连约 1 MB/min)。

⏭️ 再往后:第 4 棒收口后与 IM 五单 A–E 合流(串行建议 A → B →(C ∥ D)→ E)。

✅ 第 3 棒(执行棒续)已完成(2026-09-22 19:0x) —— 动作 = 本单 §八 步 5–8:E-2(Centrifugo v6.9.6 同机同脚本对照)+ 二次判定(连接层引入与否)+ E-4(E2EE 最小路径)+ §四 机制清单回填 A–E 五单验收段。全部读数与判据见 交接单/IM群组-稳定性机制与框架选型.md §十二 执行回报(IM线-第3棒)(含:E-2 对照表、失败口径与实测教训、二次判定陈述句、E-4 的 8 条可断言路径、五单回填行号表)。

⏭️ 第 4 棒(执行棒 · 本轮动作):本单内闭环收口两件,⛔ 不碰 A–E 结论、⛔ 不开工 A 单:

  1. E-5 = 自研扇出分片对照实验(主任务) —— 把第 3 棒写下的可证伪回退判据判定掉(回退判据 = 自研完成扇出改造后 10k 档客户端观测 P99 > 150 ms 或扇出离散中位 > 120 ms ⇒ 引入 Centrifugo 类组件)。做法:在 106 /root/im-e2/ 新建 server-shard.mjs(蓝本 = /root/im-e1/server.mjs,/metrics 字段与路径 /api/im/ws 保持一致),改造 = (a) 分片扇出(连接按编号取模分 S 片,每片 setImmediate 分时,不把整圈放同一 tick)+ (b) 同片批写(拼 Buffer 一次 write 或 cork/uncork);跑 1k/5k/10k(e2run.mjs --target=raw --server=/root/im-e2/server-shard.mjs,其余参数与第 3 棒完全一致),与第 3 棒 E-1 行逐格对照。🟢 只做正向迭代(任一档变差 ⇒ 停下复盘并如实报告)。🔴 若回退判据被触发 ⇒ ⛔ 不擅自引入组件,写清三条候选(就地继续优化 / 引入外部连接层 / 混合)各带「优点 / 缺点」竖排成段,交用户拍板。
  2. 补跑 §九 判据 6 / 7(平台回归,第 3 棒欠账) —— 本机 D:\github\dsh_shenxian 跑 npm test(Node 22,只判「0 败」);判据 7 = 既有「用户 → 自己实例」子域 upgrade 仍 101(测试集内无该用例 ⇒ 如实说明,⛔ 不自造)。

⚠️ 环境注意(第 3 棒实测,⛔ 别踩):106 用 ssh test106(别名已配;ssh [email protected] 会因 IdentitiesOnly 失败)|106 上必带 ulimit -n 65536(默认 1024)|E-2 资产在 /root/im-e2/(e2worker.mjs / e2pub.mjs / e2run.mjs / e2sum.py / config.json / centrifugo v6.9.6 二进制)|Centrifugo v6 配置键已改位(client.token.hmac_secret_key、http_api.key、端口用 --http_server.port)|47 的 sshd 在 22 端口|🔴 实验一律落 106,⛔ 47 不动一行。⚠️ 登记门禁须带 ME="<线名>-第N棒",否则会因「锁被自己占用」误报不可放行。

✅ 第 4 棒(执行棒)已完成(2026-09-22 19:3x) —— 动作 = E-5(自研扇出分片对照实验) + 补跑 §九 判据 6/7。全部读数、归因表与判定见 交接单/IM群组-稳定性机制与框架选型.md §十二 执行回报(IM线-第4棒)(含:改造点与协议对齐表、三档六跑同会话对照表、10k 六变体归因表、回退判据判定、判据 6/7 明细)。

✅ 第 5 棒(执行棒)已完成(2026-09-22 19:4x) —— 动作 = 开工 A 单(IM 五单合流第一单)。全部读数、DDL 摘要、逐条判据落位与两处口径说明见 交接单/IM群组-A-房间内核与DB.md §十 执行回报(IM线-第5棒)。以下为原要求(存档用):开工 A 单 —— 交接单/IM群组-A-房间内核与DB.md(IM 五单合流第一棒):

  1. 先逐条核实 A 单 §二 的 5 条只读前置(⚠️ 前置 1 特例:v14 若已被占 ⇒ 取下一个空号继续,⛔ 不停手)。
  2. 按 A 单 §五 步骤 1–7 执行(严格按 §三「改什么 / 不动什么」:只动 src/db/schema.ts 追加 v14、新建 src/im/**、新建 test/im-store.test.mjs 并登记进 package.json 清单)。
  3. 按 A 单 §六(含 §六.1 追加判据 = 选型单 §四 组 B/C/D 共 16 条)验收。
  4. 按 A 单 §八 格式在其尾部追加执行回报。

✅ 第 6 棒(执行棒)已完成(2026-09-22 20:2x) —— 动作 = 开工 B 单(IM 五单合流第二单)。全部读数、逐条判据表、标定数值与口径说明见 交接单/IM群组-B-平台API与用户端IM-WS通道.md §九 执行回报(IM线第6棒)**。以下为**原要求(存档用)**:**开工 B 单 —— 交接单/IM群组-B-平台API与用户端IM-WS通道.md`(A → B 串行,A 已收口):

  1. 先读 B 单 §〇 架构对齐 + §二 只读前置,逐条核实(🔴 B 单依赖 A —— src/im/** 与 v14 三表已在第 5 棒落地,前置里若仍写「A 未做」按现状更新判断,⛔ 不改 B 单结论)。
  2. 按 B 单 §五 步骤执行,改动面严格按 B 单 §三:src/im/{hub,ws}.ts + src/web/routes/im.ts + src/web/server.ts(挂载);服务器 / nginx 仅新增 /api/im/ws location。
  3. ⚠️ B 单 §五-5 含房间上限与发言预算的标定(A 单只落了待标定初值 2000 / 10 / 30 s)⇒ 本棒需给标定命令与读数。
  4. 🔴 硬回归判据 = 既有子域隧道不受影响(proxy.ts 现有 upgrade 回调按路径分流,⛔ 不改子域隧道语义);跑 npm test 只判「0 败」。
  5. 按 B 单 §八 格式在其尾部追加执行回报。

⚠️ 环境注意(第 5 棒实测,⛔ 别踩):47 的 sshd 在 22 端口|47 的 PG 是本机进程(127.0.0.1:15432)非容器 ⇒ 「docker exec dshs-pg psql」写法全失败;su postgres 无 unix socket、需 TCP+口令(⛔ 不取用凭据)|🔴 npm run verify 必红且与本线无关 —— 卡在既有脚本 scripts/verify-platform-admin-section.mjs(require() + 顶层 await);判定看 npm test 的「0 败」,或其链路其后 6 步逐条补跑 | npm test 用 Node 22(E:/ProgramData/.workbuddy/binaries/node/versions/22.22.2-3)|⚠️ npm test 前 export PATH=<上面的 node 目录>:$PATH。 ⚠️ 上游已固化事实(第 5 棒实测,⛔ 别重查):v14 已被 A 单占用(src/im/** 已存在)|A 单 §五-1 / §六-1 里的「最新 = 12」是旧值残留,真值 = 14|内核的 id 含作者(sha256(authorId ‖ canonical(payload)),见 A 单 §十.7-2)。

✅ 第 7 棒(执行棒)已完成(2026-09-22 20:2x) —— 动作 = 开工 C 单(IM 五单合流第三单)。全部读数、逐条判据表、判据 7–10 明细与 5 条口径说明见 交接单/IM群组-C-Agent接入插件.md §九 执行回报(IM线第7棒)。以下为原要求(存档用):开工 C 单 —— 交接单/IM群组-C-Agent接入插件.md(B 已收口,A → B → C 串行):

  1. 先读 C 单 §〇 架构对齐 + §二 只读前置,逐条核实(🔴 C 单依赖 A + B —— src/im/**、v14 三表、src/web/routes/im.ts 与 /api/im/ws 均已落地;前置里若仍写「B 未做」按现状更新判断,⛔ 不改 C 单结论)。
  2. 按 C 单 §五 步骤执行,改动面严格按 C 单 §三。
  3. 🔴 x-dsh-im-instance-token 的 501 预留点就是 C 单要接的地方 —— 两处:src/web/routes/im.ts 的 imAuth(会话不成且带该头 ⇒ 501)+ src/im/ws.ts 的 doUpgrade(握手前)。C 单须把它从 fail-closed 换成真校验,并按 §〇-4 绑定 instanceId → userId + 所在区(跨区本期拒绝)。
  4. 判据须给「命令 + 期望输出 + 退出码」;跑 npm test(Node 22)只判 0 败。
  5. 按 C 单 §八 格式在其尾部追加执行回报。

⚠️ 环境注意(第 6 棒实测,⛔ 别踩):本机自写脚本 / smoke:* 的 Node 进程可能不自退(伪实例子进程持句柄)⇒ 脚本显式 process.exit(),或外层 timeout 兜底并把「是否自退」如实写进回报|⚠️ fetch() 带自定义 host 头不生效 ⇒ 测子域 / 多 Host 必须用 http.request(首版拿到假 404)|⚠️ 假时钟(hub 的可注入 now)每轮必须重置,时钟倒退会让 1 s 批次闸门整窗失效(首版标定读数假 0)|47 的 sshd 在 22 端口|npm run verify 必红且与本线无关(判定看 npm test 的 0 败)。 ⚠️ 上游已固化事实(第 6 棒实测,⛔ 别重查):平台 app.server 上的 upgrade 回调只有 1 处(proxy.ts 现 759 行一带),IM 分流走它的委托钩子 setUpgradeDispatch() ⇒ ⛔ 别再加第二个 app.server.on('upgrade')(Node 会把同一事件并发投给两个监听器 ⇒ IM 路径被子域判定判失败并静默 destroy)|/api/im/ 下的非 WS upgrade fail-closed 404(⛔ 不落到隧道)|GET /api/im/stats 限 admin|hello 的 cursor 初值为 {},权威游标在 subscribed / resume 帧|DSH_IM_WS=0 可整体下线、DSH_IM_HB_MS 可改心跳周期(测试/标定用)。

⏭️ 第 8 棒(执行棒 · 下一轮动作):开工 D 单 —— 交接单/IM群组-D-插件SDK与扩展点契约.md(A → B → C → D 串行,C 已收口):

  1. 先读 D 单 §〇 架构对齐 + §二 只读前置,逐条核实(🔴 D 单依赖 A + B + C —— src/im/** 六个模块、v14 三表、/api/im/** 与 /api/im/ws、实例凭据登记簿与 poc/im-agent-bridge/ 均已落地;前置里若仍写「C 未做」按现状更新判断,⛔ 不改 D 单结论)。
  2. 按 D 单 §五 步骤执行,改动面严格按 D 单 §三。
  3. 🔴 C 单留了两处接口给 D 单接:① 本地 LLM 会话对接 = poc/im-agent-bridge/lib/index.js 的 opts.askLocalSession(现为占位 defaultAsk)② 代答标识的 UI 契约 = src/im/agent-bridge.ts 的 replyMarkerOf / replyPayloadOf(回带 agentReply / agentLabel)。⛔ 别把这两处当缺陷重做。
  4. 判据须给「命令 + 期望输出 + 退出码」;跑 npm test(Node 22)只判 0 败(本棒基线 300)。
  5. 按 D 单 §八 格式在其尾部追加执行回报。

⚠️ 环境注意(第 7 棒实测,⛔ 别踩):往 md 文件追加长文本别用 bash heredoc(单引号会炸,本轮踩过)⇒ 改成 Write 一个 .py 再跑|⚠️ 插件包 lib/index.js 是纯 JS,⛔ 不许带 TS 类型标注(import { connect, type Socket } 会让 ESM 直接 SyntaxError,本轮踩过)|本机自写脚本 / smoke:* 的 Node 进程可能不自退 ⇒ 脚本显式 process.exit() 或外层 timeout 兜底|fetch() 带自定义 host 头不生效 ⇒ 测子域 / 多 Host 必须用 http.request|47 的 sshd 在 22 端口|npm run verify 必红且与本线无关(判定看 npm test 的 0 败)。 ⚠️ 上游已固化事实(第 7 棒实测,⛔ 别重查):实例凭据 = 内存登记簿,由 web/server.ts 建唯一一份 app.imInstanceTokens 并同时交给两个 spawner(LocalSpawner 与 RemoteSpawner)⇒ 发放侧与校验侧同簿;⛔ 别在别处 new ImInstanceTokenRegistry()(那样发的 token 永远认不出)|DSH_PLATFORM_IM_URL / DSH_IM_INSTANCE_TOKEN 任一缺失 ⇒ 插件不启动(天然开关,也是唯一回滚动作)|doUpgrade 的实例凭据走注入钩子 resolveInstance(im/ws.ts ⛔ 不 import web 层,分层方向要保住)。

✅ 第 8 棒(执行棒)已完成(2026-09-22 21:1x) —— 动作 = 开工 D 单(IM 五单合流第四单)。全部读数、七个扩展点逐点表、跨场景证据、三组失败注入原文、5 条未完成项与 5 条口径说明见 交接单/IM群组-D-插件SDK与扩展点契约.md §十 执行回报(IM线-第8棒)。以下为原要求(存档用):开工 D 单 —— 交接单/IM群组-D-插件SDK与扩展点契约.md(A → B → C → D 串行,C 已收口):

  1. 先读 D 单 §〇 架构对齐 + §二 只读前置,逐条核实(🔴 D 单依赖 A + B + C —— src/im/** 六个模块、v14 三表、/api/im/** 与 /api/im/ws、实例凭据登记簿与 poc/im-agent-bridge/ 均已落地;前置里若仍写「C 未做」按现状更新判断,⛔ 不改 D 单结论)。
  2. 按 D 单 §五 步骤执行,改动面严格按 D 单 §三。
  3. 🔴 C 单留了两处接口给 D 单接:① 本地 LLM 会话对接 = poc/im-agent-bridge/lib/index.js 的 opts.askLocalSession(现为占位 defaultAsk)② 代答标识的 UI 契约 = src/im/agent-bridge.ts 的 replyMarkerOf / replyPayloadOf(回带 agentReply / agentLabel)。⛔ 别把这两处当缺陷重做。
  4. 判据须给「命令 + 期望输出 + 退出码」;跑 npm test(Node 22)只判 0 败(本棒基线 300)。
  5. 按 D 单 §八 格式在其尾部追加执行回报。

⚠️ 环境注意(第 7 棒实测,⛔ 别踩):往 md 文件追加长文本别用 bash heredoc(单引号会炸,本轮踩过)⇒ 改成 Write 一个 .py 再跑|⚠️ 插件包 lib/index.js 是纯 JS,⛔ 不许带 TS 类型标注(import { connect, type Socket } 会让 ESM 直接 SyntaxError,本轮踩过)|本机自写脚本 / smoke:* 的 Node 进程可能不自退 ⇒ 脚本显式 process.exit() 或外层 timeout 兜底|fetch() 带自定义 host 头不生效 ⇒ 测子域 / 多 Host 必须用 http.request|47 的 sshd 在 22 端口|npm run verify 必红且与本线无关(判定看 npm test 的 0 败)。

⚠️ 环境注意(第 8 棒实测,⛔ 别踩):🔴 本机 src/im/** 与 web/routes/im.ts 等服务面文件在 A–D 四单落地后仍未 commit ⇒ 它们对 git status --short 显示为整块 ??(未跟踪,不是改动);★ 「内核零改动」必须用内容级判据(git diff 对每个模块单独为空 + git status --short src/im 里无 M 标记),⛔ 不能用「status --short 无输出」(那样会假红,首版踩过)|🔴 文档库的任何 Edit/Write 都要求先持有全局执行锁(钩子会拦;追加执行回报时若锁已释放会报「无锁 ⇒ 不允许直接修改」)⇒ 追加类操作的顺序 = 抢锁 → 写文档 → 放锁 → 过门禁 → 再抢锁写入口/登记|🔴 写库测试的桩必须做「表全名归一」(p_<pluginId>_<name>,见 SDK dataTableFullName),否则「裸名 vs 全名」会被当成两张表导致判据 11/13 假绿(首版踩过)。 ⚠️ 上游已固化事实(第 8 棒实测,⛔ 别重查):SDK 契约层 = src/im/sdk/{types,host,index}.ts(ImSdkHost 是七个扩展点的唯一注册/调用入口)|ImDataPort / ImBudgetPort 是注入式端口(真库端口与建库面属「插件投放与分库线」)|超时/熔断/抛错的降级方向 = 放行(⛔ 不是拒绝发言,否则插件挂了房间就哑了)|限速只约束 agent(与 A 单 decideWrite 同向,人类成员不受限)|requiresReview 默认拒绝(未审核的新 room_type 不落进可建房列表)|同名 room_type 冲突 ⇒ 后注册者被拒并进 rejected(⛔ 不静默覆盖)。

✅ 第 9 棒(执行棒)已完成(2026-09-23 06:3x) —— 动作 = 开工 E 单(IM 五单合流第五单,本线最后一单)。全部读数、步 0 选型理由、步骤 0–11 逐条落位、判据 1–13 取证、生效链路四关与未完成项见 交接单/IM群组-E-会话界面UI.md §执行回报(IM 线第 9 棒)。以下为原要求(存档用):⏭️ 第 9 棒(执行棒)= 开工 E 单(交接单/IM群组-E-会话界面UI.md)。🔴 落点已于 2026-09-23 拍板(§0 末行),⛔ 不再征询:

  1. 先读 E 单 §〇 架构对齐 + §二 只读前置 + §四-4/§四-5(拍板),逐条核实(E 单依赖 A + B + C + D 已收口;src/im/**、v14 三表、/api/im/** 与 /api/im/ws、src/im/sdk/** 均已落地)。
  2. 按 E 单 §五 步骤执行(含新增的步 0 / 步 2 / 步 3 / 步 8),改动面严格按 E 单 §三。🔴 落点 = C′:实例内接管会话区 [data-slot="main.conversation"],顶部 tab 行;保活 = display:none;群组 tab 上限 5;新建群组入口在业务插件内。
  3. 🔴 步 0 是前置(不补则步 8 的「加入群聊」按钮无接口可调):新增「可发现房间列表 + 自助加入」;标志位在 「rooms 显式列 + v15 只增迁移」 与 「config_json」 间二选一,选型自己定并写明理由(我倾向显式列:属授权语义,按 R7 口径不宜埋 config)。
  4. 🔴 验收面:E 单 §六 判据 1–13(8 tab 形态 / 9 保活 / 10 上限 / 11 槽位失效可见 / 12 在线态 / 13 发现+加入)。
  5. 判据须给「命令 + 期望输出 + 退出码」;跑 npm test(Node 22)只判 0 败(基线 366/364 过 2 跳过)。⚠️ client bundle 改完必须重启实例(bundle 在实例启动时加载)。
  6. 按 E 单 §八 格式在其尾部追加执行回报。

⚠️ 环境注意(第 9 棒新增,⛔ 别踩):🔴 接管会话区必须多选择器兜底([data-slot="main.conversation"] → [data-slot="conversation"])+ 失效时给可见提示;官方已改过一次槽名(0.1.5-rc.1),单一选择器 ⇒ 静默失效(MCN 被咬过:点了没反应)|🔴 flex 布局用 CSS 规则 + !important 驱动,⛔ 不用 inline style(React 重渲染会重置 inline,MCN 的注释已写明)|🔴 组件卸载必须反向还原(conversation 插回原位 + 恢复 previousCss + 移除 wrapper),否则切走再回来 DOM 结构会坏|⚠️ 官方 slots 注册面很窄(实测只用到 sidebar.footer.action / settings.section)⇒ 会话区靠 DOM 接管,⛔ 不是 slots.register。 ⚠️ 上游已固化事实(第 9 棒可用,⛔ 别重查):IM 数据走同源相对路径 /api/im/**(实例内带 sid cookie ⇒ imAuth 认用户本人;⛔ 不硬编码主机 / 区域)|在线态数据源已内建(hub.ts:PresenceState='online'|'offline'、presence 按 1 s 批合并、只推订阅该房的连接)⇒ ⛔ 不新增数据源|容量上限 ROOM_LIMITS.maxMembers = 2000|store.listRooms({ownerId?,roomType?}) 已有筛选签名(扩参数即可)。

🏁 本线状态(2026-09-23 第 10 棒收口后):A–E 五单全部落地;§4 三项待拍板已于 09-23 全部拍板**(详见下文);下一步 = 动工连接层 B 案。**

✅ 三项拍板(2026-09-23,⛔ 不再征询) —— 全文见选型单 §十四 用户拍板落定:

  1. E2EE 形态档位 = 强度档(性能判据:密码学 ≈ 55 µs/条 ⇒ 占 1 s 事件循环预算 0.006%、50 msg/s 下占单核 ≈ 0.3% ⇒ CPU 维度可用)。 🔴 三处结构性代价(⛔ 不受"影响较小"覆盖):① 棘轮状态每设备持久化(丢了永久断链)② 群聊必须 Megolm 化(误用严格双棘轮 ⇒ 扇出 O(N)→O(N²))③ agent 代答重做(im_agent_grants 不成立 ⇒ D 单 §四-2 重写、E4-6 断言改写)。9 条连带重写清单见 §十四-14.3。
  2. 房间上限与预算 = 按建议固化:maxMembers 2000 / agentQuotaPer10Min 10 / silencePeriodMs 30 s 已转正式值(选型单 §十四-14.4 + A 单状态块)。🔴 适用前提 = 单进程自研扇出形态下标的 ⇒ 改形态须复测。
  3. 连接层 = 候选 B(引入 Centrifugo 类外部连接层)(选型单 §十四-14.5)。

✅ 第 12 棒(执行棒)已完成(2026-09-23 19:3x)= 动工连接层 B 案 —— 交付、判据读数、E-7/E-6 实验、容量表、P0 发现见选型单 §十五 B 案实现回报(IM 线 · 第 12 棒)。以下为原要求(存档用):动工连接层 B 案 —— 用户拍板原话「先做b」(优先级已定,⛔ 不再征询)。 ⚠️ 编号更正(2026-09-23 19:0x):本段原写「第 11 棒」,与 §0 中已完成的第 11 棒(①档位重写 + 收口)撞号 ⇒ 下一棒一律称 第 12 棒(会话名 IM线-第12棒)。

🔴 落点已经现成(本棒实查,⛔ 别从零造接口):src/im/ws.ts 已有可替换接缝 ——

  • createImWsRuntime(options: ImWsRuntimeOptions): ImWsRuntime(:209,工厂)
  • export interface ImWsRuntime(:190)+ ImWsRuntimeOptions(:163)
  • export type ImUpgradeDispatch(:188)
  • 既有槽位:resolveInstance?(:179,实例凭据注入钩子)、options.resolveInstance?.(req.headers)(:387) ⇒ B 案 = 在这个接缝上加一个 Centrifugo 后端实现,⛔ 不改内核语义、⛔ 不把 Centrifugo 概念泄漏进 src/im/**(连命名都要中性化,例如 ImConnectionBackend)。

B 案动工范围(建议顺序,可推翻):

  1. 先落"可替换连接层"抽象 —— 把现有自研 WS 实现归到一个后端(native),再加 centrifugo 后端;两后端同一接口、同一协议语义(/api/im/ws 路径不变、帧格式不变、游标语义不变)。
  2. 写 Centrifugo 适配器(🔴 含实测必做项,见下)+ 配置样例(v6 配置键已改位:client.token.hmac_secret_key / http_api.key / 端口用 --http_server.port)。
  3. 对账第 3 棒 E-2 实测(复跑同脚本同参数,判据见下)。
  4. 出容量测算表(按「机器可以换」口径:边际 70.1 KiB/连接 vs 自研 5.9 KiB ⇒ 算"要多少机器与内存",⛔ 不以此否决方案)。
  5. 顺带复测两个固化数值(2000 / 10 / 30 s 的适用前提是"单进程自研扇出" ⇒ 换形态后必须复测;⚠️ 内存类判据须重复跑 ≥3 次取中位)。

🔴 三条硬实现项(⛔ 缺一即不可上线):

  1. 必须补发自建心跳 —— 实测 Centrifugo 不发服务端 WS ping(wsPingRecv=0);客户端也不发 ping ⇒ 26 s 内 10000/10000 全被断开。 ⚠️ 且「保活计时起点必须绑该连接自己的认证完成时刻」—— 用整进程统一计时会一次掉 2773 条(这是适配器缺陷,判别实验已证)。
  2. 按可替换接口落(见上,接缝已现成)。
  3. 出容量测算表(见上第 4 点)。

验收判据(⛔ 对账式,不是重新发明):

判据 期望
npm run build rc=0
node --test test/im-store.test.mjs test/im-agent.test.mjs test/im-sdk.test.mjs test/im-ui.test.mjs 0 败(本棒基线:im-ui 29 / 全量 npm test 420-418 过 2 跳过)
npm test(Node 22) 0 败(基线 420 / 418 过 / 2 跳过)
check-layering rc=0 ✅ 无新增违规
10k P99(客户端观测) 对账 E-2 的 28.9 / 68.1 / 111.9 ms(自研 E-1/E-5 = 268.6 / 337.6 / 359.4 ms)
扇出离散中位 对账 E-2 的 17.2 / 57.9 / 97.8 ms(自研 = 171.8 / 227.6 / 225.5 ms)
心跳超时率 0(含"补发自建心跳"后的验收)
⛔ 语义不变 /api/im/ws 路径、帧格式、游标语义、DSH_IM_WS 开关全部不变

⚠️ 环境注意(第 3 棒实测,⛔ 别踩):106 用 ssh test106(别名已配;ssh [email protected] 会因 IdentitiesOnly 失败)|106 上必带 ulimit -n 65536(默认 1024)|E-2 资产在 106 /root/im-e2/(e2worker.mjs / e2pub.mjs / e2run.mjs / e2sum.py / config.json / centrifugo v6.9.6 二进制)|E-1 资产在 /root/im-e1/(server.mjs / wsworker.mjs / run.mjs / sum.py)|47 的 sshd 在 22 端口|🔴 实验一律落 106,⛔ 47(生产 Manager)不动一行。 ⚠️ 本机执行环境:每条 bash 命令前置 export PATH="/e/ProgramData/.workbuddy/binaries/PortableGit/versions/1.2.0/usr/bin:/e/ProgramData/.workbuddy/binaries/PortableGit/versions/1.2.0/bin:$PATH"|npm test 用 Node 22(E:/ProgramData/.workbuddy/binaries/node/versions/22.22.2-3)|npm run verify 必红且与本线无关(判定看 npm test 的 0 败)|往 md 追加长文本⛔ 别用 bash heredoc ⇒ 用 Write 写 .py 再跑|文档库任何 Edit/Write 都要求先持全局执行锁。

⛔ 边界:⛔ 不改 A–E 五单既有结论|⛔ 不替用户拍未决项(见 §4-5)|⛔ 不引新依赖到 src/im/** 内核(Centrifugo 只作为外部进程,不进 package.json 运行时依赖)|⛔ 不部署到 47/106 生产面|⛔ 不 commit/push/scp|⛔ 不抢跑 E2EE 强度档重写(那是另一棒,且档位重写必须先于任何 E2EE 代码动工 —— 选型单 §十四-14.3 的 9 条清单)。

✅ 2026-09-23 19:0x 已解 —— 库由 app 自动重建(18:55),第 12 棒已补登 = automation 89e11630-4a06-41ca-bfc7-3c66ff4d8a53(scheduledAt = 2026-09-23T19:10)⇒ 以下为历史记录(存档用,⛔ 不必再按"受阻"处理)。

🔴 ⚠️ 2026-09-23 18:2x 登记受阻(当时唯一未完成项):第 12 棒(动工 B 案,当时写作「第 11 棒」)未能登记 automation 接续棒 —— automation 子系统双向失败。

  • 症状:建 ⇒ database disk image is malformed;查 ⇒ file is not a database。
  • 根因(只读诊断,证据在手):活动库 = E:\ProgramData\.workbuddy\workbuddy.db(CODEBUDDY_CONFIG_DIR 实测指向此目录;35,377,152 B)。主库文件头 16 字节非 SQLite format 3(乱码);-wal 4,346,632 B;🔴 ?mode=ro&immutable=1 也读不出 ⇒ 不是单纯的 WAL 错位,是主库头部级损坏。主库 mtime 09-23 18:26、-wal 18:28(app 仍在写它)。
  • 无任何备份(目录下只有三件套,无 .bak)。
  • ⚠️ 旁证:C:\Users\Administrator\.workbuddy\workbuddy.db(09-12 07:02,192,512 B)magic 正常、30 个对象可读 ⇒ 那是旧库(非活动),⛔ 不可当备份用。
  • 🔴 处置:未擅自修 —— 修法(删 -wal/-shm 或恢复备份)属不可逆破坏性操作,且需停 app(会中断当前会话与其他会话)⇒ 属红线门禁,已报告用户等拍板(见本轮回复)。

✅ 手工接力方式(⛔ 现已不需要 —— 19:0x 已补登 automation,本段仅存档):入口 §2「第 12 棒」段已自包含(动工范围 + 三条硬实现项 + 验收判据对账表 + 环境注意 + 边界)⇒ 万一 automation 再次不可用,用户手工开一个新会话,说:

按 E:\ProgramData\AI技能\aliyun-dsh-server\接续入口_IM线_20260922.md 的 **§0 最新行 + §2「第 12 棒」**段执行(会话名 IM线-第12棒);开工第 0 步跑 state.py,第 1 步抢全局执行锁。

✅ 回头必做 —— 已于 19:0x 完成:库由 app 自愈后已立即补登第 12 棒接续棒(89e11630-4a06-41ca-bfc7-3c66ff4d8a53),「补登完成」已写进 §0。 🔴 连带待办(本线 ⛔ 别忘):其余各线的接续棒全部丢失(automation 记录随重建清空)⇒ 各线开工前须先按自家入口 §2 重登。

收尾五件(缺一算未完成):① 结果回填(选型单 §十四 追加"B 案实现回报"小节,或新建档案)② 释放锁 ③ 过登记门禁(ME="IM线-第12棒")后登记下一棒 ④ 入口 §0 追加状态行、§2 推进 ⑤ 追加 .workbuddy/memory/YYYY-MM-DD.md。

⏭️ 第 13 棒(执行棒 · 下一轮动作)= 修 §15.7 的 P0:E 单客户端协议对齐 + 保活垫片接进客户端传输层

🔴 为什么排它(属客观可判,⛔ 不是业务优先级):它阻塞「已交付功能」——E 单的客户端 bundle 一旦投放,首帧即被服务端判 bad-op 断开、平台推的帧无分支接住 ⇒ IM 的线上可见面在修掉它之前不可能通过(也阻塞硬实现项 ① 的客户端那一半)。② gateway 部署+复测 与 ③ 在线态接线排在它之后。

  1. 只读前置:逐条列出两侧的现状行号 —— 客户端 poc/im-conversation-tabs/lib/client.js 的出站帧与 msg.type 分支;平台 src/im/ws.ts 的 op 表(:264-303)与推帧集合(:265/277/284/299/336/487/561)。⚠️ 先确认第 12 棒之后两侧未变。
  2. 动工范围(严格):poc/im-conversation-tabs/lib/client.js(主件)|如确需配套,poc/im-conversation-tabs/lib/index.js、package.json|test/im-ui.test.mjs(只改被这次帧名变更直接打破的断言,逐条在回报里写清"改了哪条、为什么")+ 新建/扩一个协议一致性用例。 ⛔ 不许改 src/im/** 的服务端协议("服务端兼容 type 别名" = 永久维护两套协议,本线已定不做)。
  3. 具体修改点(建议,可推翻):
    • 订阅:{type:'subscribe', roomId, since} ⇒ {op:'subscribe', roomId}(⚠️ 平台的 subscribe 不读 since);游标补齐走独立路径 {op:'resume', roomId, since},since 取 subscribed 帧回带的 cursor(重连时才发)。
    • 保活:{type:'ping'} ⇒ {op:'pong'}(⛔ 不要发明新 op;平台对任意客户端帧都刷 lastSeenAt,且 pong 是已认 op)。计时起点 = 该连接自己的 open 时刻(⛔ 不是模块加载/进程启动时刻)—— 规则与判据见 src/im/keepalive.ts(浏览器 bundle 是纯 JS,⛔ 不能 import TS ⇒ 照同一口径在 bundle 内实现)。
    • 发送:若 UI 有输入框,发送必须走 {op:'send', roomId, payload, visibility?}(当前 bundle 没有发送路径 ⇒ 若 UI 无入口则如实记为"本期无发送面",⛔ 不硬造 UI)。
    • 收帧:msg.type ⇒ msg.op;message 的形状是 {op:'message', roomId, messages:[…]}(数组);presence 是 {op:'presence', roomId, at, members:[{memberId,state,at}]}(数组);hello / subscribed / unsubscribed / messages / sent / error 都要有分支 —— error ⛔ 不得静默丢弃(要能让用户看见)。
    • 重连:保持现有指数退避(2^n 封顶 15 s + 抖动 ✅),但必须保证每次 open 重置 ping 计时器且起点是本次 open。
  4. 判据(命令 + 期望 + 退出码):
    • npm run build rc=0 | node --check poc/im-conversation-tabs/lib/client.js rc=0
    • node --test test/im-connection-backend.test.mjs = 28 / 28 过(⛔ 不得回退)
    • node --test test/im-ui.test.mjs 0 败 | npm test(Node 22)0 败 | check-layering rc=0 ✅ 无新增违规
    • 🔴 协议一致性判据(本轮新增,必须以"两侧源码文本互相对照"的方式做机械断言,⛔ 不是"看了一遍"):① 客户端出站帧的字面量集合 ⊆ 平台认的 op 集合;② 客户端不存在 type: 形式的出站帧;③ 客户端收帧分支覆盖 hello|subscribed|unsubscribed|message|messages|presence|sent|error;④ ping 间隔与"每连接 open 时刻起计时"两条都在。
  5. 环境注意(⛔ 别踩):本机 bash 每条命令前置 export PATH="/e/ProgramData/.workbuddy/binaries/PortableGit/versions/1.2.0/usr/bin:/e/ProgramData/.workbuddy/binaries/PortableGit/versions/1.2.0/bin:$PATH"|npm test 用 Node 22(E:/ProgramData/.workbuddy/binaries/node/versions/22.22.2-3)|npm run verify 必红且与本线无关(判定看 npm test 的 0 败)|⛔ 插件包 lib/*.js 是纯 JS,不许带 TS 类型标注(第 7 棒踩过)|⛔ 往 md 追加长文本别用 bash heredoc(改用 Write 写 .py 再跑)|文档库任何 Edit/Write 需先持全局执行锁。 🔴 抢锁域必须含 poc/im-conversation-tabs(第 12 棒只声明了 poc/im-connection-gateway ⇒ 本轮实查该域当时未在锁内,⛔ 别照抄上一棒的域清单)。
  6. 边界:⛔ 不改 src/im/** 的服务端协议|⛔ 不改 A–E 五单既有结论|⛔ 不部署 / ⛔ 不投放(投放属投放线)|⛔ 不 commit/push/scp|⛔ 不引入新依赖|⛔ 不动 47/106|⛔ 不碰 §14.6 未决项。
  7. 收尾五件同本入口惯例。回填 = 交接单/IM群组-E-会话界面UI.md 追加「§执行回报(IM 线第 13 棒)」小节 + 在选型单 §十五-15.7 处加 🔻 处置标注(⛔ 原文不改)。 ✅ 连带待办已办一半(2026-09-23 19:4x,第 12 棒收口):已在投放线入口 §5「本线后续」新增第 5 条(写明 P0 是它的硬阻塞、投放前须先等本棒修完)+ 改正该入口「automation 不可用」的过时前提(库 18:55 已由 app 重建)。 ⛔ 仍未认领的部分:im-conversation-tabs 的投放本身没有归属 ⇒ 本棒至少要推进一步:本线自行排一棒,或确认投放线已认领(⛔ 不推进 ⇒ 又回到"两不管")。

⏭️ 第 14 棒(执行棒 · 下一轮动作)= 认领并执行 im-conversation-tabs 的投放(可自动的部分)+ 出线上取证清单

🔴 为什么排它:① 第 13 棒已修掉投放的唯一硬阻塞(§15.7 的 P0)⇒ 现在具备投放条件;② E 单的「线上可见面」(生效链路第 ④ 关)是 A–E 五单里唯一没有验收过的交付面 ⇒ 投掉它才算闭环;③ 本入口 §2 第 13 棒 §8 明令「本棒至少要推进一步:本线自行排一棒」—— 本条即兑现(⛔ 不推进 ⇒ 又回到"两不管")。⚠️ 与 ② gateway 部署 / ③ 在线态接线 解耦,那两项排在再后。

  1. 只读前置(先逐条核实,⛔ 别凭记忆):
    • 投放的唯一方式(CODEBUDDY.md §2):admin 在门户导入候选池 → 用户在实例「功能管理」自助启用/禁用(⛔ 不铺 profile);机制/边界/回滚见档案 16 / 36 / 65。
    • 装配协议 = link:(2026-09-23 拍板,推翻 file:)⇒ 待落地 4 处里含"重编译部署 47 / 106" —— 先确认这 4 处是否已全部落地(投放线入口 §0 末行有记录)。
    • 代码根:47 = /opt/dshs、106 = /opt/dshs-cluster(106 上没有 /opt/dshs);/opt/dsh = 平台数据根,三台同名。取 DSHS_DB_URL 只从 drop-in cluster.conf grep(⛔ 不 source /etc/dshs.env,那会拿到错的 5432)。
    • 47 的 sshd 在 22 端口;47 的 PG 是本机进程(127.0.0.1:15432)非容器 ⇒ 「docker exec dshs-pg psql」写法全失败。
  2. 动工范围(严格):① 把 poc/im-conversation-tabs/ 按装配协议打进候选池(admin 途径)② 在投放线入口 接续入口_插件投放与分库线_20260922.md §5「本线后续」第 5 条处把归属写成本线认领(⛔ 不新增重复条目、⛔ 不改该入口其余内容)③ 出**「用户启用 → 重启实例 → 线上取证」操作单**(含每条判据的命令与期望)。 ⛔ 不许改 poc/im-conversation-tabs/** 的代码(第 13 棒刚验收过;若投放中发现新缺陷 ⇒ 停下只报告,⛔ 不顺手动)|⛔ 不许改 src/im/**。
  3. 判据(命令 + 期望 + 退出码):
    • 插件包可被装配:package.json 三件齐(exports['./client'] / dsh.client.platform='web' / dsh.bundle.patch)⇒ node -e 断言 rc=0。
    • 候选池里能查到 im-conversation-tabs(admin 只读查询;⛔ 不改任何池内他项)。
    • 🔴 线上可见面(本棒的目标终点,⚠️ 需用户动作):实例「功能管理」启用 → 重启实例(bundle 在实例启动时加载)→ 取证 = ① 会话区出现 tab 行([data-slot="main.conversation"] 被接管)② /api/im/ws 握手 101 且不再出现 bad-op 断开(服务端日志)③ 房间消息真的渲染出来。 ⛔ 用户未启用 ⇒ 本项如实记为"未通过",⛔ 不伪造成功、⛔ 不代替用户点启用。
  4. 环境注意(⛔ 别踩):本机 bash 每条命令前置 export PATH="/e/ProgramData/.workbuddy/binaries/PortableGit/versions/1.2.0/usr/bin:/e/ProgramData/.workbuddy/binaries/PortableGit/versions/1.2.0/bin:$PATH"|npm test 用 Node 22(E:/ProgramData/.workbuddy/binaries/node/versions/22.22.2-3)|npm run verify 必红且与本线无关(判定看 npm test 的 0 败)|106 用 ssh test106(别名已配;ssh [email protected] 因 IdentitiesOnly 必失败)|非 ASCII 会话名的锁目录必带 cksum 后缀(属正常)|文档库任何 Edit/Write 需先持全局执行锁,且域锁必须含 poc/im-conversation-tabs(第 12 棒漏了它 ⇒ 第 13 棒实查该域当时未在锁内)。
  5. 边界:⛔ 不改 src/im/**|⛔ 不改 poc/im-conversation-tabs/** 代码|⛔ 不改 A–E 五单结论|⛔ 不 commit/push/scp|⛔ 不引入新依赖|⛔ 不铺 profile(投放只有候选池这一条路)|⛔ 不取用 47 的 PG 凭据|⛔ 不碰 §14.6 未决项。
  6. 收尾五件同本入口惯例。回填 = 交接单/IM群组-E-会话界面UI.md 追加「§执行回报(IM 线第 14 棒)」小节(⛔ 不改既有正文)+ 投放线入口 §5 第 5 条落归属。 ✅ 本条已完成(2026-09-23 20:2x) —— E 单回报已追加(该单 350 → 410 行,章节 ## 复核 §二…§八 齐全未丢);投放线入口 §5 第 5 条已改写为「归属 = IM 线自行认领完结」+「本线后续给任何插件投放都要带上共享层发布这一步」(⛔ 未新增重复条目、⛔ 未改该入口其余内容)。

✅ 第 15 棒(执行棒)已完成(2026-09-23 21:3x)= 47 就地部署连接层 gateway + 在线态取数接线

✅ 完成情况(2026-09-23 21:3x,本段保留原文备查):§2 第 15 棒四项全部落地并逐条取证 ⇒ 回填见选型单 §15.11(⚠️ 落点为 §15.11 不是本节原写的「§15.8」—— §15.8 已被「未完成项 / 已知限」占用;⛔ 未重排既有小节,只在 §15.8 表前加了一段 🔻 状态标注)。两项未完成项已闭合:②gateway 已部署到 47(网关进程 + 配置 600 + 单元 dsh-gateway.service enabled+active、只绑 127.0.0.1:18081 + 边缘片段落盘、nginx -t rc=0)|①在线态取数已接线(presenceIngest not-wired ⇒ local-hub(native) / gateway-join-leave(gateway))。🔴 本棒实测改写了 §3 一条既有结论(ping_interval 0s ⇒ 25s:网关会发协议级 JSON ping,「不发 WS ping」那条实测没错、由它推出的结论错了)⇒ 仓库与部署两侧已同步修正。⚠️ 优先判据未触发(操作单 §三 六条取证用户尚未反馈)⇒ 按默认路径执行。

🔴 为什么排它:第 12 棒把 B 案代码面做完、第 13 棒解掉 P0、第 14 棒把 E 单投放做完 ⇒ 第 12 棒 §十五 剩下的两个"未完成项"成了本线唯一的可自动推进面:① gateway 未部署(47/106 一行未动)② 在线态取数未接线(stats().presenceIngest='not-wired')。 ⚠️ 但优先判据是"用户的取证反馈"(见 §0 第 14 棒行的末句):若用户已反馈操作单 §三 的六条结果 ⇒ 先按反馈处置,本节两项顺延。 ✅ 本轮上线风险已被设计按住:DSH_IM_BACKEND 未声明 ⇒ 默认 native(src/im/connection-backend.ts:11/45)⇒ 部署网关二进制 + 配置样例本身不改任何线上行为;"切换"是另一个动作(写 DSH_IM_BACKEND=gateway + 重启 dshs),一行可回滚。

  1. 只读前置(先逐条核实,⛔ 别凭记忆):

    • 第 12 棒交付物现状:src/im/connection-backend.ts / keepalive.ts / backends/{native,gateway}.ts / test/im-connection-backend.test.mjs / poc/im-connection-gateway/{README.md,config.json,nginx-location.conf} —— 逐条 ls + grep 确认未漂移。
    • DSH_IM_BACKEND 的选择器与默认值(selectConnectionBackend,:119)+ 能力自述的构造即抛判据(:184 assertCapabilities)。
    • 47 上 /opt/dshs 的部署产物是否含 lib/im/{connection-backend,keepalive}.js + lib/im/backends/(第 12 棒只在本机改码,未部署 ⇒ 大概率没有,若确实没有,本棒第 2 步要先 build + scp)。
    • 47 的 /etc/systemd/system/dshs.service.d/ 里 DSH_IM_WS / DSH_IM_BACKEND 是否已有值(有 ⇒ 先读清再动)。
    • 106 的 Centrifugo 二进制位置(/root/im-e2/centrifugo,v6.9.6)与配置键改位(client.token.hmac_secret_key / http_api.key / --http_server.port)。
  2. 动工范围(严格):

    • ① 部署连接层代码:npm run build ⇒ 把 lib/im/** 新增产物 scp 到 47 /opt/dshs/(若 106 也要跑网关 ⇒ 同法到 /opt/dshs-cluster/);restart dshs;⚠️ 默认 native ⇒ 行为不变,重启后必须复核 /api/im/ws 仍 101。
    • ② 装外部连接层进程(47 就地):Centrifugo v6.9.6 二进制 + poc/im-connection-gateway/config.json 落到 /opt/dsh/* 下的运行位;只绑回环;systemd 单元(或前台 nohup + 记录 PID);⛔ ⛔ 必须补发自建心跳(Centrifugo 不发服务端 ping —— E-2/E-7 实测)。
    • ③ 边缘映射:poc/im-connection-gateway/nginx-location.conf 的 location = /api/im/ws —— 先只落配置文件并 nginx -t,⛔ 本棒不开流量(切流属"改线上连接层"=影响在线用户 ⇒ 单独一步、单独取证)。
    • ④ 在线态取数接线:把 stats().presenceIngest='not-wired' 换成真源(hub.ts 的 presence 已内建,1 s 批合并 ⇒ ⛔ 不新增数据源)。
    • ⛔ 不许改 poc/im-conversation-tabs/**(第 13/14 棒刚验收)|⛔ 不许改 A–E 五单结论|⛔ 不引新依赖进 src/im/**(Centrifugo 只作外部进程)|⛔ 不动 106 生产面(除 ① 的同步部署)。
  3. 判据(命令 + 期望 + 退出码):

    • npm run build rc=0 | node --test test/im-connection-backend.test.mjs = 28 / 28 过(⛔ 不得回退)| npm test(Node 22)0 败 | check-layering rc=0 ✅ 无新增违规。
    • 部署后行为不变:47 curl /api/im/ws 握手 101(未声明 DSH_IM_BACKEND 时);systemctl is-active dshs = active;门户 http://127.0.0.1:3080 200(-H 'Host: alotbuy.com')。
    • 网关进程:ss -lntp 确认只绑 127.0.0.1:<port>(⛔ 不许 0.0.0.0);/health 或等价只读口 200。
    • 保活自证(硬实现项 ①):复用第 12 棒 E-7 三向实验(off / global / conn)⇒ conn 口径掉 0;权威判据 = 服务端 num_clients 不下降 + worker closed=0(⛔ 不用 e2sum.py 的 hbTimeoutRate —— 第 12 棒已实测它是结构性非零)。
    • 在线态接线:GET /api/im/stats(admin)里 presenceIngest 不再是 not-wired,且 presence 读数随真实在线变化(给两条读数的对照)。
    • 回滚演练:DSH_IM_BACKEND 删掉 → restart dshs → 握手回 101 ⇒ 记一条。
  4. 环境注意(⛔ 别踩):本机 bash 每条命令前置 export PATH="/e/ProgramData/.workbuddy/binaries/PortableGit/versions/1.2.0/usr/bin:/e/ProgramData/.workbuddy/binaries/PortableGit/versions/1.2.0/bin:$PATH"|npm test 用 Node 22(E:/ProgramData/.workbuddy/binaries/node/versions/22.22.2-3)|npm run verify 必红且与本线无关|106 用 ssh test106(别名已配)|47 用 ssh bt-server(sshd 22 端口)|47 的 PG 是本机进程(⛔ 无 docker exec dshs-pg;⛔ 不取用凭据)|🔴 改 drop-in 才是唯一载体(同名键写 /etc/dshs.env 会被静默压掉)|🔴 改 bwrap 参数前先看目标机 bubblewrap 版本(47=0.4.0/106=0.11.0,--perms 属 0.5+)|⛔ 往 md 追加长文本别用 bash heredoc(用 Write 写 .py 再跑)|文档库任何 Edit/Write 需先持全局执行锁;域锁建议含 src/im · poc/im-connection-gateway · 交接单/IM群组-稳定性机制与框架选型.md。

  5. 边界:⛔ 不改 poc/im-conversation-tabs/**|⛔ 不改 A–E 五单结论|⛔ 本棒不切流(location 只落配置 + nginx -t,⛔ 不改生效中的 vhost 指向)|⛔ 不 commit/push/scp 源码(部署产物除外)|⛔ 不引新依赖|⛔ 不碰 §14.6 未决项|⛔ 不动 106 的用户实例面。

  6. 收尾五件同本入口惯例。回填 = 选型单 交接单/IM群组-稳定性机制与框架选型.md §十五 追加「§15.8 gateway 部署回报」小节(⛔ 不改 §十五 既有内容)+ 顺手把 §十五 的"未完成项"两条标注状态。 ✅ 已办(2026-09-23 21:3x):⚠️ 落点改为 §15.11(§15.8 已被「未完成项 / 已知限」占用,⛔ 不重排既有小节)|§15.8 表前加了 🔻 状态标注|交接单/.locks/ 释放、入口 §0/§2 已推进、当日 .workbuddy/memory/2026-09-23.md 已追加。

⏭️ 第 16 棒(执行棒 · 下一轮动作)= 换连接层的剩余代码面:能力口径修正 + 平台侧接入面 + E 单客户端双模传输 ✅ 已完成(2026-09-23 23:1x · 会话 IM线-第16棒) —— ① ② ③ 三项 + ④ 本机回环 E2E 16 / 16 全绿(掉线 0);回填见选型单 §十六 + E 单「§执行回报(IM 线第 16 棒)」。🔴 本棒七条实测踩点务必先读 §16.5(尤其第 2 条:回检共享密钥必须放 http.static_headers,http_headers 元素只是头名)。

🔴 为什么排它:第 15 棒把网关部署到 47、把在线态取数接了线,但实测同时暴露了「切流还差哪一块」——三件全部可在本机完成并自验,是本线唯一不依赖用户动作的可自动推进面:① 网关鉴权走 client.token(JWT,config.json 里没有 connect 代理)⇒ 平台侧必须先有"发连接准入"的面(现在没有);② E 单客户端 poc/im-conversation-tabs 的传输层仍按平台 IM 协议({op:'subscribe'}…),而网关只认自己的协议({"connect":…} / {"subscribe":{"channel":"im:<roomId>"}} / 空对象 {} 即 pong)⇒ 直接切流会全量连不上;③ GATEWAY_CAPABILITIES.providesServerHeartbeat=false 与第 15 棒实测(网关会发协议级 ping)不符 ⇒ 能力自述错,且它参与 assertCapabilities 的构造即抛判据。 ✅ 本轮上线风险为零:⛔ 本棒不切流、不重打包、不重投放、不需要任何部署 ⇒ 全部改动只在本机代码面 + 本机回环 E2E。 ⚠️ 第 17 棒(届时再由当棒收官时排,⛔ 不预登记) = 重打包 + 重投放 + 边缘接 vhost + 切流 + 目标档位复测(2000 / 10 / 30 s)。

  1. 只读前置(先逐条核实,⛔ 别凭记忆):

    • src/im/backends/gateway.ts 的 GATEWAY_CAPABILITIES 全字段 + src/im/connection-backend.ts 的 assertCapabilities:构造即抛的判据在哪一行、断的是什么;改 providesServerHeartbeat 会不会让某条既有断言变成"必须提供垫片"⇒ 两者一起看再动。
    • src/web/routes/im.ts 现有面:/api/im/ws(native)+ 第 15 棒新加的 POST /api/im/gateway/presence(fail-closed + secretEquals + IM_GATEWAY_KEY_ENV)⇒ 复用同一套密钥与 fail-closed 风格,⛔ 不要另造第二套。
    • poc/im-connection-gateway/config.json 当前鉴权形态(client.token.hmac_secret_key;无 connect 代理)+ 47 上的部署实况(/opt/dsh-gateway/)。
    • poc/im-conversation-tabs/lib/client.js 的传输层三处:WS 建立点、出站帧构造点、入站 switch 分发点(第 13 棒刚把出站对齐成 op: 制 ⇒ ⛔ 别回退)。
    • 47 上 /opt/dsh-gateway/gateway.env(600)密钥只读确认存在即可,⛔ 不取内容、不进日志、不写文档。
  2. 动工范围(严格):

    • ① 能力口径修正:gateway.ts 的 providesServerHeartbeat 改 true(依据 = 选型单 §15.11.3 / §15.11.4 实测)+ 连带复核 assertCapabilities 与 B 组既有用例;若某条断言语义因此改变 ⇒ 同步改断言并写清为什么。
    • ② 平台侧接入面(二选一 · 执行棒自决 · 须写清优缺点与选择理由): (a) 发连接令牌 —— 新增 POST /api/im/gateway/token(登录态校验 ⇒ HMAC-JWT,sub=userId、短 exp、用同一份网关密钥;⛔ 密钥不进响应体/日志)⇒ 客户端用 {"connect":{"token":…}}; (b) 改网关为 connect 代理 —— 网关侧加 client.connect.proxy(或等价位)指向平台新回调面 ⇒ 客户端 connect 不带 token,鉴权靠同源 cookie / 头经边缘带到平台。 两种都要满足:权威仍在平台("成员表判读房"这条不许破)+ fail-closed(无密钥 / 未登录 ⇒ 401/403,⛔ 不静默放行)。
    • ③ E 单客户端传输层双模化(poc/im-conversation-tabs/lib/client.js 及同目录件):native 走原协议逐字不变;gateway 走网关协议 —— 建连 → connect → 按房间 subscribe im:<roomId> → 收到空对象 {} 就按连接回 {} → 入站取 push.pub.data.frame 后照原路径 JSON.parse(frame) 分发(⇒ 应用层帧逐字不变)。模式来源 = 平台在只读面告诉客户端(自决:随 /api/im/stats 或专用只读面;⛔ 不许硬编码)。
    • ④ 本机回环 E2E:本机起网关(样例配置、回环且避开 47 的 18081)+ 真平台进程 ⇒ 用真客户端件跑通四条:连接 / 订阅 / 一条消息收发 / presence 一进一出。
    • ⛔ 不许改 A–E 五单结论|⛔ 不许改 src/im/** 内核语义(hub.ts / ws.ts / store.ts 是权威面;只许如 ① 那样改自述)|⛔ 本棒不切流、不重打包、不重投放|⛔ 不引新依赖进 src/**(客户端侧若确需网关 SDK ⇒ 单独说明,并给"无依赖最小实现"作对照)。
  3. 判据(命令 + 期望 + 退出码):

    • npm run build rc=0|node --test test/im-connection-backend.test.mjs 28 / 28(⛔ 不得回退)|node --test test/im-presence-ingest.test.mjs 13 / 13(⛔ 不得回退)|npm test(Node 22)0 败|check-layering rc=0 无新增违规。
    • ① 有用例能断「providesServerHeartbeat 与实测一致」;assertCapabilities 的构造即抛仍可复现(给一条反例)。
    • ② 未登录 / 无密钥 ⇒ 401 / 403;凭证正确 ⇒ 200 且响应体里没有密钥;grep -c 密钥在 stats() / 日志 / 响应体中 = 0。
    • ③ native 路径回归逐字相同(给 diff 或哈希)|gateway 路径下"空对象即回"必须有独立断言(⛔ 不许只靠端到端间接证明)。
    • ④ E2E 四条全通 + 掉线 0(沿用第 15 棒权威口径:服务端 num_clients 不降 + 客户端 closed=0,⛔ 不用 hbTimeoutRate)。
    • 回滚:本棒产物一行可回(客户端切回 native / 撤销新增面)⇒ 各记一条。
  4. 环境注意(⛔ 别踩):本机 bash 每条命令前置 export PATH="/e/ProgramData/.workbuddy/binaries/PortableGit/versions/1.2.0/usr/bin:/e/ProgramData/.workbuddy/binaries/PortableGit/versions/1.2.0/bin:$PATH"|npm test 用 Node 22(E:/ProgramData/.workbuddy/binaries/node/versions/22.22.2-3)|npm run verify 必红且与本线无关|47 用 ssh bt-server(22 端口)/106 用 ssh test106|47 的 PG 是本机进程(⛔ 无 docker exec dshs-pg;⛔ 不取凭据)|🔴 改 drop-in 才是唯一载体(同名键写 /etc/dshs.env 会被静默压掉)|🔴 网关密钥只在 /opt/dsh-gateway/{config.json,gateway.env}(600) —— ⛔ 不打印、不进文档、不写进任何 stats / 日志 / 响应|🔴 网关只许绑回环|⚠️ 本机起网关要避开 47 的 18081|⛔ 往 md 追加长文本别用 bash heredoc(用 Write 写 .py 再跑;追加一律 open(p,'ab') 二进制,判据 = b.count(b'\r') 不变)|文档库任何 Edit/Write 需先持全局执行锁。

  5. 边界:抢锁域必须含 src/im · src/web/routes/im.ts · test · poc/im-conversation-tabs(客户端传输层)· poc/im-connection-gateway · 交接单/IM群组-稳定性机制与框架选型.md · 交接单/IM群组-E-会话界面UI.md。⛔ 不改 poc/im-conversation-tabs/** 的应用层帧语义(只改传输层)|⛔ 不改 A–E 五单结论|⛔ 本棒不切流|⛔ 不 commit/push/scp 源码(本棒不需要任何部署)|⛔ 不引新依赖进 src/**|⛔ 不碰 §14.6 未决项|⛔ 不动 106 用户实例面|⚠️ 第 16 棒改完客户端也⛔ 不要重投放(用户尚未启用现版 v0.1.0 ⇒ 现版取证仍有效;重投放属第 17 棒)。

  6. 收尾五件同本入口惯例。回填 = 选型单 交接单/IM群组-稳定性机制与框架选型.md §十六(新建小节,⛔ 不改 §十五 既有内容) + E 单 交接单/IM群组-E-会话界面UI.md 追加「§执行回报(IM 线第 16 棒)」(⛔ 不改既有正文)。

⏭️ 第 17 棒(执行棒 · 下一轮动作)= 重打包 + 重投放 + 边缘接 vhost + 切流 + 目标档位复测

🔴 为什么排它:第 16 棒把连接层的代码面闭合了(能力口径 / 平台接入面 / 客户端双模 / 本机回环 E2E 全绿), 但线上仍是 native —— 连接层 gateway 虽已在 47 就地部署(第 15 棒),边缘 vhost 尚未接入、客户端仍是旧传输层、 本棒新增的 lib/** 产物与网关 config.json 回检块也没同步过去。⇒ 本棒是"让改动真正生效"的一棒。

  1. 只读前置(先逐条核实,⛔ 别凭记忆):
    • 47 现状:/opt/dsh-gateway/config.json 的回检块形态(⛔ 只读确认结构,不取密钥内容)—— 必须是 http.static_headers,⛔ 不是 http_headers(见选型单 §16.5-2)。
    • 边缘生效 vhost(ai1net.com.conf 443 块)现况 + 第 15 棒落盘的片段;nginx -t 与备份。
    • 用户是否已在实例「功能管理」启用 im-conversation-tabs 现版 v0.1.0(这决定切流顺序)。
    • 选型单 §十六 的七条踩点(⛔ 至少读第 1 / 2 / 5 条)。
  2. 动工范围:重打包(E 单客户端含本棒的 gateway 传输层)⇒ 重投放(候选池 v0.2.0)⇒ 47 lib/** 产物同步 + 网关 config.json 回检块修正形态落盘 + 重启网关 ⇒ 边缘接 vhost(插片段 + nginx -t + reload) ⇒ 切流 ⇒ 目标档位复测(2000 / 10 / 30 s,沿用第 15 棒权威口径:num_clients 不降 + closed=0)。 ⚠️ 顺序铁律:客户端新版必须先投放且实例重启生效,再切流;否则网关模式下旧客户端会全量连不上。 ⚠️ 若用户尚未启用现版 ⇒ 先做"重打包 + 重投放 + 产物同步 + 边缘片段落盘(⛔ 不 reload)", 切流留到用户启用后再排(这属"卡在用户动作",⛔ 不许替用户点)。
  3. 判据(命令 + 期望 + 退出码):npm run build rc=0|npm test(Node 22)0 败|check-layering rc=0 无新增违规| 切流后:/api/im/ws 101、/api/im/stats 的 backendKind=gateway、transport.protocol=gateway、 线上一条消息收发 + presence 一进一出、掉线 0(num_clients 不降 + closed=0)| 回滚:删边缘片段 → nginx -t → reload ⇒ 线上回到 native(第 15 棒已演练过一次,命令可复用)。
  4. 环境注意:本机 bash 前置 export PATH="/e/ProgramData/.workbuddy/binaries/PortableGit/versions/1.2.0/usr/bin:/e/ProgramData/.workbuddy/binaries/PortableGit/versions/1.2.0/bin:$PATH"| npm test 用 Node 22|47 用 ssh bt-server(22 端口)/106 用 ssh test106|🔴 网关只许绑回环| 🔴 网关密钥只在 /opt/dsh-gateway/{config.json,gateway.env}(600) —— ⛔ 不打印 / 不进日志 / 不进文档 / 不进 stats。
  5. 边界:抢锁域必须含 poc/im-conversation-tabs · poc/im-connection-gateway · src/im · src/web/routes/im.ts · 交接单/IM群组-稳定性机制与框架选型.md · 交接单/IM群组-E-会话界面UI.md。⛔ 不改 A–E 五单结论|⛔ 不碰 §14.6| ⛔ 不动 106 用户实例面|⛔ 不引新依赖进 src/**|⚠️ 切流是本棒唯一动线上的动作 ⇒ 动手前一句话说明影响谁 / 断多久。
  6. 收尾五件同本入口惯例。

§3 已取事实(2026-09-22 实搜|8 条|下一棒 ⛔ 不必重搜)

框架侧

  1. ejabberd 26.07(2026-07-30 发布)— Erlang/OTP;官方称单节点 200 万并发会话(生产实测);WhatsApp / Nintendo / BBC 在用;XMPP 核心 + 400+ XEP(MUC / MAM / PubSub / OMEMO)+ 内建 MQTT / SIP / Matrix;故障哲学 = Erlang「let-it-crash」+ 热升级;社区版 GPLv2 免费自托管,商业版 €16,000/年。
  2. OpenIM(Go 微服务)— msggateway(WS 长连接) + RPC 服务 + Kafka + MongoDB + Redis + etcd;官方口径单节点 10 万+ 连接 / P99 < 100 ms / 10 万 QPS;seq 全局序列 + 增量同步;三级缓存(LRU → Redis → DB);支持十万级大群;配置口径:1K–10K 用户 4C8G、50K–100K 需 16C32G 集群。
  3. Matrix — Synapse(Python,慢) / Dendrite(Go,性能更好);联邦 + 强制 E2EE;运维复杂度最高(官方文档自述需懂联邦协议 / 反代 / DB)。
  4. Telegram 官方服务端 ⛔ 不开源;2026 出现的第三方实现(用官方客户端改 DC 地址 + RSA 公钥接入):
    • Opengram — C#/.NET 9,fork 自 mytelegram,layer 216,Apache 2.0;微服务 + MongoDB/Redis/RabbitMQ/MinIO;CQRS;含 Calls(TURN/STUN + mediasoup)。
    • Ferrite — C#,layer 214,Cassandra/Redis/MinIO/Elasticsearch/Kafka;494 / 732 方法实现(其余返回 403 METHOD_DISABLED)。
    • teamgram-server — Go 1.23,layer 222,MySQL/Redis/etcd/Kafka/MinIO + gRPC(go-zero);⚠️ 社区版仅私聊 + 基础群,频道 / 超级群 / 机器人 / 秘密聊天在企业版。
  5. 其他候选:Tinode(Go/gRPC,移动优先)· Rocket.Chat / Mattermost / Zulip(协作型,功能重)· Openfire(Java XMPP,老牌稳定)· Jackal(Go XMPP)· lhttp(Go WS 轻框架)。

我们的约束(判据来源)

  1. 资源口径(⚠️ 2026-09-22 17:0x 用户更正:机器可以换)⇒ 资源不是选型否决项。现实基线(仅供需求测算)= 当前单实例硬顶 1024 MiB / 宿主 1870 MiB(scripts/verify-mem-model.mjs 口径)。上面第 1–4 条里,ejabberd 是单运行时(Erlang VM),其余都要外挂 3–5 个组件(Kafka / MongoDB / etcd / MinIO / RabbitMQ)⇒ 规划时算清"要多少机器与内存才能稳"并写进方案,⛔ 不得以"装不下"否掉方案。
  2. 既有资产可复用(⛔ 不要重复造):PG 13.23(控制面库 dshs + 一插件一库 dshs_pl_*,归属列强制)· Node/TS 平台与 fastify · nginx(宝塔) · 覆盖网络(relay 只绑回环、跨用户隔离是结构性的)· 联邦多区(149:数据不动、只有请求动)· 实例内可挂 dsh slots 面板。
  3. 可直接"借机制"(seq 全局序列 + 游标增量同步(OpenIM)· CQRS + 队列削峰(Opengram/Kafka 思想 ⇒ 我方落 PG FOR UPDATE SKIP LOCKED + LISTEN/NOTIFY)· 无状态网关 + 可重启(msggateway 那层)· 连接心跳与僵尸清理 · 背压 / 限速 / 熔断(142 §3.6 三条保护约束同向))。 ⚠️ 借机制的理由 = 机制更可控、判据可自验,⛔ 不再是"引框架塞不下" —— §0 口径更新后,「整框架引入」重新成为平等候选(与自研、混合同台按四维比较)。

§4 ⛔ 未拍板项(本线 ⛔ 不得替用户决定;均已在 142 §六 / 各单登记)

  1. UI 落点 ✅ 已拍板(2026-09-23):C 案(实例内 dsh 会话页 tab)+ 保活 = display:none + 群组 tab 上限 5 + 新建群组入口在业务插件内 + 开源界面只借鉴交互与组件(按 ① 方案,⛔ 不引整站)。详见 E 单 §四-4 / §四-5。 ⚠️ 仍悬的子项:E 单步 0 的「可发现 / 自助加入」标志位走显式列 + v15 迁移还是 config_json —— 属技术选型(有客观判据)⇒ 执行棒自决,⛔ 不占用户拍板额度。
  2. E2EE 形态档位 ✅ 已拍板(2026-09-23)= 强度档(用户原话「强度档是否影响性能,影响较小就用强度档」)。性能判据已量化:一条消息全部密码学开销(AES-GCM 0.5–2 µs + X25519 ~50 µs + HKDF 1–2 µs ≈ 55 µs)vs 扇出 P99 23 µs ⇒ 相对 1 s 事件循环预算 ≈ 0.006%、50 msg/s 下占单核 ≈ 0.3% ⇒ CPU 维度可用。 🔴 两处结构性代价须按实现项对待(⛔ 不受"影响较小"覆盖):① 棘轮状态必须每设备持久化(丢了永久断链 ⇒ 新增持久化表 + 恢复逻辑)② 群聊必须 Megolm 化(每发送者一条链) —— 若误用严格双棘轮做 N 人群 ⇒ 扇出 O(N) 变 O(N²),这才是会压垮连接层的那条。③ agent 代答模型须重做:im_agent_grants「按房间逐条授权」在强度档下不成立 ⇒ agent 升格一等成员设备 ⇒ D 单 §四-2 语义与表结构须重写,E4-6 断言须改写。详见选型单 §十四-14.1~14.3(含 9 条连带重写清单)。
  3. 房间上限与发言预算数值 ✅ 已拍板(2026-09-23)= 按建议固化(用户原话「按照建议执行」):maxMembers = 2000 / agentQuotaPer10Min = 10 / silencePeriodMs = 30 s 由"待标定初值"转为正式值(依据 = 第 6 棒实测扇出 P99 0.023 ms)。详见选型单 §十四-14.4 + A 单 §四-7 状态块。 🔴 适用前提(正式条款):这三个数是单进程自研扇出形态下标的 ⇒ 连接层改形态后必须复测;复测前不得当跨形态恒定值引用。⚠️ 复测要点:内存类判据须重复跑 ≥3 次取中位(E-5 跨跑方差 ±12 MiB)。
  4. 连接层三候选 ✅ 已拍板(2026-09-23)= 候选 B:引入 Centrifugo 类外部连接层(用户原话「2 选 b」)。依据 = 第 3 棒 E-2 实测(10k P99 28.9/68.1/111.9 ms vs 自研 268.6/337.6/359.4 ms;扇出离散中位 17.2/57.9/97.8 vs 171.8/227.6/225.5)。详见选型单 §十四-14.5。 🔴 三条硬实现项:① 必须补发自建心跳(Centrifugo 实测不发服务端 WS ping,客户端也不发 ⇒ 26 s 内 10000/10000 全被断开;且"保活计时起点必须绑该连接自己的认证完成时刻",整进程统一计时会一次掉 2773 条)② 按"可替换连接层"接口落(⛔ 不把 Centrifugo 概念泄漏进 src/im/** 内核)③ 出容量测算表(边际 70.1 KiB/连接 vs 自研 5.9 KiB;按"机器可以换"口径算需求,⛔ 不以此否决方案)。
  5. ✅ 下一棒先做哪一项 = 已拍板(2026-09-23)= 先做连接层 B 案(用户原话「先做b」)⇒ 第 11 棒 = 动工 B 案(范围见 §2「第 11 棒」)。⚠️ E2EE 强度档重写排在 B 案之后,且档位重写必须先于任何 E2EE 代码动工(选型单 §十四-14.3 的 9 条清单)。
  6. 🔴 跨区可见性默认(倾向"互不可见")—— 与上述各项解耦,仍在等拍板。
  7. 🔴 强度档下 agent 代答的具体形态("一等成员设备"的撤销语义 / agent 之间是否互见)—— 属 D 单重写面,须单独定。
  8. 连接层换型的部署根阻塞 ✅ 已解(2026-09-23 A 线查实):47 的代码根 = /opt/dshs、106 的 = /opt/dshs-cluster(106 上无 /opt/dshs);/opt/dsh = 平台数据根,三台同名 ⇒ 换型安装位置可判 ⇒ ② 连接层换型已无阻塞,可开工。

已拍板(⛔ 不要当未决项再问):首批场景 = 三类都要(办公群 / MUD / 跑团 ⇒ D 单判据升级为三类各跑通一个)|插件扩展点边界 = 新 room_type 需 admin 审核后生效(自定,可推翻)。 🔴 2026-09-23 新增已拍板:E2EE 形态档位 = 强度档|房间上限与预算 = 2000 / 10 / 30 s 转正式值|连接层 = 候选 B(引入 Centrifugo 类)|UI 落点 = C 案。四项均已落文档,⛔ 不必再问。

§5 开工校验

  • 源仓 HEAD:git -C D:/github/dsh_shenxian log --oneline -1(以实跑为准)。
  • 入口漂移判据:以本文件 §0 最新一行 为准;若 §0 与 §2 冲突 ⇒ 以 §0 为准。
  • ⚠️ 本机 Bash 工具 shim 已坏(dirname: command not found ⇒ ls/cat/mkdir 全 not found,export PATH 救不回)⇒ 抢锁 / 放锁 / 任何需要 shell 的活一律走: "E:/ProgramData/.workbuddy/binaries/python/versions/3.13.12/python.exe" "E:/ProgramData/AI技能/aliyun-dsh-server/tmp/claim_lock.py" --claim-exec "<线名>-第N棒"(该脚本用 subprocess 调 PortableGit 的 bash.exe 并给足 usr/bin+bin+mingw64/bin)。 ⚠️ 抢锁失败时脚本 rc=1 且中文输出乱码 ⇒ 别把「乱码」误判成「锁被占」,解锁文件读 dsh-server-docs/交接单/.exec-lock/OWNER 才准。

§6 ⛔ 不要重做

  • IM 需求基线(142)与五单 A–E 的架构对齐(2026-09-22 已完成,见各单 §〇)—— ⛔ 不要重做。
  • §3 的 8 条框架事实 —— ⛔ 不必重搜(要补新事实另说)。
  • 🔴 E-4 的档位内容 —— 2026-09-23 第 11 棒已按强度档重写完毕(选型单 §四-6 + D 单 §四-6 + §十一)⇒ ⛔ 不要按旧「工程务实档」重写一遍,也不要以为它还"待重写"。
  • 🔴 §15.7 的 P0(客户端帧协议) —— 2026-09-23 第 13 棒已修完(客户端对齐平台的 op 制,E-14 机械对账守着)⇒ ⛔ 不要重做协议对齐、⛔ 不要以为客户端还在发 type:。 ⚠️ 同时作废一条旧建议:§2「第 13 棒」原文写的「保活 ⇒ {op:'pong'}(pong 是已认 op)」是错的 —— src/im/ws.ts 的客户端 op switch 没有 case 'pong'(pong 只在模块头注释的表格里)⇒ 发它会被 bad-op 断开。客户端心跳一律发 {op:'ping'}(服务端回 pong)。⛔ 不要照旧建议改回去。
  • 🔴 三项拍板(档位 / 预算数值 / 连接层) —— 已定,⛔ 不要当未决项再问。
  • 🔴 im-conversation-tabs 的投放(可自动部分) —— 2026-09-23 20:2x 第 14 棒已做完(打包 + 入候选池 + 发布到 47 共享层,审计 id 360/361)⇒ ⛔ 不要重投、不要重打包、⛔ 不要再把它登记给别人(归属已由本线认领完结)。⚠️ 剩余唯一未闭合项 = 用户在实例「功能管理」启用(用户动作,见 交付物/IM群组-ui投放与线上取证操作单-20260923.md)。
  • 🔴 连接层 gateway 的 47 就地部署 + 在线态取数接线 —— 2026-09-23 21:3x 第 15 棒已做完(产物逐字一致属主 root:root|dsh-gateway.service enabled+active、只绑 127.0.0.1:18081|边缘片段落盘 nginx -t rc=0、生效 vhost 未改|取数 presenceIngest not-wired ⇒ local-hub(native) / gateway-join-leave(gateway),回调面 POST /api/im/gateway/presence 带 key 200 / 不带 401 / native 404)⇒ ⛔ 不要重部署、不要重复接线、⛔ 不要以为 presenceIngest 还是 not-wired。详见选型单 §15.11。
  • 🔴 client.ping_interval 口径 —— 旧值 0s 是错的(无待回 ping 时 {} 是空命令 ⇒ 被网关判 bad request 断开;240 连接实测 80/80 掉)⇒ 已改 25s。⚠️ 「网关不发服务端 WS ping」(wsPingRecv=0)那条实测本身没错 —— 它数的是 WS 控制帧,网关发的是协议级 JSON ping;错的只是由它推出的结论。⇒ ⛔ 不要照 0s 改回去、⛔ 不要说"网关不管保活";客户端保活 = 按连接收到 ping 后回 {}(⛔ 整进程统一节拍器会被判 bad request)。
  • ⚠️ 一条口径更正(第 14 棒实测,⛔ 别再按旧法取证):IM 链路服务端不写任何日志(src/im/** / src/web/routes/im.ts / src/supervisor/proxy.ts 全零日志点;reject() 只回 error 帧 + destroy())⇒ ⛔ 不要用 journalctl | grep bad-op 当判据(恒空,且"空"会被误读成"没发生");协议类取证一律走客户端(DevTools → WS 帧流水)。
  • 本线不碰别人的 lane:插件投放与分库线(接续入口_插件投放与分库线_20260922.md)、技能重组线、覆盖网络线。