Jinwoo Hong d4c19d5db4 test(mobile): let the RPC recorder open a subscription and script its frames (step 6 capability) (#20920)
* test(mobile): let the RPC recorder open a subscription and script its frames

The request-only runner threw on `client.subscribe`, which is why seven raw-port
holdouts read "the recording runner refuses to open one". It no longer does.

`ScriptedRpcTransport` drops the real `RpcClientStreamRegistry` into each physical
session, the way it already reuses `RpcClientRequestTracker` for requests, so
subscribe params, frame routing and the unsubscribe wire all come from product
code. Per session, not shared: a frame is routed by the session that published
its subscribe, and after a cutover the retiring registry is what holds a
cancelled subscribe long enough to unsubscribe it once its id arrives.

A subscribe writes to `payloads` through the same hook a request does, named by
per-method occurrence, and frame ids come from the transport's existing counter
because the real `DirectRpcClient` shares one counter across requests and streams.

New scenario step kind `frame`: it names a subscribe payload, asserts its params
the way `complete` does, and hands a whole host response to the real
`handleResponse`, so `ready`, a data event, the host's end-of-stream pair and a
refusal are one step kind rather than four.

Every `payloads` entry now carries `sent`, the request count at write time, the
same stamp `effects` already use. Without it, swapping `client.subscribe` and the
first `sendRequest` in a product source moves zero bytes: a subscribe publishes
synchronously while a request waits for connected, so the payload order is
identical either way and only `sent` moves.

The reply matrix now drives frames as sites, named by payload and occurrence
because one subscribe carries many frames. Nine of the eleven partitions apply;
the two transport rejections are what a request promise fails with and a
subscription holds none. Success shapes keep the scripted frame's `streaming`
flag, which is what routes a response to the open stream.

`useFocusEffect` is substituted as `useEffect`, so a route's focus cleanup is
recorded at unmount and a blur-triggered unsubscribe stays unrecorded; the README
says so rather than a driven focus substitute no recording reads.

Four tests, each killing a named mutation: routing a frame through the current
session instead of the publisher, delivering a frame to the request tracker,
dropping the `sent` stamp, and reading only `'complete' in step`.

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

* test(mobile): record the two runtime client-event stream consumers

Two families, both driven through the new frame step, as the capability proof for
the subscription recorder.

`session.live-worktree-name` mounts `use-live-worktree-name.ts` end to end:
subscribe, `worktree.show`, a `ready` frame, the fulfilled name, a
`worktreesChanged` frame, the follow-up `worktree.show`, then unmount and the
`runtime.clientEvents.unsubscribe` its focus cleanup sends.

`worktree.host-refresh` mounts `startHostWorktreeRefresh`, whose whole output is
when it calls the two fetches it is handed. It sends no request of its own, so it
is also the family that would have thrown `No scripted reply to drive a matrix
over` before a frame was a matrix site. The 3 s foreground poll is driven by an
`advance` step, which puts `WORKTREE_REFRESH_MS` under recorded time.

Both adapters live in one new module, registered like every other domain, so the
two families' goldens are pinned to a file that holds only them.

No product source changes and no call site migrated: the seven raw-port holdouts
and the `client.subscribe` zero-reference assertion belong to the migration PRs.

`accounts.subscribe` in `use-mobile-home-host-connections.ts` is left out. Its
snapshot decoder is re-exported through a React Native screen module the loader
cannot reach, which is the same wall the accounts read has always been behind, so
it needs a substitute beyond what these two read.

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

* test(mobile): re-record every golden for the subscription recorder

Engine files changed, so `recorderSha256` moves and every header re-digests, and
`payloads` entries carry a new `sent` key. Nothing recorded moved.

Recorded from a detached worktree at the pinned baseline with this branch's
recorder laid over it, per the README's awkward case; `baseline` is unchanged.
Decoding both sides through the value pool and ignoring `recorderSha256` and the
new `sent` key: 641 compared, 6 header-only (the six goldens with no payload at
all), 635 sent-only, 0 other, 9 added, 0 deleted.

The 9 added are the two new families: a pilot golden each, four reply-matrix sites
for the live title (two requests and two frames) and three for the host refresher
(three frames, and no request of its own).

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

* test(mobile): take the broad object parameter out of the frame partitions

`audit:anti-slop`'s no-object-parameters rule fires on a parameter typed `object`,
which the frame-partition helper took to spread a success envelope. One function
narrowing `unknown` to a spreadable envelope replaces the two that split the
check, and the streaming flag is now read as `=== true` rather than by key
presence, matching `isStreamingOpenerReply`.

An engine edit moves `recorderSha256`, so every golden re-digests again. Decoded
through the value pool, all 650 differ on that header alone and on nothing else.

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

* docs(mobile): refresh the recorder's own scenario and golden counts

The paragraph still claimed 78 scenarios and 153 goldens over 210 tests, which
went stale across the domain additions since. It is 330 scenarios, 650 goldens and
757 tests as of this branch. The figures quoted further down are measurements of
the change each one describes, so they stay as written; a line now says so.

Prose is excluded from `recorderSha256`, so this moves no golden.

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

* test(mobile): take the inert optional off a frame, and pin the replay re-read

Review of #20920 found four things the first pass got wrong.

The `optional` flag on a frame step never gated anything: the registry routes every streaming
response to the id that opened the stream, retired or not, so `frame()` only ever throws for a
non-streaming reply. Dropping the parameter, the step field and the downstream marking moves the
scenario digest of two matrix goldens and no recorded byte.

The session comment claimed a mechanism that is not there. The re-send after a cutover comes from
the logical client's own subscription replay, not from the registry being per-session; a shared
registry is byte-identical. What being per-session buys is a frame routed through the session that
published its subscribe, which is what `DirectRpcClient` does too.

The host-refresh scenario now cuts over and answers a second `ready`, so the reconnect replay
branch is recorded: deleting its re-read moves this family. Before, that branch was source no
golden reached.

README over-claimed the subscribe port as covered. Nine product call sites subscribe, two are
recorded, and the other seven are now named with what stops each.

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

* test(mobile): re-record for the frame flag removal and the replay cutover

644 goldens move on `recorderSha256` alone, from the engine edit. Two more also move
`scenarioSha256`: the live-worktree-name matrix variants that used to carry `optional: true` on a
downstream frame. Four bodies move, all in `host-worktree-refresh` — the pilot and its three matrix
goldens now record the cutover, the re-subscribe payload, the retiring unsubscribe and the extra
worktree/repo read the replay branch does. One golden is added, for the matrix site the second
subscribe payload opens.

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

* docs(mobile): count the golden the second subscribe payload adds

Prose only; moves no golden.

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

* test(mobile): pin the live-worktree-name replay re-read too

The same cutover treatment as host-refresh: the scenario now migrates the logical client, answers a
second `ready` on the re-sent subscribe, and answers the title read the replay branch makes. Before
this, deleting that re-read from `use-live-worktree-name.ts` moved no golden.

No engine file changes, so `recorderSha256` holds and 646 goldens are byte-identical. Five bodies
move with their scenario digest, all in this family, and two matrix goldens are added for the sites
the second subscribe payload and the third title read open.

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

* docs(mobile): say what a request count cannot order, and name the accounts wall

`sent` counts requests, so it orders payloads and effects against sends and not against each other.
A family that sends none has no ordering at all: `host-worktree-refresh` keeps `sent` at 0 through
every checkpoint, and moving its two initial reads across the subscribe moves no golden. The fix is
one write ordinal shared by all three lists, which forces a full refresh.

The `accounts.subscribe` wall was misdiagnosed. The loader reaches `decodeAccountsSnapshot` and it
throws its own domain error; what the runner cannot supply is the multi-host client context
`useAllHostClients` reads.

Also honest about the record recipe: where a branch must not repin `baseline`, the detached-pin
worktree is the only one that runs, merged main or not.

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

* test(mobile): file only a subscribe as an open stream, and drop three unused seams

The registry sends its unsubscribes through the same `sendEncrypted` hook as its subscribes, and the
hook filed every payload under `openStreams`. A frame aimed at an unsubscribe name therefore routed
at that wire id, matched no stream, recorded nothing and reported success — where the README promises
`Missing subscription payload`. A latch around the session's `subscribe` wrapper files only what a
subscribe published. Its test fails without the latch.

Three seams no caller varies, the same shape as the `optional` flag: `frameReplyPartitions` took a
`scripted` reply to copy `streaming` from, but every frame site scripts a streaming reply, so the
flag is stamped and a non-streaming unary closer as a base frame is called unsupported; the
divergence map's three-deep ternary is early returns, since `index > divergence` already implies
`index !== divergence`; and `MatrixSite` is no longer exported.

Body-inert: re-recording into a scratch dir at this tree moves all 653 goldens on `recorderSha256`
and nothing else, decoded through the value pool. The goldens are left stale for the merge re-record.

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

* test(mobile): re-record every golden after the main merge

One record at the pin, with this branch's recorder, scenarios and driver script overlaid on a fresh
detached worktree. Decoded through the value pool against `origin/main`: 667 shared goldens, 6
header-only on `recorderSha256`, 661 also gaining the `sent` stamp this branch puts on every payload
entry, nothing else moved, and 12 added — the two client-event families and their matrices. No
`adapterSha256` moved, so main's adapter work was already recorded against its own goldens.

Those 12 are byte-identical to their pre-merge bodies, `recorderSha256` aside, so the merge changed
nothing this branch recorded.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
2026-09-15 22:42:58 -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%