* refactor(runtime): every reader of a terminal's pane goes through paneOfPty (L80) With the layout-owner switch on, main's layout answers which tab and pane hold a terminal, and nothing reads or writes the PTY record's tabId/paneKey copies or their graph stamp. Switch-off reads the copies exactly as before. * refactor(runtime): read a terminal's pane through the runtime's paneOfPty method * fix(runtime): main's pane index answers paneOfPty before any window graph A headless host never installs a window graph, so the query asked a publisher that did not exist and every terminal read as paneless. The index is now made on first query; it publishes only once a graph is installed, as before. * test(runtime): pin that a query with no window publishes nothing * test(runtime): a terminal summary reads its pane once (L80) * feat(runtime): pin bind-first at registration, and one own-handle rule with or without a window (switch-on only) * test(runtime): census and tests that every create path binds its pane before it registers (L80) * test(runtime): a bind-first fixture that stands in for the spawn commit (L80) * test(runtime): fixtures bind a terminal's pane before registering it, as the spawn commit does (L80) Also, with the switch on: skip the bind-first check for a paired server's workspace, release a closed tab's terminal ownership once main has dropped the pane, and publish a pane's agent status from the terminal main's layout binds it to. * feat(runtime): an exit after main's close still finds the pane it retires (switch-on only, L80 Q2) Main's layout write records every pane it drops whose terminal no pane holds any more, in a new optional session field closedTerminalPanesByPaneKey (same TTL and cap as closed-tab records). Exit cleanup reads main's layout first, then that record by terminal id and incarnation, and removes the entry it used. Older builds ignore the field. * refactor(layout): one dropped-leaf step every live layout edit runs (switch-on only, L80 Q2) The step receives each dropped pane (host, worktree, tab, leaf, terminal, incarnation), computed across the whole layout so a moved pane is not dropped, and runs only for a commit that declares a live edit. Today it records the closed-pane entries; a drop is recorded once per terminal. * fix(runtime): a phone or headless tab close still stops and releases its live terminals after main's layout drops the panes (switch-on only, L80) The headless close reads each row's live terminal before main's close drops the panes it is found by, and a renderer-retired tab releases the owned terminal main left in no pane, as the explicit close already did. Close tests now commit through main's layout and bind panes first. * fix(runtime): a local workspace's terminal tab left in a paired server's partition is main's pane (switch-on only, L257) Older builds could save a local worktree's tab rows into a runtime partition (#18202); its terminals are local, so main's index takes those tabs for workspaces the router homes locally. A tab or terminal the local partition also holds is indexed once, from there; SSH and server-owned workspaces are skipped as before. * test(layout): the legacy-close comparison leaves out the closed-pane record only layout-owner builds write (L80) * test(pty): a pane whose failed start withdrew its tab resolves to terminal_not_found in layout-owner builds (L80 #7) Main's layout no longer holds the pane, so there is no pane to answer for; window-owned builds keep the disconnected stub from the exited terminal's record. * test(runtime): orca-runtime suites bind panes first, as the real store and spawn commit do (L80) Fake PTY spawns commit the binding into the pane the runtime wrote, stores list their partitions, and SSH sessions sit in their ssh:<conn> partition; terminals registered directly bind first. * fix(runtime): main's layout names a pane's terminal only when the workspace and stamped incarnation match (switch-on only, L80) A record made before its adoption registers (a host restart) or a local id reused after one matched a saved binding by id alone, so a phone or paired desktop could be handed a second handle for the pane before the terminal's own handle arrived and stay on it. * test(runtime): a restored pane is only unnamed in the adoption gap, and no local record rescues a paired server's tab (L80) * test(runtime): recovery, close and routing suites set up terminals as production does (L80) A recoverable pane is one the host inventory dropped with no exit, live terminals register rather than appear only in the window graph, and SSH stores route as the real Store does. * test(runtime): suspects 3, 4 and 5 bind, register and apply as production does; PR-1's main-layout closes in the part-10 and part-11 close tests (L80) * test(runtime): a fixture store applies main's layout commands as a profile does; PR-1's own closes in the part-05, part-08 and UUID-leaf close tests (L80) * test(runtime): the profile-store stand-in moves to its own fixture, keeping bind-first under the line limit (L80) * test(runtime): orphan adoption reads the pane from main's layout with the switch on, not the record copy (L80) * test(runtime): a phone's pane close and a pinned tab close are main's layout closes with the switch on (#31571), in the no-widening and surface-close tables (L80) * test(runtime): a paired phone close commits in main's layout with no relay ack when the switch is on (#31571) (L80) * fix(terminal): a parked pane resumes its agent once main has closed the other pane that held the session (layout-owner build, L80 P1) * test(runtime): the parked-pane suite's restart holder is a saved pane with no registered terminal, under the line limit (L80 P1) * fix(terminal): a spawn commit that binds a reused local terminal id unbinds it from the saved pane of the older process (layout-owner build, L80 P2) * test(runtime): the legacy OSC row test uses the UUID leaf main's layout holds and registers its live terminal with the switch on (L80 E5) * test(runtime): the pty-exit echo close of a dead headless tab is expected to fail with the switch on until W4's close PR (L255) * fix(terminal): a terminal record that arrives, changes incarnation or moves workspace after main composed its panes re-composes them, so a restored pane lists once (layout-owner build, L80) * fix(terminal): a pane bound to a terminal a paired server runs keeps that binding and host, and is never indexed as a local spilled tab (layout-owner build, L80/L257) * fix(terminal): a pane main owns no longer proves its terminal runs; the host's inventory and the terminal's record decide, so a dropped shell reads disconnected (layout-owner build, L80) * fix(terminal): an SSH pane whose host says its shell ended keeps its leaf on a negative or failed code, as the window does, so recovery starts a shell in it; a stale handle learns the connected replacement (layout-owner build, L80) * fix(agent-launch): a terminal main's layout still binds counts as running through a host listing gap, so a launch never starts a second shell in its pane (layout-owner build, L80) * fix(terminal): a disconnected terminal main still binds stays a stop target, never reads stopped unproven, and keeps an exit wait waiting until a certified exit (layout-owner build, L80) * test(terminal): the wait unit fixture answers the exit-settled dependency * test(terminal): realistic fixtures for three phone-row tests: unique leaf ids per pane, and a window that draws main's own panes (layout-owner build, L80) * fix(terminal): a stale handle learns a pane's replacement only when it was resolved for that pane, its own terminal ended, and the replacement is connected there now (layout-owner build, L80) * test(session-tabs): switch-on variant for a window's chat placement (main's layout order rules); headless phone tab order is L283 * test(terminal-close): switch-on close reds name their ledger rows (L255, L256, L259a, L259b); a same-id respawn's commit stamps its incarnation; the crossing terminal binds first * test(terminal): the crossing registration is refused by main's workspace fence (L256 switch-on variant); the placement test's tabs are main's saved layout * refactor(terminal): the stale-handle guard reads an issue-time pane stamp on the handle record, not a second map; moved-pane, restart and single-reader tests (layout-owner build, L80) * test(session-tabs): switch-on variants: the desktop's tab name is main's rename, and a mobile-created tab's live shell is the one its commit registers (layout-owner build, L80) * fix(terminal): a re-issued handle keeps its issue-time pane while no pane holds its terminal (review) * fix(terminal): stop targets and exit waits use main's holds check (workspace and incarnation), and any exit event settles a later wait (review) * test(session-tabs): name the switch-on placement and split-group reds L288 and L289 * fix(terminal): an exit drops a closed pane's record in the same write that retires the pane, and an unbind folds into the binding write (layout-owner build, L80) The exit used to remove main's closed-pane entry in a session write of its own, before the retirement write. It now reads the entry, and the retirement write for that pane drops it, re-checked by terminal id and incarnation. An exit that keeps the surface (relay contact lost, intentional stop) keeps the entry until a confirmed exit or its TTL. A reused terminal id's unbind is now a pure computation that applyPtyBinding applies through its existing writes. Two split-pane tests still expected a -1 exit to close the pane; they now follow the ruling that any negative code keeps it. Static-gate fixes: test fixtures and tests use typed member access instead of Reflect, and no longer use type assertions. * test(runtime): the shared fixture store announces its session writes and the L283 store lists its partitions, as a profile Store does; L289's test passes on that store (layout-owner build, L80) The fixture store bindPaneFirst fills now tells write listeners of every session write, direct or by a layout command, so main's post-commit merge runs as it does on a real Store. The notice is not enumerable, so a test's own `{ ...store }` copy, which keeps its writes elsewhere, does not inherit a subscription it would never fire. With it, the targeted split-group create (L289) passes at this head, so its it.fails mark goes. L283's test runs on a store that names its partitions and stays it.fails("L283"). * fix(terminal): recovery checks the caller's handle after the cheap guards again, so the switch-off build re-issues no handle on a refused recovery (layout-owner build, L80) The stale-handle switch point had moved the handle lookup ahead of the pane, terminal and workspace checks. That lookup can re-issue a handle, so a switch-off recovery refused on those checks gained a side effect base never had. The lookup runs after them again, and the switch-on replacement check lives in one method. * test(runtime): the layout-owner start fixture's spawn commits the pane binding before main registers the terminal, as the host does (L80 switch-on variant) The window-tab start test came in with the main merge after bind-first; its fake spawn returned a terminal no pane was bound to, so registration refused it. The fixture's spawn now binds the pane first, as every other bind-first fixture does. * fix(terminal): a new process under a reused terminal id starts with no exit code, so an exit wait on it cannot settle on the old process's code (layout-owner build, L80) The record of a reused id kept the earlier process's lastExitCode, and with the switch on an exit wait settles on any recorded code once the terminal reads disconnected. A new incarnation now clears the code and cause. Also drops the L283 test's partition-list option: the fixture store already lists its partitions. * test(runtime): switch-on variants for four tests the full local run never loaded (L80) - A restored pane's host lists the terminal it started; a pane main owns vouches for none. - The other pane holding a slept session is in main's layout, so an unverifiable holder blocks the resume as it does through a window. - Main sleeps a workspace whose panes it lists itself, even with a renderer attached (#31571). - A graph sync no longer reconnects a disconnected terminal of main's pane; the inventory decides. * test(runtime): the phone Markdown route tests use #31667's realistic store, and the untitled cases wait on L268 with the switch on The window's tab uses main's id (the file path), and the store lists its partitions and saves each open editor's unified tab and group, as the window does (the fixture from #31667). With the switch on, the three untitled cases fail on L268, main's row merge dropping the untitled flag, which #31667 fixes; they are it.fails until A1b's history includes it. * refactor(runtime): two merged files back under the line limit, by moving A1b's additions to their helpers (L80) After the main merge, the holder-pane check and a closed tab's live-terminal lookup pushed two files past the line limit. The holder check is now paneHoldsSession (main-terminal-surfaces.ts) behind one method, and the closed tab's lookup is one parent-class method. Behaviour unchanged. * test(runtime): the headless chat tests open the chat on the publish path, as every product path does; L283 stays it.fails for the main-owned reorder openChat recorded the chat only in the runtime's snapshot, so main's layout never held it and the phone's reorder was refused as an unknown tab. On the publish path main's layout holds it; the reorder is accepted but does not survive the row rebuild after a close (L283, reopened). * test(runtime): bind-first census follows the one spawn-commit step (#31596); the unbound-registration test keeps its spawn from the suites' stand-in commit After the main merge both start lanes register in spawn-commit-assembly.ts, after savePtySpawnPaneBinding (or, adopted, on the live owner's pane); the census lists that file. A phone create now writes its pane before the spawn, so the suites' stand-in spawn commit bound it and the test of a path that registers unbound passed for the wrong reason; that test's controller now opts out of the stand-in commit. * test(runtime): convert four switch-on tests the disguised full runs hid Real switch-on runs showed four files whose fakes registered terminals unbound or confirmed a stop without its exit. Bind first, stamp the source as main does, and deliver the exit a confirmed stop delivers. Switch-off unchanged. * test: type the two converted fakes instead of casting them The PTY controller fake now takes the controller's optional pane fields, and the switch-on store is a typed RuntimeStore, so neither needs an assertion.
Orca
中文 · 日本語 · 한국어 · Español · Français · Português
The AI Orchestrator for 100x builders.
Run Codex, ClaudeCode, OpenCode or Pi side-by-side — each in its own worktree, tracked in one place.
Download Orca
Features
Also in the box:
- Quick open — Search across worktrees, files, agents, commands, and repo context without leaving your flow.
- Account switcher & usage tracking — See Claude and Codex usage and rate-limit resets, and hot-swap accounts without re-logging in.
- Rich repo previews — Preview Markdown, images, PDFs, and repo docs in the workspace.
- Computer Use — Let agents operate desktop apps and visible UI when a workflow needs real interaction.
- Notifications and unread state — Know when an agent finishes or needs attention, then mark threads unread to come back later.
- And many, many more — we ship daily, so this list is perpetually behind. The changelog is the real feature list.
Supported Agents
Works with any CLI agent — if it runs in a terminal, it runs in Orca.
Claude Code
Codex
Grok
Cursor
GitHub Copilot
Muse
DeepSeek Harness
ZCode
OpenCode
MiMo Code
Amp
OpenClaude
Antigravity
Pi
oh-my-pi
Hermes Agent
Devin
Goose
Auggie
Autohand Code
Charm
Cline
CodeBuddy
Codebuff
Freebuff
Command Code
Continue
Droid
Kilocode
Kimi
Kiro
Mistral Vibe
Qwen Code
Rovo Dev
+ any CLI agent
Install
Desktop — macOS, Windows, Linux
- Download from onOrca.dev
- Or grab a build directly: macOS Apple Silicon · macOS Intel · Windows (.exe) · Linux AppImage · All builds
Or via a package manager:
# macOS (Homebrew)
brew install --cask stablyai/orca/orca
# Arch Linux (AUR) — or stably-orca-git to build from source
yay -S stably-orca-bin
Mobile Companion — iOS, Android
Pair with your desktop app to monitor and steer your agents from your phone.
- iOS: Download on the App Store
- Android: Download APK · Install guide
Community & Support
-
Discord: Join the community on Discord.
-
Twitter / X: Follow @orca_build for updates and announcements.
-
WeChat: Group 12 is full. Scan to join the Orca community WeChat group 13.
-
Feedback & Ideas: We ship fast. Missing something? Request a new feature.
-
Privacy: See the privacy & telemetry docs for what anonymous usage data Orca collects and how to opt out.
-
Show Support: Star this repo to follow along with our daily ships.
Developing
Want to contribute or run locally? See our CONTRIBUTING.md guide.
The relay that pairs the mobile app with a desktop host is also in this repository under
cloud/, with a separate pnpm workspace and setup guide.
Signed Builds
Windows code signing sponored/provided by SignPath.io, certificate by SignPath Foundation.
License
Orca is free and open source under the MIT License.










