修复完整会话历史与长气泡交互并更新 CentOS 7 发布包

提交当前工作区全部源码、回归脚本、任务记录及重新构建的 Bun baseline 发布包。发布单文件 MCP 初始化及前端资源一致性验证通过。
This commit is contained in:
shiyue
2026-10-10 23:40:16 +08:00
parent 747771a048
commit 69b6db0f5b
71 changed files with 9536 additions and 503 deletions

View File

@@ -0,0 +1,45 @@
# 完成/恢复生产路径专项回归结果
日期:2026-10-10。负责范围:`handleProcessComplete` / `recoverProcesses` / Codex App 运行态 / 保存失败分支的生产函数行为回归,不改生产实现。
## 新增脚本
`scripts/history-runtime-completion-unit.js`:从 `server.js` 按函数边界抽取真实完成、恢复与失败分支函数,配真实 `lib/session-history-store.js`、真实 `lib/agent-runtime.js` 事件解析和临时目录执行;不复制实现、不读写真实会话、不启动外部进程。已注册进 `scripts/regression.js` 的 `assertHistoryRecallContract` 专项列表。
原有 13 项覆盖:
- CLI 纯文本完成回合保持字符串内容形状,重开可读(防止 `[object Object]` 形状回归)。
- CLI 混合内容块(含图片块)与 130 条纯工具调用完整归档。
- CLI 完成保存失败时保留运行目录与 `output.jsonl`,`done` 标记 `historySaved=false`;存储恢复后由 `recoverProcesses` 恰好补齐一次。
- CLI 清理失败时重复恢复按 `runKey` 去重,不重复入库。
- Codex App 完整 turn 运行态先于预览保存(60 万字符正文与工具调用不裁剪),恢复恰好入库一次。
- Codex App 会话暂不可读时保留 `_history/_turns`、run 目录与预览状态,恢复后恰好入库一次。
- Codex App 损坏运行态文件保留,不误入库。
- Codex App 完成路径 `saveSession` 失败时先保留完整运行态,恢复后恰好一条正文且不补中断提示。
- 初次用户消息保存失败时不创建运行目录、不改写磁盘会话。
- 跨会话回传保存失败保留 `ready` 重试机会,重试恰好一个气泡;崩溃重试按处理标记去重。
- 子会话冲刷保存失败保留队列与有界重试标记,存储恢复后提交。
- 任务看板元数据适配器按 ID 重读完整档案,只提交元数据,不接受截断对象覆盖 650 条历史。
- Codex App steer 保存失败不调用 `turn/steer`。
新增两个完成入口读取失败回归(共 15 项):
- CLI `handleProcessComplete` 的 `loadSession` 返回 `null` 时,真实完整归档保持原文,真实 run 目录与 `output.jsonl` 保留;应发送 `session_load_failed`、`done.historySaved=false`。恢复读取后,真实 `recoverProcesses` 恰好入库一次,成功提交后才清理。
- Codex App `handleCodexAppTurnComplete` 的 `loadSession` 返回 `null` 时,真实完整归档保持原文,`_history/_turns`、运行态预览与输出文件保留;最新完整 turn 保持正文、工具和 `completed` 状态。恢复读取后,真实 `recoverCodexAppTurnState` 恰好入库一次,不误补中断提示。
两个用例通过损坏真实档案文件制造读取不可用(`loadSession` 收敛为 `null`):完成阶段断言归档未被重写、运行态保留;恢复阶段写回原档案后由同一存储实例补交;没有用 `saveSession=false` 替代读取故障,也不依赖跨 harness 桩函数。
## 验证结果
- 原有 13 项在此前实现上通过;新增读取失败用例的修复前运行为 **13 项通过、2 项失败**,分别真实复现 CLI 运行目录被删除和 Codex App 完整 turn 被删除。日志 `/tmp/ccweb-completion-load-null-before.txt`。
- 旧实现对照:`timeout 60s node scripts/history-runtime-completion-unit.js --baseline-head`,新增两个用例分别在 CLI 运行目录保留断言和 Codex App 运行目录保留断言上失败;它们进入真实完成函数后才失败,不依赖缺少新函数或新 writer 返回值复现。对照日志 `/tmp/ccweb-completion-load-null-head.txt`;当前共 15 项失败,预期失败清单包含新增两项。
- 修复后 15 项最终结果:主线程改为“真实损坏档案 → 完成保护 → 修复档案 → 同实例恢复补交”后复跑 `timeout 60s node scripts/history-runtime-completion-unit.js`,**15/15 通过**(2026-10-10,日志 `/tmp/completion-unit-fixed.txt`)。
- 最终全量回归:`npm run regression`,**Regression checks passed**(19:52:25→19:53:59),日志 `/tmp/cc-web-regression-final-verify.log`;运行前后 `server.js`、`public/app.js`、`scripts/regression.js`、`scripts/mock-codex-app-server.js`、`lib/session-history-store.js`、`lib/agent-runtime.js`、`lib/codex-app-runtime.js` 的 sha256 一致。
- 此前全量回归:`npm run regression`,**Regression checks passed**;当次运行前后 `server.js` md5 均为 `94fe21059863b9b1c7088c14d4743ed2`,日志 `/tmp/cc-web-regression-completion-2.log`。共享源码后来继续变化,此结果不能代替最终树验收。
- `git diff --check`、`node --check` 通过。
说明:首次全量回归(日志 `/tmp/cc-web-regression-completion.log`)在 `scripts/regression.js:9622` 出现 steer 超时,当时存在并发验收和共享源码修改;后续串行重跑曾通过。现有证据只支持“steer 时序失败”,没有充分证据把诱因归结为并发负载,也不能以一次复跑通过排除其他时序缺陷。
## 边界
以上均为临时目录与夹具验证;未读取或修改用户在另一客户端看到的真实会话,未重启生产服务,未部署。不能据此声称那 45 条旧消息已经找回。

