修复完整会话历史与长气泡交互并更新 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

@@ -20,6 +20,7 @@ This directory contains guidelines for backend development. Fill in each file wi
| [Quality Guidelines](./quality-guidelines.md) | Code standards, forbidden patterns | To fill |
| [Logging Guidelines](./logging-guidelines.md) | Structured logging, log levels | To fill |
| [Codex MCP 配置输出](./codex-mcp-config.md) | 旧类型配置兼容及 Codex 配置出口 | 已记录 |
| [完整历史保存](./session-history.md) | 完整档案、快照缓存、并发及分页契约 | 已记录 |
---

View File

@@ -38,6 +38,14 @@ Questions to answer:
- 头部和尾部片段必须独立解析,禁止直接拼接后匹配,避免截断边界组合出文件中不存在的键值。
- 预览读取必须保持明确的字节上限;解析失败时按字段缺失安全降级,不能让单个损坏会话中断整个列表。
### 完整历史与有界快照
- 具体签名、存储格式、失败矩阵及回归断言见 [完整历史保存契约](./session-history.md)。
- 会话消息数、正文和工具内容的预览上限只能作用于快照缓存;成功保存的完整历史必须由 cc-web 自身持久化,不能依赖原生客户端日志继续存在。
- 完整档案先原子提交,随后更新有界快照;完整档案失败时不得用裁剪快照覆盖上次状态。档案提交成功而快照失败时,读取和会话列表仍须能找到已提交档案。
- 可信完整读取后的修改应保留清空、撤回和插入顺序;短预览及旧修订不能覆盖完整消息或丢掉后来追加的记录,正常并发保存不得静默漏记。
- 旧版截断历史只有在确认原始记录对应边界后才能补入完整档案;新建档案本身不能证明旧历史已完整。
---
## Testing Requirements
@@ -46,6 +54,7 @@ Questions to answer:
- 部分 JSON 解析改动必须使用超过完整解析阈值的真实文件夹具,证明测试确实进入预览分支。
- 回归至少覆盖:嵌套同名键早于顶层键、顶层键位于尾部、字符串伪键与转义、`null`、字段跨截断边界、头尾拼接伪命中。
- 完整历史回归须覆盖超过快照上限、长正文与工具结果、存储重开且原始日志不可用、短快照重复保存、正常并发修改、主动清空/删除、归档写入失败及快照缓存失败。
---

View File

