# 执行记录存储与升级回收 通用迁移、备份、性能测量和发布要求见 [数据库迁移与大数据升级指南](../../development/database-migrations.md)。 ## 用户行为 子任务仍实时显示完整的文字、思考、工具过程和最终结果。运行时间较长、工具输出较多时, 数据库不再为每个流式更新重复保存此前的整份过程;本机和托管 SSH OpenCode 使用相同 的桌面存储机制。此修复不关闭子任务、不截短已有过程,也不增加工具审批。 已有数据库在首次升级时自动处理。启动页铺满窗口,显示处理数量、全宽进度条和近期 处理速度;处理时间取决于历史数据量与磁盘速度,不承诺固定几分钟。用户可以退出, 下次启动重新检查并继续转换未完成的数据;完成后回收空闲页并进入应用。失败时留在 该页提供重试与退出,磁盘空间不足会明确说明,不删除聊天来腾出空间。 以上转换和回收只针对仍需转换的旧格式数据;普通结构升级使用独立状态,不宣称正在 清理子任务历史,也不会仅因数据库有空闲页而执行全库回收。 ## 存储规则 - `messages.metadata_json` 继续保存供会话显示的完整子任务过程。 - `task_events` 的 `subagent` 内容由共享 `SubagentProgressStorage` 编码。 `progressUpdates` 只保存文字追加、变更的块,或确有删除/重排时的完整重置。 完全相同的过程使用空更新数组。工具字段按值比较,缺省字段与 `undefined` 等价, 不因 JSON 省略空字段而重复写入整个工具结果;空字符串与实际输出/错误变化仍保留。 其他事件内容不改变。 - 每个任务内按事件 ID 顺序、按 `childTaskId` 独立还原过程。事件 ID、顺序、状态、 最终输出、错误、时间及远程来源字段均保留,不按天数裁剪或删除旧事件。 - Runtime、Agent transcript、Preload 和 Renderer 的公共 `SubagentEvent` 仍传完整 `progress`;差量表示只存在于桌面数据库内部,不要求更新远端 Agent 协议。 - 远程消息投影与事件插入仍共用 SQLite 事务。重放时还原已保存内容进行等值校验, 相同来源重复事件不重复投影,不同内容继续报冲突。内存过程缓存通过已提交事件 ID 核对;事务回滚或重新打开后从数据库重建,不保存独立快照或回收日志。 - 存储层最多缓存 16 个任务;收到任务完成、错误、取消或中断的终态记录后立即移除该任务 的缓存引用,不影响其他会话。若事务随后回滚或仍收到该任务的后续事件,按已提交记录重建。 释放引用不等于进程 RSS 立即下降,垃圾回收和 Runtime 自身会话缓存另按生命周期回收。 ## 活动历史连续保存 活动历史在桌面 `AssistantDatabase` 中按变化记录写入,保留记录顺序和重复 ID 的独立位置。 连续保存使用最近一次成功提交的记录作为比较基准,不再逐次读取整份历史。关闭数据库、 清空数据或检测到其他 SQLite 连接提交后重新读取;事务失败不更新比较缓存,重试仍基于已提交内容。 本次优化不改变数据库 schema 或完整快照 IPC,也不改变 Agent、Runtime 和远程模型桥。 本机与远程任务的桌面活动记录共用此保存路径,Host 不需要更新。 WAL 增长与 SQL 全历史扫描次数的定向验证见 [BUILD.md](../../../BUILD.md); 这些指标不等于安装后应用的物理磁盘吞吐测量。 ## 会话列表读取与前端保留 列表读取与数据库存储是两个不同层次。`messages.metadata_json` 继续保留完整过程, 本次读取优化不修改 schema、迁移或用户数据: - Renderer 启动及焦点、会话、队列刷新使用 `conversations.listSummaries(detailIds)`。 普通未打开会话只返回标题、项目、置顶状态、上下文设置和消息数量/首条角色/最新消息创建时间,不解析或跨 IPC 传输正文、工具、子任务及图片操作元数据。 - 当前会话、现有 KeepAlive 视图,以及 Main 仍持有请求、数据库中仍有活动 Task 或 streaming 消息的会话返回完整消息。列表包含全部 active 会话,不限制数量, Main 按置顶优先、组内更新时间倒序返回;Renderer 使用下述展示排序。仅置顶不会加载完整历史。 - 打开历史通过 `conversations.get(id)` 读取全部消息,不使用会截断历史的分页上限。 加载时明确显示进度;失败保留会话与输入,提供原地重试。发送、队列派发和压缩上下文 必须先确认完整历史,发送时不按消息条数截断;复制与导出临时读取完整内容,不把未打开会话留入视图缓存。 - 搜索由 Main 在 SQLite 中读取标题与正文,不读取过程元数据,保持原来的 Unicode 大小写匹配和字面子串规则。范围包含全部 active 会话;前端同时匹配尚未 落库的消息。搜索状态只保留命中的 ID,不额外持有整份历史对象。 - KeepAlive 淘汰后,在现有保存周期中释放已经确认落库且空闲的详情及持久化比较引用。 未保存消息、正在执行的会话、待答/待审批内容不被淘汰。读取中的请求按会话合并; 晚到摘要不能覆盖刚打开的历史或新到的后台审批,摘要附带的实时新增消息仍增量保存。 - `messageSummary` 仅为只读列表标记,不进入持久化 header。保存继续使用已有逐消息 增量接口,空消息数组不代表删除历史。原有完整 `list()` 与数据库 `getConversation()` 接口保留供其他调用方使用。 - 图片状态通过同一合并函数处理。本地更新与 Main 图片操作按原有时间优先级合并, 未变化消息返回原对象,避免每次列表刷新后把无变化的整个历史当作脏消息再次保存。 - 展示顺序由 Renderer 的 `sortConversationsForDisplay` 统一派生,规则见 [会话列表排序](../application-tool-navigation/ui-design.md#会话置顶与操作菜单)。完整消息取 `createdAt` 最大值;摘要的 `latestMessageAt` 在已有聚合查询中取 `MAX(created_at)`, 与摘要上附带的实时消息取最大值。该字段仅用于桌面只读投影,不增加数据库列或持久化副本。 `updatedAt`/`updated_at` 继续随流式事件更新,原有快照合并、终态优先与逐消息保存规则不变。 `useConversationListOrder` 在内存中保留展示 ID 顺序,以单个 5 秒计时器应用自动排序, 并按上述交互规则暂缓重排;行数据始终使用最新会话对象,不延迟保存或同步。 - 本机和托管 SSH 的 Renderer/Main 读取、持久化与恢复投影共用以上实现。Agent 的 transcript、原生 Session、权限及模型桥协议未改变;完整列表、摘要和详情都附带 Main 当前恢复请求的问题投影。 此优化不等于工具折叠内容的延迟渲染,也不保证操作系统立即归还全部 Private Commit。 实际测量与适用边界见[实现与验证进度](./progress.md)。 ## 会话置顶 界面行为见[会话置顶与操作菜单](../application-tool-navigation/ui-design.md#会话置顶与操作菜单)。 `conversations.pinned` 是桌面数据库维护的会话属性,本地和消息通道会话共用,默认未置顶。 完整快照与摘要均返回 `pinned`,旧快照缺省按未置顶处理。写入只通过 `conversations.setPinned({ conversationId, pinned })`,经 Preload、可信发送方校验和严格 Schema 校验后更新活动会话,再发布会话变更通知;不存在的会话报错。该写入不修改时间戳、 消息或其他会话字段,普通自动保存 header 排除 `pinned`,不会覆盖置顶状态。 尚未落库的本地草稿先等待保存队列并保存,再设置置顶;消息通道会话直接调用专用接口。 Renderer 合并时独立接收 Main 的置顶属性,即使本地正文更新时间更晚也不忽略它。 请求修订号和进行中标记防止较早发出的列表/详情响应覆盖刚更新的置顶状态。 此属性不修改 Runtime 或远端 Agent 协议。 ## 旧库升级 启动检查只根据旧 schema 或仍未转换的笔记正文决定是否进入升级流程;正常写入、更新 与删除产生的 SQLite 空闲页不是旧格式证据。迁移后新增数据使用当前格式,不因正常使用 再次迁移。进入升级流程后仍分别判断结构、笔记转换与空间回收,不把旧 schema 当作 回收许可。回收只用于实际存在旧子任务事件(schema < 35)、旧活动记录(schema < 44) 或旧笔记正文的转换;例如 schema 45/46 升到当前结构不会自动转换历史或 `VACUUM`。 同一次启动在内存中保留这一决定,只有原本需要回收的流程可重试压缩;若正文已转换、仅压缩中断,退出后 重开不单凭空闲页重新进入迁移,剩余页由 SQLite 后续写入复用。不新增持久化迁移标记。 桌面在打开业务数据库之前启动独立 Node 工作线程,使用同一套编码和还原函数转换旧库。 按任务有序读取,每批最多 32 条,提交后报告进度;SQLite 保留事务原子性及正常 WAL 自动 checkpoint,每个任务结束时截断 WAL。读取缓存为 64 MiB,避免 1 MiB 级历史 快照在过小缓存中反复读取;不以关闭事务或同步落盘换取速度。 转换可以混合读取旧完整记录与已经转换的记录,也会重新编码 schema 34 中重复的工具块 更新。因此退出、磁盘写入失败后可以重新扫描并继续;已是相同 JSON 的记录不重复更新, 不建立额外的恢复状态表。最后运行 SQLite `VACUUM` 回收已释放的空间,而非 在转换前复制整份膨胀内容。回收仍需要额外可用磁盘空间,失败保留已提交转换并允许重试。 子任务整理对应 schema 35;schema 34 用户也会执行该整理,成功后后续启动直接跳过。 继续复用 schema 34 的子任务事件部分索引和同一差量表示。业务初始化随后在事务中 执行后续结构迁移,其中 schema 36 新增 `pinned INTEGER NOT NULL DEFAULT 0 CHECK(pinned IN (0, 1))`, 已有会话默认未置顶,保留原消息与时间戳;失败回滚本次字段迁移。 低于该 schema 的旧版客户端不能重新打开已经升级的数据库。需要回退客户端时,必须使用 升级前备份,不要修改 `user_version` 强行绕过版本检查。 ## 验证要求 - 使用可还原的合成过程验证文字追加、内容修订、块重排、清空和多次打开。 - 工具 fixture 必须含生产对象会携带的 `undefined` 可选字段,并经过实际 JSON 写入/重读。 同时验证旧完整事件和 schema 34 差量事件的整理、混合重试及无变化记录不被重写。 - 验证远程重复事件、冲突事件,以及消息更新失败时事件和过程缓存的事务回滚。 - 覆盖已完成历史转换、但正常聊天更新产生空闲页的后续结构升级:无转换进度、无 `VACUUM`,会话、笔记、任务事件与完整性不变;验证真实 Worker 和同次启动重试。 - 在真实旧库的已核验备份副本上运行 portable 的正常启动入口,核对事件逐条等价、 其他用户表内容、数据库完整性和实际文件大小,不能只统计空闲页或只测试内存 fixture。 - 同时验证真实本地 Runtime 和共享 Linux Host 的桌面持久化路径。真实场景必须让主代理 与子代理按正常任务需要读取、修改文件和执行测试,不能用“子代理不使用工具”的纯文字 场景代替工具存储验收;记录实际请求数、有效变化、重复工具块及还原结果。具体证据见 [实现与验证进度](./progress.md)。