修复会话切换兼容并重新打包发布

This commit is contained in:
shiyue
2026-09-13 23:59:27 +08:00
parent d9db66ca8f
commit e785e3c5dd
15 changed files with 1224 additions and 70 deletions

View File

@@ -0,0 +1,55 @@
# 切换对话诊断发现
## 当前已知
- 会话文件仍包含用户消息,暂未发现数据层删除。
- 切换通过 WebSocket `load_session` 请求完成,刷新会创建新连接。
- 服务端日志存在大量心跳终止和断线重连记录。
- 前端同时处理会话缓存、加载请求代次、历史分页、运行态恢复和滚动位置。
## 可复核证据
- `server.js:5656-5658` 普通加载只发送最近 `INITIAL_HISTORY_COUNT = 12` 条;
`server.js:10890-10907` 同时发送完整 `historyTotal`、`historyCursor`、
`historyBaseIndex` 和 `historyTruncated`,普通大历史请求可以出现
`messages=12, historyTotal=99, historyCursor=87, historyPending=false`。
- 原逻辑 `public/app.js:2607-2611` 用 `!payload.historyPending` 推导
`complete`,因此上述部分快照会被标成完整;随后
`session_info → cacheSessionSnapshot → getSessionCacheDisposition →
showCachedSession` 可在 A → B → A 时绕过服务端重新加载。
- 修复后 `normalizeSessionSnapshot` 和 `isCompleteSessionSnapshot` 共同要求
游标归零、未截断、未等待分片且缓冲数覆盖总数;缓存写入和读取都复用同一
谓词。历史分片按 `historyBaseIndex` 合并,最后一页才转换为完整缓存。
- 部分快照现在进入独立 `sessionHistoryBuffers`,仅供后续 `load_history_page`
合并;`getSessionCacheDisposition` 永远不会把它当作可展示缓存。分页游标归
零后才调用 `cacheSessionSnapshot`,因此 A → B → A 不会复用半截历史。
- `logs/process.log` 中存在 `ws_heartbeat_terminate`,例如
`2026-09-13T07:30:03.445Z`,`missedPongs=4`、`lastActivityAgeMs=150471`;
`server.js:2216-2221` 的 `markWsActivity` 已会把 `isAlive` 恢复为 `true`,
因此该记录更符合浏览器/反向代理长期未返回 pong 的半断连接,而不是服务端
漏置位。
- 客户端原先只在 `onclose` 触发重连;现增加 `WS_CLIENT_HEARTBEAT_INTERVAL_MS`
(15 秒)与 `WS_CLIENT_HEARTBEAT_TIMEOUT_MS`(10 秒),通过
`client_heartbeat → client_heartbeat_ack` 主动识别浏览器仍显示 `OPEN` 的半断,
再复用现有 `onclose` 流程重放切换请求。
## 问题边界
- 会话文件未发现消息被删除;气泡消失的直接原因是前端错误缓存/重绘,
不是数据库或服务端历史丢失。
- WebSocket 处于浏览器 `OPEN` 但链路半断时,`send(load_session)` 可能无效;
刷新页面会强制新建连接。现在客户端最多约 25 秒主动发现并重连,真实反向
代理是否丢弃控制帧仍需浏览器现场或代理配置验证。
- 未重启现有 `ccweb`,因为当前会话列表中还有另一个 running 对话;源码修复
需在安全窗口重启后才会进入线上进程。
## 2026-09-13 新增线上证据
- 用户截图中的 `Unknown type: client_heartbeat` 证实线上 PM2 仍运行未包含
`client_heartbeat` 分支的旧 `server.js`;前端先部署心跳会被旧服务端当作业务
错误,并触发连接重连循环。
- `mcp__ccweb__ccweb_list_conversations(status=running)` 显示当前会话之外仍有
一个 running 会话,按项目运维约定本轮不能重启 PM2。
- 已在前端加入 `auth_result.features.clientHeartbeat` 能力闸门:旧服务端不声明
时不发送心跳;即使旧页面已发送并收到 Unknown type,也会静默停用心跳,不再
将协议探测错误显示到聊天区。新版服务端已声明该能力,安全重启后可恢复探测。

View File

