Jinwoo Hong 47d107cf2e feat(mobile): hybrid shell route, dark behind a dev-only flag (OTA phase B, 4/4) (#21435)
* refactor(mobile): say whether a host status was readable, and carry its protocol window

`useHostStatusGates` settled the same closed gates for a host that answered
`status.get` with no capabilities and for one whose status nobody could read:
both paths produced an empty capability list and an `ok` verdict. A caller that
walls on a missing capability cannot tell those apart, and the mobile web
bundle's wall is terminal, so it must never fire for the second.

`statusReadable` distinguishes them. `hostProtocolWindow` exposes the two
protocol numbers the hook already read for `evaluateCompat`, as the reply's own
fields, so the bundle wall can evaluate its own window without a second
`status.get`. Both are additive; no existing consumer changes.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* feat(mobile): add the hybrid shell flag and the generation path both loaders demand

`orca:mobileWebShellEnabled`, default off and unreadable-is-off, in the same
shape as the terminal autocomplete flag.

`generationDirectoryPath` converts the store's `file://` uri to the absolute
path the native shell view requires: both `MobileWebShellGeneration.load`
implementations refuse anything without a leading slash, and `expo-file-system`
only ever hands out uris.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* feat(mobile): the hybrid shell session as a pure reducer

Every decision the route makes, as `(session, event) -> (state, effects)`: the
capability wall, the lazy sweep and cache read, the manifest check, the cached
build-id hit that skips paging, the offline open with no compat check, and the
three recovery rules the native shell view's contract states.

Pure, so the rules are table tests rather than a simulator run. Two latches sit
beside the state because both outlive it: `retriedOnce` spans the delete and
refetch that returns to `checking`, and `remountedOnce` spans a `ready` replaced
by a `ready` under a new session id.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* feat(mobile): the hybrid shell route, dark behind a dev-only flag (OTA phase B, 4/4)

Wires the four Phase B and A pieces together and adds no decision of its own.
`h/[hostId]/web` sits inside the existing `HostProtocolGate` tree, so the native
`desktop-too-old` wall still applies above it. With the flag off — every store
build, since the only writer is a `__DEV__` Troubleshoot toggle — the route
redirects to `h/[hostId]` and the screen is never constructed, so nothing is
fetched, written or swept.

The runner owns only the impure edges and checks an epoch before every dispatch,
so an unmount, a host change or a retry abandons work in flight and aborts a
download that would otherwise hold four of the host's read slots. The native view
is keyed on the session id, which is what makes the reducer's remount a rebuilt
WebView with every fence reinstalled.

A census test pins who touches the flag: the route reads it, the developer row
reads and writes it, and the key itself lives in one module.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* test(mobile): type the hoisted test doubles instead of asserting them

The changed-code casting gate refuses `as` in new code, and these three were
only widening an empty literal. An annotated `vi.hoisted` factory does the same
job under a check.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(mobile): read a scheduled reconnect as an unreachable host, not a dial in progress

Found on a simulator with the paired desktop stopped: the client never settles
on `disconnected`. It dials, fails, schedules a retry, and cycles `connecting`
-> `reconnecting` -> `connecting` with the delay growing to a minute. Mapping
`reconnecting` to "still connecting" left a phone holding a verified cached
generation on `checking` forever instead of opening it, which is the one case
the offline rule exists for.

`connecting` and `handshaking` are the first dial and still wait; everything
else is unreachable. The mapping moves next to the reducer it feeds, because it
is a decision and the runner is supposed to hold none.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* test(mobile): build the reachability stub instead of asserting it

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* refactor(mobile): move the shell session vocabulary into its own module

Pure move, no behaviour: the states, events, effects and gates the reducer
and its runner share now sit beside the reducer rather than inside it, so
the transition rules have room to grow under the file's line budget.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(mobile): drop a shell effect result whose flow has been superseded

Every restart of the flow bumps a number the effects of that run are stamped
with, and a result echoes it back: a manifest read still in flight when the
socket drops used to reject after the offline path had already opened the
cached generation, replacing a displayed workspace with a download failure,
and a status refetch arriving mid-check used to run the cache read and the
download twice. The gates restart no longer clears the remount latch either;
only the retry button does, so a reconnect cannot grant a second remount.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* test(mobile): census the flag across modules, not just src and app

The native view tree was outside the scan, so a reader added there would
have passed an assertion that reads as exhaustive. Proven by adding one to
the shell view module: the census fails.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(mobile): let only the state that mounted the view hear the view

A native batch reports two failures in a row, and the reducer applied both:
document-load-failed started the delete-and-refetch, render-process-gone
then made it terminal without a new flow, and the cache read the recovery
had already asked for dragged the session back to checking behind a failure
screen. A report arriving outside `ready` is from a view that is no longer
on screen, so it changes nothing.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(mobile): say a host status could not be read instead of spinning on it

A transient status.get failure settles the gate unreadable and nothing probes
it again, so the route sat on "Checking host" for as long as anyone left it
there and Try again re-read the same settled answer. It now says what
happened and offers no retry, and a status that does become readable picks
the flow back up on its own.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(mobile): restart the flow on the verdict that changed, not on every gates object

A reconnect cycle rebuilds the gates several times a second with the same
answer in them, and each one re-swept the staging tree and flipped an offline
screen to a spinner and back. Only a changed verdict restarts now, which is
also why the gates effect has to depend on the host id: two hosts whose gates
read identically would otherwise leave the second session in `checking`.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* test(mobile): cover what only the shell runner can get wrong

Three cancellations had no test: the epoch that stops a result reaching a
session that is gone, the unmount cleanup that aborts the download, and the
retry that does both before starting over. Each is now red under its own
mutant. The download also re-checks the abort before it writes, since an
abort landing between the fetch's last read and the commit would otherwise
still put a generation on disk for a screen nobody is on.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(mobile): write the shell runner's refs after the commit, not during render

React can replay or discard a render, so a handle written during one can run
effects for a session that never existed. The client and the host cache key stop
being refs at all; the effect handle is committed in an effect above every
effect that dispatches.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(mobile): keep the hybrid shell flag unreadable outside a development build

Development and release share a bundle id, and the iOS data container survives
an install-over, so a flag a developer toggled on would follow the store build
in and mount the shell on a deep link. The release read never reaches storage.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* test(mobile): drive the route's flag read as each build kind reads it

The route test exercises the real preference read, so it has to say which build
it is. A store build whose container kept a development toggle redirects.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(mobile): let a cache read that lands mid-dial wait for the compat check

A connection still being made is not a host that cannot be reached. Opening the
cached generation there skips the compat check the landing connection is what
makes answerable, so only `unreachable` takes the offline path now.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(mobile): move the developer toggle only after its write lands

The route reads the flag back from storage, so a switch that moved on the tap
let the open button race the value that was being persisted. The switch and the
button both stay put until the write settles, and a failed write keeps the
previous position.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* test(mobile): say which build kind a test runs as without asserting on globalThis

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(mobile): undo a staged generation the abort reached before the commit

The commit is the write staging cannot take back: it renames into the active
slot and moves the host index. An abort landing while the bytes were being
staged now removes the staged tree instead of activating it.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(mobile): open the cached workspace when the link, not the bundle, cut a read short

An RPC rejection can reach the reducer before the reachability change does, so
the offline gate never fires and a phone holding a valid generation reads that
the workspace could not be downloaded. A read that failed on the link now opens
what is on disk, the same path offline takes; a verdict about the bundle, from
the host or from the bytes, still fails.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(mobile): send a hybrid shell recovery through the same gate a start takes

A view failure deleted the host cache and went straight back to the manifest
check on whatever gates the ready session happened to be holding. Gates that
arrive while a generation is on screen are stored without restarting, so after a
reconnect whose status probe failed a ready session carried statusReadable false
and an empty capability list, and the recovery's manifest check walled the host
as bundle-unavailable: terminal, no retry, about a host that never answered.

The gate is now one verdict both entries read, and recovery passes its delete
through it, so an unreadable status lands on the status-unreadable message that
re-arms when a readable gate arrives, and only a readable refusal still walls.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
2026-09-18 08:22:25 -04:00
2026-05-04 20:42:03 -07:00
2026-03-16 22:27:51 -07:00
2026-03-28 10:19:14 -07:00

Orca Orca

GitHub stars Total downloads across all releases License: MIT Join the Orca Discord Follow Orca on X Supported platforms: macOS, Windows, and Linux

中文 · 日本語 · 한국어 · 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

Orca desktop app running agents in parallel worktrees, with the Orca mobile companion app in the corner

Features

Mobile Companion

Monitor and steer your agents from your phone — get notified when an agent finishes and send follow-ups from anywhere.

iOS App Store · TestFlight · Android APK 0.0.48 · Docs →

Orca desktop with the mobile companion app

Parallel Worktrees

Fan one prompt across five agents, each in its own isolated git worktree — compare the results and merge the winner.

Docs →

Parallel worktree orchestration

Terminal Splits

Ghostty-class terminals with WebGL rendering, infinite splits, and scrollback that survives restarts.

Docs →

Terminal splits

Design Mode

Click any UI element in a real Chromium window to send its HTML, CSS, and a cropped screenshot straight into your agent's prompt.

Docs →

Embedded browser and Design Mode

GitHub & Linear, Native

Browse PRs, issues, and project boards in-app — open a worktree from any task and review without a context switch.

Docs →

GitHub and Linear task workflows in Orca

SSH Worktrees

Run agents on a beefy remote box with full file editing, git, and terminals — auto-reconnect and port forwarding included.

Docs →

Remote worktrees over SSH

Annotate AI Diffs

Drop comments on any diff line and ship them back to the agent — review, edit, and commit without leaving Orca.

Docs →

Annotate AI-generated diffs

Drag Files to Agents

VS Code's editor with autosave everywhere — drag files or images straight into an agent prompt.

Docs →

Drag files and images into an agent prompt

Orca CLI

Agents drive Orca too — script every workflow with orca worktree create, snapshot, click, and fill.

Docs →

Script Orca from the CLI

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 logo Claude Code   Codex logo Codex   Grok logo Grok   Cursor logo Cursor   GitHub Copilot logo GitHub Copilot   OpenCode logo OpenCode   MiMo Code logo MiMo Code   Amp logo Amp   OpenClaude logo OpenClaude   Antigravity logo Antigravity   Pi logo Pi   oh-my-pi logo oh-my-pi   Hermes Agent logo Hermes Agent   Devin logo Devin   Goose logo Goose   Auggie logo Auggie   Autohand Code logo Autohand Code   Charm logo Charm   Cline logo Cline   Codebuff logo Codebuff   Command Code logo Command Code   Continue logo Continue   Droid logo Droid   Kilocode logo Kilocode   Kimi logo Kimi   Kiro logo Kiro   Mistral Vibe logo Mistral Vibe   Qwen Code logo Qwen Code   Rovo Dev logo Rovo Dev   + any CLI agent


Install

Desktop — macOS, Windows, Linux

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.


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 8. Group 8 may be full; if so, scan the Group 9 QR code instead.

    WeChat group 8 QR code for the Orca community  WeChat group 9 QR code for the Orca community

  • 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.

Orca contributors

GitHub star history chart for stablyai/orca

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.

S
Description
Orca is the ADE for working with a fleet of parallel agents. Run any coding agent with your own subscription. Available on desktop, mobile and remote runtime.
Readme MIT
1.5 GiB
Languages
TypeScript 95.2%
JavaScript 4%
Swift 0.2%
CSS 0.2%
HCL 0.1%