第 6 章:终端运行时——Orca 的真正内核
1. 为什么终端是全项目最复杂的子系统
Orca 托管的 Agent 都运行在终端里。终端同时是:
- 进程控制通道;
- 字节流协议;
- 全屏 TUI 图形界面;
- 用户输入设备;
- Agent 状态与标题的辅助信号;
- 跨桌面、SSH、手机的共享资源。
renderer 可重载、窗口可隐藏、SSH 可断线、daemon 可重启,而 Agent 不能轻易丢失。因此终端不是一个组件,而是一个分布式状态机。
2. 分层模型
前台数据可以走低延迟直通;恢复数据来自 provider/runtime 的权威模型。
3. IPtyProvider:可替换进程宿主
pty-provider-contract.ts 定义统一能力:
生命周期
spawn(opts):创建或请求 attach;attach(id):订阅既有 PTY;shutdown(id, opts):有 deadline 的关闭;probePtyLiveness、listProcesses。
交互
write、resize、sendSignal;getCwd、getForegroundProcess;onData、onReplay、onExit。
恢复与流控
getBufferSnapshot;serialize / revive;pauseProducer / resumeProducer;setPtyBackgrounded;acknowledgeDataEvent。
Agent authority
- claim protocol capability;
- owner listing authority;
- create-operation replay;
- PTY incarnation。
实现包括 local、daemon adapter 和 SSH provider。
4. Spawn 的输入不只是 shell command
PtySpawnOptions 还携带:
- cwd、env 与
envToDelete; - launch Agent 与 startup prompt delivery;
- worktree、tab、pane identity;
- Windows shell / WSL distro;
- Codex home 与账户选择;
- startup ingress authority;
- Agent session claim / operation id;
- AbortSignal 与 physical commit callback。
这是因为“启动 Codex”不是执行字符串 codex:还要在进程出生前完成身份、hook、账户、PATH、trust 和重试语义。
5. PtySpawnResult 是恢复协议
除了 id 和 pid,结果可能带:
incarnationId:物理进程代次;snapshot+ cols/rows:daemon reattach 画面;providerSequence:序列域继续还是重置;replay:relay 增量回放;coldRestore:磁盘 scrollback + cwd;sessionExpired:请求 attach 但原 PTY 已过期;exitedBeforeSpawnReply:响应前已退出;agentSessionEnsure:claim/adopt 结果。
它描述的不是“创建成功”,而是调用者应该怎样把 view 与一个可能已存在、已退出或从磁盘重建的终端对齐。
6. PTY identity 与 incarnation
main 维护:
ptyOwnership:PTY 到 connection/provider;ptyPaneKey/paneKeyPtyId:双向 pane 绑定;ptyIncarnationById:当前物理代次;- size、visibility、last input、output counters;
- spawn reservation 与 serializer generation。
退出事件只有在 incarnation 匹配时才生效。否则,一个旧进程迟到的 exit 可能杀死已经复用同 id 的新进程。
7. 输出链路与序列号
正常输出:
main 先喂 headless model,再投递 renderer。这样 renderer 即使丢弃后台输出,host 仍保有可序列化的终端状态。
8. 前台低延迟与后台内存上限
renderer scheduler 区分:
- 前台交互小块:立即写;
- 大量 redraw:滚动预算;
- 不可见 pane:后台队列或直接 gate;
- stale pane:丢弃 renderer backlog,等待 snapshot restore。
关键不是“绝不丢字节”,而是不让 renderer 无界保留字节,同时确保视觉状态可从 host model 重建。
主进程还可以暂停 producer,让子进程在 OS pipe 上自然 backpressure;不能暂停的旧 provider 仍依赖 pending cap 保持内存边界。
9. Hidden-output 恢复
恢复步骤:
- 请求 main/provider snapshot;
- 暂存少量竞态 live chunk;
- 清空 xterm 并按 snapshot 尺寸 replay;
- 禁止 replay 中的 terminal query 回答 shell;
- 用 seq 丢掉已包含在 snapshot 的 live chunk;
- 写入剩余 chunk;
- 发送 post-reattach mode/cursor reset。
设计文档见 terminal-main-owned-state.md。
10. Query authority:谁可以回答终端查询
TUI 会发 OSC/CSI 查询,例如颜色、光标、Kitty keyboard、设备属性。如果 snapshot replay 到 xterm 时,xterm 自动回答了历史查询,回答会被写回当前 shell,造成“时间穿越”。
Orca 因此区分:
- startup ingress authority;
- live view authority;
- hidden/main model authority;
- replay guard。
只有当前真实 view 的新查询可以回到 PTY。snapshot 里的旧查询只用于重建画面,绝不能产生副作用。
11. Stable pane 与 spawn reservation
renderer mount 可能并发触发同一 pane 两次连接。main 用 pane key reservation 和 serializer generation 保证:
- 同一 pane 同时只有一个 fresh spawn;
- 后到 attach 可复用已提交结果;
- renderer 被销毁时清理未结算 serializer;
- late promise 不能绑定到新一代 pane。
这类防线对于 React mount/unmount、window reload 和网络 retry 都必需。
12. Terminal title 与 side-effect facts
PTY bytes 中除了画面,还可能有:
- OSC title;
- bell;
- shell command finished;
- PR URL;
- cwd(OSC 7);
- color scheme query;
- Agent 辅助状态。
如果远程链路为了后台性能做 keep-tail thinning,被丢弃的字节里仍可能有 bell 或 command-finished。provider 可以发 ordered PtyBackgroundStreamEvent,让“事实”与保留字节保持顺序。
13. 关闭与退休
关闭 UI tab 不等于立即 kill;需要区分 user、cleanup、pty-exit。host 会检查:
- 当前 publication epoch;
- PTY 是否仍 live;
- 是否有另一个 leaf/owner;
- stop/hibernate transaction 是否正在保留 surface;
- 请求是否只是客户端镜像 teardown。
真实 PTY exit 才是终端 authority。runtime 会移除 leaf、修复 split/layout、清理 binding,并同步持久化 revision 后再发布 absence。
14. 调试与性能观测
源码内建:
- PTY delivery breadcrumb ring;
- renderer delivery health report;
- terminal render desync evidence;
- foreground latency/throughput budget;
- hidden flood、WebGL atlas、scroll restore E2E;
- daemon/SSH attach、early exit 与 incarnation 单测。
终端性能脚本集中在 tools/terminal-* 和大量 tests/e2e/terminal-*。
15. 源码导航
- main controller:
src/main/ipc/pty.ts - provider contract:
src/main/providers/pty-provider-contract.ts - local provider:
src/main/providers/local-pty-provider.ts - daemon adapter:
src/main/daemon/daemon-pty-adapter.ts - renderer connection:
src/renderer/src/components/terminal-pane/pty-connection.ts - terminal component:
src/renderer/src/components/terminal-pane/TerminalPane.tsx - binary stream:
src/shared/terminal-stream-protocol.ts