@@ -0,0 +1,10 @@
# 诊断进度
- 2026-09-13:承接既有只读排查结果,创建本轮诊断记录;未修改业务代码、未重启服务。
- 2026-09-13:补充部分历史快照缓存回归契约;修改前因缺少统一完整性判断而失败,进入最小前端修复阶段。
- 2026-09-13:修复 `normalizeSessionSnapshot`、缓存判定和历史分片收口;局部回归与 Node 语法检查通过,尚未重启服务。
- 2026-09-13:复核确认服务端 `markWsActivity` 已正确处理 pong,未保留冗余服务端改动;定向回归、全量回归和 `git diff --check` 均通过。因存在其他 running 对话,未执行 pm2 重启。
- 2026-09-13:补充旧坏缓存、A→B→A 切换和“部分快照→连续分页→strong cache”回归;完整性谓词显式要求 `historyBaseIndex=0`,并移除 `normalizeSessionSnapshot` 的强制 complete 覆盖入口。
- 2026-09-13:增加客户端应用层心跳与服务端应答,半断连接不再只能等待 45 秒加载超时;定向回归、全量回归、语法检查和 `git diff --check` 再次通过。
- 2026-09-13:根据用户截图确认线上旧服务端返回 `Unknown type: client_heartbeat`;增加服务端能力声明与前端能力闸门,并对旧协议错误做静默兼容。当前仍有其他 running 会话,未执行 PM2 重启。
- 2026-09-13:补充断线期间的自动重试提示,并让高亮收口最多保留一个 `.session-item.active`;Node 语法、定向回归及重新执行的完整回归均通过。线上 PM2 仍保持不重启。

View File

@@ -0,0 +1,49 @@
# 修复切换对话消息气泡消失计划
## 目标
修复会话切换时“用户消息气泡暂时消失”的前端缓存状态错误,并保留对
WebSocket 半断导致切换超时的证据与边界;通过回归测试证明部分历史快照
不会再被当作完整会话缓存。服务不重启,避免影响其他运行中的对话。
## 验收标准
- 会话 A 历史大于初始窗口时,`session_info` 的 `historyCursor > 0`、
`historyPending = false` 快照不能命中 strong cache;A → B → A 必须重新
请求 `load_session`,不能只显示最近窗口。
- 完整快照必须同时满足:`complete = true`、`historyPending = false`、
`historyCursor = 0`、`historyBaseIndex = 0`、`historyTruncated = false`、
`historyBuffered >= historyTotal`。
- 历史分片按稳定消息索引合并;只有最后一页使游标归零时才写入完整缓存,
旧分页或旧切换响应不能污染当前会话。
- 完整快照仍可 strong cache 命中;部分快照不会直接渲染为缓存会话。
- 客户端应用层心跳每 15 秒探测一次,连续 10 秒未收到服务端应答就主动关闭
当前连接并复用现有重连/切换请求重放;服务端原生 pong 心跳仍负责底层连接。
真实代理半断链路不在本轮浏览器自动化模拟范围。
## 阶段
- [完成] 1. 核对会话数据、WebSocket 协议和缓存根因
- [完成] 2. 增加部分历史快照缓存回归测试
- [完成] 3. 修复快照完整性判定与完整加载收口
- [完成] 4. 运行静态检查和回归测试
- [完成] 5. 汇总运行态限制、根因和剩余风险
- [完成] 6. 修复旧服务端与新版前端心跳协议不兼容,并补充断线提示/单一高亮收口
## 验证命令
```bash
node --check public/app.js
node --check server.js
node --check scripts/regression.js
node scripts/regression.js --target session-switch-race
node scripts/regression.js --target history-recall
node scripts/regression.js --target codexapp-stale-running
node scripts/regression.js
git diff --check
```
## 错误记录
| 旧版 PM2 服务端不认识 `client_heartbeat` | 前端先于服务端重启上线心跳 | 增加 `auth_result.features.clientHeartbeat` 能力闸门,并静默兼容旧错误 |
| 完整回归首次出现 `historyLoadMore is not defined` | 首次运行时测试进程与临时服务异常退出,定向回归可复现通过 | 重新运行完整回归已通过,未发现新的历史控件故障 |