Agent-readable wiki

Orca Developer Reference Wiki

Orca is a cross-platform Electron IDE that orchestrates multiple AI coding agents (Claude Code, Codex, Grok, Gemini, and 30+ more) running in parallel across isolated git worktrees. This reference covers the multi-process architecture, relay protocol, worktree lifecycle, source-control integration, and extension surfaces.

Pages

  1. Technical OrientationWhat Orca is, how its three Electron processes (main, preload, renderer) plus the relay server fit together, key entry points, build targets, and how the rest of this reference is organized.
  2. Relay Server & Agent Communication ProtocolThe WebSocket relay server (src/relay/) that every agent subprocess connects to for filesystem access, PTY sessions, git operations, and workspace-session state. Covers protocol handshake, handler dispatch, plugin overlay sandboxing, and the shared protocol envelope types.
  3. Worktree & Parallel Agent OrchestrationHow Orca creates, tracks, and tears down isolated git worktrees for each agent session; the agent-awake service, trust presets, hook lifecycle, worktree-removal safety checks, and the IPC surface that the renderer uses to drive orchestration.
  4. Git & Source-Control Integration LayerThe git runner, multi-provider source-control IPC handlers (GitHub, GitLab, Azure DevOps, Bitbucket, Gitea), hosted-review (PR/MR) flows, commit-message generation, and the shared git-history and upstream-status types that the renderer consumes.
  5. Renderer UI & Zustand State ManagementThe React renderer process: App entry point, Zustand store slices (worktrees, tabs, terminals, repos, agent-status), tab-group layout, terminal shell integration via xterm.js, sidebar, and the IPC bridge through which renderer slices subscribe to main-process events.
  6. CLI, Skills & Extension PointsThe orca CLI binary (src/cli/), built-in skill discovery and metadata (src/main/skills/, src/shared/skills.ts), the agent-hook relay for external automations, SSH remote-runtime support, and the operational surfaces developers use to extend or automate Orca (runtime environments, setup scripts, MCP config).

Complete Markdown

# Orca Developer Reference Wiki

> Orca is a cross-platform Electron IDE that orchestrates multiple AI coding agents (Claude Code, Codex, Grok, Gemini, and 30+ more) running in parallel across isolated git worktrees. This reference covers the multi-process architecture, relay protocol, worktree lifecycle, source-control integration, and extension surfaces.

## Context Links

