* fix(claude): a Stop ends Claude's process, and the next message resumes the conversation
Claude's Stop now ends its child after the interrupt, whatever Claude answered, once the stopped turn ends or a 3 s grace runs out. The grace lets Claude's own result move the resume point past that turn, so the next send resumes after it. Background work ends with the child. Codex keeps its child.
* test(native-chat): the router answers stopEndsSession for the session's live owner
* test(native-chat): type the Stop test's close mock as the adapter's optional method
* fix(native-chat): a Claude Stop answers on its interrupt, and the next step on the chat's lane ends the child
The Stop now answers as soon as the interrupt step finishes. Ending Claude's
process runs as a second task on the session's serialized lane, queued in the
same tick as the Stop, so a message sent meanwhile reaches only the resumed
child. That step waits for the stopped turn to end (its result, the CLI's idle,
the child's exit or its close), then ends the child; a failure there is
reported and never fails the Stop, and the wind-down it leaves owed is retried.
The interrupt's answer and the turn's end share one 3 s grace counted from when
the interrupt goes out, so an interrupt Claude never answers ends the child in
about 3 s instead of after the 30 s control deadline. The wait is derived per
call from Claude's open turn: the armed latch and the close's user-stop branch
are gone, and the close keeps its whole deadline for proving the exit.
A Stop naming a turn that has since ended (the phone names the turn it last
saw) now ends the child too when Claude took it, as it does when Claude
interrupts a handed-over follow-up whose turn has not opened.
* test(native-chat): pin a Claude Stop's race, failure and timing cases on the shipping adapter
- Nothing queued while the Stop's first step waits on the interrupt runs before
the child ends.
- A message sent with the pre-Stop fence during the Stop is accepted with no
notice, and reaches only the resumed child, after the old child's close.
- An interrupt Claude never answers ends the child within the grace.
- A close that cannot prove the exit leaves the Stop answered with its row; the
failure goes to the host's error hook.
- A Stop naming the turn that just ended ends the child when Claude interrupts
the handed-over follow-up.
- A second Stop pressed while the first ends the child stays quiet.
* fix(native-chat): a Stop naming an ended turn ends Claude's child when its interrupt fails or goes unanswered
The phone names the turn it last saw. When Claude interrupted a handed-over
follow-up for that Stop and the answer failed or never came, the child stayed.
Only a provider that answers it did not take the Stop keeps the child now.
Also pins a queue-if-active send made while the Stop ends the child.
* fix(native-chat): a Claude card's Cancel denies an approval and stops on a question, never a bare interrupt
A permission card's Cancel interrupted the turn and kept Claude running, the
old Stop on a second control: a refused interrupt let the turn run on and
background work survived. Now the host asks the provider how a card's Cancel
is answered. For Claude, an approval's Cancel sends the same reply as its Deny
option, so the turn goes on without that tool; a question's Cancel runs the
chat's Stop, which ends the child, and the next send resumes the conversation.
A provider that gives no answer keeps today's path, so Codex is unchanged.
The host decides, so desktops and phones of any version keep sending the same
cancel and get the new behaviour; nothing on the wire changes.
With no Claude caller left, the prompt-cancel interrupt is gone: the bound
claim, the cancellation observation, the admission of the prompt's
cancellation, the prompt's turn binding, and withdrawing a refused stop (every
Claude interrupt now precedes the child's end, so the recorded stop stands).
* fix(native-chat): a Claude approval card's Stop option is the chat's Stop
The approval card's "Stop" option answered Claude with a deny that interrupts
the turn and kept Claude running, the last control on a Claude chat that
interrupted without ending the child. The host now routes it, like the card's
own Cancel, through the provider: for Claude that option is the chat's Stop.
It runs the same Stop body as the Stop button, in the same order (withdraw,
the Stop takes effect, interrupt), and the step that ends the child is queued
in the same synchronous call. The body now lives in one place,
structured-agent-session-chat-stop.ts, shared by the Stop button, a question
card's Cancel and this option.
The host decides, so an old card or client that sends option `cancel` gets the
real Stop; nothing on the wire changes. The interrupting deny reply is gone.
* test(native-chat): the option-route test's answer carries a whole journal resolution
* fix(native-chat): Claude cards drop their Stop option, and a plan card's Cancel asks Claude to wait
- Approval and plan cards no longer offer "Stop". Neither reference offers a
Stop while an approval is up: the user denies, then stops. An older card's or
client's `cancel` answer is a plain deny, and the respond path is main's again.
- A plan card's X / Esc no longer answers "Keep planning", which sent Claude
straight back to revise and re-propose. It dismisses the plan: a deny that
tells Claude to end its turn and wait for the user. No interrupt; the child
stays. A tool approval's Cancel stays the Deny reply, and a question's Cancel
stays the chat's Stop.
- The provider's routing takes the pending card itself
(`routePromptCancel({ sessionId, prompt })`), so a plan is told from a tool
approval, instead of a kind plus an optional option id that meant Cancel when
absent. A routed answer may be one the card does not offer, like the
dismissal.
- The chat's Stop is reachable only through `mutateWithChatStop`, which owns the
mutation call and queues the step that ends the child in the same call, so
no caller can run the Stop and forget the child's end.
* refactor(native-chat): cancelPlan no longer takes a stopChild nothing passes
The Stop's own body ends the child; the plan's default run serves only a card's
interrupt and the background-task stop, which never end it.
* fix(native-chat): a question card's Cancel settles the card in the Stop's first step, and answers Claude when nothing stops
A question card's Cancel runs the chat's Stop. The card stayed pending until
the child ended (up to about 3 s), so it could still be pressed and the second
press was refused once the chat rested. When the Stop found nothing in flight
(Claude asking after its main turn ended), the card stayed pending for good and
Claude's request went unanswered; the phone froze the card with every button
disabled.
Now the card is recorded as cancelled by the user in the Stop's own step, with
the receipt "Cancelled on <device>". A Stop that ends the session leaves
Claude's request to end with the child, so no reply races the interrupt; one
that ends nothing declines the request itself. The provider forgets the card
once the host records it, so neither Claude's own cancel nor the child's end
writes over the user's cancel. The card's Stop stops whatever the chat has in
flight, like the Stop button, instead of a turn the card names that may have
ended.
The same dismissal, a new provider member beside the cancel route, now answers
a plan card's Cancel: recorded as cancelled by the user, and Claude told to end
its turn and wait. That replaces resolving it as an option the card does not
offer.
* test(native-chat): the question-card Stop test has Claude cancel its held request, as it does once interrupted
* fix(native-chat): a Claude Stop before the echo reads Interrupted, and a question card's Stop stays in its own turn
- A Stop pressed after Send but before Claude echoed it read as a normal finish
("Worked for 0s", a green done check): with no turn row to wait on, the child
was ended at once, and the echo that would have opened the stopped turn
landed on a retired send. The wait before the child ends is now keyed on what
Claude has in flight (an open turn or a send it has not answered), not on a
journal turn id, within the same 3 s from the interrupt and woken by the same
settles; a settle that leaves something in flight waits on. The echo opens
the turn, the aborted result ends it Interrupted. A Claude that says nothing
is ended when the grace runs out, the unanswered send doubt as before.
- A question card's Cancel takes the chat's Stop only when the card belongs to
the turn running now, judged after draining the provider's lifecycle. A card
a finished turn raised, such as a background agent's, is dismissed instead:
Claude's request is declined and nothing stops.
- Claude forgets a dismissed card before the host records it, so Claude's own
cancel landing during that write cannot replace the user's "Cancelled on
<device>".
* test(native-chat): Stop tests read background work from the host's child records, and guard the optional dismissal
Main's child records replaced the adapter's background-task callback: the
background-work test now reads the host's child record (live before the Stop,
settled after the child ends) and the session's agent status (done, Interrupted,
nothing left for Monitoring).
* fix(native-chat): a tool card's Cancel reads Cancelled, and a Stop's background work reads stopped
- A tool approval card's X / Esc already sent Claude the plain deny, but the
card read "Deny · Answered on <device>", the same as pressing Deny. It is
now a dismissal like a plan card's: recorded as cancelled by the user, with
the same deny reply, no interrupt and the child kept. Every card's Cancel
now routes to a dismissal or the chat's Stop, so the option route is gone.
- When a Stop ended Claude's child, a background agent or shell it was running
settled as an unknown ending (a neutral dot), while the same task's own stop
reads Interrupted. A close Orca asks for (a Stop, a rest, a quit) now ends
what still runs as stopped; only a session that dies on its own leaves the
ending unknown. The close reads no stop cause. A rest never meets live child
work: the idle sweep keeps a chat with any.
- If the host fails to record a dismissed card, Claude gets the card back, and
a withdrawal Claude made meanwhile closes it, as before the host took it.
* refactor(native-chat): the session mutation path gets its own module, breaking the chat-stop import cycle
chat-stop.ts imported mutateStructuredAgentSession from host-mutations.ts,
which imports mutateWithChatStop back. The mutation context and the one
admit-then-serialize path now live in structured-agent-session-mutation-context.ts,
which both import; host-mutations re-exports the context type for its other
readers.
* fix(claude): a close that saw a descendant survive leaves background work's ending unknown
A Stop, rest or quit ended still-live background tasks as stopped even when the
close found Claude's root gone but a descendant still running. Only a close
that proved the whole tree gone stops them now; otherwise they settle unknown,
as for an exit of the session's own. The stopped ending is marked as Orca's,
so a frame of the child's own still replaces it.
* docs(native-chat): comments describe the Stop as it now works
What re-drives a failed child end, when the child-work decoder stops what is
live, what a Stop's wait re-reads on a result, and why a card's Stop is
unnamed.
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
- 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 11.
-
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.










