Explorar el Código

docs: 补充容器会话级状态修复过程与一次误判教训

- 问题链:心跳不带 currentSessions → thread_id 为 NULL 匹配不上 → _extra_base 空覆盖
- App 三级匹配:thread 根 → roomId → 回落全局
- 误判记录:把「extra 只剩 2 个键」判成业务键丢失,实际业务数据都在顶层字段
  教训:判断数据是否丢失要看全字段,不能只看某个字段的子集

Co-Authored-By: Claude Code <noreply@anthropic.com>
Huccct hace 58 minutos
padre
commit
decc6ea0ab

+ 37 - 0
docs/00-tracking/2026-11-11-修改留痕-输出中残留与结束信号.md

@@ -153,3 +153,40 @@ const live = !closedBlocks && !waitingTooLong
 - [ ] 把 `m.typing` ephemeral 这个坑写进《容器接入规范提示词》(已完成)
 
 
+
+---
+
+## 四、容器会话级状态修复(2026-11-11 晚)
+
+### 4.1 问题链
+
+| # | 问题 | 根因 | 修法 |
+|---|---|---|---|
+| 1 | `extra.currentSessions` 为空 | 心跳只报 `heartbeatAt`+`balance`,不带会话表 | 容器:心跳带上 `currentSessions` |
+| 2 | 报了也匹配不上 | `thread_id` 大量为 NULL(实测 46%)→ 容器只能报 Hermes 内部 id | 容器:改报 roomId;App:加 roomId 退路匹配 |
+| 3 | `_extra_base` 空字典覆盖 | GET 失败时把 `{}` 永久缓存,之后每小时拿空底写 | 容器:失败不缓存,跳过本次 extra 写入 |
+
+### 4.2 App 侧三级匹配(`agent-state.ts::sessionStateOf`)
+
+```
+① sessionKey 含 thread 根($approot_xxx)  ← 最精确,一房多会话不串
+② sessionKey 含 roomId(!xxx)             ← 退路,DM 无 thread 时靠房间认
+③ 都不中 → undefined                        ← 回落全局状态
+```
+
+**依据**:容器实测 `chat_id`(roomId)100% 稳定——212 条会话全是 `!` 开头,
+同一房间跨 22 天、83 条会话不变。
+
+**代价**(已知接受):roomId 粒度比 thread 粗,同房间多条会话时「任一在跑」会被认为
+「这条在跑」。相比「完全匹配不上、状态永远不显示」,这个误判可接受。
+
+### 4.3 一次误判(记录以免重犯)
+
+容器和我都把「`extra` 只剩 `currentSessions` + `extraUpdatedAt`」判成「业务键被覆盖丢失」。
+
+**实际不是**:回读 agent 记录的全部字段,`botName` / `host` / `channels` / `balance` / `usage`
+全在**顶层**,一个没少;App 也只从 `extra` 读 `currentSessions` 一项。
+`extra` 只有 2 个键本来就是正常状态。
+
+**教训**:判断「数据是否丢失」要看**全字段**,不能只看某一个字段的子集;
+容器塞的探测键消失 ≠ 业务键消失。