View File

@@ -0,0 +1,73 @@
# 生产入库与失败路径只读审计
审计时间:2026-10-10 16:45—16:50(Asia/Shanghai)。
整改复核(2026-10-10):本报告保留的是修复前证据,下述七项独立缺口及 child 冲刷失败问题均已修复。`history-runtime-unit.js` 和 `history-runtime-completion-unit.js` 分别通过 13 项真实生产路径回归;后者还覆盖初次用户输入与 steer 保存失败阻断发送、跨会话返回失败保留重试、运行恢复幂等及子会话冲刷失败保留队列。详情见 `runtime-test-result.md` 与 `completion-regression-result.md`;不代表异机目标会话已恢复。
范围:当前 PRD、`research/persistence-result.md`、后端质量规范,以及 Claude/Codex CLI、Codex App、跨会话返回的生产保存路径。实现代理仍在改代码,下述行号对应审计时源码,整合前需再次核验。仅写本报告;未修改实现文件、真实会话、配置,未重启服务。
## 独立缺口
| 优先级 | 位置 | 触发场景与实际损失 | 建议行为回归 |
| --- | --- | --- | --- |
| P1 | `server.js:9487`、`:9490`、`:9516` | Claude/Codex CLI 回合只有工具,没有正文或 `contentBlocks`。完成分支条件不含 `toolCalls`,因此零入库后删除运行目录,正常成功的工具结果全部消失。恢复分支已接受纯工具,正常完成仍遗漏。 | 使用真实 `handleProcessComplete`,输入空正文和完整工具调用;归档读回全部调用与长结果,只有归档成功才删除运行目录。 |
| P1 | `lib/agent-runtime.js:519`、`:530`、`server.js:9490` | Codex CLI 同一回合先纯文本、再 JSON 内容块、最后纯文本。解析器把纯文本放 `fullText`,JSON 放 `contentBlocks`;完成保存选择 `contentBlocks || fullText`,成功归档只保留中间内容块,丢前后正文。 | 真实解析器依次输入三类 `item.completed`,再用真实完成函数保存,重开完整档案后确认前文、内容块、末尾及顺序均存在。 |
| P1 | `server.js:9699`、`:9702`、`:9707` | Codex CLI 在服务停止期间完成含图片等非文本内容块的回合。恢复判断和正文仅用 `tempEntry.fullText`:仅图片时不保存且删除日志;文本加图片时成功保存正文但丢图片。 | 用隔离 JSONL 夹具覆盖仅图片、文本加图片、混合内容;真实 `recoverProcesses` 后读回完整内容块,保存失败时目录保留。 |
| P1 | `lib/agent-runtime.js:420`、`:433`、`:458` | Claude 流式输出以 `type: "user"`、`message.content[].type: "tool_result"` 返回工具结果。解析器只在 `assistant` 下处理 `tool_result`,没有 `user` 分支,最终工具仍 `done: false` 且无 `result`。正文成功保存也不能补回工具结果。 | 真实 `processClaudeEvent` 依次输入 assistant/tool_use、user/tool_result、assistant/text、result;从自身档案读回完整工具结果。保持用户消息与 runtime 工具结果的语义区别。 |
| P1 | `server.js:12420`、`:12456`、`:12514`;`:14959`、`:14972` | 用户提问初次保存或 Codex App steer 用户消息保存返回 false,调用者仍启动任务/发送 `turn/steer`。若存储在助手完成前恢复,完成函数重新加载旧归档并成功保存助手,用户提问已经缺失;恢复输出 JSONL/完整 turn 状态不能保证包含原用户正文与附件。 | 第一次归档写失败、之后恢复可写;确认不会在未保存提问时启动/接受 runtime,或明确保留且补交相同用户消息。重开后用户目录包含原提问,重复提交不重复。 |
| P1 | `server.js:8330`、`:8344` | 跨会话返回气泡保存失败被忽略,代码仍设 `returned`、删除待返回请求、返回 true,并可启动后续任务。源会话自身缺少返回气泡,请求队列也不再保留这次交付,用户看见的成功状态与保存结果不符。 | 注入源归档保存失败,待返回请求保持可重试状态且不能宣告已交付;恢复存储后重试一次,源完整档案恰好一个返回气泡,保留交叉会话元数据。 |
| P1 | 最新 `server.js:9708`、`:9714`、`:9719`、`:9722` | CLI 正文归档提交成功后、运行目录清理前崩溃,或清理本身失败。下次重启重复解析同一 JSONL,恢复消息没有 CLI turn 标识或提交去重检查,再追加同一完整正文。Codex App 已有 turnKey 去重,CLI 尚无。 | 原子提交成功后模拟清理失败,重开并重复恢复同一运行目录;归档始终恰好一次工具/正文记录,同时正常相同正文的不同回合不能被错误合并。 |
这七项独立于已经发现的 Codex App 完成前正文/工具裁剪、运行态裁剪和任务看板克隆后丢可信来源。
主线程优先要求的用户输入路径于 16:49 再次核验:最新
`handleMessage` 的 `saveSession(session)` 为 `server.js:12432`,
`handleCodexAppSteerMessage` 的 `saveSession(currentSession)` 为
`server.js:14969`,两者均忽略返回值并继续 runtime/steer。
这个分支应该先明确失败,避免出现“助手最终成功入库,但原用户提问缺失”。
## 次要失败观察
`flushPendingCcwebMcpChildSession`(`server.js:13219`)在保存前删除
`pendingCcwebMcpChildSessionFlushes`(`:13223`),末尾虽返回
`saveSession`(`:13237`),最终状态调用者和 timer 调用者不检查结果。
如果归档瞬时失败且后续没有新 child 事件,最后的工具协作状态不再自动重试。
建议把队列移除放到保存成功后,并用最终子线程事件加一次失败/成功冲刷回归。
这里主要损失父工具的最终展示状态;不能据此推断子会话自身完整档案已经丢失。
## 已出现的修复与不应重复取消的限额
- 当前运行时创建已传 `preserveFullHistory: true`;CLI 注入的 `truncateObj` 为恒等函数。先前 CLI 的 256 KiB 正文尾部保留、2,000 字符工具结果等生产裁剪已被本轮修改绕过。本报告不再将它们作为未修独立缺口。
- 当前 `writeCodexAppTurnState` 先调用完整 `sessionHistoryStore.saveTurn`,之后才写有界运行预览;加载优先完整 turn 状态,恢复内容不再二次裁剪,恢复归档保存失败提前返回并保留完整状态。完整 turn 档案枚举也已进入重启恢复。
- 当前 CLI 正文完成保存失败和 Codex App 完成保存失败已经显式报告 `session_save_failed`、`historySaved: false` 并保留恢复材料,正常失败清理问题正在被修复。
- CLI 重启恢复已移除超过 16 MiB 就跳过输出并删除目录的分支,并在归档保存失败时 `continue` 保留输出目录。
- `sendRuntimePreview`、会话传输、标题摘要、MCP 参数校验、文件预览是独立用途的有界数据。只要未把其结果回写唯一完整正文/工具内容,不需要取消这些限额。
- `loadCodexAppTurnState` 仍对旧版有界状态设置加载限制。新增完整 turn 来源已优先读取;旧文件异常处理与完整档案损坏恢复应单独判断,不宜为解决新记录完整保存直接删除所有异常防护。
- 检索未发现除主线程已通知的任务看板适配器以外的明显 `cloneJson`/`JSON.parse(JSON.stringify(...))` 后调用统一保存的生产入口;一般标题、模式、usage、MCP 状态等保存返回值忽略主要影响元数据确认,不等同于本报告的唯一内容清理路径。
## 隔离证据
在 `timeout 60s node` 中通过 `vm.runInNewContext` 提取生产完成、恢复、跨会话返回和 child 冲刷函数,结合真实 `createAgentRuntime`。所有 fs、WebSocket、队列和保存均为内存桩;没有真实会话写入。
前三组命令分别约 0.03 秒、0.05 秒和 0.03 秒,退出码 0,断言证实:
1. CLI 纯工具回合零次保存、一次目录清理。
2. Codex CLI 混合内容成功保存丢前后纯文本。
3. 仅图片内容块的 CLI 重启恢复零次保存、一次目录删除。
4. Claude user/tool_result 解析后工具仍未完成且结果为空。
5. 跨会话源保存返回 false 时,交付函数仍返回 true 且队列请求被删除。
6. Child 最终状态冲刷保存失败,待冲刷队列仍被删除。
7. CLI 恢复保存再次失败时不会清理 pending 输出;存储恢复后可成功提交。
8. CLI 成功提交后模拟清理失败,再执行恢复会产生两条相同助手消息。
用户提问与 steer 保存失败项通过生产控制流确认,尚未执行其大依赖入口的完整行为夹具。Claude 的协议场景采用合成标准事件验证解析器;未读取本机或异机真实 runtime 日志,本报告不宣称异机实测已完成。
完成失败后的 pending 恢复结论:CLI 路径已经符合“保存失败明确失败且保留运行输出”的行为,后续存储恢复可重新归档;未修缺口是恢复内容块与提交后幂等性。Codex App 通过完整 turn 档案、失败时保留、恢复时检查 `codexAppTurnKey` 的静态链路已经覆盖同类条件,本子审计未再发现另一个正常完成后清理唯一完整 turn 内容的独立分支。磁盘持续不可写时只能如实报告失败,本报告不把物理无法提交误判为成功保存。
## 工具调用简报
- 时间:2026-10-10 16:35—16:50,Asia/Shanghai。
- `codebase-memory-mcp`:`list_projects` 与 `index_status(home-cc-web)` 返回 ready;`search_code` 收敛 `server.js` 的 `saveSession`,`search_graph`/`get_code_snippet` 定位 CLI 完成/恢复、运行事件和 Codex App 状态函数。本子线程服务可用,没有重启或重建索引。
- FastCtx `glob`/`inspect_local_file`:扫描可用技能与按指定路径读取 PRD、保存报告、质量规范和生产局部源码。个人技能中无必要匹配的只读失败审计技能。
- 本地 `rg`/局部 `sed`:交叉核验修改中的最新源码与行号。实时修改已使部分 codebase-memory 片段落后,结论以最终局部源码和隔离生产函数证据为准。
- Node VM:只读源码加内存桩验证六个失败场景;每次总超时 60 秒;无外部请求,无敏感内容输出。

