Skip to content

第 3 章:领域模型——从 Project 到 PTY ​

目标:建立 Orca 的身份层级。后续所有恢复、同步和远程执行问题,本质上都是这些身份的映射问题。

1. 领域对象总图 ​

源码中的模型正在从“一个 Repo 就是一切”演进到“Project 可以在多个 Host 上有 setup”。因此会同时看到新模型与兼容字段。

2. Project、Repo 与 ProjectHostSetup ​

Project:逻辑项目 ​

Project 表示用户心中的同一个项目:显示名、图标、provider identity,以及在哪些 host 上有 source。

ts
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 上的部署 ​

它把“逻辑项目”和“在哪台机器的哪个路径能执行”拆开:

ts
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:

ts
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 文档把它表示为:

text
<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 有两种布局树:

  1. Tab group layout:把编辑器区域分成多个 tab group;
  2. 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逻辑 panetabId:leafIdhooks、状态、绑定与恢复
terminal handleruntime APIruntime 生命周期CLI/mobile 对 terminal 的地址
ptyIdproviderprovider/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 常见形态:

text
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。

关系是:

text
一个 pane -> 当前一个 PTY -> 可能托管一个 Agent CLI
这个 Agent CLI -> 可能绑定一个 provider conversation

恢复时需要区分:

  • PTY 还活着:attach 原进程;
  • PTY 不在但 provider conversation 可恢复:启动新 CLI 并传 resume 参数;
  • 两者都没有:启动新会话;
  • 远端状态未知:不能擅自启动替代会话。

11. 一组必须保持的领域不变量 ​

  1. paneKey 中的 tab segment 必须与事件的 tabId 一致;
  2. 一个 live agent owner 由 PTY incarnation 与 host scope 共同限定;
  3. renderer 不能通过旧 session snapshot 增删 host-owned live terminal;
  4. 一个 worktree path 只能在正确 repo/host 语境中解释;
  5. folder workspace 不能进入需要 Git branch 的代码路径;
  6. SSH disconnected 只表示不可观察,不表示 worktree/PTY 不存在;
  7. ProjectGroup 的变化不能改变执行 scope。

这些不变量解释了源码中大量 validation、normalization 和 revision fencing。

12. 源码导航 ​