瀏覽代碼

docs: 容器接入 SOP v3 + 提示词 v2.1 + 项目进度资源盘点

## 新增
- 容器接入标准流程-SOP-v3:五步操作单(体检 → 修改 → 自审 → 复验 → 交付)
  每步都有可执行命令和判定标准;第 0 步是 DNS 自测(10 秒决定要不要继续)
- 项目进度与资源盘点:已完成 / 未完成(P0-P2)/ 资源清单 / 接手建议

## 更新
- 提示词 v2.1:新增「检查项 0:DNS 解析」(最优先,否则一切慢 5 秒)
  交付格式增加实测数字要求;新增「五个真实踩过的坑」对照表

## 依据
全部来自 lj-test 的实测与代码取证:
- DNS:AF_UNSPEC 5023ms vs AF_INET 16ms(每轮都卡)
- ✅ reaction:晚于正文 0.1s
- 插件开销:2~3ms(不是慢的原因)
- 历史会话 242 个 / 40MB:不进新会话上下文(不是慢的原因)

Co-Authored-By: Claude Code <noreply@anthropic.com>
Huccct 14 小時之前
父節點
當前提交
6e3dc21cb0

+ 170 - 0
docs/00-tracking/2026-11-12-项目进度与资源盘点.md

@@ -0,0 +1,170 @@
+# 项目进度与资源盘点(2026-11-12)
+
+> 本文件记录**截至 2026-11-12 的完整状态**:已完成什么、还差什么、产生了哪些资源。
+> 供接手者快速定位,不用翻几十份留痕。
+
+---
+
+## 一、总体状态
+
+| 维度 | 状态 |
+|---|---|
+| App 主干功能 | ✅ 可用 |
+| 双端(桌面/移动) | ✅ 一致 |
+| 容器接入 | ⚠️ **lj-test 已达标;lj 需补齐 2 项** |
+| 单元测试 | ✅ **1683 条全过** |
+| 类型检查 / 构建 | ✅ 通过 |
+| 线上部署 | ⏸ **未部署最新代码**(线上落后多个提交) |
+| Git | ✅ 已推送至 `3cd7684` |
+
+---
+
+## 二、已完成(按主题)
+
+### 2.1 会话状态体系(本轮核心)
+
+| 能力 | 说明 |
+|---|---|
+| **统一状态裁决** | `resolveSessionPhase()` 单点裁决,优先级:reaction > session > typing > turn |
+| **废弃全局 status** | 不再用容器级 `status` 冒充会话状态(串台根源) |
+| **三处同步** | ✅ reaction 一到,卡片 / 右上角 / 左侧**同时**收尾 |
+| **多会话隔离** | 会话页只认本会话;左侧按会话转圈 |
+| **多人共用隔离** | `currentSessions[].userId` 区分「谁在跑」 |
+| **running 过期保护** | 10 分钟未更新视为过期,防永久卡住 |
+| **会话级任务名** | `currentSessions[].task` → 顶栏显示本会话在做什么 |
+| **本地「刚发出」标记** | 点发送瞬间就转圈,不等容器上报(60 秒 TTL) |
+
+### 2.2 回合与消息
+
+| 能力 | 说明 |
+|---|---|
+| **结束信号** | ✅/❌ reaction(Hermes 内建),实测晚于正文 0.1 秒 |
+| **长任务不提前完成** | 有步骤 + 无信号 → 持续「输出中」,上限 10 分钟 |
+| **连发合并** | 用户连发多条 → 并入同一回合,回复都在最后一条下面 |
+| **历史会话不重放动画** | 已结束的块一次到位 |
+| **最后一条消息无上限残留** | 修复「26208s 输出中」 |
+
+### 2.3 渲染与排版
+
+| 能力 | 说明 |
+|---|---|
+| **删除线修复** | `8~16℃` 的成对 `~` 不再被解析成 `~~删除线~~` |
+| **Markdown 表格** | App 完整支持(容器曾误以为不支持而主动降级) |
+| **NG0600 修复** | 模板渲染期不再写 signal,消息区正常重绘 |
+
+### 2.4 网络与登录
+
+| 能力 | 说明 |
+|---|---|
+| **CORS 修复(白名单式)** | 只有 `/parse/...` 带 `X-Parse-Application-Id`,其余一律不带 |
+| **同步事件驱动** | ✅ reaction 到达即刷新容器状态,不等 15 秒轮询 |
+
+### 2.5 容器接入
+
+| 能力 | 说明 |
+|---|---|
+| **✅ reaction** | lj-test 实测通过 |
+| **`extra.currentSessions`** | 6 字段齐全(sessionKey/userId/status/task/turnStartAt/updatedAt) |
+| **DNS 卡顿** | 5023ms → **21ms** |
+| **agent-lj 接入** | 已按新规范改造完成并验证 |
+
+---
+
+## 三、未完成
+
+### 3.1 P0 · 阻断体验
+
+| # | 问题 | 现状 | 影响 |
+|---|---|---|---|
+| 1 | **线上未部署** | 线上版本落后多个提交 | 用户看到的仍是旧行为 |
+
+### 3.2 P1 · 应做
+
+| # | 问题 | 现状 | 建议 |
+|---|---|---|---|
+| 2 | **图文合并未启用** | `plugin-media-text-merge` 已就位但不在 `enabled` | 启用后有 2.5s 合并窗口 |
+| 3 | **图片回复格式** | 容器把工具过程行当正文发(无 `formatted_body`) | 容器侧:过程行不外发 / `vision_analyze` 的 question 不抄用户原话 |
+| 4 | **`RES_OPTIONS` 软覆盖** | 写的是 `${RES_OPTIONS:-single-request}` | 改成硬覆盖,防被外部值顶掉 |
+| 5 | **agent-lj 待补齐** | ✅ reaction / currentSessions 已达标 | 需补:DNS 检查、图文合并 |
+
+### 3.3 P2 · 技术债
+
+| # | 问题 | 说明 |
+|---|---|---|
+| 6 | 云函数无删除能力 | 测试数据(`__probe__` 探测记录、临时会话)清不掉 |
+| 7 | 定时上报 cron | lj-test 的 `*/2` cron `enabled=false`,靠插件补 |
+| 8 | `thread_id` 覆盖率 54% | 未覆盖的靠 roomId 退路匹配(粒度较粗) |
+
+### 3.4 已知限制(非缺陷)
+
+| 项 | 说明 |
+|---|---|
+| `$approot_xxx` 是虚拟 id | App 自造,Matrix 服务端 404。端到端可用,但**按标准 thread 语义读的地方会踩空** |
+| 多人共用容器 | 同公司多人可用,但**不能共用同一个账号**(服务端单点登录) |
+
+---
+
+## 四、资源盘点
+
+### 4.1 核心文档(`docs/03-interface/`)
+
+| 文档 | 用途 | 状态 |
+|---|---|---|
+| **`容器接入标准流程-SOP-v3-20261112.md`** | **新容器接入操作单**(体检→修改→自审→复验→交付) | 🆕 最新 |
+| **`提示词-容器自我优化-v2-20261111.md`** | **发给容器的提示词**(7 项自查 + 踩坑清单) | 🆕 最新 |
+| `容器接入完整指南-v2-20261111.md` | 原理与背景(十节 + DNS 专章) | ✅ |
+| `容器接入规范提示词-20261111.md` | 早期版本,已被上面取代 | ⚠️ 保留备查 |
+| `API-契约.md` | 云函数契约 | ⚠️ 部分过期 |
+
+### 4.2 验证/复现脚本(`scripts/`)
+
+| 脚本 | 用途 | 关键 |
+|---|---|---|
+| **`repro-new-session-send.mjs`** | 复现「新建会话发消息卡顿」 | 🔴 唯一能测出 NG0600 的脚本 |
+| `verify-unified-state.mjs` | 状态裁决五场景验证 | 真实容器数据 |
+| `verify-session-task.mjs` | 检查 `task` 字段是否就绪 | |
+| `verify-reaction-coverage.mjs` | ✅ reaction 三场景时序 | |
+| `verify-typing-order.mjs` | 反向证据:typing 为什么不能用 | |
+| `test-multi-session.mjs` | 多会话并发不串台 | |
+| `ask-agent.mjs` | 向容器发问题并收集回答 | 底层工具 |
+| `matrix-check.mjs` | 容器 Matrix 接入体检(只读) | |
+| `smoke-imhub.mjs` | 双端全路由冒烟 | 28 项断言 |
+
+### 4.3 追踪与留痕(`docs/00-tracking/`)
+
+| 文档 | 内容 |
+|---|---|
+| `2026-11-11-修改留痕-输出中残留与结束信号.md` | 本轮全部改动的完整记录 |
+| `操作追踪.md` | 按动作逐条追加(含教训) |
+| `容器取证-*.md`(约 30 份) | 容器的逐次问答原文,**未入库**(中间产物) |
+
+### 4.4 云函数(平台侧)
+
+| ID | 用途 |
+|---|---|
+| `xlosi3m46q` | 状态上报 / 查询(`extra.currentSessions` 在这里) |
+| `RP5Ht55NeX` | provision(建 Matrix 身份 + DM 房间) |
+| `AlP56LCKFm` | 取 OBS 预上传地址 |
+| `THXAOGKiPK` | 文件台账(`registerFile`) |
+| `tocXyQjyh6` | 文件读取代理(绕过 S3 CORS) |
+
+### 4.5 测试容器
+
+| 容器 | agentId | Matrix | 状态 |
+|---|---|---|---|
+| **lj-test** | `agent-node-lj-test` | `@zgdcmgnqcd:fmode.cn` | ✅ 参考实现,全部达标 |
+| **lj** | `agent-lj` | `@k0nrevcfik:fmode.cn` | ⚠️ 已接入,待补 DNS/图文合并 |
+
+---
+
+## 五、接手建议(按顺序)
+
+1. **读** `容器接入标准流程-SOP-v3` 的「第 0 步」——DNS 自测(10 秒)
+2. **跑** `npm test` 确认 1683 条全过
+3. **部署** 线上(当前落后多个提交)
+4. 按 SOP 给 `agent-lj` 补齐剩余项
+5. 跟进 P1 的三个容器侧问题
+
+---
+
+*本盘点基于 2026-11-12 的实测状态。所有数字均为实测,非估算。*