View File

@@ -0,0 +1,88 @@
# 前端历史分页实现结果
记录时间:2026-10-10 16:09(Asia/Shanghai)。
## 变更
- `public/app.js`:正文与用户提问目录按正数游标请求已有历史,`available=false` 只用于更早记录待核实的说明。
- 正文显示可读取的已保存消息数量,保留 loading、重试及来源提示。游标归零后移除加载按钮;来源状态为 false 时仍显示「更早的记录尚待核实,已保存的消息可正常查看。」
- 用户目录首屏没有提问、但仍有历史游标时保持入口可用;读取失败可重试,索引自动逐页完成。
- 来源尚未返回时,初始和重置状态不展示待核实提示;服务端明确返回的 `historyAvailable=false` 仍保留。普通完整历史没有限制提示。
- `scripts/history-frontend-unit.js`:新增行为回归,直接执行生产请求、响应处理、正文合并及目录索引函数;只模拟浏览器节点与网络发送。
## 失败与通过证据
先修改专项测试,尚未修改 `app.js` 时执行:
```bash
timeout 60s node scripts/history-frontend-unit.js
```
退出码为 **1**,失败断言为「原始来源不可用时仍须请求已保存的45条正文」,实际请求数 **0**、期望 **1**。这是旧分页门禁导致的行为失败。
完成最小修复后,以下命令全部退出 **0**:
```bash
timeout 60s node scripts/history-frontend-unit.js
node --check public/app.js
node --check scripts/history-frontend-unit.js
git diff --check -- public/app.js scripts/history-frontend-unit.js
```
已覆盖:
- 57 条已保存消息、首屏 12 条、游标 45、来源不可用:正文按 `45→33→21→9→0` 分页读全,顺序正确,无重复。
- 首屏全部为助手消息:目录入口仍可点击,并自动索引全部旧用户提问,顺序正确,无重复。
- 正文和目录分别验证未认证、连接未打开、连接不存在及当前会话不匹配时不发送请求。
- 正文和目录分别过滤错误会话、过期响应;重复请求去重,重复响应不污染结果。
- 两类请求失败后释放在途状态、保留游标、显示重试并发送新请求,loading 正常。
- 游标为零时两类请求均停止;更早记录待核实的提示仍保留且无正文加载按钮,完整历史不显示限制。
- 原有稳定消息 ID、缓存合并、重连和流式完成去重回归继续通过。
## 余下边界
- 前端只读取服务端已提供的历史;完整历史持久化及原始日志合并仍由后端实现负责。
- `available=false` 也可能只是合并边界尚未确认,不能断言原始文件不完整或消息未保存。旧版如果已经裁掉消息且没有可确证来源,前端无法补出;本次保留核实提示,不将旧存量历史宣称为完全恢复。
- 尚未在用户所指的其他客户端实际验证。本次只执行隔离的专项测试和语法检查,没有操作真实会话、远端或服务。
## 隔离浏览器归档验收
已将归档重启流程合入 `scripts/history-browser-check.js`。测试只创建临时目录、临时密码和隔离
`server.js` 子进程;运行中把夹具 rollout 重命名为 `.isolated-hidden`,停止并重新启动隔离
服务,重新登录并打开夹具会话,未读取或改写真实会话、生产服务或真实 rollout。
执行命令:
```bash
node --check scripts/history-browser-check.js
git diff --check -- scripts/history-browser-check.js
timeout --signal=TERM --kill-after=5s 60s node scripts/history-browser-check.js
```
结果全部通过,证据目录为:
`/tmp/cc-web-history-browser-60Uwf0`
浏览器阶段包含 `loaded`、`archivedAfterRestart`、`archivedPaged`、`running`、
`refreshed`、`reconnected`、`completed`、`completedRefreshed`。归档阶段的证据为:
- 重启后 `session_info.historySource` 全部为 `archive`,`historyTotal=148`,
`historyAvailable=true`。
- 归档分页共 6 个 `session_history_chunk`,每块来源均为 `archive`、总数均为 148,
协议下标连续覆盖 `0..147`,协议稳定 ID 数为 148。
- 重启后首屏为 12 条,下标为 `136..147`;点击分页直到最早的「早期问题 0」出现后,
浏览器实际读回 148 条,稳定 ID 唯一数为 148,顺序与夹具完整 ID 序列一致。
- 读完完整归档后提示为空,没有「无法恢复」或「待核实」。
- `/tmp/cc-web-history-browser-60Uwf0/archived-after-restart.png` 记录重启后首屏;
`/tmp/cc-web-history-browser-60Uwf0/archived-paged.png` 记录分页读到最早消息。
- 重启后仍继续完成原有运行中发送、刷新、WebSocket 重连、完成事件和完成后刷新检查。
本轮只修改 `scripts/history-browser-check.js` 和本报告;隔离测试进程、Firefox、
relay 及隐藏的 rollout 均在 `finally` 中清理或恢复。
## 工具调用简报
- `codebase-memory-mcp`:`list_projects` / `index_status(project="home-cc-web")` 确认索引 ready;用 `search_code` / `search_graph` 定位,再用 `get_code_snippet` 读取正文分页、目录分页及渲染函数。
- 随后查询历史控件 CSS 时索引工具返回 `Transport closed`;降级为 FastCtx 精确本地读取和搜索,没有重启服务。
- `FastCtx`:读取任务文档、规范、技能目录及专项测试;执行范围限定的测试、语法和差异检查。所有单次测试命令最大 60 秒,无重试或退避。
- 浏览器验收:使用本地 `Firefox/geckodriver`、临时隔离服务和夹具;测试完成后读取
`/tmp/cc-web-history-browser-60Uwf0/evidence.json` 及截图核对协议总数、稳定 ID 和阶段结果。

