Skip to content

第 4 章:启动、IPC 与状态同步 ​

目标:理解 Orca 从进程启动到 renderer 可交互经历哪些阶段,以及 Zustand、主进程 Store、runtime graph 如何避免互相覆盖。

1. 启动不是一个 app.whenReady() ​

Orca 启动要同时恢复 UI、持久化数据、PTY、Agent 状态、SSH、runtime RPC 和窗口。粗略时序如下:

很多启动 bug 都来自 barrier 顺序错误:例如 renderer 尚未注册 serializer 就收到 PTY replay,或旧 session layout 覆盖刚刚由 host 退休的 terminal。

2. Main 的三段启动 ​

2.1 Electron 就绪前 ​

src/main/index.ts 在 app.ready 前处理:

  • userData/profile 路径;
  • 单实例锁与 packaged CLI 重定向;
  • GPU fallback、网络兼容和进程错误 guard;
  • dev parent / signal 生命周期;
  • Windows ACL 与 shell PATH hydration。

这些操作必须早于窗口和 Store,因为它们会改变数据目录、命令行开关或进程退出策略。

2.2 服务构建 ​

随后创建长期服务:

  • Store、Stats 与 usage store;
  • OrcaRuntimeService、OrcaRuntimeRpcServer;
  • rate limit、account、automation、plugin 服务;
  • Agent hook server、browser/emulator bridge;
  • daemon/local/SSH PTY provider。

入口文件像依赖注入容器:构造对象,并把 runtime、store、window 与 callback 接起来。

2.3 首个窗口服务 ​

窗口存在后才能绑定可信 webContentsId、菜单、剪贴板、UI IPC 和推送事件。因此 registerCoreHandlers 会更新当前可信 renderer id,并用 registered 避免 macOS 再次打开窗口时重复 ipcMain.handle()。

3. IPC handler 的分层 ​

register-core-handlers.ts 是 IPC 目录。它注册约几十类 handler:

类别示例
基础资源filesystem、session、settings、shell
工作区repos、worktrees、workspace space、ports
Agentagent hooks、accounts、skills、AI Vault、native chat
ProviderGitHub、GitLab、Linear、Jira、hosted review
设备browser、emulator、speech、notifications
平台runtime、runtime environments、plugins、updater

IPC handler 通常做五件事:

  1. schema/shape 校验;
  2. 找到 Store 中的 repo/worktree/host;
  3. 选择本地、SSH 或 runtime provider;
  4. 执行资源操作;
  5. 更新持久化并向 renderer 广播。

这不是单纯 controller 层;它经常是“本机桌面客户端”的事务协调者。

4. Zustand store 如何组合 ​

renderer 的 store/index.ts 将 40 多个 slice 合成 AppState:

ts
export const useAppStore = create<AppState>()((...a) => ({
  ...createRepoSlice(...a),
  ...createWorktreeSlice(...a),
  ...createTerminalSlice(...a),
  ...createTabsSlice(...a),
  ...createUISlice(...a),
  ...createGitHubSlice(...a),
  ...createBrowserSlice(...a),
  ...createAgentStatusSlice(...a)
  // ...
}))

这种设计的优点是跨 slice 操作可以直接 get();代价是任何 selector 过宽都会造成 fanout。源码专门提供 listener census、selector benchmark 和 memory profile contributor,说明 store 规模本身已成为性能对象。

Slice 的三种状态 ​

  1. 持久化设置:sidebar、theme、card fields;
  2. 资源镜像:repos、worktrees、agent status、rate limits;
  3. 瞬时协调状态:pending create、retry ledger、modal、optimistic tickets。

不要把第二类当作事实源,也不要把第三类持久化。

5. Hydration 是合并,不是覆盖 ​

renderer 启动会读取:

  • persisted UI/settings;
  • repos/projects/groups/folder workspaces;
  • worktree metadata 与实际 Git scan;
  • workspace session(tab、layout、sleeping sessions);
  • hook status snapshot;
  • runtime environments 与 remote host catalog。

不同数据有不同权威:

数据权威来源renderer 行为
sidebar widthpersisted UI直接恢复并可写回
Git worktree 存在host Git scanmetadata 只做 fallback
live PTYprovider/runtime只 attach,不凭 session 生成
terminal layoutsession + host revision合并,不能复活 retired leaf
Agent statushook server snapshot按 pane authority 接受
SSH hostconnection store离线保留但标 disconnected

6. Renderer 事件汇聚 ​

useIpcEvents.ts 订阅主进程事件,例如:

  • PTY data / exit / restore marker;
  • Agent status set/clear;
  • worktree/head/base/remote branch 变化;
  • file watcher、notifications、usage 与 update;
  • SSH connection、ports、remote workspace;
  • GitHub PR refresh、browser 与 emulator 状态。

事件处理的关键不是“收到后 setState”,而是 admission:

  • generation 是否仍是当前连接;
  • event 的 host/connection 是否与 workspace authority 一致;
  • pane 是否已被关闭或替换;
  • snapshot version 是否比本地新;
  • optimistic mutation 是否仍拥有该字段。

7. Runtime graph:把 renderer 布局投影给 host ​

renderer 有完整 UI layout,但 runtime 需要知道哪些 tab/leaf 对外可见。RuntimeSyncWindowGraph 只传必要字段:

ts
type RuntimeSyncWindowGraph = {
  tabs: RuntimeSyncedTab[]
  leaves: RuntimeSyncedLeaf[]
  mobileSessionTabs?: RuntimeMobileSessionTabsSnapshot[]
}

runtime 返回 graph status、epoch、authoritative window id 和 Agent orchestration context。rendererGraphEpoch 用来区分 reload 前后的发布。

这是一种“UI 描述布局,host 裁决资源”的双向同步:

  • renderer 可以声明 tab 标题、顺序和 split;
  • host 可以拒绝已退出 PTY 的 leaf 重新出现;
  • headless 模式可在没有 renderer 时维护自己的 session snapshot;
  • desktop window 恢复后再接管 presentation。

8. 持久化写入的节奏 ​

主 Store 使用 debounce 避免每个 UI 小动作都写盘,同时提供同步 flush 给退出和 terminal retirement 等关键边界。

大致有三档:

  1. 普通 UI 更新:debounced save;
  2. session capture:周期性和 quit-time;
  3. 权威成员变化:同步写入后再对外发布。

第三档很重要。若 PTY exit 已从内存 snapshot 移除,但磁盘仍保留旧 leaf,进程崩溃后会把“幽灵终端”恢复回来。

9. 常见一致性模式 ​

Generation ​

连接、scan、spawn、watcher 用递增 generation 丢弃迟到结果。

Operation ticket ​

字段级 optimistic 更新携带 ticket;只允许当前 ticket 回滚,防止早请求失败覆盖后请求成功。

Snapshot version / graph epoch ​

用于全量投影的单调顺序。

Incarnation ​

用于同一逻辑 PTY id 背后的物理进程替换。

Revision fence ​

host-owned terminal membership 的持久化修订,阻止 stale renderer resurrect。

10. 一次设置更新的完整链 ​

以隐藏休眠 workspace 为例:

敏感或 main-owned 字段不会通过同一个 UI patch surface 暴露,避免 renderer 写入 telemetry ledger、secret 或 host revision。

11. 源码导航 ​