- [Agent index](https://grok-wiki.com/public/wiki/stablyai-orca-47ffb1f68457/llms.txt)
- [Human interactive wiki](https://grok-wiki.com/public/wiki/stablyai-orca-47ffb1f68457)
- [GitHub repository](https://github.com/stablyai/orca)

## Repository Metadata

- Repository: stablyai/orca

- Generated: 2026-05-27T07:48:15.466Z
- Updated: 2026-05-27T07:50:40.638Z
- Runtime: Pi · Claude Code · claude-sonnet-4-6:high
- Format: Technical
- Pages: 6

## Page Index

- 01. [Technical Orientation](https://grok-wiki.com/public/wiki/stablyai-orca-47ffb1f68457/pages/01-technical-orientation.md) - What Orca is, how its three Electron processes (main, preload, renderer) plus the relay server fit together, key entry points, build targets, and how the rest of this reference is organized.
- 02. [Relay Server & Agent Communication Protocol](https://grok-wiki.com/public/wiki/stablyai-orca-47ffb1f68457/pages/02-relay-server-agent-communication-protocol.md) - The WebSocket relay server (src/relay/) that every agent subprocess connects to for filesystem access, PTY sessions, git operations, and workspace-session state. Covers protocol handshake, handler dispatch, plugin overlay sandboxing, and the shared protocol envelope types.
- 03. [Worktree & Parallel Agent Orchestration](https://grok-wiki.com/public/wiki/stablyai-orca-47ffb1f68457/pages/03-worktree-parallel-agent-orchestration.md) - How Orca creates, tracks, and tears down isolated git worktrees for each agent session; the agent-awake service, trust presets, hook lifecycle, worktree-removal safety checks, and the IPC surface that the renderer uses to drive orchestration.
- 04. [Git & Source-Control Integration Layer](https://grok-wiki.com/public/wiki/stablyai-orca-47ffb1f68457/pages/04-git-source-control-integration-layer.md) - The git runner, multi-provider source-control IPC handlers (GitHub, GitLab, Azure DevOps, Bitbucket, Gitea), hosted-review (PR/MR) flows, commit-message generation, and the shared git-history and upstream-status types that the renderer consumes.
- 05. [Renderer UI & Zustand State Management](https://grok-wiki.com/public/wiki/stablyai-orca-47ffb1f68457/pages/05-renderer-ui-zustand-state-management.md) - The React renderer process: App entry point, Zustand store slices (worktrees, tabs, terminals, repos, agent-status), tab-group layout, terminal shell integration via xterm.js, sidebar, and the IPC bridge through which renderer slices subscribe to main-process events.
- 06. [CLI, Skills & Extension Points](https://grok-wiki.com/public/wiki/stablyai-orca-47ffb1f68457/pages/06-cli-skills-extension-points.md) - The orca CLI binary (src/cli/), built-in skill discovery and metadata (src/main/skills/, src/shared/skills.ts), the agent-hook relay for external automations, SSH remote-runtime support, and the operational surfaces developers use to extend or automate Orca (runtime environments, setup scripts, MCP config).

## Source File Index

- `electron.vite.config.ts`
- `package.json`
- `README.md`
- `skills/orchestration/`
- `src/cli/dispatch.ts`
- `src/cli/index.ts`
- `src/main/agent-awake-service.ts`
- `src/main/agent-trust-presets.ts`
- `src/main/git/runner.ts`
- `src/main/git/status.ts`
- `src/main/git/worktree.ts`
- `src/main/hooks.ts`
- `src/main/index.ts`
- `src/main/ipc/github.ts`
- `src/main/ipc/gitlab.ts`
- `src/main/ipc/hosted-review.ts`
- `src/main/ipc/worktrees.ts`
- `src/main/repo-worktrees.ts`
- `src/main/skills/discovery.ts`
- `src/main/worktree-removal-safety.ts`
- `src/preload/index.ts`
- `src/relay/dispatcher.ts`
- `src/relay/fs-handler.ts`
- `src/relay/git-handler.ts`
- `src/relay/plugin-overlay.ts`
- `src/relay/protocol.ts`
- `src/relay/pty-handler.ts`
- `src/relay/relay.ts`
- `src/relay/workspace-session-handler.ts`
- `src/renderer/src/App.tsx`
- `src/renderer/src/components/Sidebar.tsx`
- `src/renderer/src/components/terminal/TerminalShell.tsx`
- `src/renderer/src/store/index.ts`
- `src/renderer/src/store/slices/tab-group-state.ts`
- `src/renderer/src/store/slices/tabs.ts`
- `src/renderer/src/store/slices/terminals.ts`
- `src/renderer/src/store/slices/worktrees.ts`
- `src/shared/agent-hook-types.ts`
- `src/shared/agents-orchestration-steps.ts`
- `src/shared/commit-message-generation.ts`
- `src/shared/git-history.ts`
- `src/shared/hosted-review.ts`
- `src/shared/mcp-config.ts`
- `src/shared/runtime-environments.ts`
- `src/shared/runtime-rpc-envelope.ts`
- `src/shared/setup-script-imports.ts`
- `src/shared/skills.ts`
- `src/shared/types.ts`
- `src/shared/workspace-session-schema.ts`
- `src/shared/worktree-id.ts`

---

## 01. Technical Orientation

> What Orca is, how its three Electron processes (main, preload, renderer) plus the relay server fit together, key entry points, build targets, and how the rest of this reference is organized.

- Page Markdown: https://grok-wiki.com/public/wiki/stablyai-orca-47ffb1f68457/pages/01-technical-orientation.md
- Generated: 2026-05-27T07:44:46.874Z

### Source Files

- `README.md`
- `package.json`
- `electron.vite.config.ts`
- `src/main/index.ts`
- `src/preload/index.ts`
- `src/renderer/src/App.tsx`
- `src/shared/types.ts`

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:

- [README.md](README.md)
- [package.json](package.json)
- [electron.vite.config.ts](electron.vite.config.ts)
- [vite.web.config.ts](vite.web.config.ts)
- [src/main/index.ts](src/main/index.ts)
- [src/main/daemon/daemon-entry.ts](src/main/daemon/daemon-entry.ts)
- [src/main/ipc/register-core-handlers.ts](src/main/ipc/register-core-handlers.ts)
- [src/main/runtime/orca-runtime.ts](src/main/runtime/orca-runtime.ts)
- [src/preload/index.ts](src/preload/index.ts)
- [src/renderer/src/App.tsx](src/renderer/src/App.tsx)
- [src/relay/relay.ts](src/relay/relay.ts)
- [src/shared/types.ts](src/shared/types.ts)
</details>

# Technical Orientation

This page explains what Orca is, how its runtime processes fit together, where the key entry points live, and how the rest of the codebase is organized. It is the first stop for any contributor who needs a mental map before diving into a specific subsystem.

Orca is a cross-platform Electron IDE (macOS, Windows, Linux) built around a single idea: letting developers run multiple AI CLI agents—Claude Code, Codex, Grok, Gemini, and dozens more—in parallel across isolated git worktrees, with native source-control review, GitHub/GitLab/Linear integration, SSH support, and a mobile companion app, all in one window. It is BYOK/BYOC by design; no Orca account or cloud service is required.

---

## Process Architecture

Orca is composed of five distinct runtime contexts. Three are the standard Electron process types; two are purpose-built for persistent terminals and remote execution.

```text
┌────────────────────────────────────────────────────────────┐
│  Electron main process  (src/main/index.ts)                │
│  - App lifecycle, BrowserWindow creation                   │
│  - Service wiring: Store, OrcaRuntimeService, accounts,    │
│    rate limits, agent hooks, automations, keybindings      │
│  - IPC handler registry (registerCoreHandlers)             │
│  - OrcaRuntimeRpcServer  (WebSocket, port 6768/6769)       │
│  - Forks: daemon child process                             │
│                                                            │
│    ┌──────────────────────────────────────────────────┐    │
│    │ Daemon child process (daemon-entry.ts)           │    │
│    │ node-pty host; Unix socket; keeps sessions alive  │    │
│    └──────────────────────────────────────────────────┘    │
└───────────────────┬────────────────────────────────────────┘
                    │  contextBridge (IPC)
┌───────────────────▼────────────────────────────────────────┐
│  Preload script  (src/preload/index.ts)                    │
│  - Exposes window.api.* to renderer via contextBridge      │
│  - Native file drag-and-drop resolution                    │
│  - Runtime environment subscriptions                       │
└───────────────────┬────────────────────────────────────────┘
                    │  window.api.*
┌───────────────────▼────────────────────────────────────────┐
│  Renderer process (src/renderer/src/App.tsx)               │
│  React 19 + Zustand + xterm.js + Monaco + Tailwind         │
│  Sidebar / Terminal / Editor / Source Control / Browser    │
└────────────────────────────────────────────────────────────┘

┌────────────────────────────────────────────────────────────┐
│  Relay server  (src/relay/relay.ts)                        │
│  Standalone Node.js; deployed to remote SSH host via SCP   │
│  stdin/stdout JSON-RPC to main process; Unix socket recon. │
└────────────────────────────────────────────────────────────┘

┌────────────────────────────────────────────────────────────┐
│  CLI binary  (src/cli/index.ts  →  out/cli/index.js)       │
│  WebSocket client to OrcaRuntimeRpcServer on main           │
└────────────────────────────────────────────────────────────┘
```

---

## Main Process (`src/main/index.ts`)

The main process is the app's composition root. Its single `app.whenReady()` block:

1. Reads or initializes the SQLite-backed `Store` (settings, repos, worktrees).
2. Instantiates every service: `OrcaRuntimeService`, `CodexAccountService`, `ClaudeAccountService`, `RateLimitService`, `AutomationService`, `KeybindingService`, crash-reporting, telemetry, stats.
3. Starts the agent hook server and the persistent-terminal daemon (via `startFirstWindowStartupServices`).
4. Creates the single `BrowserWindow` (`createMainWindow`) and registers all IPC handlers with `registerCoreHandlers`.
5. Starts `OrcaRuntimeRpcServer`—a WebSocket server (default port 6768, dev port 6769) that exposes the runtime to the CLI and mobile app.

A `--serve` flag enables a headless mode (`orca serve`) where the window is never created and the runtime is exposed over WebSocket only, making it usable as a background server for the Orca CLI.

Sources: [src/main/index.ts:1-120](), [src/main/index.ts:480-560]()

### Services Wired at Startup

| Service | Source module | Role |
|---|---|---|
| `Store` | `src/main/persistence.ts` | SQLite-backed settings, repos, worktrees |
| `OrcaRuntimeService` | `src/main/runtime/orca-runtime.ts` | Live tab/pane graph, PTY handles, worktree management |
| `OrcaRuntimeRpcServer` | `src/main/runtime/runtime-rpc.ts` | WebSocket RPC for CLI and mobile |
| `CodexAccountService` | `src/main/codex-accounts/service.ts` | Codex auth + managed home |
| `ClaudeAccountService` | `src/main/claude-accounts/service.ts` | Claude auth |
| `RateLimitService` | `src/main/rate-limits/service.ts` | Per-agent rate-limit polling |
| `AutomationService` | `src/main/automations/service.ts` | Automation rule execution |
| `KeybindingService` | `src/main/keybindings/keybinding-service.ts` | File-backed keybinding overrides |
| `AgentHookServer` | `src/main/agent-hooks/server.ts` | Loopback receiver for hook callbacks |
| `CrashReportStore` | `src/main/crash-reporting/crash-report-store.ts` | Process-gone diagnostics |

### IPC Handler Registration

`registerCoreHandlers` (`src/main/ipc/register-core-handlers.ts`) wires every `ipcMain.handle` and `ipcMain.on` call in one place, covering: app identity, repos, worktrees, PTY, filesystem, filesystem watcher, GitHub, GitLab, hosted review, Linear, Azure DevOps, Bitbucket, Gitea, feedback, crash reporting, export, stats, memory, rate limits, runtime, runtime environments, notifications, notebook, onboarding, developer and computer-use permissions, browser, speech, SSH, workspace cleanup/space/ports, settings, keybindings, account management (Codex, Claude, Grok, Gemini, Copilot, Cursor, OpenCode, Droid, Hermes, and more), and telemetry.

Sources: [src/main/ipc/register-core-handlers.ts:1-50]()

### Daemon Child Process

`src/main/daemon/daemon-entry.ts` is compiled as a separate Rollup entry (`daemon-entry` in `electron.vite.config.ts`) and placed outside the ASAR archive (`externalizeDeps`) so `child_process.fork()` can execute it from disk. It hosts `node-pty` for persistent terminal sessions: PTYs stay alive when the renderer reloads, crashes, or the window is temporarily hidden. The main process connects to it over a Unix socket (`--socket`) with a token (`--token`) for authentication.

Sources: [electron.vite.config.ts:46-65](), [src/main/daemon/daemon-entry.ts:1-50]()

---

## Preload Script (`src/preload/index.ts`)

The preload is the security boundary. It runs in a privileged context with access to both Node.js and the DOM, and uses Electron's `contextBridge` to expose a typed `window.api` object to the renderer. The renderer has no direct access to Node.js or Electron internals.

Key responsibilities:
- **`window.api.*`** — hundreds of typed async/sync wrappers around `ipcRenderer.invoke` and `ipcRenderer.send`, organized by domain: `app`, `repos`, `worktrees`, `pty`, `gh`, `gl`, `linear`, `ssh`, `settings`, `keybindings`, `runtime`, `speech`, `automations`, `crashReports`, and more.
- **Native file drop** — `dragover` and `drop` listeners use `webUtils.getPathForFile` (Electron 28+ API, unavailable in the renderer's isolated world) to resolve `File` objects to real filesystem paths, then classify the drop target by walking `composedPath()` for `data-native-file-drop-target` markers.
- **Notification sound** — a cached `HTMLAudioElement` for notification sounds, deduplicated per playing duration to match VS Code's `AccessibilitySignalService` pattern.
- **Runtime environment subscriptions** — imported helper `subscribeRuntimeEnvironmentFromPreload` manages polling/push subscriptions for runtime environment state.

Sources: [src/preload/index.ts:1-70](), [src/preload/index.ts:145-230]()

---

## Renderer Process (`src/renderer/src/App.tsx`)

The renderer is a React 19 single-page application bundled by Vite with Tailwind CSS v4. All state is managed via Zustand (`useAppStore`). The root `App` component:

- Drives the three-column layout: left sidebar (worktrees/repos), main content area (terminal panes, editor, browser), right sidebar (source control, file search, PR checks, explorer).
- Handles startup hydration: fetches settings → repos → worktrees → persisted UI → session → keybindings → SSH reconnect → terminal reconnect, with carefully ordered async steps and a recovery path that avoids writing corrupted state to disk on failure.
- Subscribes to IPC push events via `useIpcEvents()` (agent status, PTY data, settings changes, SSH state, GitHub PR refresh, etc.).
- Manages the global keyboard shortcut dispatcher: `keybindingMatchesAction` resolves shortcut actions against the user's `KeybindingOverrides`.
- Lazy-loads heavy page components: `Landing`, `TaskPage`, `AutomationsPage`, `Settings`, `SkillsPage`, `OnboardingFlow`, `QuickOpen`, `WorktreeJumpPalette`, and others.

The renderer also includes a **web build variant** (`vite.web.config.ts`) that compiles the same React application for browser hosting—used when the relay server or `orca serve` exposes a web client for mobile pairing. Output goes to `out/web/`.

Sources: [src/renderer/src/App.tsx:1-100](), [src/renderer/src/App.tsx:350-500](), [vite.web.config.ts:1-32]()

---

## Relay Server (`src/relay/relay.ts`)

The relay is a **standalone Node.js script** (not Electron) deployed to remote SSH hosts. Its lifecycle:

1. The Electron main process SCPs the compiled relay bundle to the remote host and launches it over an SSH `exec` channel.
2. Relay reads/writes framed JSON-RPC over `stdin`/`stdout`.
3. On client disconnect it opens a Unix domain socket (`relay.sock`) and enters a configurable grace period (default: `DEFAULT_SSH_RELAY_GRACE_PERIOD_SECONDS`), keeping all PTYs alive.
4. A subsequent connection passes `--connect`, which bridges the new SSH channel to the existing socket.

The relay registers handlers for: PTY (`PtyHandler`), filesystem (`FsHandler`), git (`GitHandler`), preflight (`PreflightHandler`), external automations (`ExternalAutomationsHandler`), port scanning (`PortScanHandler`), agent exec (`AgentExecHandler`), and workspace session (`WorkspaceSessionHandler`). It also runs a `RelayAgentHookServer` so remote agent hook callbacks route back to the Orca desktop.

Sources: [src/relay/relay.ts:1-80]()

---

## CLI (`src/cli/`)

The `orca` CLI binary (`out/cli/index.js`, also registered as `bin.orca` in `package.json`) is a pure TypeScript/Node.js program compiled separately from the Electron build. It connects to the running Orca desktop app's `OrcaRuntimeRpcServer` WebSocket to control worktrees, terminals, and agents programmatically. Key modules:

| File | Role |
|---|---|
| `src/cli/index.ts` | Entry point, command dispatch |
| `src/cli/args.ts` | Argument parsing |
| `src/cli/runtime-client.ts` | WebSocket client for RPC |
| `src/cli/handlers/` | Per-command implementations |

Sources: [package.json:6-8]()

---

## Build System

Orca uses **electron-vite** (`electron.vite.config.ts`) as the primary bundler for the three Electron contexts, plus a standalone Vite config for the web renderer. Rollup inputs are declared explicitly.

### Build Targets

| npm script | Output | Description |
|---|---|---|
| `build:electron-vite` | `out/main/`, `out/preload/`, `out/renderer/` | All three Electron process bundles |
| `build:relay` | `out/relay/` | Relay server (plain Node.js) |
| `build:cli` | `out/cli/` | CLI binary |
| `build:web` | `out/web/` | Web renderer for mobile/browser pairing |
| `build` | all of the above | Full production build (runs typecheck first) |
| `build:release` | all of the above | Release build (skips typecheck, runs native verification) |

### Main-Process Rollup Entries

The main process defines five separate entry points in `electron.vite.config.ts`:

| Entry key | Source | Purpose |
|---|---|---|
| `index` | `src/main/index.ts` | Main app entry |
| `daemon-entry` | `src/main/daemon/daemon-entry.ts` | Persistent PTY host (ASAR-unpacked) |
| `computer-sidecar` | `src/main/computer/sidecar-entry.ts` | Computer-use sidecar |
| `stt-worker` | `src/main/speech/stt-worker.ts` | Speech-to-text worker |
| `agent-hooks/managed-agent-hook-controls` | `src/main/agent-hooks/managed-agent-hook-controls.ts` | Hook control module for CLI |

Sources: [electron.vite.config.ts:43-70]()

### Compile-Time Telemetry Gate

`electron.vite.config.ts` substitutes three build constants at compile time (`ORCA_BUILD_IDENTITY`, `ORCA_POSTHOG_WRITE_KEY`, `ORCA_DIAGNOSTICS_TOKEN_URL`). CI injects real values via GitHub Actions secrets; every other build resolves them to `null`, making `IS_OFFICIAL_BUILD` false and short-circuiting all telemetry transport. There is no runtime environment variable fallback.

Sources: [electron.vite.config.ts:14-40]()

---

## Key Source Directories

```
src/
├── cli/           CLI binary (standalone Node.js, WebSocket RPC client)
├── main/          Electron main process
│   ├── agent-hooks/   Hook install/server for agent status callbacks
│   ├── daemon/        Persistent PTY daemon (forked child process)
│   ├── ipc/           All ipcMain handler registrations by domain
│   ├── runtime/       OrcaRuntimeService: live tab/pane/PTY graph
│   ├── startup/       Process configuration, path hydration, single-instance lock
│   ├── telemetry/     PostHog transport, consent, cohort classifier
│   ├── window/        BrowserWindow creation and attachment helpers
│   └── ...            Per-integration dirs: github/, gitlab/, linear/, ssh/, claude/, codex/, ...
├── preload/       Electron preload (contextBridge → window.api.*)
├── relay/         Remote SSH relay server (standalone Node.js)
├── renderer/      React UI
│   └── src/
│       ├── App.tsx        Root component, startup hydration, global shortcuts
│       ├── store/         Zustand store slices
│       ├── components/    All UI components (Sidebar, Terminal, RightSidebar, …)
│       ├── hooks/         React hooks (IPC subscriptions, shortcuts, scroll, …)
│       ├── lib/           Pure utilities (theme, keyboard, telemetry, session, …)
│       ├── runtime/       Runtime graph sync (mobile/web session publishing)
│       └── assets/        CSS design tokens, fonts
├── shared/        Types and pure logic shared across all contexts
│   ├── types.ts           Core domain types (Repo, Worktree, GlobalSettings, …)
│   ├── agent-detection.ts Agent status inference from OSC terminal titles
│   ├── keybindings.ts     KeybindingActionId, override types, matcher
│   └── ...                ssh-types, runtime-types, automations-types, …
└── types/         Ambient .d.ts shims (env.d.ts, vite/client, build-constants)
```

### `src/shared/` — The Cross-Context Contract

`src/shared/` is imported by main, preload, renderer, CLI, and relay alike. It must never import from Electron or Node-only modules. `src/shared/types.ts` defines the canonical domain types (`Repo`, `Worktree`, `GlobalSettings`, `TabGroupLayoutNode`, etc.). Per the project convention, all project-owned types live in `.ts` files, not `.d.ts`, so `skipLibCheck` does not silently swallow type errors.

Sources: [src/shared/types.ts:1-80]()

---

## Data Flow: IPC from Renderer to Main

```mermaid
sequenceDiagram
    participant R as Renderer (React)
    participant P as Preload (contextBridge)
    participant M as Main Process
    participant S as Service / Native

    R->>P: window.api.pty.spawn(opts)
    P->>M: ipcRenderer.invoke('pty:spawn', opts)
    M->>S: LocalPtyProvider or DaemonPtyProvider
    S-->>M: { id, snapshot, ... }
    M-->>P: resolve
    P-->>R: Promise<{ id, ... }>
    M->>P: ipcRenderer.send / webContents.send('pty:data', {id, data})
    P->>R: onData callback (window.api.pty.onData)
```

Push events travel in the reverse direction: the main process calls `mainWindow.webContents.send(channel, payload)`, and the preload registers `ipcRenderer.on(channel, listener)` listeners that fan out to renderer callbacks registered via `window.api.<domain>.on*` subscriptions.

---

## Runtime Topology at a Glance

`OrcaRuntimeService` (`src/main/runtime/orca-runtime.ts`) is the central state machine for the live session. It owns:
- The window tab/pane graph (`syncWindowGraph`) used to publish the layout to mobile and the CLI.
- All active PTY handles, keyed by pane key.
- Worktree creation, removal, and lineage.
- Commit-message agent orchestration.
- Account service integration for launch-time auth preparation (Claude, Codex).

The `OrcaRuntimeRpcServer` wraps the service and exposes it over WebSocket using the `RuntimeRpcEnvelope` framing defined in `src/shared/runtime-rpc-envelope.ts`. The CLI and the mobile app both use this channel.

Sources: [src/main/runtime/orca-runtime.ts:1-55]()

---

## Wiki Structure

The remaining pages in this wiki cover individual subsystems in depth. The natural reading order after this orientation is:

- **Terminal & PTY** — `node-pty`, daemon persistence, relay PTY, xterm.js renderer integration
- **Worktrees & Repos** — git worktree lifecycle, sparse checkout, lineage tracking
- **Agent Hooks** — hook installation, loopback server, relay agent-hook server, status inference
- **SSH & Relay** — relay deployment, reconnect protocol, port forwarding
- **Source Control** — git staging, diff annotation, PR/MR review workflows
- **GitHub / GitLab / Linear Integration** — IPC handler design, caching, mutation broadcasts
- **Runtime RPC & CLI** — WebSocket envelope, CLI command dispatch, `orca serve` mode
- **Settings & Persistence** — SQLite Store, session JSON, UI state
- **Telemetry & Observability** — consent, PostHog gate, OpenTelemetry tracing lane, crash reporting
- **Design System** — CSS tokens, shadcn primitives, Tailwind v4 usage

Every subsystem corresponds to a directory under `src/main/`, `src/relay/`, or `src/renderer/src/components/` with the same domain name.

---

## 02. Relay Server & Agent Communication Protocol

> The WebSocket relay server (src/relay/) that every agent subprocess connects to for filesystem access, PTY sessions, git operations, and workspace-session state. Covers protocol handshake, handler dispatch, plugin overlay sandboxing, and the shared protocol envelope types.

- Page Markdown: https://grok-wiki.com/public/wiki/stablyai-orca-47ffb1f68457/pages/02-relay-server-agent-communication-protocol.md
- Generated: 2026-05-27T07:45:05.224Z

### Source Files

- `src/relay/relay.ts`
- `src/relay/protocol.ts`
- `src/relay/dispatcher.ts`
- `src/relay/pty-handler.ts`
- `src/relay/fs-handler.ts`
- `src/relay/git-handler.ts`
- `src/relay/workspace-session-handler.ts`
- `src/relay/plugin-overlay.ts`
- `src/shared/runtime-rpc-envelope.ts`

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:

- [src/relay/relay.ts](src/relay/relay.ts)
- [src/relay/protocol.ts](src/relay/protocol.ts)
- [src/relay/dispatcher.ts](src/relay/dispatcher.ts)
- [src/relay/pty-handler.ts](src/relay/pty-handler.ts)
- [src/relay/fs-handler.ts](src/relay/fs-handler.ts)
- [src/relay/git-handler.ts](src/relay/git-handler.ts)
- [src/relay/workspace-session-handler.ts](src/relay/workspace-session-handler.ts)
- [src/relay/plugin-overlay.ts](src/relay/plugin-overlay.ts)
- [src/relay/relay-handshake.ts](src/relay/relay-handshake.ts)
- [src/relay/agent-hook-server.ts](src/relay/agent-hook-server.ts)
- [src/shared/runtime-rpc-envelope.ts](src/shared/runtime-rpc-envelope.ts)
</details>

# Relay Server & Agent Communication Protocol

The Orca relay is a self-contained Node.js daemon deployed to remote hosts (via SCP over an SSH exec channel). It acts as the single transport bridge between the Electron main process and every feature that needs to run on the remote machine: PTY sessions, filesystem access, git operations, workspace-session state, and agent-status hooks. All communication flows over a binary-framed JSON-RPC 2.0 protocol initially carried on stdin/stdout, with an optional Unix domain socket fallback that keeps live PTY sessions alive across app restarts.

Understanding the relay is essential for contributors working on SSH workspaces, agent integration, or any feature that has a "remote" code path. The relay is versioned independently from the Orca Electron app, and its protocol is carefully versioned to support rolling upgrades, grace-period reconnection, and multi-client fanout for synced remote workspaces.

---

## Architecture Overview

```text
┌───────────────────────────────────────────────────────────┐
│  Orca Electron (main process / SSH channel)               │
│  SshChannelMultiplexer ↔ framed JSON-RPC                  │
└────────────────┬──────────────────────────────────────────┘
                 │ stdin / stdout  (initial SSH exec channel)
                 │ OR Unix domain socket (--connect bridge)
┌────────────────▼──────────────────────────────────────────┐
│  relay.ts  —  daemon entry point                          │
│  ┌───────────────────────────────────────────────────┐    │
│  │  RelayDispatcher  (protocol.ts framing)            │    │
│  │  ┌──────────┐ ┌─────────┐ ┌──────┐ ┌──────────┐  │    │
│  │  │PtyHandler│ │FsHandler│ │GitH. │ │WsSession │  │    │
│  │  └──────────┘ └─────────┘ └──────┘ └──────────┘  │    │
│  │  ┌───────────────────┐  ┌────────────────────────┐│    │
│  │  │PluginOverlayMgr   │  │RelayAgentHookServer    ││    │
│  │  │(~/.orca-relay/)   │  │(loopback HTTP :0)      ││    │
│  │  └───────────────────┘  └────────────────────────┘│    │
│  └───────────────────────────────────────────────────┘    │
│  Unix domain socket  (relay.sock)  ← --connect bridge     │
└───────────────────────────────────────────────────────────┘
                 │
          agent CLIs in PTYs (Pi, OMP, OpenCode)
          post hook events → loopback HTTP → agent.hook JSON-RPC
```

---

## Wire Protocol

### Binary Frame Format

Every byte on the wire — in both directions — is wrapped in a fixed 13-byte header defined in `src/relay/protocol.ts`.

| Offset | Size | Field | Description |
|--------|------|-------|-------------|
| 0 | 1 | `type` | `1` = Regular JSON-RPC, `2` = Handshake, `9` = KeepAlive |
| 1 | 4 | `id` | Outgoing sequence number (uint32BE) |
| 5 | 4 | `ack` | Highest received sequence number from peer (uint32BE) |
| 9 | 4 | `length` | Payload byte count (uint32BE) |
| 13 | N | payload | JSON-encoded JSON-RPC 2.0 message (Regular/Handshake) or empty (KeepAlive) |

Maximum payload size is 16 MB (`MAX_MESSAGE_SIZE = 16 * 1024 * 1024`). Oversized frames are discarded rather than raising an exception so the decoder stays synchronized with the stream.

Sources: [src/relay/protocol.ts:14-18](), [src/relay/protocol.ts:78-102]()

### JSON-RPC 2.0 Envelope

Regular frames carry a standard JSON-RPC 2.0 object:

```typescript
// Request (client → relay)
{ jsonrpc: '2.0', id: number, method: string, params?: Record<string, unknown> }

// Response (relay → client)
{ jsonrpc: '2.0', id: number, result?: unknown }
{ jsonrpc: '2.0', id: number, error?: { code: number, message: string, data?: unknown } }

// Notification (either direction, no id)
{ jsonrpc: '2.0', method: string, params?: Record<string, unknown> }
```

Cancellation uses a standard `rpc.cancel` notification with `{ id: <request-id> }`, which aborts the corresponding in-flight `AbortController`.

Sources: [src/relay/protocol.ts:59-75](), [src/relay/dispatcher.ts:185-191]()

### KeepAlive

The dispatcher emits a `KeepAlive` frame (type `9`, empty payload) on a 5-second interval (`KEEPALIVE_SEND_MS = 5_000`) for every attached client. The timer is `unref()`-ed so it does not prevent natural process exit.

Sources: [src/relay/protocol.ts:27](), [src/relay/dispatcher.ts:232-252]()

### File Streaming Constants

Large file reads use a chunked streaming subprotocol:

| Constant | Value |
|----------|-------|
| `STREAM_CHUNK_SIZE` | 256 KB |
| `MAX_CONCURRENT_STREAMS` | 16 |
| `RelayErrorCode.TooManyStreams` | -33006 |
| `RelayErrorCode.StreamProtocolError` | -33007 |

Sources: [src/relay/protocol.ts:35-42]()

### Runtime RPC Envelope

`src/shared/runtime-rpc-envelope.ts` defines a second, higher-level envelope used by runtime clients (CLI, desktop) that wrap agent subprocess responses — distinct from the relay wire frame. It is validated with Zod and carries a `runtimeId` for routing:

```typescript
type RuntimeRpcSuccess<TResult> = { id: string; ok: true; result: TResult; _meta: { runtimeId: string } }
type RuntimeRpcFailure = { id: string; ok: false; error: { code: string; message: string }; _meta?: { runtimeId: string | null } }
type RuntimeRpcKeepaliveFrame = { _keepalive: true }
```

Sources: [src/shared/runtime-rpc-envelope.ts:4-70]()

---

## Startup & Handshake

### Normal Mode

1. The Electron app deploys `relay.js` via SCP and launches it with `ssh exec`.
2. The relay starts a Unix domain socket server (`relay.sock` next to `relay.js`, perms `0o600` set atomically via `umask(0o177)` before `listen()`).
3. After all async setup (socket server + agent hook server bound), the relay writes `RELAY_SENTINEL` (`ORCA-RELAY v0.1.0 READY\n`) to stdout.
4. The client sees the sentinel, begins sending framed JSON-RPC data over stdin.

Sources: [src/relay/protocol.ts:6](), [src/relay/relay.ts:398-400](), [src/relay/relay.ts:340-351]()

### --connect Bridge Mode (Reconnection)

When the Orca app restarts after a disconnect, the live relay is still running in its grace period with PTYs intact. A new SSH exec channel runs `relay.js --connect`, which:

1. Opens a TCP connection to the existing `relay.sock`.
2. Performs a **version handshake** (Handshake frame type) before any JSON-RPC frames are sent.
3. On success, writes `RELAY_SENTINEL` to the new channel's stdout and pipes `stdin ↔ socket`.

The handshake uses a dedicated frame type (`MessageType.Handshake = 2`) carrying JSON:

```typescript
// Bridge → Daemon
{ type: 'orca-relay-handshake', version: string }

// Daemon → Bridge (accept)
{ type: 'orca-relay-handshake-ok', version: string }

// Daemon → Bridge (reject)
{ type: 'orca-relay-handshake-mismatch', expected: string, got: string }
```

A version mismatch causes the bridge to exit with code **42** (`EXIT_CODE_VERSION_MISMATCH`), which the Electron client maps to a non-retryable `RelayVersionMismatchError`.

Sources: [src/relay/relay-handshake.ts:20-24](), [src/relay/relay-handshake.ts:80-120](), [src/relay/relay.ts:118-164]()

### Grace Period

When stdin closes (SSH channel drops), the relay does **not** exit immediately. It starts a configurable grace timer (default from `DEFAULT_SSH_RELAY_GRACE_PERIOD_SECONDS`) during which all PTYs remain alive and `relay.sock` stays open. The grace is canceled the moment a `--connect` client connects. The host can reduce or eliminate the grace window via the `SSH_RELAY_CONFIGURE_GRACE_TIME_METHOD` request/notification.

```
SIGHUP is explicitly ignored (instead of using Node's default exit)
so the relay survives the OS signal sent when the SSH session drops.
```

Sources: [src/relay/relay.ts:86-90](), [src/relay/relay.ts:402-415](), [src/relay/relay.ts:451-458]()

### Stale Socket Detection

On startup, if `relay.sock` already exists (`EADDRINUSE`), the relay probes the path with a short `connect()` call (`STALE_SOCKET_PROBE_TIMEOUT_MS = 500 ms`). If the probe is refused (`ECONNREFUSED`), the socket is stale (left by a crashed relay), and the relay unlinks it and retries `listen()`. If the probe connects, a live daemon owns it and startup fails cleanly.

Sources: [src/relay/relay.ts:253-325]()

---

## Dispatcher (`RelayDispatcher`)

`RelayDispatcher` is the central hub that receives raw binary data from one or more clients, decodes frames, routes JSON-RPC messages to handlers, and multiplexes outgoing frames back to each client.

```mermaid
sequenceDiagram
    participant Client as Orca (SSH/socket)
    participant Dispatcher as RelayDispatcher
    participant Handler as MethodHandler

    Client->>Dispatcher: feed(chunk) [framed JSON-RPC]
    Dispatcher->>Dispatcher: FrameDecoder → handleFrame
    alt Request
        Dispatcher->>Handler: handler(params, context)
        Handler-->>Dispatcher: result / Error
        Dispatcher-->>Client: JSON-RPC Response frame
    else Notification
        Dispatcher->>Handler: handler(params, context)
    else rpc.cancel
        Dispatcher->>Dispatcher: AbortController.abort()
    end
    Dispatcher->>Client: keepalive frame (every 5s)
```

### Multi-Client Support

A `RelayClient` record tracks per-client state: `FrameDecoder`, write callback, outgoing sequence counter, highest received sequence (for acks), a generation counter, and a `closed` flag.

- **Primary client**: the initial stdin/stdout transport. Its write callback is replaced (via `setWrite`) when a `--connect` socket client takes over.
- **Secondary clients**: attached via `attachClient()` for synced-workspace scenarios where multiple Orca instances share one relay.

Sequence counters and decoder state reset on reconnect so the new client's multiplexer starts fresh at seq=1 without triggering false positive timeout alarms on the client side.

Sources: [src/relay/dispatcher.ts:36-55](), [src/relay/dispatcher.ts:62-88]()

### Request Lifecycle & Stale Detection

Each in-flight request gets an `AbortController`. The `RequestContext` passed to handlers exposes:

- `isStale()` — returns `true` if the client disconnected mid-flight (generation mismatch, client removed, or signal aborted).
- `signal` — an `AbortSignal` usable in `fetch`/`fs` calls.

Handlers check `context.isStale()` after any `await` and drop responses to disconnected clients. Mutating work (e.g., a `pty.spawn` that completed while the client was reconnecting) is torn down immediately.

Sources: [src/relay/dispatcher.ts:162-200]()

---

## Handler Domains

### PtyHandler — `pty.*`

`PtyHandler` wraps `node-pty` (loaded lazily; absent module returns a clean error) and registers the following RPC surface:

| Method | Type | Description |
|--------|------|-------------|
| `pty.spawn` | Request | Spawn a login shell, returns `{ id }` |
| `pty.attach` | Request | Attach to existing PTY; returns buffered replay (last 100 KB) |
| `pty.shutdown` | Request | SIGTERM + 5 s SIGKILL fallback, or immediate SIGKILL |
| `pty.sendSignal` | Request | Send an allowlisted POSIX signal |
| `pty.getCwd` | Request | Resolve current working directory via `/proc`/`lsof` |
| `pty.resize` | Notification | Resize PTY columns/rows |
| `pty.data` | Notification | Client → relay: write data to PTY |
| `pty.getDefaultShell` | Request | Resolve default login shell |
| `pty.serialize` / `pty.revive` | Request | Persist PTY state across relay cold restarts |
| `pty.listProcesses` | Request | List active PTYs with cwd and title |

**Spawn env composition**: `process.env` is merged with the renderer-supplied env, then each registered `PtyEnvAugmenter` is applied in order. Augmenters inject `ORCA_AGENT_HOOK_*` coordinates (from the agent hook server) and plugin overlay paths (`OPENCODE_CONFIG_DIR`, `PI_CODING_AGENT_DIR`). Augmenter values override renderer-supplied env, ensuring remote paths win over local `userData` paths.

**Grace timer**: the PTY handler owns the grace countdown. It is started when stdin closes and canceled when any client sends data.

Sources: [src/relay/pty-handler.ts:106-140](), [src/relay/pty-handler.ts:155-205](), [src/relay/pty-handler.ts:261-340]()

### FsHandler — `fs.*`

`FsHandler` exposes a full filesystem API over the relay channel:

| Method | Description |
|--------|-------------|
| `fs.readDir` | Directory listing with symlink-target type resolution |
| `fs.readFile` | Full file read (UTF-8) |
| `fs.readFileStream` | Chunked streaming read (256 KB chunks, max 16 concurrent) |
| `fs.writeFile` | Atomic write (rejects directories) |
| `fs.stat` / `fs.lstat` | File metadata; `stat` follows symlinks |
| `fs.createFile` / `fs.createDir` / `fs.createDirNoClobber` | Create operations |
| `fs.deletePath` | Delete file or directory (recursive flag required for dirs) |
| `fs.rename` / `fs.copy` | Move and copy |
| `fs.realpath` | Resolve symlink target |
| `fs.search` | Full-text search via `rg` (ripgrep), fallback to `git grep` |
| `fs.listFiles` | File listing via `rg --files` → `git ls-files` → readdir fallback |
| `fs.watch` / `fs.unwatch` | Filesystem watch via `@parcel/watcher`; emits `fs.changed` notifications |
| `fs.workspaceSpaceScan` | Workspace Space directory scan |
| `fs.cancelStream` | Abort an in-progress streaming read |

File watches are tracked per client: when a client disconnects, its watch subscriptions are released automatically via `onClientDetached`. Maximum concurrent watches: 20.

All paths pass through `expandTilde()` before any fs operation.

Sources: [src/relay/fs-handler.ts:66-95](), [src/relay/fs-handler.ts:157-230]()

### GitHandler — `git.*`

`GitHandler` shells out to `git` for all operations. Key RPC methods:

| Method | Description |
|--------|-------------|
| `git.status` | Working tree status |
| `git.checkIgnored` | Check paths against `.gitignore` |
| `git.history` | Commit log |
| `git.commit` | Create commit (used by worktree ops) |
| `git.diff` | File diff |
| `git.stage` / `git.unstage` / `git.bulkStage` | Staging area management |
| `git.discard` / `git.bulkDiscard` | Discard working-tree changes |
| `git.conflictOperation` | Conflict detection |
| `git.branchCompare` / `git.commitCompare` | Branch/commit diff |
| `git.upstreamStatus` | Upstream divergence |
| `git.fetch` / `git.push` / `git.pull` | Remote operations |
| `git.rebaseFromBase` | Rebase workflow |
| `git.listWorktrees` / `git.addWorktree` / `git.removeWorktree` | Git worktree management |
| `git.exec` | Validated arbitrary `git` exec (args validated via `validateGitExecArgs`) |
| `git.isGitRepo` | Check whether a path is inside a git repo |

Max git output buffer: 10 MB. Bulk stage/unstage operations are chunked at 100 paths.

Sources: [src/relay/git-handler.ts:59-88](), [src/relay/git-handler.ts:44-46]()

### WorkspaceSessionHandler — `workspace.*`

Persists shared workspace state for synced remote workspaces. Snapshots are written as JSON to `~/.orca/sessions/<namespace>.json` using atomic rename (write to `.tmp`, then `rename()`), with `0o700` directory permissions and `0o600` file permissions.

| Method | Description |
|--------|-------------|
| `workspace.get` | Read the current session snapshot for a namespace |
| `workspace.patch` | Apply a `replace-session` patch; emits `workspace.changed` notification to all clients |
| `workspace.presence` | Heartbeat for connected clients; expires stale entries after 45 s |

The `patch` method includes optimistic concurrency: it compares `baseRevision` to the stored revision and returns `{ ok: false, reason: 'stale-revision', snapshot }` on mismatch.

The session shape tracks `activeRepoId`, `activeWorktreeId`, `activeTabId`, `tabsByWorktree`, and `terminalLayoutsByTabId`.

Sources: [src/relay/workspace-session-handler.ts:39-75](), [src/relay/workspace-session-handler.ts:107-160]()

---

## Plugin Overlay Sandboxing

### Problem

Agent CLIs (OpenCode, Pi, OMP) read configuration from paths like `OPENCODE_CONFIG_DIR` or `PI_CODING_AGENT_DIR`. In local Orca sessions these paths point to Electron `userData`. In SSH sessions the remote host has no `userData`, so the relay must materialize equivalent overlay directories on the remote filesystem.

### Solution: `PluginOverlayManager`

On receipt of `agent_hook.installPlugins` (sent by Orca at session-ready), the relay stores the plugin source bodies in memory:

- `opencodePluginSource` → `orca-opencode-status.js`
- `piExtensionSource` → Pi-flavored `orca-agent-status.ts`
- `ompExtensionSource` → OMP-flavored `orca-agent-status.ts`

Source bodies are sent over the wire (not bundled with the relay binary) because Orca and the relay are versioned independently and plugin source changes with every agent event addition. A 256 KB cap per source body is enforced (`assertPluginSourceUnderByteCap`).

On each `pty.spawn`, the `PtyEnvAugmenter` calls `materializeOpenCode(id, sourceDir)` or `materializePi(id, sourceDir, kind)`, which:

1. Removes any prior overlay for this id (`safeRemoveOverlay`).
2. Creates `~/.orca-relay/<type>-overlays/<sha256(id)[0:32]>/`.
3. Mirrors the user's existing config dir into the overlay (skipping the Orca-owned plugin file).
4. Writes the fresh plugin source into the overlay's `plugins/` or `extensions/` directory.

The resulting path is injected as `OPENCODE_CONFIG_DIR` or `PI_CODING_AGENT_DIR` into the PTY env, overriding any renderer-supplied value.

On PTY exit, `clearOverlay(id)` removes the overlay directories for all known roots in one pass.

```text
~/.orca-relay/
├── opencode-overlays/<sha256(paneKey)>/
│   ├── <user's config mirrored>
│   └── plugins/
│       └── orca-opencode-status.js   ← written fresh on each spawn
├── pi-overlays/<sha256(paneKey)>/
│   ├── <user's ~/.pi/agent mirrored>
│   └── extensions/
│       └── orca-agent-status.ts
├── omp-overlays/<sha256(paneKey)>/
│   └── extensions/
│       └── orca-agent-status.ts
└── agent-hooks/
    └── <endpoint env file>           ← hook server binding info
```

Sources: [src/relay/plugin-overlay.ts:1-43](), [src/relay/plugin-overlay.ts:190-255]()

---

## Agent Hook Server

The relay hosts a loopback HTTP server (`RelayAgentHookServer`) on `127.0.0.1:0` so agent CLIs running inside relay-spawned PTYs can POST hook events without leaving the host. Bind happens before `RELAY_SENTINEL` is written to ensure every PTY spawned immediately after the sentinel sees the correct `ORCA_AGENT_HOOK_*` env coordinates.

Parsed hook payloads are forwarded as `agent.hook` JSON-RPC notifications over the existing SSH channel:

```
agent CLI → POST /hook/<source> (loopback HTTP)
          → RelayAgentHookServer
          → dispatcher.notify('agent.hook', envelope)
          → SSH channel → Orca main process
```

The server caches the last status per `paneKey`. After a `--connect` reconnect, Orca issues `agent_hook.requestReplay` to re-receive cached entries, closing the race where a status notification was sent during reconnection.

Sources: [src/relay/relay.ts:208-255](), [src/relay/relay.ts:279-310]()

---

## Session & Utility Requests

Beyond the four primary handler domains, `relay.ts` registers a small set of session-level requests directly on the dispatcher:

| Method | Description |
|--------|-------------|
| `session.registerRoot` | No-op (kept for protocol back-compat with older clients) |
| `session.resolveHome` | Expand `~` to `homedir()` on the remote host |
| `SSH_RELAY_CONFIGURE_GRACE_TIME_METHOD` | Adjust the PTY grace period (e.g., set to 0 before system sleep) |
| `relay.status` | Health check: PID, uptime, memory, active PTY count, socket state, grace state |
| `agent_hook.installPlugins` | Cache plugin source bodies for overlay materialization |
| `agent_hook.requestReplay` | Replay cached per-pane hook payloads after reconnection |

Sources: [src/relay/relay.ts:198-215](), [src/relay/relay.ts:219-230](), [src/relay/relay.ts:248-262]()

---

## Reconnection Flow

```mermaid
sequenceDiagram
    participant Orca as Orca (new launch)
    participant Bridge as relay.js --connect
    participant Daemon as relay daemon (grace)

    Orca->>Bridge: SSH exec relay.js --connect
    Bridge->>Daemon: connect(relay.sock)
    Bridge->>Daemon: Handshake frame {type, version}
    Daemon-->>Bridge: handshake-ok (or mismatch → exit 42)
    Bridge->>Orca: write RELAY_SENTINEL to stdout
    note over Daemon: cancelGrace("socket client accepted")
    Orca->>Bridge: JSON-RPC frames (stdin)
    Bridge->>Daemon: forwarded via socket
    Daemon-->>Bridge: JSON-RPC frames (socket)
    Bridge->>Orca: forwarded to stdout
    Orca->>Daemon: agent_hook.requestReplay
    Daemon-->>Orca: replay cached pane statuses
```

Sources: [src/relay/relay.ts:118-164](), [src/relay/relay-handshake.ts:65-95](), [src/relay/relay.ts:357-390]()

---

## Summary

The relay is a purpose-built, electron-free Node.js process that bridges an SSH exec channel to a full suite of remote capabilities. Its binary frame protocol (13-byte header, JSON-RPC 2.0 payload, sequence+ack tracking, keepalive) provides reliable ordering and reconnection safety. The dispatcher's multi-client model, generation-based stale detection, and per-request `AbortController` make it safe for live PTY state to survive app restarts. Plugin overlay sandboxing ensures that agent CLIs running in remote PTYs receive the correct status-reporting extensions even when the relay binary and Orca app are deployed at different versions. The workspace session handler adds collaborative multi-client session persistence on top of the same JSON-RPC channel, completing the relay's role as the universal remote-capability substrate for Orca.

---

## 03. Worktree & Parallel Agent Orchestration

> How Orca creates, tracks, and tears down isolated git worktrees for each agent session; the agent-awake service, trust presets, hook lifecycle, worktree-removal safety checks, and the IPC surface that the renderer uses to drive orchestration.

- Page Markdown: https://grok-wiki.com/public/wiki/stablyai-orca-47ffb1f68457/pages/03-worktree-parallel-agent-orchestration.md
- Generated: 2026-05-27T07:47:12.893Z

### Source Files

- `src/main/repo-worktrees.ts`
- `src/main/worktree-removal-safety.ts`
- `src/main/agent-awake-service.ts`
- `src/main/agent-trust-presets.ts`
- `src/main/hooks.ts`
- `src/main/ipc/worktrees.ts`
- `src/shared/worktree-id.ts`
- `src/shared/workspace-session-schema.ts`
- `src/shared/agents-orchestration-steps.ts`

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:

- [src/main/repo-worktrees.ts](src/main/repo-worktrees.ts)
- [src/main/worktree-removal-safety.ts](src/main/worktree-removal-safety.ts)
- [src/main/agent-awake-service.ts](src/main/agent-awake-service.ts)
- [src/main/agent-trust-presets.ts](src/main/agent-trust-presets.ts)
- [src/main/hooks.ts](src/main/hooks.ts)
- [src/main/ipc/worktrees.ts](src/main/ipc/worktrees.ts)
- [src/main/ipc/worktree-remote.ts](src/main/ipc/worktree-remote.ts)
- [src/main/worktree-orphan-gitdir-proof.ts](src/main/worktree-orphan-gitdir-proof.ts)
- [src/shared/worktree-id.ts](src/shared/worktree-id.ts)
- [src/shared/workspace-session-schema.ts](src/shared/workspace-session-schema.ts)
- [src/shared/agents-orchestration-steps.ts](src/shared/agents-orchestration-steps.ts)
</details>

# Worktree & Parallel Agent Orchestration

Orca models each agent session as an isolated git worktree. Every workspace the user opens or creates maps to one linked worktree directory — a full checkout of a separate branch that shares the `.git` object store with the repository's primary worktree. This design lets multiple AI agents run concurrently without touching each other's working files.

This page covers the full lifecycle: how worktree identities are encoded, how listings fan out across local and SSH repos, the safety checks that guard removal, the agent-awake service that prevents the OS from suspending while agents are active, the trust-preset mechanism that bypasses agent startup prompts, the hook system that runs setup and teardown scripts, and the Electron IPC surface that ties the renderer's orchestration UI to the main-process backend.

---

## Worktree Identity

Every worktree in Orca is referenced by a **worktree ID** — a stable compound string of the form:

```
{repoId}::{worktreePath}
```

The `::` separator (exported as `WORKTREE_ID_SEPARATOR`) lets callers split the ID without knowing the platform path separator. Folder-mode repos, which have no linked-worktree graph, use a richer identity to support multiple concurrent workspace sessions inside the same directory:

```
{repoId}::{folderPath}::workspace:{uuid}
```

The `FOLDER_WORKSPACE_INSTANCE_SEPARATOR` constant (`::workspace:`) marks the boundary between the real filesystem path and the per-session UUID. `splitWorktreeIdForFilesystem` strips the UUID suffix so callers that need a CWD still get the real folder path.

Sources: [src/shared/worktree-id.ts:1-50]()

---

## Worktree Listing

`listRepoWorktrees` in `src/main/repo-worktrees.ts` is the authoritative entry point for enumerating a repo's worktrees at runtime. It dispatches across three modes:

| Repo mode | Resolved worktrees |
|---|---|
| **Folder** | Returns a single synthetic `GitWorktreeInfo` with `isMainWorktree: true`; no git call is made |
| **SSH** | Delegates to the SSH git provider; returns `[]` if the provider hasn't yet reattached during startup |
| **Local git** | Calls `listWorktrees(repo.path)` which runs `git worktree list` |

The folder branch creates a stub so Orca's worktree-first UI has a single stable workspace identity even when there is no linked-worktree graph.

Sources: [src/main/repo-worktrees.ts:1-40]()

---

## IPC Surface

The renderer communicates with the main process exclusively through Electron IPC. All worktree handlers are wired in `registerWorktreeHandlers` (`src/main/ipc/worktrees.ts`). Previous handlers are removed before re-registration so macOS window recreation does not accumulate duplicates.

### Handler catalogue

| IPC channel | Description |
|---|---|
| `worktrees:listAll` | Lists visible worktrees for all repos in parallel |
| `worktrees:list` | Lists visible worktrees for a single repo |
| `worktrees:listDetected` | Returns full `DetectedWorktree` data including visibility & ownership |
| `worktrees:create` | Creates a local, remote, or folder workspace |
| `worktrees:resolvePrBase` | Resolves the Git base for a GitHub PR |
| `worktrees:resolveMrBase` | Resolves the Git base for a GitLab MR |
| `worktrees:remove` | Runs archive hook, tears down PTYs, then removes the worktree |
| `worktrees:updateMeta` | Persists metadata changes without notifying other subscribers |
| `worktrees:listLineage` | Returns the full parent→child lineage map |
| `worktrees:updateLineage` | Reassigns a workspace's parent worktree |
| `worktrees:persistSortOrder` | Batch-stamps descending `sortOrder` timestamps for sidebar ordering |
| `hooks:check` | Reads and parses `orca.yaml`, flags unrecognized top-level keys |
| `hooks:createIssueCommandRunner` | Writes a platform-appropriate runner script for issue automation |
| `hooks:inspectSetupScriptImports` | Inspects candidate setup-script import files |
| `hooks:readIssueCommand` | Reads the local `.orca/issue-command` override and/or shared YAML command |
| `hooks:writeIssueCommand` | Writes or clears the per-user issue command override |

### Parallel listing

`worktrees:listAll` issues `listRepoWorktrees` for all repos concurrently with `Promise.all`, so the total latency is bounded by the slowest single repo rather than by the sum of all repos.

Sources: [src/main/ipc/worktrees.ts:343-380]()

### Concurrent removal deduplication

Double-clicks, stale toast actions, and sidebar races can all target the same worktree for removal simultaneously. The handler keeps a `worktreeRemovalsInFlight` map keyed by `worktreeId`. When a second call arrives with the same `optionsKey` (force/skipArchive combination), it receives the existing promise. A different `optionsKey` (e.g., normal then force) throws immediately.

Sources: [src/main/ipc/worktrees.ts:595-620]()

---

## Worktree Creation Flow

Creation dispatches to one of three helpers depending on repo mode:

```text
worktrees:create
    ├─ isFolderRepo  →  createFolderWorkspace()    (metadata only, no git)
    ├─ repo.connectionId  →  createRemoteWorktree() (via SSH git provider)
    └─ local          →  createLocalWorktree()      (git worktree add + hooks)
```

After a successful create, Orca:
1. Stamps provenance metadata (`orcaCreatedAt`, `orcaCreationSource: 'desktop'`) into the store.
2. Optionally runs the `setup` hook inside the new worktree directory.
3. Emits a `workspace_created` telemetry event.
4. Calls `notifyWorktreesChanged` to push a refresh to the renderer.

Sources: [src/main/ipc/worktrees.ts:430-495]()

---

## Worktree Removal Safety

`src/main/worktree-removal-safety.ts` provides layered protection before any destructive filesystem operation.

### Path-level guards

`isDangerousWorktreeRemovalPath` returns `true` (blocking removal) for:

- Empty or whitespace-only paths
- A path equal to the repository root
- A path that is the filesystem root (e.g., `/` or `C:\`)
- A path that **contains** the repository root (i.e., the repo is nested inside the target)
- A path that **contains** the user's home directory (`os.homedir()`)

Platform-aware path operations (`posix` vs `win32`) are selected based on whether any path looks like a Windows absolute path. This ensures Windows UNC paths routed through WSL work correctly.

Sources: [src/main/worktree-removal-safety.ts:43-73]()

### Git-registration guards

`findRegisteredDeletableWorktree` confirms that:
1. The requested path is registered in `git worktree list`.
2. The worktree is not `isMainWorktree`.
3. The path passes `isDangerousWorktreeRemovalPath`.
4. No other registered worktree lives **inside** the target path.

The last check protects against `git worktree remove --force` treating a nested linked worktree's working files as untracked directories.

Sources: [src/main/worktree-removal-safety.ts:75-112]()

### Orphaned directory proof

When a worktree directory exists on disk but is no longer registered with Git, `canSafelyRemoveOrphanedWorktreeDirectory` uses `gitFileProvesOrphanedWorktreeDirectory` (in `worktree-orphan-gitdir-proof.ts`) to confirm the directory's `.git` file actually points into this repo's `.git/worktrees/<name>` admin directory — and that the admin entry's back-reference (`gitdir` file) resolves back to the candidate. Only if both ends of the link match is the directory safe to remove recursively.

### Orca-provenance cleanup eligibility

`canCleanupUnregisteredOrcaWorktreeDirectory` decides whether an unregistered directory can be cleaned up by checking:

1. **Current provenance**: `orcaCreatedAt` + `orcaCreationSource` ∈ `{desktop, runtime, cli, ssh}`.
2. **Legacy evidence**: presence of `createdAt`, `createdWithAgent`, `pushTarget`, `sparseBaseRef`, `sparsePresetId`, or `preserveBranchOnDelete`.
3. **Path shape**: matches the `workspaceDir/<repo>/<name>` structure Orca used before explicit provenance was added.

Sources: [src/main/worktree-removal-safety.ts:120-165]()

### Removal sequence

```mermaid
stateDiagram-v2
    [*] --> PathSafetyCheck
    PathSafetyCheck --> GitRegistrationCheck : path is safe
    PathSafetyCheck --> Blocked : dangerous path
    GitRegistrationCheck --> ArchiveHook : registered & deletable
    GitRegistrationCheck --> OrphanProof : not in git list
    OrphanProof --> OrphanCleanup : .git file proves orphan + force flag
    OrphanProof --> Blocked : cannot prove orphan
    ArchiveHook --> PTYTeardown : hook completed (or skipped)
    PTYTeardown --> GitRemove : PTYs killed
    GitRemove --> MetaCleanup : git worktree remove
    GitRemove --> PruneThenClean : orphaned worktree error
    OrphanCleanup --> MetaCleanup
    PruneThenClean --> MetaCleanup
    MetaCleanup --> [*] : notifyWorktreesChanged
    Blocked --> [*] : throws error
```

---

## Agent-Awake Service

`AgentAwakeService` (`src/main/agent-awake-service.ts`) prevents the OS from sleeping or suspending the application while one or more agents are actively working.

### Architecture

The service is cross-platform and uses three independent mechanisms simultaneously:

| Mechanism | Platform | Implementation |
|---|---|---|
| `powerSaveBlocker.start('prevent-display-sleep')` | All (Electron) | Blocks display sleep; must call `isStarted()` to reconcile after OS resumes |
| `MacosSystemSleepAssertion` | macOS | Platform sleep assertion via native API |
| `LinuxLidSleepAssertion` | Linux | Lid-close inhibitor lock |

Windows lid-close is intentionally omitted because keeping the machine awake across a lid close requires mutating the user's global power plan.

### Wake eligibility

A status record is considered wake-eligible only when all three conditions hold:
- `state === 'working'`
- `observedInCurrentRuntime === true` (guards against stale status from a prior Electron launch)
- `receivedAt` is within the last **2 hours** (`AGENT_AWAKE_STATUS_STALE_AFTER_MS = 2 * 60 * 60 * 1000`)

A `setTimeout` is scheduled for the earliest impending staleness expiry. When no timer is relevant, no timer is set. The timer is `unref()`'d so it does not prevent Node.js from exiting.

On OS resume (`powerMonitor 'resume'` event), the service calls `refresh('power-resume')` to re-evaluate and re-acquire any blockers the OS may have dropped during sleep.

Sources: [src/main/agent-awake-service.ts:22-30](), [src/main/agent-awake-service.ts:107-125]()

---

## Trust Presets

Before an agent TUI launches in a new worktree, Orca pre-writes the same trust artifacts that the agent CLI writes after the user manually accepts a "Do you trust this folder?" prompt. Without this, Orca's URL injection (injected via bracketed paste once the TUI is up) would be intercepted by single-character menu reads and would either select a random option or quit the session.

| Agent | Trust artifact location | Format |
|---|---|---|
| **Cursor** | `~/.cursor/projects/<slug>/.workspace-trusted` | `{ trustedAt, workspacePath }` JSON |
| **Copilot** | `~/.copilot/config.json` → `trustedFolders[]` | Appended path string |
| **Codex** | `~/.codex/config.toml` + Orca-managed `CODEX_HOME/config.toml` | `[projects."<path>"] trust_level = "trusted"` |

All three functions resolve the worktree path through `realpathSync` before any comparison or write, because macOS reports `/tmp/x` and `/private/tmp/x` as the same inode but agent trust comparators use `realpath()` during lookup.

Sources: [src/main/agent-trust-presets.ts:1-55]()

For Codex, the trust is written to **both** `~/.codex/config.toml` and the Orca-managed `CODEX_HOME` config because Orca launches Codex with its own `CODEX_HOME` environment, and both paths must agree.

Sources: [src/main/agent-trust-presets.ts:90-100]()

---

## Hook Lifecycle

Orca reads hooks from `orca.yaml` in the repository root (committed, project-wide defaults) and from `.orca/issue-command` (per-user local override, gitignored). The `orca.yaml` schema supports two top-level keys:

```yaml
scripts:
  setup: |
    npm install
    npm run build
  archive: |
    echo "Cleaning up..."
issueCommand: gh issue view $ISSUE_NUMBER
```

### Hook execution at creation

When a worktree is created, `shouldRunSetupForCreate` consults the repo's `setupRunPolicy` (`run-by-default`, `ask`, or manual `decision` argument). If setup should run, Orca:
1. Calls `createSetupRunnerScript` to write a platform runner script into the worktree's git storage path (`git rev-parse --git-path orca/setup-runner.sh`).
2. Sets env vars: `ORCA_ROOT_PATH`, `ORCA_WORKTREE_PATH`, `ORCA_WORKSPACE_NAME`, plus legacy `CONDUCTOR_ROOT_PATH` and `GHOSTX_ROOT_PATH` aliases.
3. On WSL worktrees, path-translates all env var values from Windows UNC to Linux paths.

The runner script is written in Bash (`#!/usr/bin/env bash\nset -e`) on POSIX/WSL and in a `@echo off` + `call <cmd>` / `if errorlevel 1 exit /b` pattern on native Windows so multi-line scripts fail fast, matching `set -e` semantics.

Sources: [src/main/hooks.ts:262-320]()

### Hook execution at removal

Before `git worktree remove` runs, the IPC handler calls `runHook('archive', canonicalWorktreePath, repo)`. On SSH repos, the archive script is forwarded to `provider.execNonInteractive(...)` with a 2-minute timeout. Failure is logged but does not abort the removal.

### Source policy

`resolveHookCommandSourcePolicy` merges the committed `scripts:` block with the local Settings override according to three modes:

| Policy | Behavior |
|---|---|
| `local-only` | Only the per-user Settings script runs |
| `run-both` | Committed script + local script are concatenated, committed first |
| _(default)_ | Committed YAML script is authoritative; local script is ignored |

### Issue command

The issue command is resolved via `readIssueCommand`: the local `.orca/issue-command` file takes precedence over `issueCommand:` in `orca.yaml`. `writeIssueCommand` creates `.orca/` on first write and ensures it appears in `.gitignore`.

Sources: [src/main/hooks.ts:160-195]()

---

## Session State Schema

Workspace UI state (active tabs, terminal layouts, open editors, browser workspaces) is persisted on shutdown and restored on next launch. The schema is validated with Zod at the read boundary via `parseWorkspaceSession`, which returns a discriminated union `{ ok: true, value }` or `{ ok: false, error }` so callers can fall back to defaults without a try/catch.

The schema is intentionally **tolerant of extra fields** (additive forward compatibility) but strict on the types of fields it reads. A single corrupted field (e.g., `NaN` in `lastVisitedAtByWorktreeId`) is stripped rather than failing the entire session, keeping the blast radius small.

Sources: [src/shared/workspace-session-schema.ts:1-30](), [src/shared/workspace-session-schema.ts:205-240]()

---

## Orchestration UI Model

`AGENTS_STEPS` in `src/shared/agents-orchestration-steps.ts` defines the three informational steps shown in Orca's "Explore Orca" onboarding modal:

| Step ID | Name | Role |
|---|---|---|
| `statuses` | Visibility | Shows which agents are working, waiting, live, or blocked |
| `orchestration` | Orchestration | Explains how agents can manage and coordinate workspaces |
| `usage` | Usage *(optional)* | API usage and rate-limit monitoring across connected accounts |

These steps are UI copy; the orchestration engine itself lives in the IPC and runtime layers described above.

---

## Data Flow Summary

```text
Renderer (React)
  │  worktrees:create / worktrees:remove / worktrees:listAll
  ▼
registerWorktreeHandlers   (src/main/ipc/worktrees.ts)
  ├─ listRepoWorktrees      → git worktree list  /  SSH provider  /  folder stub
  ├─ worktree-removal-safety → isDangerousPath, git registration, orphan proof
  ├─ hooks (orca.yaml)      → setup runner  /  archive hook  /  issue-command
  ├─ agent-trust-presets    → Cursor / Copilot / Codex trust files
  └─ AgentAwakeService      → Electron powerSaveBlocker + macOS/Linux assertions
```

Each worktree that Orca creates carries:
- A stable `{repoId}::{worktreePath}` identity used by PTY sessions, tabs, editors, and browsers.
- `WorktreeMeta` persisted in the store with provenance fields (`orcaCreatedAt`, `orcaCreationSource`) that gate safe cleanup later.
- Platform-specific runner scripts written into the git storage directory so setup and teardown commands do not depend on the renderer remaining open.

When agents finish and worktrees are removed, the archive hook runs first, PTY processes are killed, git deregisters the worktree, and the store prunes the metadata — leaving no stale git locks or dangling session state.

---

## 04. Git & Source-Control Integration Layer

> The git runner, multi-provider source-control IPC handlers (GitHub, GitLab, Azure DevOps, Bitbucket, Gitea), hosted-review (PR/MR) flows, commit-message generation, and the shared git-history and upstream-status types that the renderer consumes.

- Page Markdown: https://grok-wiki.com/public/wiki/stablyai-orca-47ffb1f68457/pages/04-git-source-control-integration-layer.md
- Generated: 2026-05-27T07:45:41.391Z

### Source Files

- `src/main/git/runner.ts`
- `src/main/git/worktree.ts`
- `src/main/git/status.ts`
- `src/main/ipc/github.ts`
- `src/main/ipc/gitlab.ts`
- `src/main/ipc/hosted-review.ts`
- `src/shared/git-history.ts`
- `src/shared/hosted-review.ts`
- `src/shared/commit-message-generation.ts`

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:

- [src/main/git/runner.ts](src/main/git/runner.ts)
- [src/main/git/status.ts](src/main/git/status.ts)
- [src/main/git/worktree.ts](src/main/git/worktree.ts)
- [src/main/git/history.ts](src/main/git/history.ts)
- [src/main/git/upstream.ts](src/main/git/upstream.ts)
- [src/main/ipc/github.ts](src/main/ipc/github.ts)
- [src/main/ipc/gitlab.ts](src/main/ipc/gitlab.ts)
- [src/main/ipc/hosted-review.ts](src/main/ipc/hosted-review.ts)
- [src/main/ipc/worktrees.ts](src/main/ipc/worktrees.ts)
- [src/main/source-control/hosted-review.ts](src/main/source-control/hosted-review.ts)
- [src/main/source-control/hosted-review-creation.ts](src/main/source-control/hosted-review-creation.ts)
- [src/shared/git-history.ts](src/shared/git-history.ts)
- [src/shared/git-history-types.ts](src/shared/git-history-types.ts)
- [src/shared/hosted-review.ts](src/shared/hosted-review.ts)
- [src/shared/commit-message-generation.ts](src/shared/commit-message-generation.ts)
- [src/shared/git-effective-upstream.ts](src/shared/git-effective-upstream.ts)
</details>

# Git & Source-Control Integration Layer

Orca's source-control integration layer bridges the Electron renderer process and the local or remote git repository. It owns every subprocess invocation of `git`, `gh`, and `glab`; parses working-tree and upstream status; provides a provider-neutral abstraction over hosted reviews (GitHub PRs, GitLab MRs, Bitbucket, Azure DevOps, and Gitea); and exposes commit-history data and AI-generated commit messages to the UI. Because Orca also runs on Windows with repos living on WSL filesystems and over SSH, the layer must transparently route subprocess calls through `wsl.exe` or SSH relay RPCs without requiring callers to know the execution environment.

This page documents the architecture and responsibilities of each module in the layer, the IPC channel contract between renderer and main process, the multi-provider review flow, and the shared types that both sides of the IPC boundary consume.

---

## Architecture Overview

```text
┌──────────────────────────────────────────────────────────────┐
│ Renderer process                                             │
│   UI components  ──► preload IPC bridge                      │
└────────────────────────────┬─────────────────────────────────┘
                             │ Electron ipcMain channels
┌────────────────────────────▼─────────────────────────────────┐
│ Main process – IPC handlers (src/main/ipc/)                  │
│   github.ts  │  gitlab.ts  │  hosted-review.ts  │ worktrees  │
└──────┬────────────┬─────────────────┬────────────────────────┘
       │            │                 │
       ▼            ▼                 ▼
┌─────────────────────────────────────────────────────────────┐
│ Domain services                                             │
│  source-control/hosted-review.ts  (provider router)         │
│  source-control/hosted-review-creation.ts  (eligibility)    │
│  git/status.ts   git/history.ts   git/upstream.ts           │
│  git/worktree.ts                                            │
└────────────────────────┬────────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────────┐
│ git/runner.ts  (subprocess execution + WSL routing)          │
│  gitExecFileAsync │ ghExecFileAsync │ glabExecFileAsync      │
└─────────────────────────────────────────────────────────────┘
       │ shell          │ gh CLI           │ glab CLI
       ▼                ▼                 ▼
   git binary     GitHub remote      GitLab remote
```

```text
Shared types consumed by both sides of IPC boundary:
  src/shared/git-history-types.ts   – GitHistoryItem, GitHistoryResult, lane colors
  src/shared/hosted-review.ts       – HostedReviewInfo, HostedReviewProvider, creation types
  src/shared/commit-message-generation.ts – CommitMessageDraftContext, prompt builder
  src/shared/git-effective-upstream.ts    – GitUpstreamStatus resolution
```

---

## The Git Runner (`src/main/git/runner.ts`)

The runner is the single subprocess execution gateway for all git operations in the main process. Its central job is transparent WSL routing: when a repository lives on a WSL filesystem (a UNC path like `\\wsl.localhost\Ubuntu\...`), native Windows binaries cannot access it efficiently. The runner detects these paths and rewrites the invocation as `wsl.exe -d <distro> -- bash -c "cd '<linuxPath>' && git ..."`, including translating any Windows paths that appear in command arguments.

Sources: [src/main/git/runner.ts:1-60]()

### Exported runners

| Function | Purpose |
|---|---|
| `gitExecFileAsync(args, opts)` | Async git command; WSL-aware; wrapped in an observability span. |
| `gitExecFileAsyncBuffer(args, opts)` | Returns `Buffer` stdout for binary blobs (`git show`). |
| `gitExecFileSync(args, opts)` | Synchronous fallback. |
| `gitSpawn(args, opts)` | Streaming spawn; used for long-running git operations. |
| `ghExecFileAsync(args, opts)` | GitHub CLI with retry/backoff. |
| `glabExecFileAsync(args, opts)` | GitLab CLI, parallel retry policy. |
| `commandExecFileAsync(cmd, args, opts)` | Generic WSL-aware exec (ripgrep, etc.). |
| `wslAwareSpawn(cmd, args, opts)` | Streaming spawn for non-git binaries. |

Sources: [src/main/git/runner.ts:224-310]()

### `gh` / `glab` retry and idempotency

Both `ghExecFileAsync` and `glabExecFileAsync` implement a structured retry policy against transient HTTP failures (5xx, connection resets, 429 without `Retry-After`). Critically, retries are gated on **idempotency detection** to avoid duplicating writes:

- Explicit `-X POST/PATCH/PUT/DELETE` args → non-idempotent, no retry.
- GraphQL `query=mutation ...` → non-idempotent.
- `gh issue close`, `gh pr merge`, etc. (known write verbs) → non-idempotent.
- All reads default to idempotent and will retry up to two times with 250 ms → 1 s backoff.
- When the server returns a `Retry-After` header, that delay is honored (capped at 30 s).

```typescript
// src/main/git/runner.ts
const GH_RETRY_DELAYS_MS = [250, 1000] as const
const GH_RETRY_AFTER_MAX_MS = 30_000
```

Sources: [src/main/git/runner.ts:370-470]()

### WSL CLI fallback chain

On Windows, if `gh.exe` is absent from the host PATH (WSL-only setups), the runner:
1. Detects `ENOENT` on the host binary.
2. Attempts to resolve the default WSL distro.
3. Retries the call through `wsl.exe -d <distro> -- gh ...`.

For repo-scoped calls the distro is derived automatically from the UNC cwd. For global calls (rate-limit checks, auth) a `wslDistro` hint parameter is provided.

Sources: [src/main/git/runner.ts:97-160]()

---

## Status & Diff Engine (`src/main/git/status.ts`)

`getStatus()` is the primary read path for working-tree state. It runs `git status --porcelain=v2 --branch --untracked-files=all` (with `-c core.quotePath=false` so non-ASCII filenames pass through as raw UTF-8) and parses the structured output into `GitStatusEntry[]`.

```typescript
// Simplified from src/main/git/status.ts
export async function getStatus(worktreePath, options): Promise<GitStatusResult> {
  const conflictPromise = detectConflictOperation(worktreePath)  // concurrent fs probe
  const statusPromise = gitExecFileAsync(statusArgs, { cwd, env: gitOptionalLocksDisabledEnv() })
  // ...parse ahead/behind, entries, upstream...
  await attachLineStats(worktreePath, entries)
  return { entries, conflictOperation, head, branch, upstreamStatus }
}
```

The `conflictOperation` field indicates whether the repository is mid-merge, mid-rebase, or mid-cherry-pick, detected by probing `.git/MERGE_HEAD`, `.git/rebase-merge/`, and `.git/CHERRY_PICK_HEAD`. Rebase detection uses the directory rather than `REBASE_HEAD` because the directory persists across all rebase steps.

Sources: [src/main/git/status.ts:37-105](), [src/main/git/status.ts:340-385]()

### Line-count attachment

After collecting status entries, `attachLineStats` concurrently runs `git diff --numstat` (staged and unstaged separately) plus counts additions in untracked files. This gives the renderer per-file addition/deletion counts for the sidebar without blocking on status. The calls are always concurrent:

```typescript
const [stagedStats, unstagedStats, untrackedStats] = await Promise.all([...])
```

Sources: [src/main/git/status.ts:130-160]()

### Diff functions

| Function | Description |
|---|---|
| `getDiff(path, file, staged, compareAgainstHead)` | Returns `GitDiffResult` for a file in a given area. |
| `getBranchCompare(path, baseRef)` | Full compare from merge-base to HEAD; used by branch-compare panel. |
| `getBranchDiff(path, { headOid, mergeBase, filePath })` | File-level diff within a branch compare. |
| `getCommitCompare(path, commitId)` | Per-commit diff with parent detection and root-commit handling. |
| `getCommitDiff(path, { commitOid, parentOid, filePath })` | File diff for a specific commit. |

Binary files (images, PDFs) are detected via a buffer heuristic (`isBinaryBuffer`) and returned as base64 when the extension is in the previewable MIME type map.

Sources: [src/main/git/status.ts:410-480]()

### Staged commit context

`getStagedCommitContext()` collects the current branch name, the `--name-status` summary, and the full `--patch` of the staged index. This struct (`CommitMessageDraftContext`) is the input to AI commit-message generation.

Sources: [src/main/git/status.ts:575-610]()

---

## Upstream Status (`src/main/git/upstream.ts`)

`getUpstreamStatus()` computes the `GitUpstreamStatus` value that the renderer displays as the ahead/behind indicator. It delegates to `getEffectiveGitUpstreamStatus` (from `src/shared/git-effective-upstream.ts`), which handles three cases:

1. **Configured upstream** — `git rev-parse HEAD@{u}` succeeds; use ahead/behind from that ref.
2. **No configured upstream but `origin/<branch>` exists** — fall back to the inferred remote branch.
3. **Patch-equivalence** — when the branch is behind its upstream, `git log --cherry-mark --right-only HEAD...upstream` is used to determine if the "behind" commits are already applied as equivalent patches (indicating a clean rebase). This affects push-button label selection.

Sources: [src/main/git/upstream.ts:1-55](), [src/shared/git-effective-upstream.ts:1-60]()

---

## Git History (`src/main/git/history.ts`, `src/shared/git-history.ts`)

The history module is designed around a **pluggable executor** pattern so the same history-loading logic can run both locally (routing through `gitExecFileAsync`) and over SSH (routing through a relay RPC):

```typescript
// src/main/git/history.ts
export async function getHistory(worktreePath, options): Promise<GitHistoryResult> {
  return loadGitHistoryFromExecutor(
    (args, cwd) => gitExecFileAsync(args, { cwd }),
    worktreePath, options
  )
}
```

`loadGitHistoryFromExecutor` (in `src/shared/git-history.ts`) resolves the current ref, the configured upstream ref, and any optional base ref, then runs a single `git log --topo-order --decorate=full` covering all reachable commits. From the result it computes:

- `hasIncomingChanges` — upstream OID differs from merge-base.
- `hasOutgoingChanges` — local HEAD differs from merge-base.
- `hasMore` — whether the log was truncated at the `limit` cap.

Sources: [src/main/git/history.ts:1-18](), [src/shared/git-history.ts:95-160]()

### Shared history types (`src/shared/git-history-types.ts`)

| Type | Role |
|---|---|
| `GitHistoryItem` | Single commit row: id, parentIds, subject, author, timestamp, statistics, references. |
| `GitHistoryItemRef` | A branch, tag, remote-tracking, or detached HEAD ref attached to a commit. |
| `GitHistoryResult` | Full result: items, currentRef, remoteRef, baseRef, mergeBase, hasIncoming/Outgoing, hasMore. |
| `GitHistoryGraphColorId` | Semantic color token IDs (`git-graph-lane-1`…`git-graph-lane-5`, ref, remote-ref, base-ref) consumed by the renderer's commit graph. |
| `GitHistoryExecutor` | `(args: string[], cwd: string) => Promise<{stdout: string}>` — the seam that enables SSH routing. |

Sources: [src/shared/git-history-types.ts:1-70]()

---

## Worktree Management (`src/main/git/worktree.ts`)

The worktree module wraps `git worktree add`, `git worktree remove`, and `git worktree list`. Key behaviors:

- **Base-ref persistence**: After creating a worktree, the effective base branch is written to `git config branch.<name>.base` so the branch-compare panel can show a sensible default diff target. If the write fails (e.g., config lock), the stale value is also cleared so downstream consumers never trust outdated lineage.
- **WSL path translation**: `git worktree list` output contains Linux-native paths when running inside WSL. The runner's `translateWslOutputPaths` helper rewrites these to Windows UNC form so Node's `fs` module can open them.
- **Safety bounds**: `discardChanges` and `bulkDiscardChanges` call `isWithinWorktree` before any `rm -rf` to prevent path-traversal outside the worktree root.

Sources: [src/main/git/worktree.ts:1-90]()

---

## IPC Handler Layer

### GitHub handlers (`src/main/ipc/github.ts`)

`registerGitHubHandlers` registers all `ipcMain.handle` channels under the `gh:` namespace. It applies a consistent **security gate** (`assertRegisteredRepo`) that resolves the repo path and verifies it matches a path the user explicitly registered in Orca's store before any CLI or API call is issued.

Representative channels:

| Channel | Action |
|---|---|
| `gh:prForBranch` | Fetch the open PR for the current branch, with linked PR and fallback PR hints. |
| `gh:prChecks` / `gh:prCheckDetails` | CI check status and per-job detail. |
| `gh:prComments` | Review comments and threads. |
| `gh:mergePR` | Merge with selected strategy. |
| `gh:rerunPRChecks` | Re-trigger failed CI checks. |
| `gh:getProjectViewTable` | GitHub Projects (Projects v2) table data. |
| `gh:listAccessibleProjects` | Cross-org project enumeration for the project picker. |

After every successful write mutation, a `gh:workItemMutated` push event is broadcast to all renderer `webContents` **except** the originating one (which already updated its cache optimistically).

Sources: [src/main/ipc/github.ts:100-160]()

### GitLab handlers (`src/main/ipc/gitlab.ts`)

`registerGitLabHandlers` mirrors the same `assertRegisteredRepo` pattern and registers channels under `gitlab:`:

| Channel | Action |
|---|---|
| `gitlab:viewer` | Authenticated user identity. |
| `gitlab:mrForBranch` | Merge request for the current branch. |
| `gitlab:mr` | MR by IID. |
| `gitlab:listMRs` / `gitlab:listIssues` | Paginated list queries. |
| `gitlab:mergeMR` / `gitlab:closeMR` / `gitlab:reopenMR` | MR lifecycle mutations. |
| `gitlab:addMRComment` / `gitlab:addIssueComment` | Discussion thread actions. |

Sources: [src/main/ipc/gitlab.ts:1-80]()

### Hosted-review handlers (`src/main/ipc/hosted-review.ts`)

`registerHostedReviewHandlers` exposes provider-neutral channels under `hostedReview:`:

| Channel | Description |
|---|---|
| `hostedReview:forBranch` | Resolve the current review for a branch across all providers. |
| `hostedReview:getCreationEligibility` | Preflight check before showing the create-review UI. |
| `hostedReview:create` | Execute the create flow (push + PR/MR creation). |

The handler also performs **worktree path authorization**: for SSH-connected repos it normalizes POSIX remote paths; for local repos it verifies via `resolveRegisteredWorktreePath` that the worktree belongs to the repo before proceeding.

Sources: [src/main/ipc/hosted-review.ts:1-138]()

---

## Multi-Provider Review Abstraction (`src/main/source-control/`)

### Provider detection and routing (`hosted-review.ts`)

`getHostedReviewForBranch` probes each provider client in priority order to discover which service hosts the remote:

```
GitLab (glab project slug) → GitHub (gh repo slug)
  → Bitbucket → Azure DevOps → Gitea
```

Each branch resolves to at most one `HostedReviewInfo` object, which carries a normalized `provider` discriminant (`'github' | 'gitlab' | 'bitbucket' | 'azure-devops' | 'gitea' | 'unsupported'`) and common fields (`number`, `title`, `state`, `url`, `status`, `mergeable`, `headSha`, `conflictSummary`).

Sources: [src/main/source-control/hosted-review.ts:94-212]()

### Eligibility and creation (`hosted-review-creation.ts`)

Before the renderer shows the create-review button, `getHostedReviewCreationEligibility` runs a preflight that checks:

- Provider detection (sequentially queries each provider slug endpoint).
- Authentication (gh auth status).
- Dirty working tree.
- Detached HEAD.
- Branch is not the default branch.
- Upstream presence (`hasUpstream`), ahead-count (`> 0`), and behind-count (blocks if behind—must sync first).

Blocked states are typed as `HostedReviewCreationBlockedReason` and map to a `HostedReviewCreationNextAction` that the UI uses to route the user to the right next step (commit, push, sync, authenticate, or open existing review).

The `createHostedReview` function re-runs eligibility at submission time as a last-chance guard against stale renderer state, then delegates to the provider-specific client (currently only GitHub PR creation is implemented via `createGitHubPullRequest`; other providers surface `unsupported_provider`).

Sources: [src/main/source-control/hosted-review-creation.ts:38-100](), [src/main/source-control/hosted-review-creation.ts:201-300]()

---

## Shared Types and Contracts

### `src/shared/hosted-review.ts`

The canonical type file shared by both sides of the IPC boundary. Key types:

| Type | Description |
|---|---|
| `HostedReviewProvider` | String union: `'github' \| 'gitlab' \| 'bitbucket' \| 'azure-devops' \| 'gitea' \| 'unsupported'`. |
| `HostedReviewInfo` | Normalized review record consumed by the renderer's source-control panel. |
| `HostedReviewCreationEligibility` | Full preflight result including `canCreate`, `blockedReason`, `nextAction`, `defaultBaseRef`. |
| `HostedReviewCreationBlockedReason` | Discriminated string for each possible blocking state. |
| `HostedReviewQueueSummary` | Review queue row with decision, thread summary, reviewer list. |

Sources: [src/shared/hosted-review.ts:1-140]()

### `src/shared/commit-message-generation.ts`

Defines the prompt-building contract for AI commit-message generation:

- `CommitMessageDraftContext` — branch name, staged summary (`--name-status`), and staged patch.
- `buildCommitMessagePrompt(context, customInstructions)` — assembles the LLM prompt, truncating the patch to a safe size and appending optional user instructions. Rules are embedded in the prompt: imperative mood, ≤ 72-char subject, no trailing period, no `Co-authored-by` trailers.
- `splitGeneratedCommitMessage(message)` — splits the raw LLM output into `subject` / `body` / `message`, trimming trailing periods and enforcing the 72-char cap.

Sources: [src/shared/commit-message-generation.ts:1-75]()

---

## Security Boundary: `assertRegisteredRepo`

Every IPC handler that operates on a repository path calls a local `assertRegisteredRepo` function before any git or API call. This function:

1. Resolves the provided `repoPath` to an absolute path using `path.resolve`.
2. Looks up the path in the application's persistent store.
3. Throws `'Access denied: unknown repository path'` if not found.

The pattern is replicated independently in `ipc/github.ts`, `ipc/gitlab.ts`, and `ipc/hosted-review.ts`. The hosted-review handler additionally validates that the `worktreePath` argument belongs to the registered repo before passing it to the creation logic, using `listRepoWorktrees` to enumerate valid paths.

Sources: [src/main/ipc/gitlab.ts:36-44](), [src/main/ipc/hosted-review.ts:18-33]()

---

## SSH and Remote Execution

The entire git execution layer is designed to work transparently over SSH. When a repo has a `connectionId`, operations that require git subprocess calls delegate to `getSshGitProvider(connectionId).exec(args, repoPath)` or provider-specific RPCs (`getStatus`, `getUpstreamStatus`). The `GitHistoryExecutor` interface abstraction in `src/shared/git-history.ts` is the canonical example: the same `loadGitHistoryFromExecutor` function runs locally or over SSH by swapping the executor argument.

Sources: [src/main/source-control/hosted-review-creation.ts:81-100](), [src/shared/git-history-types.ts:64-68]()

---

## Summary

The git and source-control integration layer is organized as a strict layered stack: a subprocess execution core (`runner.ts`) with WSL and Windows compatibility; domain services (`status.ts`, `history.ts`, `upstream.ts`, `worktree.ts`) that build structured results from raw git output; a multi-provider source-control adapter (`source-control/hosted-review.ts`) that normalizes GitHub, GitLab, Bitbucket, Azure DevOps, and Gitea behind a single `HostedReviewInfo` type; and Electron IPC handlers (`ipc/github.ts`, `ipc/gitlab.ts`, `ipc/hosted-review.ts`) that enforce repo-path authorization before exposing any operation to the renderer. Shared type files in `src/shared/` let both the renderer and the main process reason about history, review state, upstream divergence, and commit-message prompts without duplicating definitions across the IPC boundary.

---

## 05. Renderer UI & Zustand State Management

> The React renderer process: App entry point, Zustand store slices (worktrees, tabs, terminals, repos, agent-status), tab-group layout, terminal shell integration via xterm.js, sidebar, and the IPC bridge through which renderer slices subscribe to main-process events.

- Page Markdown: https://grok-wiki.com/public/wiki/stablyai-orca-47ffb1f68457/pages/05-renderer-ui-zustand-state-management.md
- Generated: 2026-05-27T07:47:47.123Z

### Source Files

- `src/renderer/src/App.tsx`
- `src/renderer/src/store/index.ts`
- `src/renderer/src/store/slices/worktrees.ts`
- `src/renderer/src/store/slices/tabs.ts`
- `src/renderer/src/store/slices/terminals.ts`
- `src/renderer/src/store/slices/tab-group-state.ts`
- `src/renderer/src/components/Sidebar.tsx`
- `src/renderer/src/components/terminal/TerminalShell.tsx`

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:

- [src/renderer/src/App.tsx](src/renderer/src/App.tsx)
- [src/renderer/src/store/index.ts](src/renderer/src/store/index.ts)
- [src/renderer/src/store/types.ts](src/renderer/src/store/types.ts)
- [src/renderer/src/store/slices/worktrees.ts](src/renderer/src/store/slices/worktrees.ts)
- [src/renderer/src/store/slices/tabs.ts](src/renderer/src/store/slices/tabs.ts)
- [src/renderer/src/store/slices/terminals.ts](src/renderer/src/store/slices/terminals.ts)
- [src/renderer/src/store/slices/tab-group-state.ts](src/renderer/src/store/slices/tab-group-state.ts)
- [src/renderer/src/store/slices/agent-status.ts](src/renderer/src/store/slices/agent-status.ts)
- [src/renderer/src/store/slices/repos.ts](src/renderer/src/store/slices/repos.ts)
- [src/renderer/src/hooks/useIpcEvents.ts](src/renderer/src/hooks/useIpcEvents.ts)
- [src/renderer/src/components/Sidebar.tsx](src/renderer/src/components/Sidebar.tsx)
- [src/renderer/src/components/sidebar/index.tsx](src/renderer/src/components/sidebar/index.tsx)
- [src/renderer/src/components/terminal/TerminalShell.tsx](src/renderer/src/components/terminal/TerminalShell.tsx)
- [src/renderer/src/components/terminal-pane/TerminalPane.tsx](src/renderer/src/components/terminal-pane/TerminalPane.tsx)
</details>

# Renderer UI & Zustand State Management

Orca's renderer process is a React application running inside Electron's `BrowserWindow`. It owns all user-visible UI: the left sidebar, the workspace area with its terminal/editor/browser tabs, the right sidebar, the status bar, and every modal. All application state—worktrees, tabs, terminals, agents, settings, and UI chrome—is managed in a single **Zustand store** composed from around 29 slice creators. This page documents the App entry point, how the store is constructed, what each major slice owns, how the tab-group layout model works, how the terminal shell integrates xterm.js, what the sidebar renders, and how the renderer subscribes to main-process events through the IPC bridge.

The renderer is the only Electron surface a user directly interacts with. Understanding its state model is the key to understanding data flow, feature boundaries, and startup/shutdown sequencing across the entire application.

---

## App Entry Point (`App.tsx`)

`App.tsx` is the root React component. It is responsible for startup hydration, keybinding dispatch, session persistence, theme application, and hosting all top-level layout regions.

### Startup Hydration Sequence

On mount, a single `useEffect` runs an async initialization chain:

```
fetchSettings()
  → fetchRepos()
  → fetchAllWorktrees()
  → fetchWorktreeLineage()
  → window.api.ui.get()          // load persisted sidebar/filter state
  → window.api.session.get()     // load persisted tabs/terminals
  → fetchKeybindings()
  → SSH reconnect (eager + deferred)
  → reconnectPersistedTerminals()
  → setHydrationSucceeded(true)  // unlock session writer
```

Settings are fetched first because `activeRuntimeEnvironmentId` controls whether subsequent calls go to the local IPC or a remote runtime server. If any step throws, the renderer stays in a "no-save" degraded mode (`hydrationSucceeded` stays `false`) so session persistence does not overwrite on-disk data with a partially-hydrated in-memory state.

Sources: [src/renderer/src/App.tsx:319-480]()

### Action Subscriptions

Rather than registering one `useAppStore` subscription per action, `App` consolidates all stable action references into a single `useShallow` subscription. This prevents React from registering a separate subscription for each action selector, avoiding re-renders on every unrelated store mutation.

```tsx
// src/renderer/src/App.tsx
const actions = useAppStore(
  useShallow((s) => ({
    toggleSidebar: s.toggleSidebar,
    fetchRepos: s.fetchRepos,
    // ...~25 more action refs
  }))
)
```

Sources: [src/renderer/src/App.tsx:219-260]()

### Session Persistence

A `createSessionWriteSubscriber` subscription (outside React's render cycle) writes the workspace session to disk via a debounced `window.api.session.set()` call whenever tab, terminal, or editor state changes. On `beforeunload`, a synchronous `window.api.session.setSync()` captures terminal scrollback buffers before the renderer tears down.

Sources: [src/renderer/src/App.tsx:580-610, 630-680]()

### Layout Regions

```text
┌─────────────────────────────────────────────────────────┐
│  Titlebar (drag region + nav buttons + WindowControls)   │
├──────────┬──────────────────────────────────┬───────────┤
│          │         Workspace Area           │           │
│ Sidebar  │  TabBar (≥2 tabs) + TerminalPane │  Right    │
│          │  or: Landing / Settings / etc.   │  Sidebar  │
├──────────┴──────────────────────────────────┴───────────┤
│  StatusBar                                               │
└─────────────────────────────────────────────────────────┘
```

`activeView` selects the workspace content (`'terminal'`, `'settings'`, `'activity'`, `'space'`, `'skills'`). The sidebar is hidden for `settings`, `activity`, `space`, and `skills` views. The right sidebar is guarded by `canShowRightSidebarForView(activeView)`.

Sources: [src/renderer/src/App.tsx:540-556]()

### Windows-Specific Chrome

On Windows, `titleBarStyle: 'hidden'` removes the native OS title bar, so `App.tsx` renders custom `<WindowControls>` with SVG Fluent/Win11-style minimize/maximize/close buttons. Close routes through `window.api.ui.requestClose()` so the Electron `'close'` event fires and the terminal-running guard remains active.

Sources: [src/renderer/src/App.tsx:107-170]()

---

## Zustand Store Architecture

### Single Store, Slice Composition

The entire renderer state lives in one Zustand store exported as `useAppStore`:

```ts
// src/renderer/src/store/index.ts
export const useAppStore = create<AppState>()((...a) => ({
  ...createRepoSlice(...a),
  ...createWorktreeSlice(...a),
  ...createTerminalSlice(...a),
  ...createTabsSlice(...a),
  ...createUISlice(...a),
  ...createAgentStatusSlice(...a),
  // ... 23 more slices
}))
```

`AppState` is a TypeScript intersection type of all slice interfaces:

```ts
// src/renderer/src/store/types.ts
export type AppState = RepoSlice & WorktreeSlice & TerminalSlice & TabsSlice
  & UISlice & SettingsSlice & KeybindingsSlice & GitHubSlice & AgentStatusSlice
  & /* ...20 more */ WorkspaceCleanupSlice
```

Sources: [src/renderer/src/store/index.ts:1-60](), [src/renderer/src/store/types.ts:1-48]()

### Complete Slice Inventory

| Slice | Key state | Purpose |
|---|---|---|
| `repos` | `repos[]` | Git repository list; add/remove/update |
| `worktrees` | `worktreesByRepo`, `activeWorktreeId` | Worktree lifecycle, creation, deletion, lineage |
| `terminals` | `tabsByWorktree`, `ptyIdsByTabId`, `terminalLayoutsByTabId` | PTY sessions, xterm split-pane layouts |
| `tabs` (unified) | `unifiedTabsByWorktree`, `groupsByWorktree`, `layoutByWorktree` | Unified tab model with split-group layout tree |
| `ui` | `sidebarOpen`, `sidebarWidth`, `activeView`, `activeModal` | UI chrome, sidebar geometry, modal routing |
| `settings` | `settings` | User settings; theme, font, keybindings |
| `keybindings` | `keybindings` | Custom keybinding map |
| `github` | PR/issue caches | GitHub PR/issue data by repo |
| `hosted-review` | Hosted review cache | GitLab/GitHub review state |
| `agent-status` | `agentStatusByPaneKey`, `retainedAgentsByPaneKey` | Real-time agent status per pane |
| `editor` | `openFiles`, `activeFileId`, `editorDrafts` | Open files, draft content, view modes |
| `browser` | `browserTabsByWorktree` | In-app browser tabs |
| `ssh` | `sshConnectionStates` | SSH connection registry |
| `workspace-cleanup` | Cleanup job state | Worktree cleanup wizard |
| `worktree-nav-history` | Navigation stack | Worktree history back/forward |
| … | … | Rate limits, dictation, stats, memory, etc. |

### Dev / E2E Store Exposure

In development and E2E mode (guarded by `import.meta.env.DEV || e2eConfig.exposeStore`), the store is attached to `window.__store` so Playwright tests can read state directly without DOM scraping.

Sources: [src/renderer/src/store/index.ts:62-67]()

---

## Slice Deep Dives

### Worktrees Slice

**State fields:** `worktreesByRepo`, `detectedWorktreesByRepo`, `worktreeLineageById`, `activeWorktreeId`, `deleteStateByWorktreeId`, `sortEpoch`, `everActivatedWorktreeIds`, `lastVisitedAtByWorktreeId`.

`worktreesByRepo` holds visible worktrees per repo; `detectedWorktreesByRepo` holds the richer `DetectedWorktreeListResult` including hidden and foreign-ownership worktrees.

**Runtime routing:** All IPC calls are routed through `getActiveRuntimeTarget(settings)`. If `activeRuntimeEnvironmentId` is set, calls go to a remote runtime server via `callRuntimeRpc`; otherwise they call `window.api.worktrees.*` locally.

```ts
// Simplified from worktrees.ts
const target = getActiveRuntimeTarget(get().settings)
const result = target.kind === 'local'
  ? await window.api.worktrees.create(createArgs)
  : await callRuntimeRpc(target, 'worktree.create', { ... })
```

**Hydration-time purge:** On `fetchAllWorktrees`, after all repos report authoritative results, the slice diffs `tabsByWorktree` keys against the live worktree id set and purges stale entries—worktrees deleted in a previous session that left orphaned tab state behind.

**Atomic teardown on delete:** `removeWorktree` calls `buildWorktreePurgeState`, which removes ~25 per-worktree keys from state in one atomic `set()`. This prevents any intermediate state where, for example, a tab references a worktree that no longer exists.

Sources: [src/renderer/src/store/slices/worktrees.ts:1-50, 430-480, 580-640]()

---

### Tabs Slice (Unified Tab Model)

The unified tab model is the central abstraction for all workspace content (terminals, editors, browser tabs). A `Tab` has a `contentType` (`'terminal' | 'editor' | 'diff' | 'browser' | 'conflict-review'`), an `entityId` (the PTY tab id, file id, or browser tab id it wraps), and a `groupId`.

**State fields:** `unifiedTabsByWorktree`, `groupsByWorktree`, `activeGroupIdByWorktree`, `layoutByWorktree`.

#### Tab Group Layout Tree

`layoutByWorktree` maps each worktree id to a `TabGroupLayoutNode`—a binary tree of `leaf` and `split` nodes:

```text
TabGroupLayoutNode =
  | { type: 'leaf'; groupId: string }
  | { type: 'split'; direction: 'horizontal' | 'vertical';
      first: TabGroupLayoutNode; second: TabGroupLayoutNode; ratio: number }
```

This mirrors VS Code's editor grid. `createEmptySplitGroup` inserts a new sibling leaf via `replaceLeaf`; `closeEmptyGroup` prunes a leaf via `removeLeaf` and collapses the parent split. Ratios are adjusted with `setTabGroupSplitRatio`.

#### MRU-Aware Tab Selection

Each `TabGroup` carries `recentTabIds`—a most-recently-used stack of tab ids within the group. When the active tab is closed, `pickNextActiveTab` walks the MRU stack to restore the previously focused tab rather than selecting the visual neighbor.

#### Active Surface Derivation

`deriveActiveSurfaceForWorktree` computes `activeFileId`, `activeBrowserTabId`, and `activeTabId` from group-and-tab state. It examines the active group's `activeTabId`, checks whether its `entityId` still exists in `openFiles` / `browserTabsByWorktree` / `tabsByWorktree`, and falls back gracefully. This keeps the four top-level active-surface fields (`activeTabId`, `activeFileId`, `activeBrowserTabId`, `activeTabType`) consistent with the unified model.

Sources: [src/renderer/src/store/slices/tabs.ts:1-100, 200-280, 350-400]()

---

### Terminals Slice

**Key state fields:** `tabsByWorktree` (legacy `TerminalTab[]` per worktree), `ptyIdsByTabId` (list of active PTY ids per tab), `terminalLayoutsByTabId` (xterm split-pane layout snapshot per tab), `unreadTerminalTabs`, `expandedPaneByTabId`, `canExpandPaneByTabId`.

The legacy `tabsByWorktree` record predates the unified tab model. `reconcileWorktreeTabModel` (in the tabs slice) migrates entries from `tabsByWorktree` that are not yet in `unifiedTabsByWorktree` into the unified model on demand.

`reconnectPersistedTerminals` is called at startup. It iterates the persisted tab list, spawns or attaches PTYs for each tab (local `window.api.pty.create` or remote `callRuntimeRpc`), and populates `ptyIdsByTabId`. It flips `workspaceSessionReady` once complete, which is the gate that allows the session write subscriber to begin persisting state.

Sources: [src/renderer/src/store/slices/terminals.ts:155-230]()

---

### Agent Status Slice

The agent status slice tracks real-time AI agent progress per xterm pane. State is keyed by `paneKey` (`${tabId}:${leafId}`).

**Key state fields:**
- `agentStatusByPaneKey` — live entries; updated at PTY event frequency
- `retainedAgentsByPaneKey` — snapshots of finished/vanished agents kept for the dashboard and sidebar hover
- `retentionSuppressedPaneKeys` — pane keys torn down by user action (tab close, X button); suppresses re-retention

**Lifecycle:** `setAgentStatus` upserts a live entry. `removeAgentStatus` removes a live entry and, unless suppressed, promotes it to `retainedAgentsByPaneKey`. `dropAgentStatus` (user-initiated) removes the entry and marks it suppressed so it will not reappear. `removeAgentStatusByTabPrefix` sweeps all entries for a closed tab atomically.

`RetainedAgentEntry` captures the full `TerminalTab` and `AgentType` at retention time so the dashboard can render completed rows even after the tab is gone.

Sources: [src/renderer/src/store/slices/agent-status.ts:1-80]()

---

### Tab-Group State Helpers (`tab-group-state.ts`)

This module contains pure, stateless functions used by both the tabs slice and the terminals slice:

| Helper | Role |
|---|---|
| `findTabAndWorktree` | Linear search across `unifiedTabsByWorktree` to find a tab by id |
| `findGroupForTab` | Look up a `TabGroup` by id within a worktree |
| `findGroupAndWorktree` | Cross-worktree group lookup |
| `findTabByEntityInGroup` | Find a tab by `entityId` + optional `contentType` within a specific group |
| `ensureGroup` | Create a root group if none exists for a worktree |
| `updateGroup` | Replace a group by id in a group array |
| `dedupeTabOrder` | Remove duplicate ids from a tab order list |
| `pushRecentTabId` | Prepend to the MRU stack, capping length |
| `sanitizeRecentTabIds` | Remove MRU entries that are no longer in the tab order |
| `pickNextActiveTab` | Walk MRU stack to select the next active tab after a close |

Sources: [src/renderer/src/store/slices/tab-group-state.ts:1-145]()

---

## IPC Bridge: `useIpcEvents`

`useIpcEvents` is the single hook that wires all main-process-to-renderer push events. It lives in `src/renderer/src/hooks/useIpcEvents.ts` and is called once at the top of `App`. It registers all subscriptions inside one `useEffect` and cleans them all up on unmount.

### Main-Process Events Handled

| IPC Channel | Store Action Called |
|---|---|
| `repos.onChanged` | `fetchRepos()` (skipped if runtime env is active) |
| `worktrees.onChanged` | `fetchWorktrees(repoId)` + diff-purge of removed worktree ids |
| `worktrees.onBaseStatus` | `updateWorktreeBaseStatus(event)` |
| `agent:status` | `setAgentStatus(paneKey, payload)` with pending-retry for unregistered pane keys |
| `ssh:state-changed` | `setSshConnectionState(targetId, state)` |
| `remote-workspace:snapshot` | `applyRemoteWorkspaceSnapshot(targetId, snapshot)` — merges remote session |
| `window:close-requested` | Confirm-close flow; dispatches `beforeunload` to trigger buffer captures |
| `ui:zoom-in/out` | `applyUIZoom()` + CSS var update |
| `tab:switch`, `tab:switch-recent` | `handleSwitchTab`, `handleSwitchRecentTab` |
| `terminal:split-pane` | Insert new leaf into `terminalLayoutsByTabId` via `addSplitLeafToLayout` |
| `worktrees.onRemoteChanged` | Refetch worktrees for remote runtime repos |
| `runtime:browser-driver-state` | `setDriverForBrowserPage` |
| `runtime:terminal-driver-state` | `setDriverForPty` |

The agent-status events include a retry mechanism: if the pane key is not yet registered (the PTY is still starting), the event is queued and retried every 100 ms for up to 15 seconds.

Sources: [src/renderer/src/hooks/useIpcEvents.ts:1-100, 580-680]()

### Architecture Diagram

```mermaid
sequenceDiagram
    participant Main as Electron Main Process
    participant API as window.api (preload IPC bridge)
    participant Hook as useIpcEvents (renderer)
    participant Store as useAppStore (Zustand)
    participant UI as React Components

    Main->>API: worktrees.onChanged({repoId})
    API->>Hook: callback fires
    Hook->>Store: fetchWorktrees(repoId)
    Store->>API: window.api.worktrees.listDetected({repoId})
    API->>Store: DetectedWorktreeListResult
    Store->>UI: state update → re-render Sidebar/WorktreeList

    Main->>API: agent:status payload
    API->>Hook: callback fires
    Hook->>Store: setAgentStatus(paneKey, payload)
    Store->>UI: agentStatusByPaneKey → WorktreeCardAgents re-render
```

---

## Sidebar

The left sidebar is implemented in `src/renderer/src/components/sidebar/index.tsx` (re-exported from `components/Sidebar.tsx`).

### Structure

```text
<Sidebar>
  ├── <SidebarNav />          — top navigation icons (workspaces, settings, skills…)
  ├── <SidebarHeader />       — repo filter chip + add-repo button
  ├── <WorktreeList />        — virtualized list of WorktreeCard rows
  ├── <SetupScriptPromptCard /> — setup-script nag if pending
  └── <SidebarToolbar />      — sort, group, filter controls + resize handle
```

`WorktreeList` is virtualized and maintains scroll position across worktree remounts via `worktreeScrollOffsetRef` and `worktreeScrollAnchorRef` refs that are owned by `App` and passed down, so the scroll state survives sidebar visibility toggles.

The sidebar has a resizable width (`MIN_WIDTH=220`, `MAX_WIDTH=500`) managed by `useSidebarResize`. The live draft width is applied as a CSS custom property `--workspace-sidebar-live-width` during drag for zero-jank resizing.

```tsx
// sidebar/index.tsx (simplified)
<div ref={containerRef} className="... bg-sidebar ...">
  <SidebarNav />
  <SidebarHeader />
  <WorktreeList scrollOffsetRef={...} scrollAnchorRef={...} />
  <SetupScriptPromptCard />
  <SidebarToolbar onResizeStart={onResizeStart} />
</div>
```

`WorktreeCard` renders an individual workspace row with inline agent-status indicators from `agentStatusByPaneKey` and `retainedAgentsByPaneKey`.

Sources: [src/renderer/src/components/sidebar/index.tsx:1-110]()

---

## Terminal Shell Integration

### `TerminalShell` Component

`src/renderer/src/components/terminal/TerminalShell.tsx` is a thin presentational wrapper. It receives all state and callbacks as props (no direct store access) and renders:

1. A `<TabBar>` (only if `tabs.length >= 2`)
2. One `<TerminalPane>` per visible terminal tab
3. An `editorPanel` slot for the editor/browser surface

```tsx
// TerminalShell.tsx (condensed)
<div className="flex flex-col flex-1 min-w-0 min-h-0 overflow-hidden">
  {activeWorktreeId && (
    <TabBar tabs={tabs} activeTabId={activeTabId} ... />
  )}
  {tabs.map(tab => (
    <TerminalPane key={tab.id} tab={tab} ... />
  ))}
  {editorPanel}
</div>
```

Sources: [src/renderer/src/components/terminal/TerminalShell.tsx:60-160]()

### `TerminalPane` and xterm.js

`TerminalPane` (`src/renderer/src/components/terminal-pane/TerminalPane.tsx`) owns the xterm.js integration:

- **PTY connection:** `connectPanePty` (via `pty-transport.ts`) attaches xterm.js to a local or remote PTY. PTY data handlers are registered/unregistered via `ensurePtyDispatcher` / `unregisterPtyDataHandlers`.
- **Split-pane layout:** `terminalLayoutsByTabId[tabId]` holds a `TerminalLayoutSnapshot`—a binary tree of `TerminalPaneLayoutNode` (`leaf | split`), mapping `leafId → ptyId`. The pane serializes its current layout into this tree on resize events and shutdown.
- **Fit/resize:** `fitPanes` and `SYNC_FIT_PANES_EVENT` ensure xterm columns/rows match the DOM element size. `useLayoutEffect` in `App` dispatches this event whenever the sidebar opens or closes so there is no transient mis-sized terminal frame.
- **Scrollback capture:** On `beforeunload`, registered `shutdownBufferCaptures` callbacks serialize xterm scrollback into the store for `window.api.session.setSync`.
- **Mobile/remote driver overlay:** `MobileDriverOverlay` is conditionally mounted when a remote runtime driver (mobile session, relay) is active for a pane's PTY.

Terminal tab titles update through `runtimePaneTitlesByTabId` (per-leaf title tracking), which feeds into the unified tab label via `updateUnifiedTerminalLabel` in the terminals slice.

Sources: [src/renderer/src/components/terminal-pane/TerminalPane.tsx:1-80]()

---

## Startup State Flow Summary

```mermaid
stateDiagram-v2
    [*] --> Mounting: App mounts
    Mounting --> FetchingSettings: fetchSettings()
    FetchingSettings --> FetchingRepos: fetchRepos()
    FetchingRepos --> FetchingWorktrees: fetchAllWorktrees()
    FetchingWorktrees --> HydratingUI: window.api.ui.get()
    HydratingUI --> HydratingSession: window.api.session.get()
    HydratingSession --> SSHReconnect: reconnect SSH targets
    SSHReconnect --> TerminalReconnect: reconnectPersistedTerminals()
    TerminalReconnect --> Ready: setHydrationSucceeded(true)
    Ready --> [*]: workspaceSessionReady = true; session writer unlocked

    HydratingSession --> DegradedMode: any step throws
    DegradedMode --> [*]: workspaceSessionReady = true; hydrationSucceeded = false
```

In `DegradedMode`, the UI mounts with whatever state was hydrated before the failure. The session write subscriber stays gated (`hydrationSucceeded = false`) to avoid overwriting on-disk data with partial in-memory state. A toast offers a one-click "Restart now" relaunch action.

Sources: [src/renderer/src/App.tsx:319-480]()

---

## Summary

The renderer process is organized around a single Zustand store composed from ~29 slices. `App.tsx` is the only component that drives startup hydration, keybinding dispatch, theme application, and session persistence. The IPC bridge (`useIpcEvents`) funnels all main-process push events into store actions through `window.api` preload bindings. The unified tab model (tabs slice) models all workspace content—terminal, editor, browser—as `Tab` records organized in a binary split-group layout tree, with MRU-aware close and drag-reorder semantics. The sidebar virtualizes the worktree list and displays live agent status drawn from the agent-status slice. Terminal shell integration via xterm.js lives in `TerminalPane`, which manages PTY connections, split-pane layout trees, scrollback capture, and fit synchronization.

---

## 06. CLI, Skills & Extension Points

> The orca CLI binary (src/cli/), built-in skill discovery and metadata (src/main/skills/, src/shared/skills.ts), the agent-hook relay for external automations, SSH remote-runtime support, and the operational surfaces developers use to extend or automate Orca (runtime environments, setup scripts, MCP config).

- Page Markdown: https://grok-wiki.com/public/wiki/stablyai-orca-47ffb1f68457/pages/06-cli-skills-extension-points.md
- Generated: 2026-05-27T07:48:15.455Z

### Source Files

- `src/cli/index.ts`
- `src/cli/dispatch.ts`
- `src/main/skills/discovery.ts`
- `src/shared/skills.ts`
- `src/shared/agent-hook-types.ts`
- `src/shared/mcp-config.ts`
- `src/shared/runtime-environments.ts`
- `src/shared/setup-script-imports.ts`
- `skills/orchestration/`

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:

- [src/cli/index.ts](src/cli/index.ts)
- [src/cli/dispatch.ts](src/cli/dispatch.ts)
- [src/cli/runtime-client.ts](src/cli/runtime-client.ts)
- [src/cli/runtime/client.ts](src/cli/runtime/client.ts)
- [src/cli/handlers/agent-hooks.ts](src/cli/handlers/agent-hooks.ts)
- [src/cli/handlers/core.ts](src/cli/handlers/core.ts)
- [src/cli/handlers/environment.ts](src/cli/handlers/environment.ts)
- [src/cli/handlers/orchestration.ts](src/cli/handlers/orchestration.ts)
- [src/cli/specs/index.ts](src/cli/specs/index.ts)
- [src/cli/specs/core.ts](src/cli/specs/core.ts)
- [src/cli/specs/environment.ts](src/cli/specs/environment.ts)
- [src/cli/specs/agent-hooks.ts](src/cli/specs/agent-hooks.ts)
- [src/cli/specs/orchestration.ts](src/cli/specs/orchestration.ts)
- [src/main/skills/discovery.ts](src/main/skills/discovery.ts)
- [src/main/agent-hooks/managed-agent-hook-controls.ts](src/main/agent-hooks/managed-agent-hook-controls.ts)
- [src/main/agent-hooks/server.ts](src/main/agent-hooks/server.ts)
- [src/shared/skills.ts](src/shared/skills.ts)
- [src/shared/agent-hook-types.ts](src/shared/agent-hook-types.ts)
- [src/shared/mcp-config.ts](src/shared/mcp-config.ts)
- [src/shared/runtime-environments.ts](src/shared/runtime-environments.ts)
- [src/shared/setup-script-imports.ts](src/shared/setup-script-imports.ts)
- [src/shared/setup-script-import-providers.ts](src/shared/setup-script-import-providers.ts)
- [skills/orchestration/SKILL.md](skills/orchestration/SKILL.md)
</details>

# CLI, Skills & Extension Points

The Orca `orca` CLI binary exposes every runtime capability as a composable command surface — from launching the desktop app to managing worktrees, orchestrating multi-agent workflows, and toggling agent status hooks. It is the primary integration surface for automation scripts, AI coding agents (Claude Code, Codex, Gemini, etc.), and CI pipelines.

This page covers the structure and responsibilities of `src/cli/`, the skill discovery system (`src/main/skills/discovery.ts` + `src/shared/skills.ts`), the agent-hook relay that injects Orca state into external agents, SSH remote-runtime support, and the extension points developers use to wire Orca into custom workflows (MCP config detection, setup-script import, pairing environments).

---

## Architecture Overview

```text
┌─────────────────────────────────────────────────────────┐
│                    orca CLI binary                      │
│  src/cli/index.ts  →  dispatch.ts  →  handlers/        │
│                              │                          │
│  specs/          (command definitions + flag schemas)   │
│  runtime-client.ts  (local IPC or remote WebSocket)     │
└──────────────┬──────────────────────────────────────────┘
               │ RPC calls
       ┌───────▼────────────────────────┐
       │       Orca Runtime (main proc) │
       │  ┌────────────────────────┐    │
       │  │  skills/discovery.ts   │    │
       │  │  agent-hooks/server.ts │    │
       │  │  orchestration engine  │    │
       │  └────────────────────────┘    │
       └────────────────────────────────┘
               │ pairing / WebSocket
       ┌───────▼──────────────────────┐
       │    Remote Orca Runtime       │
       │  (SSH host, Tailscale, LAN)  │
       └──────────────────────────────┘
```

---

## CLI Entry Point and Dispatch

### `src/cli/index.ts` — Entry point

`main()` is the entry point for the installed binary. It:

1. Parses `argv` via `normalizeCommandPositionals` + `parseArgs`.
2. Resolves help paths and prints help when `--help` or an unknown path is given.
3. Validates command + flags against `COMMAND_SPECS` before performing any runtime lookup, so typos produce a useful error before "Orca is not running" is ever reported.
4. Constructs a `RuntimeClient` with the appropriate remote-pairing context.
5. Calls `dispatch(commandPath, ctx)`.

Three top-level command groups bypass remote-environment selection entirely: `environment`, `serve`, and `agent`. The `shouldIgnoreRemoteSelection` guard passes `null` explicitly for `pairingCode` and `environmentSelector` so the `RuntimeClient` constructor cannot re-activate the `ORCA_PAIRING_CODE` / `ORCA_ENVIRONMENT` env-var fallback for those local-only commands.

Sources: [src/cli/index.ts:18-73]()

### `src/cli/dispatch.ts` — Handler registry

`dispatch.ts` assembles a flat `Map<string, CommandHandler>` at module load time by iterating over every handler group in declaration order. It throws at startup on any duplicate key, preventing silent handler shadowing.

```typescript
// src/cli/dispatch.ts (simplified)
const groups = [
  CORE_HANDLERS, AUTOMATION_HANDLERS, REPO_HANDLERS,
  WORKTREE_HANDLERS, FILE_HANDLERS, TERMINAL_HANDLERS,
  BROWSER_NAV_HANDLERS, /* … browser variants … */
  ORCHESTRATION_HANDLERS, COMPUTER_HANDLERS,
  AGENT_HOOK_HANDLERS, ENVIRONMENT_HANDLERS
]
```

The `dispatch` function looks up the handler by the `commandPath` joined with spaces (`"orchestration send"`, `"agent hooks on"`, etc.) and calls it with a `HandlerContext` containing `{ flags, client, cwd, json }`.

Sources: [src/cli/dispatch.ts:1-63]()

### Command Specs

Every command is formally declared in `src/cli/specs/`. Each `CommandSpec` carries a `path` (the command words), `summary`, `usage` string, `allowedFlags`, optional `notes`, and `examples`. The merged array `COMMAND_SPECS` in `src/cli/specs/index.ts` is the single source of truth for help text, validation, and auto-completion.

Sources: [src/cli/specs/index.ts](), [src/cli/specs/core.ts]()

---

## Command Surface Reference

### Core Commands

| Command | Description |
|---|---|
| `orca open` | Launch the Orca desktop app and wait until the runtime is reachable |
| `orca serve` | Start a headless runtime server (foreground); supports `--port`, `--pairing-address`, `--mobile-pairing`, `--no-pairing` |
| `orca status` | Report app/runtime/graph readiness; exits non-zero when runtime is unreachable |

### Repository & Worktree Commands

| Command | Description |
|---|---|
| `orca repo list / add / show / set-base-ref / search-refs` | Manage registered repositories |
| `orca worktree list / show / current / create / set / rm / ps` | Manage Git worktrees; `current` resolves `$PWD` to an Orca worktree selector |

### Terminal Commands

| Command | Description |
|---|---|
| `orca terminal list / show / read / send / wait` | Inspect and interact with live PTY sessions |
| `orca terminal create / split / close / stop / switch / focus / rename` | Lifecycle management |

`terminal wait --for tui-idle` detects the working→idle OSC title transition for recognized agent CLIs (Claude Code, Codex, Gemini, etc.), enabling race-free agent coordination without sleep loops.

### Orchestration Commands

Orchestration commands require the Orca runtime to be running and the experimental orchestration feature to be enabled.

| Command Group | Description |
|---|---|
| `orchestration send / check / reply / inbox` | SQLite-backed inter-agent message passing with group addressing (`@all`, `@idle`, `@claude`, `@codex`, etc.) |
| `orchestration task-create / task-list / task-update` | DAG task tracking; tasks auto-promote to `ready` when all deps complete |
| `orchestration dispatch / dispatch-show` | Assign a ready task to a terminal, optionally injecting a preamble |
| `orchestration ask` | Block until a named terminal replies with an answer; outputs bare JSON for `jq` piping |
| `orchestration gate-create / gate-resolve / gate-list` | Human-in-the-loop decision gates that block task dispatch |
| `orchestration run / run-stop` | Automated background coordinator loop |
| `orchestration reset` | Clear tasks, messages, or all orchestration state |

`orchestration check --wait` emits JSON heartbeat lines to stderr every 15 s (configurable via `ORCA_HEARTBEAT_INTERVAL_MS`) while polling, so the parent agent process never mistakes silence for a stall.

Sources: [src/cli/handlers/orchestration.ts:1-50](), [src/cli/specs/orchestration.ts:1-40](), [skills/orchestration/SKILL.md]()

---

## Remote Runtime and Environment Management

### `RuntimeClient` — Local vs Remote transport

`RuntimeClient` (src/cli/runtime/client.ts) transparently routes RPC calls either to the local Orca socket or to a remote Orca instance over an encrypted WebSocket.

Remote mode is activated by:
- `--pairing-code <orca://pair#...>` flag on any command
- `--environment <selector>` flag
- `ORCA_PAIRING_CODE` / `ORCA_REMOTE_PAIRING` environment variable
- `ORCA_ENVIRONMENT` environment variable

When remote, the client checks runtime protocol compatibility (`evaluateRuntimeCompat`) before the first non-`status.get` call, so stale CLI binaries talking to newer runtimes report actionable version errors.

Sources: [src/cli/runtime/client.ts:1-70]()

### Environment Commands

`environment add / list / show / rm` manage a persistent store of named remote environments (saved to `orca-data.json` in the user-data directory). Each environment holds one or more `RuntimeAccessEndpoint` records of kind `websocket`, each carrying an `endpoint` URL, `deviceToken`, and `publicKeyB64`.

```
orca environment add --name work-laptop --pairing-code orca://pair#...
orca environment list
orca environment rm --environment work-laptop
```

The `--environment` flag (or `ORCA_ENVIRONMENT`) on any ordinary command selects the environment by name or ID, routing that command to the remote runtime over WebSocket.

Sources: [src/shared/runtime-environments.ts:1-80](), [src/cli/handlers/environment.ts](), [src/cli/specs/environment.ts]()

### `orca serve` — Headless SSH Runtime

`orca serve` starts the Orca runtime server without opening a desktop window. The `--pairing-address` flag overrides the announced endpoint address so remote clients connecting via SSH port forward, Tailscale, or a public tunnel see the correct reachable address instead of `localhost`.

```bash
# On the remote host:
orca serve --pairing-address 100.64.1.20 --port 6768

# On the local machine:
orca environment add --name ssh-host --pairing-code <emitted code>
orca worktree list --environment ssh-host
```

Sources: [src/cli/specs/core.ts:12-31]()

---

## Agent-Hook Relay

### Purpose

The agent-hook relay injects Orca's view of agent state (idle/working/done) into external AI agents. Each supported agent tool has a managed hook service that installs a lightweight script into the agent's config directory; when the agent transitions state, it POSTs to Orca's loopback HTTP endpoint.

### Supported Agents

`AGENT_HOOK_TARGETS` in `src/shared/agent-hook-types.ts` enumerates the ten currently managed agents:

| Agent | Service file |
|---|---|
| `claude` | `src/main/claude/hook-service` |
| `codex` | `src/main/codex/hook-service` |
| `gemini` | `src/main/gemini/hook-service` |
| `cursor` | `src/main/cursor/hook-service` |
| `grok` | `src/main/grok/hook-service` |
| `copilot` | `src/main/copilot/hook-service` |
| `droid` | `src/main/droid/hook-service` |
| `command-code` | `src/main/command-code/hook-service` |
| `antigravity` | `src/main/antigravity/hook-service` |
| `hermes` | `src/main/hermes/hook-service` |

Sources: [src/shared/agent-hook-types.ts:5-15](), [src/main/agent-hooks/managed-agent-hook-controls.ts:1-60]()

### Protocol Version

`ORCA_HOOK_PROTOCOL_VERSION = '1'` is shared between the main-process server and each hook service. The server logs a warning when it receives a request from a different version, making stale installed scripts diagnosable rather than silently producing partial payloads.

Sources: [src/shared/agent-hook-types.ts:29-42]()

### CLI Commands

```bash
orca agent hooks status   # Show enabled state and per-agent install status
orca agent hooks on       # Enable hooks; applies to running runtime or writes to disk
orca agent hooks off      # Disable hooks and remove managed hook entries
```

The `on`/`off` commands first attempt to update the running runtime via RPC (`settings.update`). If the runtime is not reachable, they fall back to writing the setting directly into `orca-data.json` and re-applying hook install/remove locally. The `appliedBy` field in the output (`runtime` vs `offline`) reports which path was taken.

Sources: [src/cli/handlers/agent-hooks.ts:75-120](), [src/cli/specs/agent-hooks.ts]()

### Hook Server (`src/main/agent-hooks/server.ts`)

The main-process hook server runs a loopback HTTP listener protected by a bearer token. It:
- Parses incoming hook payloads with the shared `normalizeHookPayload` pipeline from `src/shared/agent-hook-listener.ts`.
- Writes endpoint files to disk so agents not using the HTTP path can read state via filesystem.
- Fans out `AgentStatusIpcPayload` to registered listeners (UI panes).
- Persists `last-status.json` atomically (write-then-rename) so the dashboard can replay retained rows on Orca restart.
- Exposes an `ingestRemote` entry point for relay-forwarded events arriving over SSH/WebSocket, bypassing HTTP.

Sources: [src/main/agent-hooks/server.ts:1-60]()

---

## Skill Discovery

### Type Model (`src/shared/skills.ts`)

Skills are file-system packages with a `SKILL.md` entry point. The shared type layer defines:

| Type | Purpose |
|---|---|
| `DiscoveredSkill` | A fully resolved skill package (id, name, description, providers, paths, install status, file count) |
| `SkillDiscoverySource` | A scan root with label, path, `sourceKind`, and a flag for whether it exists |
| `SkillDiscoveryResult` | The aggregate output of a scan: `skills[]`, `sources[]`, `scannedAt` |
| `SkillProvider` | `'codex' \| 'claude' \| 'agent-skills'` |
| `SkillSourceKind` | `'home' \| 'repo' \| 'bundled' \| 'plugin'` |

Sources: [src/shared/skills.ts]()

### Discovery Algorithm (`src/main/skills/discovery.ts`)

`discoverSkills({ repos, homeDir, cwd })` performs a parallel multi-root filesystem scan:

**Scan roots (built by `buildSkillDiscoverySources`):**

| Root ID | Path | `sourceKind` | Providers |
|---|---|---|---|
| `home-codex` | `~/.codex/skills` | `home` | `codex` |
| `home-agents` | `~/.agents/skills` | `home` | `agent-skills` |
| `home-claude` | `~/.claude/skills` | `home` | `claude` |
| `codex-plugin-cache` | `~/.codex/plugins/cache` | `plugin` | `codex`, `agent-skills` |
| `repo-agents-<hash>` | `<repoPath>/.agents/skills` | `repo` | `agent-skills` |
| `repo-claude-<hash>` | `<repoPath>/.claude/skills` | `repo` | `claude` |

Remote repos (with a `connectionId`) are excluded from scanning — only local paths are traversed.

`findSkillFiles` walks each root up to a depth of 4 (9 for `plugin` roots), following symlinks with realpath deduplication to prevent loops. Skills under `~/.agents/skills/.system/` are re-classified as `sourceKind: 'bundled'`. The scan is capped at `MAX_SKILL_FILES = 200` files per skill package and reads at most `MAX_MARKDOWN_BYTES = 256 KiB` of each `SKILL.md` for frontmatter extraction.

Each discovered skill is assigned a stable `id` — a 16-character SHA-1 hex digest of its file path — so the UI can refer to skills by a durable opaque key.

Sources: [src/main/skills/discovery.ts:1-230]()

### Orchestration Skill

`skills/orchestration/SKILL.md` is a bundled skill that teaches AI agents the full orchestration command surface: message groups (`@all`, `@idle`, `@claude`, etc.), the task DAG lifecycle, dispatch circuit-breaking, decision gates, and the coordinator loop pattern. It is available to any agent-skills-capable provider and documents `ORCA_TERMINAL_HANDLE` auto-resolution, the `--inject` preamble mechanism, and the heartbeat stderr protocol.

Sources: [skills/orchestration/SKILL.md]()

---

## MCP Config Detection

`src/shared/mcp-config.ts` provides a provider-neutral MCP server configuration inspector used both by the CLI and the UI. It recognises four candidate config file formats:

| Format | Relative path | Key |
|---|---|---|
| `workspace` | `.mcp.json` | `mcpServers` |
| `cursor` | `.cursor/mcp.json` | `mcpServers` |
| `claude` | `.claude.json` | `mcpServers` |
| `claude` (workspace) | `.claude/mcp.json` | `mcpServers` |

`inspectMcpConfigContent` parses whichever file exists, classifies each server entry as `stdio` (has `command`), `http` (has `url` or `type: 'http'`), or `unknown` (missing both), and marks entries `enabled` / `disabled` / `invalid`. Sensitive environment values are automatically masked by `maskMcpEnv` using a pattern that matches common secret key names and value shapes (`sk-`, `ghp_`, `xoxb-`, etc.), preventing accidental exposure in CLI output or logs.

Sources: [src/shared/mcp-config.ts:1-60](), [src/shared/mcp-config.ts:62-80]()

---

## Setup Script Import

`src/shared/setup-script-imports.ts` discovers existing dev-environment bootstrap configs in the current repository and converts them into a normalized `SetupScriptImportCandidate` for Orca's worktree setup hook.

**Supported import providers:**

| Provider | Config file(s) |
|---|---|
| `superset` | `.superset/config.json`, `.superset/config.local.json` |
| `conductor` | `conductor.json` |
| `codex` | Codex environment config |
| `cmux` | `.cmux/cmux.json` or `cmux.json` |

Each provider is inspected in parallel via `Promise.all`. For `cmux`, the inspector scans the `commands` array for an entry whose `name`, `title`, or `keywords` match setup-related terms (`setup`, `init`, `initialize`, `install`). Unsupported config fields are collected and surfaced as `unsupportedFields` warnings rather than hard failures, allowing partial imports.

Sources: [src/shared/setup-script-imports.ts:1-40](), [src/shared/setup-script-import-providers.ts]()

---

## Data Flow: CLI Invocation to RPC Call

```mermaid
sequenceDiagram
    participant Shell
    participant CLI as src/cli/index.ts
    participant Dispatch as dispatch.ts
    participant Handler as handlers/<group>.ts
    participant Client as RuntimeClient
    participant Runtime as Orca Runtime

    Shell->>CLI: orca <command> [flags]
    CLI->>CLI: parseArgs + validateCommandAndFlags
    CLI->>Client: new RuntimeClient(pairingCode, envSelector)
    CLI->>Dispatch: dispatch(commandPath, ctx)
    Dispatch->>Handler: handler(ctx)
    Handler->>Client: client.call('method', params)
    alt Local runtime
        Client->>Runtime: Unix socket / named pipe IPC
    else Remote runtime
        Client->>Runtime: WebSocket (encrypted, bearer-token auth)
    end
    Runtime-->>Client: RPC response
    Client-->>Handler: RuntimeRpcSuccess<TResult>
    Handler->>Shell: printResult (text or JSON)
```

Sources: [src/cli/index.ts:42-77](), [src/cli/runtime/client.ts:30-70]()

---

## Global Flags

All commands accept two universal flags:

| Flag | Effect |
|---|---|
| `--json` | Emit machine-readable JSON output instead of human-readable text |
| `--pairing-code <code>` | Override remote runtime connection for this invocation |
| `--environment <selector>` | Select a saved named environment for this invocation |

The `--json` flag is threaded through `HandlerContext` and passed to `printResult`, which chooses between a formatter function and `JSON.stringify` of the raw RPC success payload.

---

## Extension Points Summary

| Surface | How to use |
|---|---|
| **Custom MCP servers** | Add entries to `.mcp.json` / `.cursor/mcp.json` / `.claude.json`; Orca inspects them on repo load |
| **Agent skills** | Drop a directory containing `SKILL.md` under `~/.agents/skills/`, `~/.claude/skills/`, `~/.codex/skills/`, or `<repo>/.agents/skills/` |
| **Agent status hooks** | `orca agent hooks on` installs managed scripts; toggle with `orca agent hooks off` |
| **Remote automation** | `orca serve --pairing-address <host>` + `orca environment add` wires a remote runtime into any CLI invocation via `--environment` or `ORCA_ENVIRONMENT` |
| **Orchestration automation** | `orca orchestration *` commands are scriptable from any shell; `ORCA_TERMINAL_HANDLE` is injected into Orca-managed terminals automatically |
| **Setup script import** | Place `conductor.json`, `.superset/config.json`, `.cmux/cmux.json`, or a Codex environment config in the repo root; Orca imports the `setup` command into the worktree hook on first load |

The CLI is fully scriptable and JSON-output-capable on every command, making it suitable for use inside agent tool calls, CI pipelines, and external automation scripts without any proprietary SDK dependency.

Sources: [src/cli/dispatch.ts](), [src/main/skills/discovery.ts:175-220](), [src/shared/mcp-config.ts:39-55](), [src/shared/setup-script-import-providers.ts]()

---