View File

@@ -0,0 +1,15 @@
# cc-web 完整历史持久化设计调查
调查日期:2026-10-10。范围:只读检查代码、现有任务和规范,只写入本研究文档。当前 PRD 仍在更新,本文描述完整历史存档设计,不将旧 PRD 的快照分页范围视为最终范围。
## 明确推荐
新增 cc-web 自有的完整会话归档,将其作为 `saveSession` 的持久化提交点;现有受限 JSON 快照保留为列表、首屏和搜索预览缓存。`loadSession` 优先从已提交归档恢复完整会话,原生 Codex/Claude 日志只作已确认边界的补充来源。完整归档不得调用现有 `sanitizeSessionForPersist`、`sanitizeMessageForPersist` 或其文本、消息、数组裁剪逻辑。
最小版本建议使用独立 `lib/session-history-store.js`,同目录临时文件 + 原子 rename 保存一份紧凑 JSON。归档包括完整持久化会话字段,而不只包括 `messages`,以便完整归档写入成功、受限快照失败时,重启仍恢复本次提交的线程绑定、清空状态和消息顺序。没有归档的旧会话先读取现有快照,保留“前缀未知”的事实,不能因为新建归档就宣称旧历史完整。
正常业务读取完整归档后,保存的是当前完整消息数组的精确状态,而不是永久 upsert。这样自然保留 `/clear` 的空数组、撤回/删除和在 steer 用户消息之前插入旧 turn 输出的语义。对来源为受限快照、旧修订或导入的消息数组,使用来源与修订信息区分:它们不能凭消息数量或长度覆盖完整归档。导入应按可靠标识确认重叠后合并,保留 cc-web 独有气泡;无法确认时拒绝破坏性替换并保留旧归档。
保存顺序:构造完整、可序列化的提交记录 → 写完整归档并原子提交 → 更新内存归档状态 → 尽力写受限快照 → 通知搜索/统计索引。完整归档失败时不覆盖旧快照、不更新成功状态,返回失败。完整归档成功而受限快照失败时,归档已经是本次持久状态,`loadSession` 必须读它;推荐保存结果表示“归档已提交”,缓存失败单独记录并在下次修复,避免上层误以为用户消息未保存而重复执行。
本文后续部分继续记录源码证据、接入点、成本和必须回归的边界。