+ 300 - 0
docs/03-interface/容器接入标准流程-SOP-v3-20261112.md

@@ -0,0 +1,300 @@
+# 新容器接入 Fmode App · 标准操作流程(SOP v3.0)
+
+| 字段 | 值 |
+|---|---|
+| 版本 | v3.0(2026-11-12) |
+| 参考实现 | `agent-node-lj-test`(`@zgdcmgnqcd:fmode.cn`)—— 全部项已实测通过 |
+| 适用范围 | 任何要接入 Fmode App 的容器(Hermes 架构) |
+| 配套 | `容器接入完整指南-v2`(原理)、`提示词-容器自我优化-v2`(发给容器) |
+
+> **本 SOP 分五步**:① 体检 → ② 修改 → ③ 自审 → ④ 复验 → ⑤ 交付。
+> **每一步都有可执行的命令和判定标准**,不靠感觉。
+
+---
+
+## 第 0 步:先做这一项(10 秒,决定要不要继续)
+
+```bash
+python3 -c "import socket,time;t=time.time();socket.getaddrinfo('api.fmode.cn',443,socket.AF_UNSPEC);print('解析耗时=%.0f ms' % ((time.time()-t)*1000))"
+```
+
+| 结果 | 含义 | 动作 |
+|---|---|---|
+| < 100 ms | ✅ 正常 | 继续第 1 步 |
+| **> 1000 ms** | ❌ **DNS 卡顿** | **先修这个**,见 §1.1 |
+
+> 这个问题会让**每一轮响应慢 5 秒**,且**新会话也一样慢**。
+> 极易被误判成「模型慢」,实际是网络层。lj-test 实测 5023ms,修好后 21ms。
+
+---
+
+## 第 1 步:体检(只读,不改任何东西)
+
+### 1.1 DNS 解析(最优先)
+
+**根因**:glibc 默认把 A 和 AAAA 查询**并发塞进同一个 UDP socket**,
+Docker DNS 转发器处理不了,**丢掉一个**,只能等 5 秒超时。
+
+**对照实测**(lj-test):
+
+| 方式 | 耗时 |
+|---|---|
+| `AF_UNSPEC`(默认) | 5023 ms |
+| `AF_INET`(强制 IPv4) | 16 ms |
+
+**修法(按可行性排序)**:
+
+| 方案 | 覆盖 | 持久 | lj-test 可行 |
+|---|---|---|---|
+| ① compose 加 `dns_opt: [single-request]` | 全部进程 | ✅ | 需平台侧 |
+| ② `/etc/resolv.conf` 加 `options single-request` | 全部进程 | ⚠️ 重启覆盖 | ❌ root 属主 |
+| ③ s6 run 脚本 `export RES_OPTIONS=single-request` | 该服务及子进程 | ✅ | ✅ **已采用** |
+| ④ `usercustomize.py` 注入 | 仅 Python | ✅ | ✅ 备选 |
+
+> ⚠️ 方案 ③ 用 `${RES_OPTIONS:-single-request}` 会被外部值覆盖,
+> 想锁死写成 `export RES_OPTIONS=single-request`。
+
+### 1.2 结束信号(✅ reaction)
+
+```bash
+grep -n "MATRIX_REACTIONS" /opt/data/.env || echo "(未设置,默认 true)"
+```
+
+**实测正常表现**(`/sync` 回读):
+
+```
++0.9s  👀
++4.6s  正文
++4.7s  ✅      ← 晚于正文 0.1 秒
+```
+
+| 检查 | 期望 |
+|---|---|
+| `MATRIX_REACTIONS` | `true`(默认就是,别关) |
+| 每轮结束是否打 ✅ | 是(Hermes 内建,`adapter.py:2331`) |
+| 失败是否打 ❌ | 是 |
+
+> ❌ **不要用 `m.typing=false`**:实测早于正文 0.6 秒,会让 App「还没回就说完了」。
+> 如果装过发 typing 的插件,**禁用它**。
+
+### 1.3 状态上报(`extra.currentSessions`)
+
+**云函数**:`xlosi3m46q`,`field: "extra"`
+
+> 🔴 **必须放在 `extra` 里面**。直接当顶层字段会 **HTTP 400**(白名单没有它)。
+> 实测:`{field:"currentSessions"}` → 400;`{field:"extra", value:'{"currentSessions":[...]}'}` → 200。
+
+**每条记录的 6 个字段**:
+
+```json
+{
+  "sessionKey": "!roomId 或 $approot_xxx",
+  "userId": "@用户的mxid:fmode.cn",
+  "status": "running",
+  "task": "本轮提问首行,60 字内",
+  "turnStartAt": 1794405380428,
+  "updatedAt": 1794405433842,
+  "ttlMs": 600000
+}
+```
+
+| 字段 | 数据来源 | 不做的后果 |
+|---|---|---|
+| `sessionKey` | 优先 `thread_id`,退 `chat_id`(roomId) | App 匹配不上,状态永远不显示 |
+| `userId` | `state.db.sessions.user_id` | 多人共用时 A 跑步影响 B |
+| `task` | `pre_llm_call` 的 `user_message` 首行 | 顶栏只能显示「正在处理」 |
+| `updatedAt` | 写入时的 epoch ms | App 无法判断 running 是否过期 |
+
+**三条硬性要求**:
+
+1. 每个会话**单独一条**,不要只报容器级 `status`
+2. **心跳也要带上**(不能只在 turn 首尾写)
+3. `idle` 记录有 TTL 清理(建议 10 分钟)
+
+### 1.4 文件交付
+
+```bash
+ls /opt/data/plugins/plugin-deliverable-inject/ 2>/dev/null || echo "(未安装)"
+```
+
+| 组件 | 作用 | 必需 |
+|---|---|---|
+| `plugin-deliverable-inject` | 把「已上云」链接写进回复正文 | ✅ |
+| `fmode-deliverable-hook.py`(shell hook) | 上传 + 登记台账 | ✅ |
+| 云函数 `AlP56LCKFm` | 取 OBS 预签名 URL | ✅ |
+| 云函数 `THXAOGKiPK` | 登记文件台账(`registerFile`) | ✅ |
+
+**链接格式**(App 正则识别):
+
+```
+正文末尾空一行
+**已上云(可直接下载)**
+- https://s3.fmode.cn/...
+```
+
+域名必须是 `s3.fmode.cn` 或 `file-cloud.fmode.cn`。
+
+### 1.5 图文合并
+
+```bash
+ls /opt/data/plugins/plugin-media-text-merge/ 2>/dev/null
+grep -n "plugin-media-text-merge" /opt/data/config.yaml
+```
+
+| 检查 | 期望 |
+|---|---|
+| 目录存在 | ✅ |
+| 在 `plugins.enabled` 里 | **当前 lj-test 不在** → 图文被拆两轮 |
+| `plugin.yaml` 的 `name` | 与目录名一致(加载器按**目录名**匹配,不一致不影响) |
+
+> 启用后有 **2.5 秒合并窗口**(设计上的等待,不是性能损耗)。
+
+### 1.6 能力边界
+
+```bash
+ls -ld /opt/hermes /opt/data
+test -w /opt/data/plugins && echo "plugins 可写" || echo "不可写"
+```
+
+**lj-test 实测**:
+
+| 路径 | 权限 | 能做什么 |
+|---|---|---|
+| `/opt/hermes` | `root:root`,**不可写** | 只能读 |
+| `/opt/data` | `hermes` 属主 | 插件、配置、脚本 |
+| `/etc/hosts`、`/etc/resolv.conf` | root 只读 | 改不了 |
+
+---
+
+## 第 2 步:修改(按体检结果逐项做)
+
+### 2.1 修改清单
+
+| # | 改什么 | 在哪改 | 需要重启 |
+|---|---|---|---|
+| 1 | DNS | `/run/service/gateway-default/run` 加 `export RES_OPTIONS=single-request` | ✅ |
+| 2 | `currentSessions` 带 `userId`/`task`/`updatedAt` | `plugin-active-reporter/__init__.py` | ✅ |
+| 3 | 心跳带 `currentSessions` | 同上 | ✅ |
+| 4 | 启用图文合并 | `config.yaml` 的 `plugins.enabled` | ✅ |
+| 5 | 交付链接 | `plugin-deliverable-inject`(已装则核对) | ❌ |
+| 6 | 排版规范 | 提示词约束,见 §2.3 | ❌ |
+
+### 2.2 重启命令
+
+```bash
+s6-svc -r /run/service/gateway-default
+# 或
+/opt/hermes/.venv/bin/hermes gateway run --replace
+```
+
+> ⚠️ 重启会**打断正在进行的对话**。批量改完一次重启,不要每改一项重启一次。
+
+### 2.3 排版规范(不改代码,改行为)
+
+| 规则 | 错误 | 正确 |
+|---|---|---|
+| `~` 范围 | `8~16℃`(两对 ~ 被解析成 `~~删除线~~`) | `8~16℃` 或 `8 至 16℃` |
+| 表格 | 「客户端不支持表格,改用列表」 | **App 完整支持**,直接写标准 Markdown 表格 |
+| 过程行 | 工具行与结论混排 | 工具行**独占一行**,固定 emoji 开头 |
+| 回执 | 复述用户原文当过渡语 | 短回执(一句「收到」),正文单独一条 |
+| 图片 | `question` 参数抄用户原话 | 简短、去人称(如「读出截图中的文件名」) |
+
+---
+
+## 第 3 步:自审(改完自己先验一遍)
+
+**每一项都要贴实测输出,不许只说「已完成」。**
+
+### 3.1 检查表
+
+| # | 项 | 命令 | 通过标准 |
+|---|---|---|---|
+| 1 | DNS | `python3 -c "...getaddrinfo..."` | **< 100 ms** |
+| 2 | ✅ reaction | 发一条消息,`/sync` 读事件 | ✅ 晚于正文 |
+| 3 | `currentSessions` | 调 `xlosi3m46q` 读 `extra` | 每条有 `sessionKey`/`userId`/`status`/`task`/`updatedAt` |
+| 4 | 心跳带会话 | 等一次心跳后回读 | `currentSessions` 仍在 |
+| 5 | 图文合并 | 发图+文字 | **只回一轮** |
+| 6 | 交付链接 | 让它产出一个文件 | 正文有 `**已上云(可直接下载)**` |
+| 7 | 表格 | 让它用表格回答 | 正常渲染成表格 |
+
+### 3.2 自审脚本(可直接发给容器)
+
+见 `提示词-容器自我优化-v2-20261111.md` 的「交付格式」一节。
+
+---
+
+## 第 4 步:复验(App 侧独立回读,**不采信容器自述**)
+
+### 4.1 为什么必须独立复验
+
+实测教训:有容器口头说「改完了」,实际改的是**只读文件**,一个字没生效。
+另有容器说「`currentSessions` 是错的字段名」,实际是**放错了位置**(没放进 `extra`)。
+
+### 4.2 复验命令(App 侧跑)
+
+```bash
+# 会话级状态
+node scripts/verify-session-task.mjs
+
+# 状态裁决(需要容器有活跃会话时最准)
+node scripts/verify-unified-state.mjs
+
+# reaction 时序
+node scripts/verify-reaction-coverage.mjs
+```
+
+### 4.3 App 端专项复验
+
+```bash
+# 新建会话首条消息(NG0600 / 卡顿回归)
+node scripts/repro-new-session-send.mjs <agentId>
+```
+
+**通过标准**:输入框清空 < 500ms、消息出现 < 500ms、**页面错误为空**。
+
+> 🔴 这条最关键:单元测试测不出「模板渲染期写 signal」这类框架级错误,
+> 必须用真实浏览器。曾因此排查了 8 轮。
+
+---
+
+## 第 5 步:交付确认
+
+### 5.1 最终验收(App 里人工过一遍)
+
+- [ ] 新建会话 → 立刻发消息,**消息立即出现**
+- [ ] 容器回复完,**卡片 / 右上角 / 左侧同时**切换状态
+- [ ] 发图 + 文字,**只回一轮**
+- [ ] 让它出文件,右侧「成果」有卡片且刷新后仍在
+- [ ] 让它用表格回答,**表格正常渲染**
+- [ ] 长任务(20+ 工具调用)期间,状态**不提前**变「已完成」
+- [ ] 打开历史会话,**无打字动画**
+- [ ] 两个会话并发,状态互不干扰
+
+### 5.2 交付物
+
+| 物 | 位置 |
+|---|---|
+| 本 SOP | `docs/03-interface/容器接入标准流程-SOP-v3-20261112.md` |
+| 接入原理 | `docs/03-interface/容器接入完整指南-v2-20261111.md` |
+| 发给容器的提示词 | `docs/03-interface/提示词-容器自我优化-v2-20261111.md` |
+| 复现/验证脚本 | `scripts/repro-new-session-send.mjs`、`verify-*.mjs` |
+
+---
+
+## 附:lj-test 的实测基线(供新容器对照)
+
+| 项 | lj-test 实测值 |
+|---|---|
+| DNS 解析(修复后) | 21 ms |
+| 首字延迟 | 2~9 秒(上游模型 6~7s 为主) |
+| ✅ reaction 时序 | 晚于正文 **0.1 秒** |
+| `currentSessions` | 6 字段齐全,`sessionKey` 为 `!roomId` 或 `$approot_xxx` |
+| `thread_id` 覆盖率 | matrix 会话 54% 有值 |
+| 插件开销 | 4 个每轮执行,合计 **2~3 ms**(不是慢的原因) |
+| 模型 | `deepseek-v4-flash`,未开 reasoning |
+| `/opt/hermes` | root 属主,**不可写** |
+| 会话规模 | 242 会话 / 40 MB / 10601 消息(**不影响**新会话速度) |
+
+---
+
+*本 SOP 所有判定标准均来自 2026-11-11/12 的实测,非推断。*

