* fix(terminal): every explicit terminal close commits through one main transaction
A renderer save cannot shrink terminal membership once main owns a repo's
topology, so desktop tab and pane closes, CLI split-pane closes and mobile
split-pane closes only became durable when the killed process's exit retired
the surface. A close whose kill failed or threw, or whose exit was never
certified, came back after a reload.
Every close now reaches closeTerminalSurface: the renderer sends an explicit
intent for user and cleanup closes, the CLI and mobile split-pane closes commit
the pane after their stop, and the headless and relayed mobile closes reuse the
same commit. A failed flush keeps the in-memory removal and no longer cancels
the kill. Exit retirement is unchanged.
* fix(terminal): tell the desktop renderer to drop a split pane main closed
A CLI or mobile close of one pane in a split commits the pane in main, but the
desktop kept showing it until reload when no exit arrived to remove it. The
close now sends a leaf-addressed notice: a mounted pane closes by leaf id, and
a parked tab collapses its stored layout. Addressing by leaf makes the notice
and the renderer's exit handling no-ops after each other, which replaces the
numeric pane-id notice that could close the whole tab when the exit won.
* fix(terminal): a pane close never widens into a whole-tab close
A leaf-addressed close fell through to the whole-tab close whenever main's layout no longer
held that leaf as one of several. Main's exit handling retires an exited split pane from the
saved layout, so closing that pane afterwards (the exited-pane overlay's Close, or a CLI close
whose stop delivers the exit first) removed the whole tab, live sibling included, and the
next renderer save could not restore it. A pane close is now a no-op unless its leaf is in a
multi-pane layout.
Also updates two mobile split-close assertions to expect the leaf-addressed notice, and adds a
test that a relayed mobile close of a renderer-listed tab still reaches the renderer's pin guard.
* test(terminal): cover the PTY-handle branch of a CLI split-pane close
The existing CLI split test resolves its handle through the renderer graph, so the branch
that closes a runtime-owned pane by its PTY handle had no test failing without its commit.
* fix(terminal): main records every terminal tab close, and seeding reads the records
* test(terminal): pin the renderer close mirror against an early SSH pull, and main's record writes against later saves
* fix(terminal): a CLI pane close with an unconfirmed stop closes only that pane
`orca terminal close <handle>` on one pane of a split used to close the
whole tab, live sibling included, whenever that pane's stop could not be
confirmed (for example an unreachable SSH host). An unconfirmed stop is
unverifiable, not a reason to drop siblings: the close now commits only
that leaf, tells the renderer to drop that leaf, and leaves the owed kill
to the controller's existing SSH pending-kill path.
On a host where no renderer lists the tab, main now also removes the
closed pane from the paired-client snapshot (with its retirement proof),
since no exit may arrive to do it.
* docs(terminal): correct the pull merge's record-safety comment
The mirror is written by closeTab before main commits, so the claim that only a
main-committed close writes a record was inaccurate; also reflow a split comment.
* refactor(terminal): one resolver decides whether a pane close becomes a tab close
Every explicit close now states its target as `{kind:'tab'}` or `{kind:'pane', leafId}`; no
optional leaf id silently means the whole tab. Main resolves a close it started in exactly one
place, reading the copy of the tab's panes its layout owner holds (the renderer-published layout
for tabs the desktop renderer lists, main's session layout otherwise). Only `last-pane` escalates,
through the existing tab path so the renderer's pin guard still runs; an unknown pane never widens.
- The CLI and phone paths drop their per-site sibling counts for the resolver.
- The notifier splits into a tab-only close and a leaf-addressed pane close.
- The headless tab closer takes a parent tab id, so a pane row cannot reach it.
- A phone close of one pane on a host with no desktop window now stops and closes only that pane.
- A phone close of one pane with no live process record closes that pane, not its tab.
* fix(cli): an unverifiable stop says the close happened
`orca terminal close` still exits 1 when the process stop cannot be verified, but its message now
says the terminal was closed and names the host's reason, instead of "close failed". It promises
that the kill retries on reconnect only when the SSH relay itself never answered the stop, the one
case a recorded kill order backs.
* fix(terminal): a phone pane close commits even when its kill fails
A paired client's close of one pane threw `terminal_close_failed` before committing anything when
the controller reported the kill failed, so the pane stayed. The kill is now best-effort, as it is
for a whole-tab close: the pane's removal always commits and the failure stays on the PTY's
liveness verdict.
* fix(terminal): a pane close widens only when a copy shows it is the last pane
The close resolver read an owner copy that records no panes as "the tab has
one pane", so a CLI close of one pane of a split, addressed while the
renderer listed the tab before publishing its panes, closed the whole tab.
Every copy now counts only if it records at least one pane, read in the
owner's order with the published rows as the last fallback, and a pane
close widens only when a copy lists that pane as the tab's only one. An
unsplit tab whose saved layout predates its pane still closes: its
published row names the pane.
* fix(cli): promise a kill retry only when the host recorded the kill
The close receipt inferred "the kill retries when the host reconnects" from
the stop reason's text, which a new transport message or a reworded error
would silently break.
An explicit close now records the replayable kill order when its stop goes
unconfirmed, before sending the follow-up kill (whose own failure is
recorded only once its RPC settles), and reports that on the receipt as an
optional `pendingKillRecorded`. The CLI promises the retry only from that
field, so an older host, which never sends it, gets no promise.
* test(pty): justify the controller cast the recorded-stop tests extend
* fix(terminal): parse the close target with typed narrowing
The low-evidence lint gate rejects Reflect.get and broad object parameters,
which failed static analysis. Narrow with 'in' checks instead and cover the
boundary parser's accept and reject cases.
* test(terminal): drive the close-record tests through the durable store
* fix(terminal): a desktop tab close is not refused by a split that bound while it waited
The renderer has already removed and killed a tab it closes, so its close intent now skips the
owner fence phone and CLI closes use. Before, a split pane whose binding was admitted between the
close request and its durable write made main refuse the close, and the tab came back on the next
launch whenever its processes did not exit.
* chore(terminal): note that closedByLayoutOwner goes away once main owns the terminal layout
* chore: take the base branch's lockfile, which a merge had reverted
* test(terminal): reload the close-intent fixture through the SQLite profile store
Main now requires a SQLite profile-state authority for a writable Store, so the save-and-reload
close tests build and reopen their store through the shared SQLite test harness.
* docs(terminal): state the close-record rule in the merge's active-workspace comment
* fix(terminal): the first close of a tab keeps its record, and every lookup honours the TTL
* test(terminal): give the relay reattach close record a recent close time
* fix(terminal): a close that removes a listed tab records itself; only an echo is skipped
The echo of a close main already made never finds the tab listed, and the close transaction already skips it when a live record exists. The record helper kept the first record as well, which only ever applied to a close that did remove a listed tab, and there it kept a stale reason and TTL.
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
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
- Running
orca serveon a headless Linux server? See the headless Linux server guide.
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 0.0.50 · Install guide
Community & Support
-
Discord: Join the community on Discord.
-
Twitter / X: Follow @orca_build for updates and announcements.
-
WeChat: Scan to join the Orca community WeChat group 10.
-
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.