View File

@@ -0,0 +1,52 @@
# 完整历史持久化实现记录
日期:2026-10-10。负责范围:服务端完整归档、保存入口与生命周期、专项回归。
## 失败回归证据
先新增 `scripts/session-history-store-unit.js`,使用生产保存、裁剪、读取、resolver 函数及临时文件系统。原生日志测试通过真实 JSONL 文件和生产 `createCodexRolloutStore` 解析,未使用消息数组 mock 替代读取。
旧实现运行 `timeout 60s node scripts/session-history-store-unit.js` 退出码 1,9 项失败、2 项通过,约 1.2 秒:
- 650 条保存后重开只读回最后 180 条及提示;快照上限调为 500 仍损失 150 条。
- 长正文、125 条工具调用、工具参数/结果、长数组和深层元数据未完整保存。
- 受限短预览再次保存被接受,旧修订也可覆盖后来已提交的追加。
- 完整档案不存在,原子归档失败保护和缓存失败提交语义不成立。
- 真实 rollout 确认前缀后 resolver 仅在内存合并,没有建立自身档案。
完整原始输出在临时日志 `/tmp/ccweb-persistence-before.txt`;本文件记录可审计结论,未写入任何真实会话或配置。
## 已确定设计
- 新增 `lib/session-history-store.js`,`sessions/_history/<id>.json` 为完整会话的原子提交点。
- 归档保存完整 JSON 数据,排除既有运行态句柄,不进入消息/正文/工具/数组预览裁剪。
- 可信加载来源与归档修订使用内存身份信息,root 快照或任意短预览不能伪造完整加载。
- 可信当前修订精确替换消息数组;可信旧修订基于加载基线合并本次变更,保留后来已提交的追加和元数据,冲突字段保留已提交值。未经可信加载的短预览拒绝覆盖档案。
- 完整归档成功、快照失败视作已提交;`loadSession` 无 root JSON 仍优先读归档,列表接入归档孤儿缓存修复。
- 旧截断状态继续保留。只有现有边界规则确认原生前缀后,才把补齐结果提交档案。
## 实现与验证
核心模块与 `saveSession` / `loadSession` 接入已完成;统一生命周期已覆盖:
- `resolveSessionHistory` 使用既有边界规则确认后先提交归档,提交失败只返回原快照。
- `loadSession` 即使 root JSON 缺失仍读取归档;列表枚举孤立档案,按归档修订修复有界缓存,缓存继续失败时使用内存有界预览展示。
- 导入统一先读取可信档案;已有会话只有可靠边界确认后才补前缀,保留 cc-web 专有气泡和顺序。
- `/clear` 建立新的清空修订,旧运行对象不能复活原线程历史或返回输出;删除会话同步删除自身档案。
- 迁移保留原始快照备份,使用统一完整归档入口,不再直接写无界 root JSON。
当前专项检查:
- `timeout 60s node scripts/session-history-store-unit.js`:完整持久化回归 **21 项通过**。
- `timeout 60s node scripts/session-history-unit.js`:原服务端历史回归 **12 项通过**。
- `timeout 60s node scripts/history-frontend-unit.js`:前端历史分页、目录索引、认证隔离和重试通过。
- `timeout 60s node scripts/history-runtime-unit.js`:生产 runtime 完整采集 **13/13 通过**,覆盖长正文、纯文本/结构块混合、纯工具、Claude tool_result、Codex App 工具数组及有界传输。
- `timeout 60s node scripts/history-runtime-completion-unit.js`:完成、恢复及保存失败生产路径 **13/13 通过**,已接入全量回归。包含真实保存失败注入、保留唯一运行输出、存储恢复后恰好提交一次、跨会话与 child 冲刷重试,以及任务看板 650 条历史保持完整;详情见 `completion-regression-result.md`。
- `npm run regression`:**Regression checks passed**。
- 隔离 Firefox 归档重启验收:`historySource=archive`、148 条消息、6 个分页块、下标连续且无重复;原始日志夹具移开后仍可读。
生产路径补充保护:普通纯文本 CLI 回合保留字符串内容形状;结构化或混合回合才使用内容块数组;初次用户消息、steer、跨会话回传均在保存成功后才继续;CLI/Codex App 恢复在会话读取或归档保存失败时保留运行目录和完整运行态,避免清理唯一可恢复记录;CLI 使用 runKey 防止清理失败后的重复入库。
旧版本对照:`timeout 60s node scripts/history-runtime-unit.js --baseline-head` 复现预期的 9 项历史缺陷,证明专项断言确实覆盖原问题。核心文件 Node 语法检查与 `git diff --check` 通过。
边界:以上均为隔离临时目录、夹具或本地回归;未读取或修改用户在另一客户端看到的真实会话,未重启生产服务,不能据此声称那 45 条旧消息已经找回。