@@ -0,0 +1,100 @@
# 完整历史保存与分页契约
## 1. 范围与触发
会话保存、读取、原始日志补齐、导入、运行结束及重启恢复涉及本契约。成功保存的正文与工具数据必须能从 cc-web 自身读取;预览大小限制只用于快照缓存和实时工具预览,不能限制完整历史及完成消息的阅读。
## 2. 函数签名
```js
createSessionHistoryStore({ sessionsDir, fs? })
store.load(id) // 完整会话或 null;注册可信加载基线
store.save(session) // { revision, complete, session, rebased };失败抛错
store.markLegacy(session) // 注册旧快照来源与完整性状态
store.markComplete(session, { reset? }) // 确认可靠补齐或主动清空;返回状态还原函数
store.state(session) // 来源、修订与完整性;不暴露消息基线
store.listIds()
store.revision(id)
store.remove(id)
store.saveTurn(id, state) // 完整 Codex App 运行态先原子提交
store.loadTurn(id) // 优先于有界运行预览
store.listTurnIds()
store.removeTurn(id) // 完整消息已提交或主动清空后才清理
saveSession(session) // 完整档案提交成功为 true,否则 false
loadSession(id) // 优先完整档案;无档案才读取旧快照
resolveSessionHistory(session) // { messages, source, available, recoverable }
```
## 3. 存储与响应契约
- `${SESSIONS_DIR}/_history/<id>.json` 是持久提交点,格式为 `{ version: 1, revision, complete, resetRevision, session }`。`session` 包含完整可序列化字段,排除运行态句柄;正文、工具、数组和深度不使用预览限额。
- `<id>.json` 是有界快照缓存。`historyArchiveRevision` 位于顶层首字段,用来判断缓存是否过期;不得从嵌套同名键推断修订。
- `CC_WEB_SESSION_PERSIST_MAX_MESSAGES`、`CC_WEB_SESSION_MESSAGE_CONTENT_MAX_CHARS` 等预览配置不能限制完整档案;真实完成和恢复路径也不得先裁剪消息再提交。
- 可信来源保存在存储实例的 `WeakMap`,不能凭 JSON 的修订字段伪造。当前修订精确保存删除、清空和插入;旧修订根据加载基线合并互不冲突的修改,保持活跃消息及工具对象引用。
- 归档提交顺序为临时文件写入、文件 `fsync`、原子 `rename`、推进基线,再更新快照。档案失败不得更新快照;档案已提交而快照失败仍返回成功。
- 列表枚举快照与档案 ID 的并集。首次快照失败后,孤立档案仍须可读、可列出;缓存继续失败时使用内存有界预览。
- Codex App 运行态写入 `_history/_turns/<id>.json`,包含完整正文、工具及映射;有界运行态文件只作预览。重启枚举完整运行态,恢复消息提交成功后才清理,异常档案保留证据。
- 用户提问必须保存成功后才启动 runtime 或接受 steer;完成、重启恢复及跨会话返回必须检查保存结果,失败保留可重试内容。CLI 恢复标识须区分回合,同一已提交回合不可因清理失败再次追加。
- 确认原始边界后的旧前缀必须先进入自身档案。`complete=false` 保留旧缺口状态,不能因创建档案就宣称旧历史完整。
- 响应的 `historyCursor` 是尚未展示的已有消息数;正数允许分页。`historyAvailable=false` 只说明旧来源待核实,不能阻断正文或用户目录。完整档案响应 `historySource='archive'`、`historyAvailable=true`。
## 4. 校验与错误矩阵
| 条件 | 行为 |
|---|---|
| 非字母、数字、下划线或连字符 ID | `history_invalid_id`,不写入 |
| 已有档案但传入未可信加载的短预览 | `history_untrusted_source`,保留已有档案 |
| 档案 JSON 或结构异常 | 读取失败,禁止回退短快照覆盖档案 |
| 旧修订缺可靠消息 ID | `history_stale_unidentified`,不覆盖新记录 |
| 旧清空请求遇到后来追加 | `history_stale_clear`,要求重新加载 |
| 清空已提交后旧对象再保存 | 允许无冲突元数据更新;禁止旧历史及线程复活 |
| 会话已删除或档案已移除 | `history_removed`,旧对象不能重新创建 |
| 完整档案写入失败 | `saveSession=false`,保留上次提交;调用方不得宣称保存成功并清理唯一未提交输出 |
| 完整档案成功、快照失败 | `saveSession=true`,记录缓存错误,重开从档案读取 |
| 原始日志缺失或边界未确认 | 仅返回自身已有消息,保留待核实状态,不猜测补入 |
## 5. 正常、基本与失败场景
- 正常:650 条及长正文、125 条工具调用保存后,快照仍有界;存储重开和原始日志移开后,完整内容相等。
- 基本:短会话重复保存后正文、顺序和稳定 ID 不变;主动删除、清空、撤回不会复活旧消息。
- 失败:磁盘写入失败只保留上次提交及可重试的未提交内容;旧短预览不能作为新完整历史覆盖已有档案。
## 6. 必需回归与断言
- `scripts/session-history-store-unit.js`:实际生产保存和读取入口、超限内容、存储重开、两类写失败、孤立档案、并发及活跃引用、删除/清空/撤回、确认前缀先归档。
- 真实完成与恢复路径使用超限正文和工具,断言归档原样读回;不能仅绕过生产入库函数直接构造消息数组。
- 真实 runtime 事件覆盖纯工具、纯文本与结构块混合、Claude user/tool_result;恢复覆盖图片内容块、提交失败保留以及重复恢复幂等性。任务看板的克隆适配器需重新可信加载并只更新元数据。
- `scripts/session-history-unit.js` 与 `scripts/history-frontend-unit.js`:57 条、首屏 12 条、剩余 45 条在来源待核实状态下仍全部可读,顺序正确、无重复,用户目录完成索引。
- 隔离浏览器:确认旧前缀后移开夹具原始日志,重启测试服务、重新登录,再从 `archive` 分页到最早消息;继续运行、刷新及重连保持内容。
- 单测设置 60 秒上限;真实会话和生产服务不能代替隔离夹具。
## 7. 错误与正确示例
```js
// 错误:把预览当成唯一提交,完整正文在写档案前已经裁剪。
session.messages.push({ content: truncateTextValue(entry.fullText, previewLimit) });
writeFileAtomicSync(sessionPath(session.id), buildSessionJsonForPersist(session).json);
// 正确:保存完整值,归档先提交,再生成有界预览缓存。
session.messages.push({ content: entry.fullText });
const committed = store.save(session);
try {
const preview = buildSessionJsonForPersist({ ...committed.session, historyArchiveRevision: committed.revision });
writeFileAtomicSync(sessionPath(session.id), preview.json);
} catch (error) {
// 完整档案已提交,缓存失败不改成历史保存失败。
plog('WARN', 'session_snapshot_cache_save_failed', { error: error.message });
}
```
实际代码还须处理稳定 ID、可信加载、运行态排除和快照写失败;这些保护由统一保存入口承担。
## 8. 同一任务的自动续跑与运行中重连
- 自动延续重试交接完整 `fullText`、全部工具、消息及工具去重映射、稳定助手消息ID和逻辑回合标识。模型提示可只保留末尾7000字,气泡与归档不能使用该提示替代完整输出。
- 清理中断轮前先提交完整运行态;重试等待期仍须被认定为运行中,并可通过 `resume_generating` 读回完整状态。完整保存失败须保留唯一输出,不能继续破坏性交接。
- 重试使用独立 `runtimeItemScope`,原生item ID跨轮复用不能覆盖较早输出;高频长正文增量可分片发送,拼接结果必须等于原始增量。
- 正常新用户任务从空正文开始。耗尽、取消、启动失败及最终保存失败均须保留已产生正文,并防止旧回合晚到通知结束新回合。
- `turn/start`已发送且原生回合ID未知时取消,旧轮须继续持有线程门禁。通知先到也归旧轮;确认身份并精确中断后才能放行新任务,RPC超时不能凭空释放身份。
- 完整运行态、`done.assistantMessage`和历史分页应提供全部正文及工具输入/结果;不能让有界工具预览在完成或刷新时覆盖完整气泡。
- 必需回归:`codexapp-retry-history-unit.js`覆盖A/B/C三次续跑、等待期重连、全部工具、存储重开、耗尽、取消、启动交错、保存失败、重启恢复及正常新轮隔离;`history-runtime-unit.js`同时检查完整读取与有界实时预览。