+ 62 - 10
docs/03-interface/提示词-容器自我优化-v2-20261111.md

@@ -1,17 +1,56 @@
-# 提示词:让容器按 Fmode App 最新规范自我优化
+# 提示词:让容器按 Fmode App 最新规范自我优化(v2.1 · 2026-11-12)
 
 > **用法**:整份发给容器,它会自己对照检查、改代码、验证、回报。
-> **依据**:全部来自 2026-11-11 对 `agent-node-lj-test` 的实测与代码取证。
-> **配套文档**:`docs/03-interface/容器接入完整指南-v2-20261111.md`
+> **依据**:全部来自 2026-11-11/12 对 `agent-node-lj-test` 的实测与代码取证。
+> **配套文档**:`容器接入标准流程-SOP-v3-20261112.md`(操作步骤)、`容器接入完整指南-v2-20261111.md`(原理)
 
 ---
 
 ## 你的任务
 
-对照下面 **6 条检查项**逐条自查你当前的实现。
+对照下面 **7 条检查项**逐条自查你当前的实现。
 每条给出:**当前状态 + 证据(文件:行号 或 实测输出)+ 需要改什么**。
 
-**做不到的如实说「做不到 + 原因」,不要声称已完成。** App 侧会回读 Matrix 事件和云函数数据核实。
+**做不到的如实说「做不到 + 原因」,不要声称已完成。**
+App 侧会独立回读 Matrix 事件和云函数数据核实 —— 有过「口头说改完了、实际改的是只读文件」的先例。
+
+---
+
+## 检查项 0:DNS 解析(**先做这个**,否则一切都会慢 5 秒)
+
+```bash
+python3 -c "import socket,time;t=time.time();socket.getaddrinfo('api.fmode.cn',443,socket.AF_UNSPEC);print('%.0f ms' % ((time.time()-t)*1000))"
+```
+
+| 结果 | 判定 |
+|---|---|
+| < 100 ms | ✅ 跳过本项 |
+| **> 1000 ms** | ❌ 必须修 |
+
+### 根因(实测确认)
+
+glibc 默认把 **A 和 AAAA 查询并发塞进同一个 UDP socket** 发出;
+Docker DNS 转发器处理不了这种并发双包,**丢掉一个**,只能等 5 秒超时。
+
+**实测对照**(lj-test):
+
+```
+AF_UNSPEC(默认) → 5023 ms
+AF_INET(强制v4) →   16 ms
+```
+
+⚠️ 这是**全局问题**:不只 `api.fmode.cn`,`baidu.com` 也卡 5036ms。
+**每轮都要解析一次 → 新会话同样慢**,极易被误判成「模型慢」。
+
+### 修法(按可行性试)
+
+| 顺序 | 方案 | 命令 |
+|---|---|---|
+| 1 | `/etc/resolv.conf` 加 `options single-request` | 通常 root 只读,改不了 |
+| 2 | s6 run 脚本 export | `echo 'export RES_OPTIONS=single-request' >> /run/service/gateway-default/run` |
+| 3 | Python 层 `usercustomize.py` | 只覆盖 Python 进程 |
+
+改完**必须再跑一次上面的自测命令**,贴出修复后的耗时。
 
 ---
 
