|
@@ -0,0 +1,185 @@
|
|
|
|
|
+# 踩坑记录:会话隔离与容器状态判定(2026-11-08)
|
|
|
|
|
+
|
|
|
|
|
+| 字段 | 值 |
|
|
|
|
|
+|---|---|
|
|
|
|
|
+| 日期 | 2026-11-08 |
|
|
|
|
|
+| 来源场景 | 用户真机 + 桌面实测:新建会话串历史 / 容器不回复 / 状态显示不实 |
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 一、现象
|
|
|
|
|
+
|
|
|
|
|
+用户报告三个问题,串起来看像「一个 bug」,实际是**三条独立链路**:
|
|
|
|
|
+
|
|
|
|
|
+1. **新建会话仍能看到历史消息** —— 新建后发「你好」,上方出现几天前的内容和「加载更早的消息」
|
|
|
|
|
+2. **发消息容器不回复** —— 界面上只有自己发的「你好」,没有 agent 回复,并出现「检测到自动对话循环,已暂停自动回复」
|
|
|
|
|
+3. **容器状态不对** —— 没对容器做任何操作,却显示「正在执行」
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 二、根因
|
|
|
|
|
+
|
|
|
|
|
+### 2.1 新建会话串历史:三条回填路径,只修了两条
|
|
|
|
|
+
|
|
|
|
|
+历史内容进入消息区有三条路径,新建态保护最初只加在前两条:
|
|
|
|
|
+
|
|
|
|
|
+| 路径 | 有保护? |
|
|
|
|
|
+|---|---|
|
|
|
|
|
+| `storeVersion` effect(/sync 落地后历史回填) | ✅ 已有 |
|
|
|
|
|
+| `appendIncoming`(实时事件追加) | ✅ 已有 |
|
|
|
|
|
+| **`paginate()`(向上翻页)** | ❌ **漏了** |
|
|
|
|
|
+
|
|
|
|
|
+而且 `paginate()` 的 `paginateBack()` 翻的是**整个房间**的 timeline,不是当前 thread 的 history。
|
|
|
|
|
+
|
|
|
|
|
+**触发链**:
|
|
|
|
|
+```
|
|
|
|
|
+新建会话 → 首屏只有 0~1 条消息、不满一屏
|
|
|
|
|
+ → 移动端 fillViewportIfNeeded() / 桌面端贴底逻辑自动 paginate()
|
|
|
|
|
+ → 拉回 30 条房间历史
|
|
|
|
|
+ → 按会话过滤时把旧内容认领进来
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+页面上还会出现「加载更早的消息」按钮,因为 `hasMoreHistory` 取自 `paginationToken()` —— **房间级** token,跟当前 thread 毫无关系。
|
|
|
|
|
+
|
|
|
|
|
+**第二个坑**:`_ownedThreadRoots` 只在 `openReal()` 里重建,而 `startBlank()` **不经过 `openReal`**,集合里残留着上一个会话的容器线程根。`paginate()` 恰好走这个集合。
|
|
|
|
|
+
|
|
|
|
|
+### 2.2 「容器不回复」:判断错了两次
|
|
|
|
|
+
|
|
|
|
|
+**第一次判断(错误,已撤回)**:认定 `MATRIX_REQUIRE_MENTION=true` 是根因。
|
|
|
|
|
+
|
|
|
|
|
+依据是文档里一句「私聊必须 false」。但直接问容器、看 `adapter.py` 源码后发现:
|
|
|
|
|
+
|
|
|
|
|
+```python
|
|
|
|
|
+if not thread_id:
|
|
|
|
|
+ if is_dm:
|
|
|
|
|
+ synthetic = (self._dm_mention_threads and is_mentioned) or self._dm_auto_thread
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+`REQUIRE_MENTION` 与 `is_dm` 是**两个分支**,私聊走 `_dm_mention_threads`,跟那个开关无关。
|
|
|
|
|
+
|
|
|
|
|
+**实际根因**:容器**有回复**,约 13 秒后到达 Matrix,thread 根完全正确。是 **LoopGuard 把回复挡在了界面外**。
|
|
|
|
|
+
|
|
|
|
|
+`loop-guard.ts:142-150` 的 `repeated` 规则:
|
|
|
|
|
+- **不要求连续**(不是「连续 3 条相同」)
|
|
|
|
|
+- **不区分会话**(在 A、B、C 三个会话各发一条「你好」也能凑够 3 条)
|
|
|
|
|
+- `dismissedFor` 按 **agentId** 记而非 sessionId → 切会话时暂停态残留
|
|
|
|
|
+
|
|
|
|
|
+### 2.3 容器状态:4 套判定并存
|
|
|
|
|
+
|
|
|
|
|
+| 体系 | 判定函数 | 用在哪 |
|
|
|
|
|
+|---|---|---|
|
|
|
|
|
+| A 运行态(原始) | `AGENT_STATUS_TEXT` / `agentStatusTone` / `AGENT_TONE` | 移动通讯录、生命卡、两端工作台 |
|
|
|
|
|
+| B 接入态 | `agentPresence()` | PC 通讯录 |
|
|
|
|
|
+| C 合成态 | `resolveAgentState()`(**判心跳**) | 只有移动会话列表 |
|
|
|
|
|
+| D 任务行 | `TaskPulseService.describe()` | PC 头部、右栏 |
|
|
|
|
|
+
|
|
|
|
|
+同一个僵尸容器(45 天没心跳、`status=running`):移动列表判红「离线」,PC 左栏显示绿「正在执行」。
|
|
|
|
|
+
|
|
|
|
|
+更深一层:容器 reporter 把 `status: "running"` **硬编码**(`操作追踪.md:909-914`),没任务时也照报。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 三、解决过程
|
|
|
|
|
+
|
|
|
|
|
+| 步骤 | 做法 |
|
|
|
|
|
+|---|---|
|
|
|
|
|
+| 1 | 加运行时诊断(`__fmodeDebugOwner` 开关 + 归属判定现场日志) |
|
|
|
|
|
+| 2 | 读编译产物确认执行顺序,发现保护在「发送请求返回时」就解除了 |
|
|
|
|
|
+| 3 | 改为「等 timeline 里真出现本会话消息」才解除 |
|
|
|
|
|
+| 4 | 用真实账号 + 浏览器自动化,**每 3/8/15/25 秒三方采样**(Matrix / App 内存 / 界面) |
|
|
|
|
|
+| 5 | 采样结果显示:Matrix 上 13 秒时已有回复且 thread 正确;界面上 `loopPaused=true` |
|
|
|
|
|
+| 6 | 直接通过 Matrix 向容器发问(自造 thread 根,不污染会话),让它自查配置 |
|
|
|
|
|
+| 7 | 读 `adapter.py` 源码,确认 `REQUIRE_MENTION` 与 DM 无关,**撤回原判断** |
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 四、经验总结(可复用要点)
|
|
|
|
|
+
|
|
|
|
|
+### 4.1 判断「数据没到」之前,先确认数据到底在不在
|
|
|
|
|
+
|
|
|
|
|
+本次两次误判(犯同一个错):**基于代码推断就下结论,没去拿运行时证据**。
|
|
|
|
|
+
|
|
|
|
|
+正确姿势:写一个三方采样脚本,**同时**看「服务端有什么 / 前端内存有什么 / 界面显示什么」。三者一对比,是丢在传输、丢在过滤、还是丢在渲染,一目了然。
|
|
|
|
|
+
|
|
|
|
|
+### 4.2 时间点 ≠ 状态点
|
|
|
|
|
+
|
|
|
|
|
+「什么时候解除保护」这类设计,用**时间点**(发送返回后 / commit 后)永远会漏掉窗口期。应该用**状态**(目标条件是否真的成立)。
|
|
|
|
|
+
|
|
|
|
|
+本项目在这上面连续错了两次:
|
|
|
|
|
+- 第一次:`commit()` 时解除(本地记账,网络还没发)
|
|
|
|
|
+- 第二次:`sendReal()` 返回时解除(只是本机发出,服务端还没并入)
|
|
|
|
|
+
|
|
|
|
|
+最终改为:**timeline 里出现本会话消息**才解除。
|
|
|
|
|
+
|
|
|
|
|
+### 4.3 同一个语义只能有一个判定函数
|
|
|
|
|
+
|
|
|
|
|
+项目里 4 套状态判定并存,导致「同一容器在不同页面结论相反」。`resolveAgentState` 早就写好了(设计意图就是「唯一展示口径」),只是没人用。
|
|
|
|
|
+
|
|
|
|
|
+**规则**:数据的「呈现判定」必须收敛到单一纯函数,各页面只负责渲染。
|
|
|
|
|
+
|
|
|
|
|
+### 4.4 文档里的「配置建议」不等于「根因」
|
|
|
|
|
+
|
|
|
|
|
+`docs/03-interface/容器绑定App…20261103.md:169` 写着「私聊必须 false」,速查表也列着「发消息容器不回 ← REQUIRE_MENTION=true」。这两处都是**配置规范**,不是本次故障的解释。
|
|
|
|
|
+
|
|
|
|
|
+看到文档有对应条目时,还要**验证容器实际走的是不是那个分支**。本次差一点就直接按文档去重启容器了。
|
|
|
|
|
+
|
|
|
|
|
+### 4.5 让容器自查比自己猜快
|
|
|
|
|
+
|
|
|
|
|
+容器能读自己的 `.env`、`os.environ`、源码、进程状态。发一条结构化问题(要求「只读、不改配置、逐步回读证据」),它给的证据比前端推演戏可靠得多。
|
|
|
|
|
+
|
|
|
|
|
+本次就是靠它发现了 `container_environment` 里根本没有 `MATRIX_*`、值来自 `os.environ` + `adapter.__init__` 读一次。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 五、遗留(明日处理)
|
|
|
|
|
+
|
|
|
|
|
+### P0
|
|
|
|
|
+
|
|
|
|
|
+| # | 问题 | 定位 |
|
|
|
|
|
+|---|---|---|
|
|
|
|
|
+| 1 | LoopGuard 误判,把正常回复挡在界面外 | `core/loop-guard.ts:142-150`、`core/loop-guard.service.ts:86-100` |
|
|
|
|
|
+| 2 | 新建会话内存残留多条「你好」(用户只发一条,`messages()` 返回 3 条) | 待采样 `_messages` 每次变化定位 |
|
|
|
|
|
+
|
|
|
|
|
+### P1
|
|
|
|
|
+
|
|
|
|
|
+| # | 问题 | 说明 |
|
|
|
|
|
+|---|---|---|
|
|
|
|
|
+| 3 | 回复延迟约 13 秒,期间只有「正在思考…」 | 缺「已送达,等待回复」类反馈 |
|
|
|
|
|
+| 4 | 容器 reporter `status=running` 硬编码 | 前端已兜底,根治需容器整改 |
|
|
|
|
|
+
|
|
|
|
|
+### P2
|
|
|
|
|
+
|
|
|
|
|
+| # | 问题 |
|
|
|
|
|
+|---|---|
|
|
|
|
|
+| 5 | `belongsToSession()` 有重复代码(`:1670-1693` 与 `:1716-1739`),第二段永远进不去 |
|
|
|
|
|
+| 6 | `app-lj-test-v1`、`lj` 两个容器配置未核查 |
|
|
|
|
|
+| 7 | 移动端缺少 LoopGuard 提示(PC 有,移动端没有任何提示) |
|
|
|
|
|
+
|
|
|
|
|
+### 容器配置现状
|
|
|
|
|
+
|
|
|
|
|
+| 项 | 值 | 说明 |
|
|
|
|
|
+|---|---|---|
|
|
|
|
|
+| `MATRIX_REQUIRE_MENTION` | 文件已改 `false`,**进程仍是 `true`** | 已证明与本问题无关;未重启 |
|
|
|
|
|
+| `MATRIX_IGNORE_USER_PATTERNS` | `@agent-node-lj-test:fmode\.cn` | 不含用户 mxId,正常 |
|
|
|
|
|
+| 值的注入方式 | `os.environ`,`adapter.__init__` 读一次 | 改文件不热生效 |
|
|
|
|
|
+| 容器内能否自助重启 | ❌ 无 `docker.sock`、无 s6 控制权 | 需宿主执行 |
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 六、是否已沉淀到规则
|
|
|
|
|
+
|
|
|
|
|
+- [ ] 已写入 claude-rules 规则
|
|
|
|
|
+- [x] 已写入项目 docs(本文件 + `2026-11-08-修改留痕-会话隔离与容器状态统一.md`)
|
|
|
|
|
+- [x] `操作追踪.md` 已追加
|
|
|
|
|
+
|
|
|
|
|
+**建议反哺规则**(待评审):
|
|
|
|
|
+> 规则 8 补充:数据「没到达」类问题,须先做**服务端 / 内存 / 界面三方采样**再下结论,禁止仅凭代码推断定根因。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 七、关联
|
|
|
|
|
+
|
|
|
|
|
+- 修改留痕:`docs/00-tracking/2026-11-08-修改留痕-会话隔离与容器状态统一.md`
|
|
|
|
|
+- 操作追踪:`docs/00-tracking/操作追踪.md`(2026-11-08 各节)
|
|
|
|
|
+- 相关代码:`src/app/core/chat.service.ts`、`src/app/core/agent-state.ts`、`src/app/core/loop-guard.ts`、`src/app/core/assistant-turn.ts`
|
|
|
|
|
+- 相关文档:`docs/03-interface/容器会话与线程机制-20261028.md`、`docs/03-interface/会话存储与生命周期-20261101.md`
|