* test(mobile): record main's session reply behaviour at every unrecorded read site Step 7 for the session domain changes how 51 RPC readers read a *malformed* reply. Eleven of the session read sites had no recording family, so main's answer to a malformed reply at those sites was undocumented and the reader change would have had nothing to move. This commit is the before picture, taken from main's own tree with no product edit in it. Ten new families, twelve scenarios, twenty-five goldens: - `session.review-file-diff` / `session.review-branch-diff` — `git.diff` and `git.branchDiff` read through the review projection, which the Changes screen's verbatim readers do not cover. - `session.review-git-mutations` — the single-file `git.stage` / `git.discard` and the bulk stage sweep's second `git.stage`. - `session.review-send-sheet` — `session.tabs.list` read for the agent terminals the send sheet lists, the third reader on that method. Needs an `open-send-sheet` action on the review-action adapter, which re-digests that family's eight goldens on `adapterSha256` and nothing else. - `session.browser-tab-create` — `browser.tabCreate`. - `agentSession.structured-create` — `agentSession.create`, whose family base only ever covered the support probe. - `session.tab-rename` / `session.tab-close-session` — `terminal.rename` and `session.tabs.close`. - `settings.new-tab-local-agents` — `preflight.detectAgents`, the arm the new-tab loader takes for a workspace with no connection. `baseline` is repinned to main's tip because two commits (#20659, #21004) touched a fenced path after the pilot's pin, so `--record` refuses on main's own tree until it moves. The repin is what rewrites `baseline` on all 705 existing goldens; nothing else about them moves. Decoded against origin/main through the value pool: 705 header-only (`baseline` on every one, `adapterSha256` on the eight review-action goldens), 0 body-moved, 25 added, 0 deleted. Not covered, with the reason: the chunked clipboard upload's `appendImageUploadChunk`, `commitImageUpload` and `abortImageUpload` cannot be matrixed, because `replyMatrixSites` takes every completion in the base scenario and the chain's later params carry the `uploadId` the start reply named. Driving `clipboard.startImageUpload#1` therefore makes main send an append whose params no scripted step matches, and the recorder raises `Request params mismatch: clipboard.appendImageUploadChunk#1` instead of recording. The two families were written, probed and removed. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(mobile): checked reply readers for the session domain (step 7) Fifty-one unchecked reply readers across nine files become checked zod readers, so a malformed host reply surfaces as one readable error at the operation boundary instead of a downstream `TypeError`, a rendered `undefined`, or a screen left ready over garbage. Deliberately a behaviour change on malformed replies only. Eight schema modules, one per reply family, each recording the consumer line behind every requirement and the host handler that publishes it: - `clipboard-image-reply-schema.ts` — the upload slot's `uploadId`, the commit and single-frame path strings, and the two legs whose body nothing reads. - `github-pr-mutation-reply-schema.ts` — the `{ ok, error }` status envelope as two variants, and the bare-boolean confirmation. - `github-pr-entity-reply-schema.ts` / `github-pr-read-reply-schema.ts` — the seven PR sidebar reads. Every identity requirement the hand parsers had is kept, so a payload that degraded to null still degrades to null; what changes is a payload that is not the declared container at all. - `diff-review-reply-schema.ts` — the normalized branch compare, the review notes on the worktree record, the three file-diff arms, and the file-level git mutations. - `review-terminal-reply-schema.ts`, `session-launch-reply-schema.ts`, `session-read-reply-schema.ts`, `session-write-reply-schema.ts` — the review send sheet, the launch paths, the session screen's reads and its writes. Requirements are exactly the members a consumer reads unguarded, everything else is a salvaged optional with main's own default applied in the transform, and no schema is `.strict()`: a member a newer host adds passes through untouched. Enum arm sets that a reader compares against pass through or degrade to the arm the reader handles most conservatively; the two closed sets — the committed change status and the diff kind — are closed because main *dropped* an arm it did not know rather than passing it through, and degrading them would draw a row or render a diff main never did. No member is coerced on the way back to the host. `github-pr-parsers.ts`, `github-pr-comment-parsers.ts` and `github-pr-value-readers.ts` are gone; their suite is now the parity record for the schemas that replaced them, with the four cases that refuse rather than degrade marked as such. Twelve call-site casts are deleted, and three dead "response was invalid" branches with them: the reader refuses those replies now, so the error names its method. The nine session files come off `unchecked-rpc-reader-inventory.ts` entirely rather than being lowered. `git show --stat` on this commit touches nothing under `mobile/rpc-foundation`. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): unit-pin every session reply schema's decision Three kinds of case, one per kind of decision the schemas encode: a member a consumer reads unguarded is required and its absence refuses, an arm set a reader compares against degrades to the arm that reader handles most conservatively, and a reply whose arms need different members is declared as variants and each arm is read. The last suite is the wire-compatibility claim: a member no reader knows passes straight through, on the markdown document, the upload slot and the terminal inventory alike, so a newer host is never refused for a field mobile does not read. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): refresh the corpus for the session domain's checked readers Repins `baseline` to the last commit touching a fenced path and re-records all 730 goldens, which is the disclosed behaviour change taken as an observation. Decoded through the value pool against the pre-refactor tree on this branch: 688 header-only with `baseline` the only key that moved, 42 body-moved, 0 added, 0 deleted. The 42 are seven named scenarios and thirty-five matrix goldens, and every moved checkpoint's own reply is malformed or refused. Three `normal` partitions appear in the list and none of them reads a well-formed reply differently: the review file-diff family's base scenario drives three legs and its third is scripted `{ kind: 'unknown' }`, so that leg's checkpoint moves in every variant, the varied leg included. The same append-only-history effect puts `pr-read-upstream-error`'s `no-pr` checkpoint in the list for the malformed PR recorded before it. What the corpus now records, in one sentence: a property read on null, a V8 destructuring message shown to the user, and four hand-written "response was invalid" strings are replaced by one message that names the method, and four screens that published a malformed payload as ready state now show an error instead. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(mobile): split the expanded check run out of the PR read schemas `github-pr-read-reply-schema.ts` was 328 code lines against the 300-line cap. The expanded check run and the annotations, jobs and steps listed under it are one reply with no reader in common with the other six, so they move to `github-pr-check-reply-schema.ts` whole. A move, not an edit: no schema changes and no golden moves. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): repin the corpus to the branch's last fenced-path commit The schema-module split touched `mobile/src`, so `--record` refuses on the pin the previous refresh left behind. Repins to that commit and re-records. Decoded against the previous corpus: 730 header-only with `baseline` the only key that moved, 0 body-moved, 0 added, 0 deleted — the split is a move, and the corpus says so. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(mobile): drop the worktree display-name cast's type import The live-title read is typed by its schema now, so the cast it annotated is gone and the import it needed with it. oxlint flags the leftover. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): repin the corpus to the branch tip The unused-import removal touched a fenced path, so the pin moves with it. Decoded against the previous corpus: 730 header-only on `baseline` alone, 0 body-moved, 0 added, 0 deleted. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): contain a refused prChecks reply to the checks section The checks read was the one phase-1 dependency that could take the whole PR sidebar down. `loadPrSidebarData` routed `!checksOutcome.ok` through `failureState`, so a host whose `github.prChecks` shape drifted cost the user the title, body, comments, reviewers and merge controls — everything they opened the sidebar for — over a section that renders a row of icons. Main never noticed because its unchecked reader answered `[]` for the same reply; this branch's reader refuses it, which is correct, and which is what makes the containment necessary. Contained the way phase 2 already is: a failed read keeps `kind: 'ready'`, empties `checks`, and carries the message in a new `checksError` so the checks section can say what happened. The sidebar can no longer reach `error` or `blocked` on the checks read alone. Also pins the enum departure this PR makes deliberately. The degrading arm sets go through `salvagedOptional(name, z.enum(...))` rather than `openEnum` because `openEnum` refuses a non-string where main mapped it to the conservative arm; nothing held that, and all 2477 tests stayed green against the swap. Six cases now hold both halves: a non-string degrades on the three open sets, and an unknown arm drops the row on the closed ones. Four deletions the reviewer found: a reaction-token alias with no importers, the `errorType`/`fetchedAt` the branch-lookup reader fabricated to satisfy a type whose only consumer reads neither, two bare schema aliases, and a quick-commands pass-through with two callers. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): repin the corpus to the containment commit `--record` refuses unless the product tree equals `baseline`, so the fix above moves the pin. The corpus re-recorded in place against it: 730 goldens, every one header-only on `baseline`, no observation moved. No observation moved because no family reaches the code the fix changed. The `github.pr-read` family calls the seven wrapper reads directly and records their `{ ok, error }` outcomes; `loadPrSidebarData` sits a layer above that and no scenario mounts it. The prChecks outcome is identical before and after — what changed is what the sidebar does with it — so the unit suite is the only oracle for the containment. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): record the PR sidebar's checks containment The containment landed with no golden: no scenario mounted `loadPrSidebarData`, so the row in the delta table rested on unit tests alone. `PrSidebarLoadDeps` is five client-taking functions, so a new adapter drives phase 1 directly and records the `PrSidebarState` it resolves to — no React host, and no edit to an existing adapter, so no recorded golden moves. Two scenarios: a normal load, and one whose checks leg answers a shape the reader refuses. The matrix over the base then drives all eleven partitions at `github.prChecks#1`, and every one of them records `ready` with a `checksError` where main took the whole sidebar to `error`. `pr-sidebar-checks-failure-state` is the mutant that routes the refusal back through `failureState`; it moves both `pr-sidebar-checks-refused` and the prChecks matrix golden. Also pins two closed-and-required enum decisions that were free to become defaults — an unknown check-summary state drops the summary block, an unknown reaction content drops the reaction — deletes four exported type aliases and five enum constants with no reader outside their own file, makes `PRChecksSection`'s `checksError` required so a second caller cannot silently lose the message, and stops the header reading "No checks" when the checks were unreadable rather than absent. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): repin the corpus to the pr-sidebar family commit Six new goldens — two pilots and the four matrix sites the base scenario scripts — and `baseline` on the 730 that already existed. No body moved and no `adapterSha256`: the family is a new adapter module, so nothing recorded through another one re-digests. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): re-record the corpus against the merged main Repins `baseline` to the merge commit and re-records all 736 goldens in place. Against `origin/main` the 705 shared goldens move only on `baseline` (672 of them header-only), leaving the same 33 body moves and the same partitions the branch carried before the merge, plus its 31 added goldens. Every body also takes main's recorder shape from #21088: `sent` becomes `ordinal` over one interleaved write counter, subscriptions record a cleanup checkpoint, and a salvaging read now reports a `reply-salvage` effect naming what it dropped. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): keep an explicit null on the two tri-state PR flags `autoMergeAllowed` and `mergeQueueRequired` carry three answers, not two: `null` is GitHub saying auto-merge is not allowed, `undefined` is the host not carrying the member at all. The readers coalesced the null away, so a well-formed reply read differently from the parsers they replaced, which preserved it explicitly. Both shared types already declare `boolean | null`. No consumer separates the two today — `pull-request-auto-merge-availability` compares with `=== true` and `!== false` — so this is parity, not a visible fix, which is exactly why it needed a test. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): repin the corpus to the tri-state flag commit All 736 goldens move on `baseline` alone: no scenario scripts an explicit null on either flag, so preserving it changes no recorded screen. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(mobile): check the two session-write readers #21083 brought Step 7 empties the session block of the unchecked-reader inventory, and #21083 landed two readers into it after that: the New Tab create's member read of `tab`, and the display-mode toggle's payload. Converting them is what keeps the claim true — a session line reappearing would mean the domain is not migrated. `created-terminal-tab` requires `tab.id` and `tab.type === 'terminal'`, because the strip keys the new tab on the id and spreads the rest into a union whose arm `type` picks. `terminal`, `title` and `terminalTheme` stay optional behind main's own guards, and unknown members pass through. `terminal-display-mode-set` reads nothing, so it takes the same `z.unknown()` the other five unread writes take. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): repin and re-record over #21083's corpus All 736 goldens this branch already had move on `baseline` alone, and #21083's 22 arrive beside them. One of the 22 moves against main's own recording: `matrix-session.create-terminal-session.tabs.createterminal-1`, where the New Tab create's five malformed partitions read `Cannot read properties of undefined (reading 'tab')` and now read the method's own message. Two of them also stop unsubscribing the terminal the user was watching before the property read threw, so a create that never happened no longer costs the live pane its subscription. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * docs(mobile): say what carries a refused create reply to the catch Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
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
OpenCode
MiMo Code
Amp
OpenClaude
Antigravity
Pi
oh-my-pi
Hermes Agent
Devin
Goose
Auggie
Autohand Code
Charm
Cline
Codebuff
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 or join TestFlight
- Android: Download APK 0.0.48 · 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 8. Group 8 may be full; if so, scan the Group 9 QR code instead.
-
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.












