第 3 章:领域模型——从 Project 到 PTY
1. 领域对象总图
源码中的模型正在从“一个 Repo 就是一切”演进到“Project 可以在多个 Host 上有 setup”。因此会同时看到新模型与兼容字段。
2. Project、Repo 与 ProjectHostSetup
Project:逻辑项目
Project 表示用户心中的同一个项目:显示名、图标、provider identity,以及在哪些 host 上有 source。
type Project = {
id: string
displayName: string
providerIdentity?: { provider: 'github'; owner: string; repo: string }
sourceRepoIds: string[]
}Repo:一个可执行的源码落点
Repo 仍保存大量兼容状态:本地路径、SSH connection、execution host、base ref、worktree 目录、Git remote、hook 和 source-control AI 设置。
关键点:Repo 不一定是 Git 仓库。kind 可为 git 或 folder。这让普通目录也能成为 Agent workspace。
ProjectHostSetup:Project 在某个 host 上的部署
它把“逻辑项目”和“在哪台机器的哪个路径能执行”拆开:
type ProjectHostSetup = {
projectId: string
hostId: ExecutionHostId
repoId: string
path: string
setupState: 'ready' | 'not-set-up' | 'setting-up' | 'error' | 'unsupported'
setupMethod: 'legacy-repo' | 'imported-existing-folder' | 'cloned' | 'provisioned'
}这是多主机模型的关键。未来同一 GitHub repo 可以在 local、SSH server 和 paired runtime 上各有一份 checkout,而不是复制三个互不相关的 Repo 概念。
定义集中在 src/shared/types.ts。
3. ProjectGroup:导航组织,不是执行边界
ProjectGroup 支持文件夹导入、嵌套、排序与折叠。它的注释明确指出:分组只属于 sidebar,执行仍是 repo-scoped。
这条边界很重要。把 UI 分组误当成权限或执行 scope,会让 Git、PTY 和自动化逻辑错误地依赖展示结构。
4. Workspace 的两种形态
Orca 把可工作的上下文统一为 WorkspaceScope:
type WorkspaceScope =
| { type: 'worktree'; worktreeId: string }
| { type: 'folder'; folderWorkspaceId: string }
type WorkspaceKey = `worktree:${string}` | `folder:${string}`Worktree workspace
对应 Git worktree 或主 checkout,拥有 branch、HEAD、diff、PR、lineage 等 Git 语义。
Folder workspace
对应一个普通目录,仍可启动终端、Agent、编辑文件和绑定任务,但没有 Git branch 假设。
很多代码必须同时考虑这两种 scope,所以源码规范反复强调“不要假设 workspace 一定是 git worktree”。
5. Worktree:Git 事实 + Orca 元数据
一个 worktree 的身份不是短 UUID,而是 repo 与路径的组合。CLI 文档把它表示为:
<repoId>::<absoluteWorktreePath>这使 worktree identity 在重新扫描 Git 后仍可重建,但也带来跨平台路径规范化问题。Windows、WSL、SSH 都需要按执行主机的 path flavor 比较。
Worktree 是扫描到的当前事实,WorktreeMeta 是 Orca 持久化的产品元数据。两者通常包含:
- display name、comment、pin、unread、archive;
- linked GitHub/GitLab/Linear/Jira item;
- workspace status 和排序;
- 创建 Agent、自动重命名、setup 行为;
- ownership(Orca managed / external / legacy / scratch)。
这种拆分允许 Git 工作树暂时离线时,sidebar 仍保留可解释的 metadata fallback。
6. Lineage:并行任务的谱系
Orca 不只记录 Git base,还记录工作上下文的父子关系:
WorktreeLineage:子 worktree 从哪个 parent/base 产生;WorkspaceLineage:child workspace 的更通用谱系;- origin:
orchestration、cli或manual; - capture confidence:
explicit或inferred。
为什么不能只看 Git commit graph?因为两个任务可能从同一 commit 分出,但产品层有明确的父子派工关系;folder workspace 甚至没有 commit graph。
7. TabGroup、Tab 与两层 split tree
Orca 有两种布局树:
- Tab group layout:把编辑器区域分成多个 tab group;
- Terminal pane layout:一个 terminal tab 内继续 split 成多个 terminal leaf。
TabContentType 除 terminal 外,还包括 file、diff、markdown、image、PDF、browser、settings 和 plugin tab。Terminal 只是统一 tab 框架中的一种内容。
8. Leaf、Pane Key、Terminal Handle 与 PTY ID
这是最容易混淆的四个身份。
| 身份 | 所属层 | 稳定范围 | 用途 |
|---|---|---|---|
leafId | 布局 | tab 生命周期 | split tree 中的叶节点 |
paneKey | 逻辑 pane | tabId:leafId | hooks、状态、绑定与恢复 |
| terminal handle | runtime API | runtime 生命周期 | CLI/mobile 对 terminal 的地址 |
ptyId | provider | provider/incarnation 生命周期 | 真实 PTY 路由 |
paneKey 由稳定 UUID leaf 构成。旧版数字 leaf(如 pane:1)在迁移中被视为不够可靠,因为重排后可能指向不同 pane。相关品牌类型在 stable-pane-id.ts。
为什么不能只用 ptyId
- 新 shell 可能复用同一个业务 session id;
- daemon / relay 需要全局路由前缀;
- tab 尚未完成注册时 PTY 已可能退出;
- pane detach 后 UI 所有者变化,进程不一定变化。
因此 PtySpawnResult 还带 incarnationId,用来区分同一个 ptyId 背后的不同物理进程。
9. ExecutionHostId:能力究竟在哪执行
执行 host 常见形态:
local
ssh:<target-id>
runtime:<environment-id>connectionId 是旧的/具体 SSH 路由字段,executionHostId 是更统一的 host 所有权。代码中经常需要同时读两者,是模型迁移期的现实。
一个 workspace 的 host 决定:
- Git 命令由本机 runner、WSL、SSH provider 还是 runtime RPC 执行;
- PTY 使用 local/daemon、SSH 或 remote runtime transport;
- 文件 watcher 和 browser backend 在哪台机器;
- session persistence 写到哪个 host partition。
10. Agent Session 不是 Terminal Session
Agent provider 自己还有 conversation/session identity,例如 Codex session id、Claude transcript path、Pi session path。它们被归一化为 AgentProviderSessionMetadata。
关系是:
一个 pane -> 当前一个 PTY -> 可能托管一个 Agent CLI
这个 Agent CLI -> 可能绑定一个 provider conversation恢复时需要区分:
- PTY 还活着:attach 原进程;
- PTY 不在但 provider conversation 可恢复:启动新 CLI 并传 resume 参数;
- 两者都没有:启动新会话;
- 远端状态未知:不能擅自启动替代会话。
11. 一组必须保持的领域不变量
paneKey中的 tab segment 必须与事件的tabId一致;- 一个 live agent owner 由 PTY incarnation 与 host scope 共同限定;
- renderer 不能通过旧 session snapshot 增删 host-owned live terminal;
- 一个 worktree path 只能在正确 repo/host 语境中解释;
- folder workspace 不能进入需要 Git branch 的代码路径;
- SSH disconnected 只表示不可观察,不表示 worktree/PTY 不存在;
- ProjectGroup 的变化不能改变执行 scope。
这些不变量解释了源码中大量 validation、normalization 和 revision fencing。
12. 源码导航
- 领域总类型:
src/shared/types.ts - workspace scope:
src/shared/workspace-scope.ts - worktree id:
src/shared/worktree-id.ts - stable pane:
src/shared/stable-pane-id.ts - runtime types:
src/shared/runtime-types.ts - session resume:
src/shared/agent-session-resume.ts