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

120 lines
8.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>
GET /api/sessions/:id?toolDetail=<JSON详情引用>&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
也能显示已产生正文和全部工具概要。停止运行但尚未归档的保留态显示后须收束生成状态。
- 首屏、分页及订阅恢复中,普通工具只传概要和 `detailRef`,不携带完整输入输出。
工具详情独立通过已鉴权 HTTP 读取,响应为 `session_tool_detail` 和完整 `tool`。
引用绑定会话、消息身份、工具位置/ID和内容摘要;运行引用另绑定回合对象的唯一作用域。
历史内容变更返回409,不能用错误位置冒充原工具;运行结束后可按助手消息身份读取归档。
计划、协作状态、提问和图片等直接显示的工具保留完整载荷。存储内容不受概要传输影响。
- HTTP 只读取 cc-web 自身已保存记录;首屏不扫描原生日志、不协调跨会话、不清未读。
归档按块有界读取;旧快照保留来源待核实状态。磁盘和 JSON 处理仍在同一服务进程,
传输拆分不代表所有同步 IO 已异步化。
- 会话列表优先读取归档元数据,禁止为刷新列表读取正文块或重建快照。
列表字段按归档修订及快照文件状态缓存,标题、未读和目标更新后立即失效。
大旧快照只扫描有界头尾;尾部从根对象结束符反向单次扫描完整顶层字段,
禁止对每个嵌套id/title候选重复解析整个后缀,否则会阻塞同进程的HTTP请求。
- 订阅校验元数据、绑定实时输出、清未读并冲刷已有跨会话回复,不发送历史消息。
比较清未读前的归档修订;有差异时由浏览器再通过 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` |
| 工具详情引用格式错误或跨会话 | 400 `tool_detail_invalid` |
| 引用对应工具已变更或不再存在 | 409 `tool_detail_changed` |
| 元数据、清单或历史块读取失败 | 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` 查询历史。
- 同一浏览器回归还覆盖32KB/s传输下100个大工具的首屏、折叠工具不构建详情DOM、
展开完整详情,以及运行工具完成归档后继续使用原引用读取详情。接口回归比较原始输入输出,
检查概要字节数、跨会话/过期引用、鉴权,以及订阅不能再重发大工具结果。
- `node scripts/session-metadata-unit.js`:高密度嵌套伪字段的解析次数有界、转义与截断边界、
缺失/损坏快照仍从归档列举、不读正文不写快照、归档修订更新使缓存失效。
- `node scripts/regression.js --target session-preview-metadata`:大旧快照的顶层元数据正确,
WebSocket刷新列表与HTTP首屏并发时,HTTP读取不能因列表解析排队数秒。
- 外部 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);
```
生产函数还必须执行控制器身份、请求代次和完成事件版本校验,不能仅复制此示意代码。