6.3 KiB
6.3 KiB
会话查询与实时订阅契约
1. 适用范围与触发条件
浏览器打开会话、查看更早消息、索引用户目录、定位消息和恢复运行视图时适用。 已有实现把这些读取绑定到 WebSocket 的 OPEN、鉴权和运行绑定;假 OPEN 时请求可能 丢在旧连接里,直到45秒超时。历史查询必须有独立 HTTP 通道,重连只恢复实时订阅。 这项代码事实不能用于断言每次现场故障都来自同一原因。
2. 接口与函数签名
GET /api/sessions/:id?requestId=<id>
GET /api/sessions/:id/history?before=<非负整数>&requestId=<id>
Authorization: Bearer <已有登录令牌>
handleSessionQueryApi(req, res, url, encodedId, historyPage = false)
resolveSessionHistory(session, { count, before, persistedOnly: true })
loadSession(id, { metadata: true, throwOnError: true })
WS → { type: 'subscribe_session', sessionId, requestId, historyRevision }
WS ← { type: 'session_subscribed', sessionId, requestId, historyRevision, historyChanged, isRunning }
3. 请求、响应与并发约束
- 首屏返回
type='session_info'、最近12条、historyPending=false和会话元数据。 分页返回type='session_history_chunk',向前最多24条,remaining=0。 - 两者都提供
sessionId/requestId/messages/historyTotal/historyBaseIndex/historyCursor/historyAvailable/historySource/historyRevision;requestId最多160字符,浏览器生成 有界且唯一的代次标识,成功响应必须同时匹配会话和请求。 - 首屏的
runtime是完整resume_generating快照或null,即使没有 WebSocket 也能显示已产生输出。停止运行但尚未归档的保留态显示后须收束生成状态。 - HTTP 只读取 cc-web 自身已保存记录;首屏不扫描原生日志、不协调跨会话、不清未读。 归档按块有界读取;旧快照保留来源待核实状态。磁盘和 JSON 处理仍在同一服务进程, 传输拆分不代表所有同步 IO 已异步化。
- 订阅校验元数据、绑定实时输出、清未读并冲刷已有跨会话回复,不发送历史消息。 比较清未读前的归档修订;有差异时由浏览器再通过 HTTP 对账。确认后返回清未读 产生的新修订,避免元数据保存导致重复对账。
- 首屏、正文分页、目录分页和消息定位均不检查 WebSocket 是否已连接或鉴权。 WebSocket 关闭不能取消 HTTP 读取、清除分页状态或结束首屏加载。
- 新选择、同会话新代次和退出视图取消旧 HTTP;控制器、视图代次和活动分页对象 三重隔离迟到结果。错误恢复保留已有视图和分页游标,取消后清除加载标志。
session_subscribed和带请求标识的运行快照必须属于当前订阅或视图。 订阅触发的历史对账保留实时输出,不再应用较早的 HTTP runtime,也不重复订阅。- 若查询期间已收到该会话的
done,弃用较早快照并重新查询终态;不能让完成后的 页面被旧isRunning=true快照覆盖。 - HTTP 45秒硬超时只限制真实请求;每个成功、失败或取消出口都清理计时器。
Server-Timing和session_http_query记录请求、阶段、状态、数量和耗时,不记录正文。
4. 校验与错误矩阵
| 条件 | HTTP / 订阅行为 |
|---|---|
| 缺失或无效 Bearer | 401 not_authenticated |
| URI 解码失败或 ID 含字母数字、连字符之外字符 | 400 history_invalid_id;不得清洗后别名到另一会话 |
| 游标为负数、小数或非安全整数 | 400 history_invalid_cursor |
| 会话确实缺失 | 404 session_not_found |
| 元数据、清单或历史块读取失败 | 500 history_read_failed,禁止吞成404 |
| 实时绑定失败 | WS session_subscribe_failed,历史继续可读 |
| 新选择取消、迟到响应或不匹配订阅 | 不修改新视图及其加载状态 |
所有 HTTP 错误携带 type='error'、code/message/sessionId/requestId。未完成身份
校验或 URI 解码时 sessionId 可以为空,不泄露磁盘路径及消息内容。
5. 正常、基本与失败场景
- 正常:57条归档关闭 WS 后首屏12条、继续分页24条和21条,按原顺序且无重复。
- 基本:运行中会话在假 OPEN 时仍显示历史和完整运行输出,实时订阅独立恢复。
- 交错:HTTP 读完后任务完成,订阅修订变化触发 HTTP 对账,最终回复显示且无生成占位。
- 失败:A 响应晚于 B,A 的正文和运行态都不能覆盖 B;坏历史返回500并保留当前视图。
6. 必需回归与断言
scripts/session-http-load-frontend-unit.js执行生产请求函数:无 WS 读取、真实 abort、 忽略取消后依然到达的响应、同会话代次、分页取消、完成交错、500和超时计时器释放。node scripts/regression.js --target session-http-load:隔离真实服务,断 WS 后查询、 首屏/分页、鉴权、坏 ID/游标、损坏清单/块、独立订阅、修订变化和完整运行快照。node scripts/session-http-load-regression.js --browser:真实 Firefox 经转发层测试 假 OPEN、读取途中断 WS、完全断连后切换、分页、迟到 A 响应、完成间隙及历史500。 断言浏览器不发送load_session/load_history_page/resume_session查询历史。- 外部 CDN 不稳定时可设置
CC_WEB_BROWSER_CDN_DIR,使用与页面相同版本的原始marked.min.js/highlight.min.js/mermaid.min.js/atom-one-dark.min.css本地夹具;仅转发层 改写这些静态 URL,生产请求函数与渲染器保持原样,避免把登录前静态资源超时当作切换失败。 - 所有数据、原生目录和 Codex home 锁路径必须隔离;不修改 HOME/CODEX_HOME 或生产锁。 单次测试最多60秒,不能用生产会话的读写代替隔离验证。
7. 错误与正确示例
// 错误:已有令牌也必须先等长连接;假 OPEN 时只能等待业务超时。
if (ws.readyState === 1 && wsAuthenticated) ws.send(JSON.stringify({ type: 'load_session', sessionId }));
// 正确:历史独立读取,成功应用后再接入实时订阅。
const snapshot = await fetchSessionQuery(sessionId, requestId, { signal });
handleServerMessage(snapshot);
requestSessionResume(sessionId);
生产函数还必须执行控制器身份、请求代次和完成事件版本校验,不能仅复制此示意代码。