# 切换对话诊断发现 ## 当前已知 - 会话文件仍包含用户消息,暂未发现数据层删除。 - 切换通过 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,也会静默停用心跳,不再 将协议探测错误显示到聊天区。新版服务端已声明该能力,安全重启后可恢复探测。