fix: 消除会话列表解析阻塞并按需加载工具详情
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
```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)
|
||||
@@ -29,11 +30,20 @@ WS ← { type: 'session_subscribed', sessionId, requestId, historyRevision, hist
|
||||
- 两者都提供 `sessionId/requestId/messages/historyTotal/historyBaseIndex/historyCursor/`
|
||||
`historyAvailable/historySource/historyRevision`;`requestId` 最多160字符,浏览器生成
|
||||
有界且唯一的代次标识,成功响应必须同时匹配会话和请求。
|
||||
- 首屏的 `runtime` 是完整 `resume_generating` 快照或 `null`,即使没有 WebSocket
|
||||
也能显示已产生输出。停止运行但尚未归档的保留态显示后须收束生成状态。
|
||||
- 首屏的 `runtime` 是 `resume_generating` 快照或 `null`,即使没有 WebSocket
|
||||
也能显示已产生正文和全部工具概要。停止运行但尚未归档的保留态显示后须收束生成状态。
|
||||
- 首屏、分页及订阅恢复中,普通工具只传概要和 `detailRef`,不携带完整输入输出。
|
||||
工具详情独立通过已鉴权 HTTP 读取,响应为 `session_tool_detail` 和完整 `tool`。
|
||||
引用绑定会话、消息身份、工具位置/ID和内容摘要;运行引用另绑定回合对象的唯一作用域。
|
||||
历史内容变更返回409,不能用错误位置冒充原工具;运行结束后可按助手消息身份读取归档。
|
||||
计划、协作状态、提问和图片等直接显示的工具保留完整载荷。存储内容不受概要传输影响。
|
||||
- HTTP 只读取 cc-web 自身已保存记录;首屏不扫描原生日志、不协调跨会话、不清未读。
|
||||
归档按块有界读取;旧快照保留来源待核实状态。磁盘和 JSON 处理仍在同一服务进程,
|
||||
传输拆分不代表所有同步 IO 已异步化。
|
||||
- 会话列表优先读取归档元数据,禁止为刷新列表读取正文块或重建快照。
|
||||
列表字段按归档修订及快照文件状态缓存,标题、未读和目标更新后立即失效。
|
||||
大旧快照只扫描有界头尾;尾部从根对象结束符反向单次扫描完整顶层字段,
|
||||
禁止对每个嵌套id/title候选重复解析整个后缀,否则会阻塞同进程的HTTP请求。
|
||||
- 订阅校验元数据、绑定实时输出、清未读并冲刷已有跨会话回复,不发送历史消息。
|
||||
比较清未读前的归档修订;有差异时由浏览器再通过 HTTP 对账。确认后返回清未读
|
||||
产生的新修订,避免元数据保存导致重复对账。
|
||||
@@ -56,6 +66,8 @@ WS ← { type: 'session_subscribed', sessionId, requestId, historyRevision, hist
|
||||
| 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`,历史继续可读 |
|
||||
| 新选择取消、迟到响应或不匹配订阅 | 不修改新视图及其加载状态 |
|
||||
@@ -79,6 +91,13 @@ WS ← { type: 'session_subscribed', sessionId, requestId, historyRevision, hist
|
||||
- `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,生产请求函数与渲染器保持原样,避免把登录前静态资源超时当作切换失败。
|
||||
|
||||
@@ -57,3 +57,13 @@ Questions to answer:
|
||||
<!-- Component-related mistakes your team has made -->
|
||||
|
||||
(To be filled by the team)
|
||||
|
||||
### MCP 结果组件
|
||||
|
||||
- 渲染 MCP 工具结果前,先区分直接业务对象和 `CallToolResult` 包装;包装结果优先
|
||||
读取 `structuredContent`,缺失或无效时再解析 `content` 中的文本业务 JSON。
|
||||
不应直接用包装对象的 `ok` 判断业务失败。
|
||||
- `isError: true` 表示工具调用失败,不能被内部 `ok: true` 覆盖;保留明确错误消息、
|
||||
未完成时的等待状态和畸形结果的兜底提示。解包限定标准层级,避免循环或递归嵌套。
|
||||
- 行为回归必须使用真实 MCP 包装形状,同时覆盖对象与 JSON 字符串、成功附件与
|
||||
直链图片、业务失败与 MCP 错误;历史回放和实时更新应共用相同的解析及渲染入口。
|
||||
|
||||
@@ -47,7 +47,8 @@ Questions to answer:
|
||||
- 历史游标表示服务端当前记录中尚未展示的消息数量。正数游标可以分页,与外部原始记录是否可确认恢复分开判断。
|
||||
- `historyAvailable=false` 只用于旧历史来源的核实说明,不能阻断已保存消息的正文或用户提问目录请求,也不能据此宣称消息已丢失或无法恢复。
|
||||
- 读取完已保存消息后,游标归零应停止请求;确有旧来源待核实时保留准确说明,正常完整历史不显示该说明。
|
||||
- `resume_generating`是完整运行态快照,以正文及全部工具权威替换当前流式状态,并复用同一逻辑助手气泡;不盲目拼接不同用户任务。
|
||||
- `resume_generating`以完整正文及全部工具概要权威替换当前流式状态,并复用同一逻辑助手气泡;不盲目拼接不同用户任务。普通工具的 `detailRef` 必须跟随运行状态保存,不能在重建工具时丢失。
|
||||
- 折叠工具只构建摘要,首次展开才读取完整详情并构建详情DOM。详情请求单独鉴权、超时、重试,不阻塞会话首屏;切换会话取消详情请求,迟到结果不得修改新视图。工具概要只供显示,禁止回写为完整历史。
|
||||
- 长气泡折叠只改变显示。完整正文保留在原始消息和DOM中;多次原生回复默认收起较早段,末段可见;单次超长回复可展开完整内容。
|
||||
- 折叠状态按会话与稳定消息身份记录;完成、切换和重新渲染不得把被折叠的内容当作已删除内容保存。
|
||||
- 复制按钮与定位、分支同排,仅从完整原始正文中复制最后一个原生回复段。按围栏外的 `agent-message-divider`切分,不以最后自然段或DOM的最后一个p元素判断;代码块、列表保留,时间分隔、工具和按钮不进入剪贴板。没有原生分界时复制整条回复。
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"creator": "shiyue",
|
||||
"assignee": "shiyue",
|
||||
"createdAt": "2026-10-14",
|
||||
"completedAt": "2026-10-15",
|
||||
"completedAt": "2026-10-19",
|
||||
"branch": null,
|
||||
"base_branch": "main",
|
||||
"worktree_path": null,
|
||||
|
||||
@@ -1,46 +1,76 @@
|
||||
{
|
||||
"browser": "Firefox 157.0.1",
|
||||
"browserEvidence": "/tmp/cc-web-session-http-FcFyDq/browser-evidence.json",
|
||||
"apiEvidence": "/tmp/cc-web-session-http-VTWGN4",
|
||||
"verified": [
|
||||
"WebSocket 假 OPEN 下首屏和完整运行输出",
|
||||
"HTTP 读取途中断开 WebSocket 仍完成",
|
||||
"拒绝 WebSocket 重连后仍可切换",
|
||||
"HTTP 分页58条顺序正确且无重复",
|
||||
"A 迟到响应不会覆盖 B",
|
||||
"读取与订阅之间完成的任务通过修订对账补齐",
|
||||
"损坏历史500立即结束加载并保留当前视图",
|
||||
"浏览器不发送 WebSocket 历史读取请求",
|
||||
"同会话代次、分页取消、完成交错、错误响应和渲染异常",
|
||||
"HTTP 鉴权、非法ID/游标、损坏块/清单、修订变化与运行快照"
|
||||
"checkedAt": "2026-10-19T02:57:49.386Z",
|
||||
"status": "verified",
|
||||
"rootCause": "WebSocket会话列表的尾部重复JSON解析阻塞服务主线程,HTTP进入处理函数前排队约79秒",
|
||||
"fixes": [
|
||||
"归档列表只读元数据、按修订缓存,不读取正文或重建快照",
|
||||
"旧快照尾部反向单次扫描顶层字段",
|
||||
"首屏和运行快照只传普通工具概要,展开后HTTP读取完整详情",
|
||||
"折叠工具延迟构建详情DOM与切换取消"
|
||||
],
|
||||
"otherPassed": [
|
||||
"activation": {
|
||||
"status": "online",
|
||||
"pid": 2886941,
|
||||
"startedAt": "2026-10-19T02:41:10.051Z",
|
||||
"servedFrontendMatchesSource": true
|
||||
},
|
||||
"productionBrowser": {
|
||||
"browser": "158.0",
|
||||
"evidence": "/tmp/ccweb-switch-live-AdoSFT/evidence.json",
|
||||
"stages": [
|
||||
{
|
||||
"id": "0b768f2e-acc2-4faa-8cce-841531cf3643",
|
||||
"durationMs": 352,
|
||||
"count": 12,
|
||||
"tools": 122,
|
||||
"streaming": false,
|
||||
"errors": []
|
||||
},
|
||||
{
|
||||
"id": "ccc82825-4d91-4db7-96fe-315a2c30061f",
|
||||
"durationMs": 430,
|
||||
"count": 8,
|
||||
"tools": 311,
|
||||
"streaming": false,
|
||||
"errors": []
|
||||
},
|
||||
{
|
||||
"id": "e28cff1a-100e-4225-a33e-d7745017ab79",
|
||||
"durationMs": 184,
|
||||
"count": 12,
|
||||
"tools": 227,
|
||||
"streaming": true,
|
||||
"errors": []
|
||||
}
|
||||
],
|
||||
"toolDetailComplete": true
|
||||
},
|
||||
"productionConcurrentListStress": {
|
||||
"evidence": "/tmp/ccweb-switch-live-3DL94a/evidence.json",
|
||||
"switches": 9,
|
||||
"minMs": 120,
|
||||
"maxMs": 462,
|
||||
"allDisplayedWithoutErrors": true,
|
||||
"toolDetailComplete": true,
|
||||
"readOnly": true
|
||||
},
|
||||
"passed": [
|
||||
"session-metadata-unit",
|
||||
"session-preview-metadata: 密集工具伪字段列表与HTTP并发",
|
||||
"session-http-load: 首屏/分页/错误/运行恢复/工具详情",
|
||||
"Firefox:32KB/s大工具首屏、折叠懒建、完整详情、断WS切换、迟到隔离、完成交错(/tmp/cc-web-session-http-xd0sGW)",
|
||||
"session-switch-race",
|
||||
"title-history-outline",
|
||||
"frontend-asset-version",
|
||||
"history-frontend-unit",
|
||||
"history-runtime-unit",
|
||||
"session-history-unit: 12项",
|
||||
"history-runtime-completion-unit: 15项",
|
||||
"JavaScript语法和git diff --check"
|
||||
"history-runtime-completion-unit:15项",
|
||||
"ccweb-display-image-unit",
|
||||
"孤立归档列表不重建快照(完整历史套件第10项)",
|
||||
"JS语法、git diff --check",
|
||||
"Bun baseline打包与MCP初始化"
|
||||
],
|
||||
"incompleteAt60Seconds": [
|
||||
"history-recall 大套件",
|
||||
"session-history-store-unit 全套:前24项已显示通过"
|
||||
"session-history-store-unit完整套件(60秒已输出24项通过)"
|
||||
],
|
||||
"activation": "verified",
|
||||
"activationEvidence": {
|
||||
"checkedAt": "2026-10-15T00:17:13.907+08:00",
|
||||
"status": "online",
|
||||
"pid": 2353800,
|
||||
"restarts": 86,
|
||||
"port": 8002,
|
||||
"version": "953d5e1a0bbdcf56",
|
||||
"httpStatus": 401,
|
||||
"httpErrorCode": "not_authenticated",
|
||||
"requestIdEchoed": true,
|
||||
"servedFrontendMatchesSource": true,
|
||||
"hasHttpQuery": true,
|
||||
"hasRealtimeSubscription": true
|
||||
"release": {
|
||||
"path": "dist-exe/cc-web-bun-linux-x64-baseline.tar.gz",
|
||||
"sha256": "ef5801d70fd04737c7c149335967a59b36f2039ab89b1e1bc5612ef997a15d6f"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
{"file":".trellis/spec/frontend/quality-guidelines.md","reason":"前端验证要求"}
|
||||
{"file":".trellis/tasks/10-17-display-image-render-failure/prd.md","reason":"检查真实故障与业务边界"}
|
||||
@@ -0,0 +1,4 @@
|
||||
{"file":".trellis/spec/frontend/index.md","reason":"前端开发规范"}
|
||||
{"file":".trellis/spec/frontend/quality-guidelines.md","reason":"前端验证要求"}
|
||||
{"file":".trellis/spec/frontend/component-guidelines.md","reason":"DOM 组件风格"}
|
||||
{"file":".trellis/tasks/10-17-display-image-render-failure/prd.md","reason":"真实故障形状与修复边界"}
|
||||
20
.trellis/tasks/10-17-display-image-render-failure/prd.md
Normal file
20
.trellis/tasks/10-17-display-image-render-failure/prd.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# 修复 ccweb_display_image 图片展示失败
|
||||
|
||||
## 需求
|
||||
用户截图中会话 0bb322b1-e9f8-47e7-9af1-970411bfec5a 连续八张图片均显示“图片渲染失败”。定位并修复现有显示链路,使已保存图片在刷新后正常显示,并给后续调用提供相同行为。
|
||||
|
||||
## 已确认原因
|
||||
真实存档 tool.result 是 MCP CallToolResult 的 JSON:`{content:[{type:'text',text:'业务 JSON'}],structuredContent:{ok:true,image,attachment},_meta:null}`。当前前端只解析一层 JSON 后直接检查 payload.ok,因此把成功的 MCP 包装结果识别为失败。服务端业务结果确实 ready。
|
||||
|
||||
## 实现边界
|
||||
- 在前端正确解析直接业务结果与 MCP structuredContent / content 文本形式。
|
||||
- 保留明确的业务错误消息、等待状态和异常格式兜底。
|
||||
- 使用实际历史结果形状补充行为回归,包括已有图片附件和直链图片。
|
||||
- 不修改用户历史数据,不改协议,不重新生成图片。
|
||||
- 保留工作区中已经存在的文件浏览器图片画廊改动;改动前核对 git diff。
|
||||
|
||||
## 验收
|
||||
- 截图中八个已有成功结果可以解析得到正确 attachment。
|
||||
- 渲染组件进入图片预览分支,不再显示误报失败。
|
||||
- 错误响应与不合法结果不被误报成功。
|
||||
- 必要测试通过;运行服务能提供新静态文件,若需要重启必须先查其他运行对话。
|
||||
26
.trellis/tasks/10-17-display-image-render-failure/task.json
Normal file
26
.trellis/tasks/10-17-display-image-render-failure/task.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"id": "display-image-render-failure",
|
||||
"name": "display-image-render-failure",
|
||||
"title": "修复图片展示失败",
|
||||
"description": "",
|
||||
"status": "completed",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "shiyue",
|
||||
"assignee": "shiyue",
|
||||
"createdAt": "2026-10-17",
|
||||
"completedAt": "2026-10-17",
|
||||
"branch": null,
|
||||
"base_branch": "main",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [],
|
||||
"parent": null,
|
||||
"relatedFiles": [],
|
||||
"notes": "",
|
||||
"meta": {}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
|
||||
@@ -0,0 +1,4 @@
|
||||
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
|
||||
{"file": ".trellis/spec/frontend/index.md", "reason": "前端入口规范"}
|
||||
{"file": ".trellis/spec/frontend/quality-guidelines.md", "reason": "保留既有前端加载行为"}
|
||||
{"file": ".planning/file-browser-image-gallery/findings.md", "reason": "现有图片弹层与加载入口"}
|
||||
25
.trellis/tasks/10-17-file-browser-image-gallery/prd.md
Normal file
25
.trellis/tasks/10-17-file-browser-image-gallery/prd.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# 文件预览全屏与目录切图
|
||||
|
||||
## 用户需求
|
||||
当前图片预览被容器缩放,需要点击放大全屏,并能左右切换当前文件夹图片;优先复用 cc-web 现成组件。
|
||||
|
||||
## 验收条件
|
||||
- 点击文件预览中已加载的图片打开已有附件图片弹层的全屏模式,图片适应整个可用视口,保留加载、失败、关闭操作。
|
||||
- 查看器提供前后按钮和左右方向键,在当前文件夹中按文件列表顺序切换支持的图片;跳过目录和文本。
|
||||
- 显示文件名、当前位置;单图与首尾行为清晰。
|
||||
- 移动端按钮可用,关闭全屏返回原文件浏览器,不误触关闭文件浏览器。
|
||||
- 图片按需鉴权加载,SVG 延续现有安全路径;切换时处理加载、失败、过期请求和 Blob URL 释放。
|
||||
- 文件夹变化或文件浏览器关闭时正确清理查看器状态。
|
||||
- 原有消息图片查看体验保持可用。
|
||||
|
||||
## 已定位的复用入口
|
||||
- public/app.js:openAttachmentPreviewModal / closeAttachmentPreviewModal;现有组件目前是居中大图弹层,没有缩放平移功能。
|
||||
- public/app.js:openFileBrowserFile / renderFileBrowserDirectory / loadFileBrowserDirectory / showFileBrowser。
|
||||
- 文件图片继续通过 fetchAuthImage 读取 /api/fs/image,SVG 用 data URL,其余用 Blob URL。
|
||||
|
||||
## 约束
|
||||
复用现有组件,不新增图库依赖;不做额外代码审计;本轮不打包、不提交、不推送、不重启。
|
||||
|
||||
## 验证结果
|
||||
- file-browser-image、file-preview-encoding、frontend-asset-version 专项回归通过。
|
||||
- Chromium 149 桌面和手机视口 20 项交互验证通过,页面无未捕获异常。
|
||||
26
.trellis/tasks/10-17-file-browser-image-gallery/task.json
Normal file
26
.trellis/tasks/10-17-file-browser-image-gallery/task.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"id": "file-browser-image-gallery",
|
||||
"name": "file-browser-image-gallery",
|
||||
"title": "文件预览复用全屏图片查看器并支持目录切图",
|
||||
"description": "",
|
||||
"status": "completed",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "shiyue",
|
||||
"assignee": "shiyue",
|
||||
"createdAt": "2026-10-17",
|
||||
"completedAt": "2026-10-17",
|
||||
"branch": null,
|
||||
"base_branch": "main",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [],
|
||||
"parent": null,
|
||||
"relatedFiles": [],
|
||||
"notes": "已复用附件图片弹层实现全视口图库;三项专项回归和 Chromium 桌面/手机 20 项交互验证通过;按用户要求跳过额外审计;未打包、提交、推送或重启。",
|
||||
"meta": {}
|
||||
}
|
||||
Reference in New Issue
Block a user