View File

@@ -40,7 +40,15 @@ Questions to answer:
<!-- How server data is cached and synchronized -->
(To be filled by the team)
- 历史游标表示服务端当前记录中尚未展示的消息数量。正数游标可以分页,与外部原始记录是否可确认恢复分开判断。
- `historyAvailable=false` 只用于旧历史来源的核实说明,不能阻断已保存消息的正文或用户提问目录请求,也不能据此宣称消息已丢失或无法恢复。
- 读取完已保存消息后,游标归零应停止请求;确有旧来源待核实时保留准确说明,正常完整历史不显示该说明。
- `resume_generating`是完整运行态快照,以正文及全部工具权威替换当前流式状态,并复用同一逻辑助手气泡;不盲目拼接不同用户任务。
- 长气泡折叠只改变显示。完整正文保留在原始消息和DOM中;多次原生回复默认收起较早段,末段可见;单次超长回复可展开完整内容。
- 折叠状态按会话与稳定消息身份记录;完成、切换和重新渲染不得把被折叠的内容当作已删除内容保存。
- 复制按钮与定位、分支同排,仅从完整原始正文中复制最后一个原生回复段。按围栏外的 `agent-message-divider`切分,不以最后自然段或DOM的最后一个p元素判断;代码块、列表保留,时间分隔、工具和按钮不进入剪贴板。没有原生分界时复制整条回复。
- 单测覆盖末段边界、代码围栏伪分界、流式分界尚未传完、文本块、短消息与长消息。隔离浏览器须实际检查展开完整内容、末段复制、刷新与重连,以及手机44px触控范围。
- 单条长回复收起时,定位按钮先调用折叠开关展开并更新记忆状态,再计算末段坐标;不能定位到高度裁剪区中仍不可见的文本。
---