修复会话切换兼容并重新打包发布
This commit is contained in:
55
.planning/2026-09-13-session-switch-diagnosis/findings.md
Normal file
55
.planning/2026-09-13-session-switch-diagnosis/findings.md
Normal 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,也会静默停用心跳,不再
|
||||
将协议探测错误显示到聊天区。新版服务端已声明该能力,安全重启后可恢复探测。
|
||||
10
.planning/2026-09-13-session-switch-diagnosis/progress.md
Normal file
10
.planning/2026-09-13-session-switch-diagnosis/progress.md
Normal 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 仍保持不重启。
|
||||
49
.planning/2026-09-13-session-switch-diagnosis/task_plan.md
Normal file
49
.planning/2026-09-13-session-switch-diagnosis/task_plan.md
Normal 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` | 首次运行时测试进程与临时服务异常退出,定向回归可复现通过 | 重新运行完整回归已通过,未发现新的历史控件故障 |
|
||||
10
.planning/full-outline-history/findings.md
Normal file
10
.planning/full-outline-history/findings.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# 发现记录
|
||||
|
||||
## 2026-09-13
|
||||
|
||||
- “定位”按钮对应 `user-outline-panel`;`buildUserOutlineItems()` 当前只扫描 `messagesDiv.querySelectorAll('.msg.user[data-message-id]')`,因此只包含已渲染的最近消息。
|
||||
- `session_info` 已携带 `historyCursor` / `historyTotal`,服务端 `load_history_page` 可按 `before` 返回旧消息页;现有前端收到该响应后会无条件 `prependHistoryMessages()`,所以不能直接用现有手动分页状态填充定位列表。
|
||||
- 旧消息点击定位需要同时处理两类目标:已渲染消息直接滚动;未渲染消息先通过消息索引加载对应页,再将聊天区滚动到目标。完整索引缓存不能依赖 DOM 元素 ID。
|
||||
- 定位面板本身已有独立 `max-height` 和 `overflow-y: auto`,不会因为完整索引而扩大聊天区;需要保留该隔离边界。
|
||||
- 旧消息点击定位使用独立的 `load_history_page` 目标页请求:按消息索引只取包含目标的一页,进入聊天区后再滚动到目标;完整定位索引请求只保存用户消息摘要,不渲染旧消息。
|
||||
- `session_history_chunk` 当前按 `activeHistoryPageRequest` 分支后统一调用 `prependHistoryMessages()`;独立定位请求必须用独立 request 状态在该分支前截获,否则会破坏“隐藏历史不占聊天空间”的要求。
|
||||
19
.planning/full-outline-history/progress.md
Normal file
19
.planning/full-outline-history/progress.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# 进度日志
|
||||
|
||||
## 2026-09-13
|
||||
|
||||
- 已定位问题:定位列表只读当前聊天 DOM,45 条窗口之外的旧用户消息被完全遗漏。
|
||||
- 已确认修复边界:完整历史只进入独立索引;聊天 DOM 和消息滚动空间保持最近窗口策略。
|
||||
- 已确认旧条目点击应走独立目标页请求;完整定位请求必须在 `session_history_chunk` 中提前截获,避免调用 `prependHistoryMessages`。
|
||||
- 新增失败回归:要求独立定位索引状态、分页请求函数、旧页拦截分支和按 `data-message-index` 定位入口;修改前按预期失败。
|
||||
- 已实现摘要索引、定位专用分页请求、旧页不渲染分支、面板加载/重试状态,以及隐藏条目按消息索引重新加载。
|
||||
- 全量 `npm run regression` 首次因历史处理器回归夹具缺少新状态桩失败,已补齐夹具;第二次完整回归通过。
|
||||
- 旧条目点击使用独立 `load_history_page` 目标页请求,只在用户明确点击时把目标所在页加载进聊天区;定位列表后台索引不会改变聊天 DOM 高度。
|
||||
- 最终验证通过:`npm run regression`、`node --check public/app.js`、`node --check scripts/regression.js`、`git diff --check`。
|
||||
- 目标页响应与完整定位分页均按独立 requestId 分支处理;断线和错误会清理悬挂请求,避免后续历史响应误消费。
|
||||
- 补齐无近期用户消息的边界:只要仍有可用旧历史,定位按钮保持可打开,以便触发完整索引分页。
|
||||
- 根据实际截图修正历史入口布局:`history-load-more` 移入 `#messages` 的首位,初始停在底部时不悬浮;滚到消息顶部才显示,继续滚动时随内容离开。
|
||||
- 为消息重绘和历史前插补充控件保留/插入锚点,避免重绘时丢失历史入口或把消息插到控件上方。
|
||||
- 用户进一步明确交互:不是“跟历史内容一起在当前视口显示”,而是“仅作为消息内容首行,滚到最顶才露出”;当前 DOM/CSS 已按此语义实现。
|
||||
- 运行中服务静态核验通过:`/app.js` 含定位目标页逻辑,`/index.html` 使用 `20260913-history-control-position` 样式版本,刷新即可生效。
|
||||
- 临时 `补齐定位列表完整历史 TO DO list.csv` 已清理;计划文件保留用于追踪本次设计决策。
|
||||
49
.planning/full-outline-history/task_plan.md
Normal file
49
.planning/full-outline-history/task_plan.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# 补齐定位列表的完整历史索引
|
||||
|
||||
## 目标
|
||||
|
||||
让“定位”列表展示当前会话的全部用户消息,即使聊天区只保留最近 45 条用于显示;旧消息只进入定位索引,不重新插入聊天 DOM,也不额外占用聊天滚动空间。
|
||||
|
||||
## 阶段
|
||||
|
||||
| 阶段 | 状态 | 验收标准 |
|
||||
|---|---|---|
|
||||
| 1. 核对定位列表与历史分页的数据链路 | complete | 明确当前定位数据仅来自 DOM,并确认可复用的历史分页响应 |
|
||||
| 2. 补充完整定位索引的失败回归 | complete | 回归能证明隐藏旧消息时定位列表仍应获得完整用户消息 |
|
||||
| 3. 增加独立的定位历史索引缓存 | complete | 打开定位列表时可按页读取完整历史,且不改变聊天消息 DOM |
|
||||
| 4. 支持旧消息定位加载与滚动 | complete | 点击未渲染旧消息会按消息索引加载所需页并滚动到目标 |
|
||||
| 5. 运行定向回归、语法和差异检查 | complete | 定向回归、JS 语法检查、git diff --check 全部通过 |
|
||||
| 6. 清理临时记录并交付修改结果 | complete | TODO CSV 删除,明确完整列表与聊天空间隔离的实现和验证结果 |
|
||||
|
||||
## 机器可读阶段状态
|
||||
|
||||
### Phase 1:核对定位列表与历史分页的数据链路
|
||||
**Status:** complete
|
||||
|
||||
### Phase 2:补充完整定位索引的失败回归
|
||||
**Status:** complete
|
||||
|
||||
### Phase 3:增加独立的定位历史索引缓存
|
||||
**Status:** complete
|
||||
|
||||
### Phase 4:支持旧消息定位加载与滚动
|
||||
**Status:** complete
|
||||
|
||||
### Phase 5:运行定向回归、语法和差异检查
|
||||
**Status:** complete
|
||||
|
||||
### Phase 6:清理临时记录并交付修改结果
|
||||
**Status:** complete
|
||||
|
||||
## 关键决策
|
||||
|
||||
- 不改变聊天区“最近 45 条 + 手动回看”的显示策略。
|
||||
- 不把定位列表的完整历史通过隐藏 DOM 塞进消息滚动区;旧消息只保存在轻量索引对象中。
|
||||
- 复用 `load_history_page` 的现有分页协议,新增请求标识分支,避免与用户点击“查看更早消息”的聊天分页互相抢状态。
|
||||
|
||||
## 错误记录
|
||||
|
||||
| 错误 | 尝试 | 处理 |
|
||||
|---|---|---|
|
||||
| 定向历史回归按预期失败:缺少 `currentOutlineHistoryState` | 1 | 证明旧实现没有独立定位索引;已补充失败契约后进入实现 |
|
||||
| 全量回归提取历史处理器时报 `activeOutlineHistoryRequest is not defined` | 1 | 为现有历史处理器回归夹具补充独立定位状态和合并函数桩;随后全量回归通过 |
|
||||
9
.planning/history-control-position/findings.md
Normal file
9
.planning/history-control-position/findings.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# 发现记录
|
||||
|
||||
## 2026-09-13
|
||||
|
||||
- 用户截图显示“还有 16 条更早消息 / 查看更早消息”以胶囊形式停在消息内容上方,用户明确要求它回到“上面再显示”,不要悬着跟随。
|
||||
- `public/index.html` 已将控件放在 `.messages-wrap` 内、`#messages` 之前,但 `public/style.css` 给 `.history-load-more` 设置了 `position: absolute`、`top`、`left` 和水平位移,因此它覆盖消息且随可视区域悬浮。
|
||||
- `.messages-wrap` 当前没有纵向 flex 布局,`#messages` 仅使用 `height: 100%`;改为顶部普通节点 + 下方 `flex: 1` 滚动区即可保留现有分页、prepend 和滚动补偿逻辑。
|
||||
- 本次最小实现不需要改变历史消息协议或 `requestOlderHistory`;只调整布局样式并补充静态契约回归,避免引入新的滚动副作用。
|
||||
- `public/index.html` 原先固定使用旧的 `style.css` 查询版本;已更新为 `20260913-history-control-position`,避免浏览器缓存旧悬浮样式。
|
||||
16
.planning/history-control-position/progress.md
Normal file
16
.planning/history-control-position/progress.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# 进度日志
|
||||
|
||||
## 2026-09-13
|
||||
|
||||
- 建立本次历史消息入口位置修复计划,尚未开始代码修改。
|
||||
- 完成定位:`.history-load-more` 使用绝对定位覆盖消息;`.messages-wrap` 未为控件预留布局空间。
|
||||
- 确定最小修复方案:父容器纵向 flex,控件置于顶部正常流,消息区使用 `flex: 1` 独立滚动。
|
||||
- 新增 `history-recall` 位置契约,先按预期捕获当前 `position: absolute` 实现;进入 CSS 布局修复。
|
||||
- 确认 HTML 顺序已经是控件在 `#messages` 之前,无需改动历史协议或分页脚本;通过父容器布局让该顺序真正占据顶部空间。
|
||||
- CSS 已移除绝对定位、top/left/transform 和毛玻璃覆盖效果;`.messages-wrap` 改为纵向 flex,`.messages` 改为剩余空间滚动。
|
||||
- `node scripts/regression.js --target history-recall` 已通过。
|
||||
- 窄屏沿用 `max-width: min(92%, 560px)` 与内容收缩规则,不新增覆盖定位。
|
||||
- `node --check public/app.js`、`node --check scripts/regression.js`、`git diff --check` 均已通过。
|
||||
- 临时 `修复历史消息入口位置 TO DO list.csv` 已按流程清理;本次源码改动涉及 `public/index.html`、`public/style.css` 和 `scripts/regression.js`。
|
||||
- 同步更新 `public/index.html` 的样式查询版本,且历史回看回归新增缓存失效断言;新增验证仍全部通过。
|
||||
- 运行中 `127.0.0.1:8002` 已直接返回新 CSS 和新样式版本 URL;无需重启,刷新页面即可加载。
|
||||
43
.planning/history-control-position/task_plan.md
Normal file
43
.planning/history-control-position/task_plan.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# 修复历史消息入口位置
|
||||
|
||||
## 目标
|
||||
|
||||
将“还有 N 条更早消息 / 查看更早消息”入口放回消息流顶部的正常文档流中,避免它在滚动时悬浮、跟随内容或遮挡消息;同时保留连续翻页和失败重试能力。
|
||||
|
||||
## 阶段
|
||||
|
||||
| 阶段 | 状态 | 验收标准 |
|
||||
|---|---|---|
|
||||
| 1. 定位历史入口的 DOM、脚本和样式链路 | complete | 明确控件是否由 fixed/sticky 定位、插入到哪个滚动容器及滚动时的行为 |
|
||||
| 2. 补充控件位置回归断言 | complete | 回归能锁定入口必须位于消息流顶部且不使用悬浮定位 |
|
||||
| 3. 调整历史入口的 DOM 插入和滚动逻辑 | complete | 控件作为消息列表首部普通节点显示,加载后不跟随视口悬浮 |
|
||||
| 4. 调整样式并兼容窄屏布局 | complete | 桌面和窄屏下入口均保持正常流布局,不遮挡消息内容 |
|
||||
| 5. 运行定向回归、语法检查和差异检查 | complete | 相关回归、JS 语法检查、git diff --check 全部通过 |
|
||||
| 6. 清理临时记录并交付修改结果 | complete | TODO CSV 删除,工作区仅保留本次修改并明确验证结果 |
|
||||
|
||||
## 机器可读阶段状态
|
||||
|
||||
### Phase 1:定位历史入口的 DOM、脚本和样式链路
|
||||
**Status:** complete
|
||||
|
||||
### Phase 2:补充控件位置回归断言
|
||||
**Status:** complete
|
||||
|
||||
### Phase 3:调整历史入口的 DOM 插入和滚动逻辑
|
||||
**Status:** complete
|
||||
|
||||
### Phase 4:调整样式并兼容窄屏布局
|
||||
**Status:** complete
|
||||
|
||||
### Phase 5:运行定向回归、语法检查和差异检查
|
||||
**Status:** complete
|
||||
|
||||
### Phase 6:清理临时记录并交付修改结果
|
||||
**Status:** complete
|
||||
|
||||
## 错误记录
|
||||
|
||||
| 错误 | 尝试 | 处理 |
|
||||
|---|---|---|
|
||||
| 定向历史回归按预期失败:历史控件仍为绝对定位 | 1 | 已证明新增契约能捕获当前悬浮实现,进入布局修复 |
|
||||
| `curl` 返回 23 | 1 | 仅因输出管道被 `rg -m 1` 提前关闭;同一响应已成功打印目标 CSS 片段,非服务错误 |
|
||||
Reference in New Issue
Block a user