# 运行中气泡续接独立审查 ## 审查结论 服务端续接、完整运行态保存、取消门禁及前端折叠/复制通过。本次发现 3 项 P2 均已修复并通过原函数回归;当前没有未解决的 P1/P2,审查通过。 审查服务端基线为 `/tmp/ccweb-streaming-retry-server-before.js`,仅核对本任务新增差异;未把上一任务完整历史归档改动重复归入本任务。 ## 发现及处理 | 级别 | 问题与触发条件 | 位置 | 当前状态 | | --- | --- | --- | --- | | P2 | A 的 `turn/start` 已发出但原生 ID 未知时取消,旧 entry 曾被释放;此时开始同线程 B,A 的 `turn/started` 先于 A RPC 回包到达,会按 thread 匹配到 B,旧通知能够写入或结束 B。 | `server.js` 的 `findCodexAppEntryByRuntime`、`startCodexAppTurn`、`handleCodexAppAbortSession` | 已修。未知 ID 取消保留门禁,通知/RPC 确认身份后只中断精确旧回合;RPC 超时仍保留门禁,app-server 明确退出后提交旧正文并释放。 | | P2 | 重连可显示完整 105/130 条工具,但完成或历史读取仍经 40 条工具上限,变成 40 条记录加省略标记;`done` 重建气泡会再次缩短工具列表、输入及结果。 | `server.js:5796` 的 `sanitizeMessageForTransport`;`public/app.js:9237` 的完成气泡重建 | 已修。完成/历史单消息读取保留完整 `toolCalls`,实时 `tool_start`/`tool_end` 预览上限保留。 | | P2 | 单条回复超过折叠阈值且包含多个自然段时,默认 `.assistant-single-collapsed` 以 420px 高度裁剪正文。点击“定位”仍直接选择末文本段并滚动,没有展开,目标段落继续被 `overflow:hidden` 隐藏。 | `public/app.js:2468` 的 `scrollAssistantBubbleToLastSection`;`public/style.css` 的 `.assistant-single-collapsed .assistant-reply-content` | 已修。定位前点击已有展开控件,同步展开状态后再计算末段坐标;原函数测试验证展开先于定位,多原生段气泡保持原收起状态。 | ## 失败证据与修复验证 取消竞态先用真实路由/退休函数 VM 验证:退休 `turnId:null` 的取消 entry 后,建立同线程 `turnId:null` 的 B,路由旧 A 通知得到 `{ retired: 0, routedToNewEntry: true }`。 新增真实开轮/路由/通知回归在修复前失败于“旧 A 身份未知时必须阻止同线程 B 开始”,实际 `beforeIdentity.ok=true`。工具完整读取断言在修复前失败于 130 条工具不能按预览上限替代完整列表。两项修复后均转绿。 取消回归执行真实 `startCodexAppTurn`、`handleMessage`、`findCodexAppEntryByRuntime`、`findCodexAppRouteByRuntime`、`adoptCodexAppUnroutedTurn`、`handleCodexAppNotification`、`handleCodexAppAbortSession`、`handleCodexAppServerExit` 和完成保存函数;仅模型 RPC、时钟及无关配置/生命周期回调由隔离夹具替代。覆盖: - A 通知先于 A RPC 返回,未知身份时禁止 B 开轮;身份确认后中断 A,再开始 B;A 的迟到完成不能结束 B,B 的正文只归档一次且前后文完整。 - A RPC 超时后仍保留未知身份门禁;晚到 ID 通知触发精确 A 中断,随后 B 正常完成。 - A 取消且 ID 未知时 app-server 明确退出,先保存 A 既有完整前文,再释放 B 门禁;B 完整完成。 ## 最终执行结果 所有单元/专项命令使用 60 秒上限,存储测试使用 `/dev/shm` 隔离临时目录。此前普通 `/tmp` 存储专项达到 60 秒上限;改用 `/dev/shm` 后完整专项约 12 秒通过,无需放宽超时。 | 命令 | 结果 | 退出码 | | --- | --- | --- | | `TMPDIR=/dev/shm timeout 60 node scripts/codexapp-retry-history-unit.js` | 21/21,含真实 mock 协议三轮续接 | 0 | | `TMPDIR=/dev/shm timeout 60 node scripts/history-runtime-unit.js` | 13/13,完整 130 条工具及 24006 字输入、48006 字结果读回;实时预览仍有界 | 0 | | `timeout 60 node scripts/assistant-bubble-controls-unit.js` | 最终 8/8,末原生段、多自然段/代码、Markdown 空白语义、围栏及未完整分界不误复制,单条长回复定位先展开 | 0 | | `timeout 60 node scripts/streaming-retry-frontend-unit.js` | 5/5,多次续接、刷新/重连/切换、完成与保存失败气泡 | 0 | | 两个所改测试文件 `node --check`、限定文件 `git diff --check` | 通过 | 0 | ## 验证版本 | 文件 | SHA-256 | | --- | --- | | `scripts/codexapp-retry-history-unit.js` | `8ff35d8e3722fec5fd2ba8fac5ffac94b69e711f5bcddf2150bc10c865e73b54` | | `scripts/history-runtime-unit.js` | `0e1e549f2ece5337eb7808f235b6cadc4ec9192c7cf9561a77b71eca66c35ae4` | | `server.js` | `c964060ad121bf16edd44caf94226455cf8a434bb8b14b9f5c434a3215d6b404` | | `lib/codex-app-runtime.js` | `6fe66921ed8998ed1b1478eb804c0ad965ff40fff7a18dd8086e3de42b67c122` | | `public/app.js` | `433b4e68a9e91440eada679199a86ca7a84cf28b0f979eb3f46044be99fe757e` | | `scripts/assistant-bubble-controls-unit.js` | `f60bb8c1c393503c461885fd2ee7ef7636dacd2a94c3654ddcac470dc449ca63` | ## 验证边界 - 本代理只修改上述两个测试文件及本报告,不修改实现、真实会话、原生 rollout、生产服务或其他任务 CSV。 - 本代理没有执行全量回归或真实浏览器验收;主代理执行全量回归。前端代理已完成正式浏览器验收,退出码 0,证据在 `/dev/shm/ccweb-streaming-retry-browser-G8wkN6`,本代理只读核对报告与实现。 - 原函数/隔离 mock 回归证明修复实现,不证明生产已部署或目标会话旧前文已补回 UI。 - 完整正文用于复制;折叠仅控制 DOM 可见性。原生段复制包含该段原始 Markdown、多自然段及代码,不含工具、分隔时间或按钮。 ## 工具调用简报 本地使用 FastCtx 读取任务契约、当前源码及基线差异,执行隔离回归;codebase-memory 起初索引 ready 且成功定位函数,之后 snippet 返回索引不可用,降级到精确本地源码读取。没有对外发送会话内容。