第 11 章:会话、持久化与恢复
1. 持久化不是一个 JSON 文件就够了
Orca 的状态按语义分散:
| 存储 | 内容 | 一致性需求 |
|---|---|---|
| 主 PersistedState JSON | repo、settings、UI、workspace sessions | 原子写、迁移、备份 |
| Agent hook last-status | 最近 Agent 状态 | debounce + age bound |
| Browser session partition/meta | cookie、profile、browser state | Chromium 管理 + registry |
| AI Vault parse cache | 会话索引解析结果 | 可丢弃缓存 |
| Orchestration SQLite | run/task/message/dispatch/receipt | 事务与幂等 |
| Terminal history/model | scrollback/snapshot/cwd | 有界、provider/runtime 权威 |
2. PersistedState 的骨架
主类型包含:
type PersistedState = {
schemaVersion: number
repos: Repo[]
projects: Project[]
projectHostSetups: ProjectHostSetup[]
projectGroups: ProjectGroup[]
folderWorkspaces: FolderWorkspace[]
worktreeMeta: Record<string, WorktreeMeta>
worktreeLineageById: Record<string, WorktreeLineage>
settings: GlobalSettings
ui: PersistedUIState
workspaceSession: WorkspaceSessionState
workspaceSessionsByHostId?: Record<ExecutionHostId, WorkspaceSessionState>
sshTargets: SshTarget[]
automations: Automation[]
automationRuns: AutomationRun[]
}完整定义在 src/shared/types.ts。
3. Store 的 load 流程
Store 的 load 不只按 schemaVersion 执行一次性迁移,还用字段级 guard 保留用户后续选择。例如某个默认值从 false 改 true,只对“尚未迁移过的旧用户”翻转一次,之后用户手动关闭必须继续生效。
4. 原子写、hash 去重与备份
Store 的写入策略:
- debounce 普通更新;
- 内容 hash 相同则跳过;
- 临时文件 + rename;
- 轮换多个
.bak; - backup 有最小时间间隔,避免 300ms debounce 生成一组几乎相同的备份;
- quit/关键资源退休时同步 flush。
加密字段使用 Electron safeStorage。由于每次加密随机 IV 不同,hash 去重必须按解密后的语义 payload 计算,不能直接 hash ciphertext。
5. 为什么 migration 代码很长
桌面应用的数据会跨数百个版本原地升级,不能靠清库。迁移涉及:
- retired setting 和 default 变化;
- worktree card/schema;
- SSH target id;
- local session 拆分为 host partitions;
- terminal numeric leaf 转 UUID;
- sidekick 重命名为 pet;
- automation target 与 output;
- Windows/WSL runtime 默认;
- source-control provider identity。
迁移原则是“只改可证明的旧默认,不改用户自定义”。因此常见独立 _xxxMigrated 标记。
6. Workspace session 保存什么
session 不是完整运行时 dump,主要保存 presentation 与恢复线索:
- tab groups、tab order、active tab;
- terminal pane split layout;
- tab title、color、pin;
- terminal binding / saved cwd / startup config;
- sleeping Agent session metadata;
- editor open files 与 mode;
- browser tab projection。
它不能证明 PTY 活着。恢复时必须向 provider/runtime 重新验证。
7. 按执行主机分区
历史上只有 workspaceSession,现在 local 保留在这个 legacy 字段以支持降级,非本机 host 放在 workspaceSessionsByHostId。
workspaceSession -> local
workspaceSessionsByHostId['ssh:a'] -> SSH A
workspaceSessionsByHostId['runtime:b'] -> Runtime Bhost partition 避免相同 repo/worktree id 在不同执行机上的 terminal 布局互相覆盖。
8. Pane identity 迁移
旧 terminal layout 使用数字 leaf,无法在跨重排/重启时稳定识别。load 阶段会:
- 为 layout leaf 分配稳定 UUID;
- remap pane-keyed records;
- 保留可证明的 legacy alias;
- 将无法安全迁移的 live PTY 标记为 unsupported;
- 防止旧 renderer 写回数字 leaf 复活旧格式。
迁移不是简单字符串替换,因为同一数字 leaf 在多个 tab/host 里可能碰撞。
9. Terminal membership revision
当 PTY exit 时,host 必须从 session 中移除 terminal binding。若 renderer 稍后用旧 snapshot 保存 UI metadata,不能把 leaf 加回来。
解决方案是每个 repo/host 的单调 topology revision:
- host 退休 terminal;
- revision 递增并同步 flush;
- renderer 旧写入只能更新仍存在对象的 metadata;
- terminal membership 以 host revision 为准。
这比永久保存每个 deleted pane tombstone 更有界。
10. Agent hook 状态的独立文件
Agent status 更新频率高、生命周期与 UI settings 不同,因此保存为 last-status.json:
- 250ms trailing debounce;
- quit-time flush;
- 只保留 7 天内记录;
- launch token 只存 hash;
- hydrate 时严格校验 pane/tab/worktree/connection;
- idle subagent 等无法证明的 liveness 不跨重启恢复。
11. AI Vault 缓存与 transcript
会话原文仍属于各 Agent。Orca 保存的 parse cache 可随时重建,因此设计成:
- 有文件 version/mtime/size identity;
- 限制 entry 与总字节;
- SQLite parser 可放 worker;
- remote scan 通过 runtime;
- title/preview 在读取时做长度和隐藏块过滤。
Native Chat watcher 则增量读取原 transcript,不把整个对话复制进主 PersistedState。
12. Orchestration 为何用 SQLite
run/task/message/dispatch 需要:
- 原子状态转换;
- sequence 与 cursor;
- 幂等 mutation receipt;
- worker report settlement;
- inbox delivery / acknowledgment;
- federation relay ack。
这些是关系与事务,不适合频繁重写一个大 JSON。OrchestrationDb 使用 schema version 和 hardened DB files,详见第 12 章。
13. Crash consistency
需要考虑四个典型崩溃点:
文件临时写了一半
主文件解析失败,尝试备份环。
PTY 已退出,session 尚未写盘
退休操作同步 flush,减少幽灵 leaf。
spawn 已物理提交,响应尚未返回
physical commit callback 保留 operation fence,禁止安全重试假设。
renderer crash,PTY 继续输出
main-owned terminal model 与 daemon 存活;renderer 重启后请求 snapshot。
14. 数据最小化
持久化代码反复区分:
- renderer 可读写的 UI state;
- main-owned telemetry/credit ledger;
- secret;
- live-only token/interaction key;
- 可重建 cache;
- 真实 resource authority。
不是所有内存字段都应该进入磁盘;也不是所有磁盘字段都应该通过 preload 暴露给 renderer。
15. 源码导航
- 主 Store:
src/main/persistence.ts - session IPC:
src/main/ipc/session.ts - workspace session 类型:
src/shared/types.ts - hook persistence:
src/main/agent-hooks/server.ts - AI Vault cache:
src/main/ai-vault/session-parse-cache-persistence.ts - orchestration DB:
src/main/runtime/orchestration/db.ts