@@ -198,19 +237,32 @@ test -w /opt/data/plugins && echo "plugins 可写" || echo "不可写"
 
 ## 交付格式
 
-| 检查项 | 当前状态 | 证据 | 要改什么 |
+| 检查项 | 当前状态 | 证据(要贴实测数字/原文) | 要改什么 |
 |---|---|---|---|
-| 1. 结束信号 | | 文件:行号 / 实测 | |
-| 2. currentSessions | | | |
+| **0. DNS 解析** | | `___ ms`(修复前→修复后) | |
+| 1. 结束信号 | | `/sync` 看到的 ✅ 时刻 | |
+| 2. currentSessions | | `extra` 回读原文 | |
 | 3. 心跳带会话 | | | |
-| 4. 排版规范 | | | |
-| 5. thread 归属 | | | |
+| 4. 排版规范 | | 是否有「因不支持表格而降级」 | |
+| 5. thread 归属 | | thread_id 覆盖率 | |
 | 6. 能力边界 | | | |
 
 最后给一段结论:**哪些已满足、哪些要改、哪些做不到及原因**。
 
 ---
 
+## ⚠️ 五个真实踩过的坑(务必避开)
+
+| # | 坑 | 实际情况 |
+|---|---|---|
+| 1 | 以为字段名是 `usageSessions` | 错。**字段名就是 `currentSessions`**,只是必须放在 `extra` 里;直接当顶层字段才 400 |
+| 2 | 以为「矩阵客户端不支持表格」 | 错。**App 完整支持**,不要主动降级成列表 |
+| 3 | 以为 `m.typing=false` 可以当结束信号 | 错。实测早于正文 0.6 秒。**用 ✅ reaction** |
+| 4 | 声称「已完成」但没实测 | 有过容器改的是**只读文件**,一个字没生效。**必须贴实测输出** |
+| 5 | 以为「历史会话太多导致慢」 | 错。实测 242 会话 / 40MB **不进新会话上下文**。慢的是 DNS |
+
+---
+
 ## ⚠️ 三条纪律
 
 1. **不要声称「已完成」而不给证据** —— App 侧会回读 Matrix 事件和云函数数据核实。