View File

@@ -0,0 +1,99 @@
# 运行时完整采集回归结果
本子任务仅新增 `scripts/history-runtime-unit.js` 和本报告;不修改生产实现、
已有 regression 或其它单测。测试直接加载生产 factory 和事件处理函数,
使用内存 WebSocket 接收器,不读取或写入真实会话,不操作远端或重启服务。
## 首次代码状态与失败证据
时间:2026-10-10 16:52:53(Asia/Shanghai)。
并行实施代理已先完成默认完整采集改造,因此未复现“长正文被裁剪”的修复前
状态,仅记录当时真实结果:
| 场景 | 期望 | 首次只读探针实际 |
| --- | --- | --- |
| Claude 长正文 | 1,050,006 字符原样保留 | 1,050,006 字符,完全相等 |
| Codex CLI 长正文 | 1,050,006 字符原样保留 | 1,050,006 字符,完全相等 |
| Codex App 单次长增量 | 1,050,006 字符原样保留 | 1,050,006 字符,完全相等 |
| Claude assistant/tool_use → user/tool_result | 工具 `done=true`,结果为输入原值 | `done=false`,`result` 缺失 |
| Codex CLI 纯正文 → JSON 文本块 → 纯正文 | 保存路径选用的内容包含三段正文 | `fullText` 含三段,`contentBlocks` 只有中间正文 |
## HEAD 旧版本失败对照
命令:
```bash
timeout 60s node scripts/history-runtime-unit.js --baseline-head
```
脚本通过 `git show HEAD:lib/agent-runtime.js`、`git show HEAD:lib/codex-app-runtime.js`
读取真实旧版本源码,用 `node:module` 在内存中编译,不写入工作区。旧版本
共复现 9 项预期失败(退出码 0 表示“对照成立”):
| 失败用例 | 旧版本表现 |
| --- | --- |
| Claude CLI 长正文完整采集 | 正文被 256 KiB 上限裁剪 |
| Codex CLI 长正文完整采集 | 正文被 256 KiB 上限裁剪 |
| Codex App 长正文完整采集 | 单次增量被 16 KiB 流上限裁剪 |
| Claude user/tool_result 长结果完整采集 | `user` 事件未处理,工具仍 `done=false` |
| Codex CLI 长工具结果完整采集 | 结果被 2,000 字符上限裁剪 |
| Codex App 超限工具输入、结果与数组完整采集 | entry 内输入/结果被预览限额裁剪 |
| 超出预览条数上限 40 的 130 条工具仍完整采集 | 工具数组只留前 120 条 |
| Codex CLI 正文与 JSON 内容块混合采集 | `contentBlocks` 与 `fullText` 二选一导致丢两端 |
| 生产 WebSocket 正文与工具预览保持有界 | 缺少 `tool_end`(Claude `user` 缺口的连带失败) |
`Claude CLI JSON 字符串工具参数使用生产解析`、`Codex App 增量与完成事件补尾
且不重复`、`重连/历史工具列表预览遵守生产条数上限` 在旧版本即已通过,属于
防回退断言,不计入基线失败。
## 当前实现复跑
命令:
```bash
timeout 60s node scripts/history-runtime-unit.js
```
当前(2026-10-10 17:24,Asia/Shanghai)实现共 13 个用例,13 个全部通过,
退出码为 0。实施代理已补齐 Claude `user/tool_result` 事件处理,以及 Codex
CLI 混合正文的统一完整内容保存路径。
最终输出:
```text
通过:生产 runtime 完整采集与有界传输
checkedTextChars=1050006
checkedToolInputChars=24006
checkedToolResultChars=48006
checkedToolCalls=130
webSocketPreviews=275
passed=13
```
覆盖项包括:三条长正文完整采集、Claude JSON 字符串工具参数经生产
`sanitizeToolInput` 解析且不裁剪、Claude `user/tool_result` 长结果、Codex
CLI 长工具输入与长结果、Codex App 超限输入/结果/数组、130 条超限工具的
entry 完整性与 130 次预览事件、Codex CLI 混合正文、Codex App 增量与完成事件
补尾去重、正文与工具预览的传输上限、40 条工具预览上限。
## 覆盖边界与局限
- 生产完成/重启恢复、输入保存失败、跨会话失败、child 队列、CLI 幂等性由
主后端代理的 `scripts/session-history-store-unit.js` 覆盖,本脚本不重复。
- 脚本只覆盖运行时事件到 entry/WebSocket 的链路,不覆盖真实会话落盘、
真实 WebSocket 连接、前端渲染与生产服务重启。
- 预览断言依赖 `server.js` 中的常量与函数源码提取;若这些符号被重命名,
脚本会以断言失败提示,而不是静默放宽。
## 工具调用简报
- 时间:2026-10-10 16:45—17:25,Asia/Shanghai。
- `codebase-memory-mcp`:`list_projects` 与 `index_status(home-cc-web)` 返回
`ready`;`search_graph`、`search_code` 与 `get_code_snippet` 定位
`processRuntimeEvent`、`processCodexAppNotification`、`sanitizeToolInput`、
`safeJsonParse`、`sanitizeMessageForTransport` 等生产入口,未重建索引、
未重启服务。
- FastCtx 文件工具未返回所需能力,降级使用 `rg`、`sed` 做只读校验。
- Node 回归:内存 WebSocket + 生产 sanitizer,不落盘;`--baseline-head`
仅内存编译 Git 旧版本模块。