Files
cc-web/.trellis/spec/backend/session-query.md

101 lines
6.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 会话查询与实时订阅契约
## 1. 适用范围与触发条件
浏览器打开会话、查看更早消息、索引用户目录、定位消息和恢复运行视图时适用。
已有实现把这些读取绑定到 WebSocket 的 OPEN、鉴权和运行绑定;假 OPEN 时请求可能
丢在旧连接里,直到45秒超时。历史查询必须有独立 HTTP 通道,重连只恢复实时订阅。
这项代码事实不能用于断言每次现场故障都来自同一原因。
## 2. 接口与函数签名
```text
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. 错误与正确示例
```js
// 错误:已有令牌也必须先等长连接;假 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);
```
生产函数还必须执行控制器身份、请求代次和完成事件版本校验,不能仅复制此示意代码。