Brennan Benson 15472cd4c6 feat(native-chat): keep restart recovery available in status bar (#21397)
* feat(native-chat): keep restart recovery available in status bar

* fix(native-chat): source the restart offer from the host and retire it on recovery

Closing the reconnect dialog spent the durable recovery offer, so looking around
before deciding lost the recovery for good. The offer now survives a close, and
the status bar carries it — but a durable offer needs a way to die, and it only
had a reconnect, an explicit dismiss, and a 24h expiry.

The claim's launch-scoped lifecycle moves into its own collaborator, which splits
what the host ADVERTISES from the evidence it holds. A resume-capable hold that
hands a marked chat its provider child back is the recovery the offer existed to
perform, so it stops being advertised and stops being written back at quit, while
the marker stays valid evidence — a user who reopened a chat can still ask the
agent to carry on. Teardown re-derives the snoozed offer rather than round-tripping
raw markers, and this teardown's own witness now outranks the stale claim for the
same chat instead of being overwritten by it, which was silently persisting an old
turn id and making the next launch refuse the chat that was actually mid-turn.

On the renderer the candidate list gets its own producer against
agentSession.restartResumable, so the status entry and the dialog read one
host-owned answer instead of the dialog pushing its local state at a sibling. The
entry re-reads the host before reopening, so a reopened list can never name a chat
the host would now refuse; dialog open becomes the external one-shot request
rather than a flag mirrored into render state, which is what let a reopen replay
the launch answer and re-offer chats already reconnected. Dismiss all is quiet
rather than destructive, saves the preference like every other exit, and reports a
write the host never confirmed instead of trapping the dialog open.

* fix(native-chat): keep a durable offer a launch never read, and settle the one a continuation spent

Teardown replaced the recovery capsule with whatever this launch still owed,
and a launch that never read the offer owes nothing — so a quit after a
failed first read, a disabled flag, or a window that never mounted deleted a
recovery the user was never shown. The write-back now distinguishes "claimed
and still owed" from "never claimed": the first is re-derived as before, the
second carries forward verbatim, because nothing revealed those sessions and
the predicate would refuse every one for want of a journal nobody opened.

Reconnect and continue spent the same claims Reconnect does but never shrank
the offer, leaving the status bar counting chats the host had already handed
back and sending the user to an entry that re-reads, finds nothing and does
nothing.

* fix(native-chat): stop a teardown answering for an offer it could not read

Two ways the write-back deleted a durable recovery offer nobody had seen.

A take that FAILED left the claim holding an empty list and reporting that
this launch had answered for the offer. The markers were still on disk,
unread and unknowable, and teardown then overwrote them with its own empty
list. It now writes nothing at all unless it has a witness of its own.

`owed()` read "has the capsule been touched" where it meant "did anything
here LOOK at the offer" — and its own write-back read counted. Teardown is
retried when a phase fails, so the second attempt re-derived carried markers
against a session map eviction had already emptied, refused every one, and
wiped what the first attempt had just carried forward. The flag is now set
only by the paths that actually read or act on the offer.

The mock guard for the carry could not fail: it indexed the session it
claimed nothing had revealed, so re-deriving passed and the verbatim carry
was never the reason it went green. It now runs against no indexed session,
which is what an unread offer looks like.

Also drops the `Not now` row from the preference table, where it was paired
with a dismiss method it no longer calls, and asserts the same thing where
the snooze is already covered. Splits the marker predicate's journal reader
out of the resume host, which was at its line ceiling.

* fix(native-chat): clear the corrupt recovery capsule the take refused

A capsule whose contents no longer parse made take() throw before it ever
reached the clear, so the bad file survived every launch. Nothing else
rewrites it now that a teardown owing nothing readable declines to write, and
the freshness filter runs after the parse, so the 24h window could not release
it either: one corrupt file refused recovery forever.

Clear it inside the same transaction that failed to read it, then rethrow, so
the poison dies on the next launch while callers still see why the take failed.
A clear that fails is swallowed rather than allowed to mask the parse error.
Refusing to expose partial candidates is unchanged, and a read that fails for
any other reason still writes nothing.

* feat(native-chat): make resume the one restart action, and make it actually resume

The restart prompt offered two actions: "Reconnect all", which reattached
and sent nothing — exactly what opening the chat already does — and
"Reconnect and continue", which reattached and asked the agent to carry
on. The vacuous one is gone, the "Not now" button and the info popover
with it, and the feature is now called resume throughout.

"Don't ask again (resume automatically)" now runs the action the button
runs: the launch calls agentSession.restartContinue instead of
agentSession.restartResume, so the preference means what it says. Several
comments asserted the opposite as a structural guarantee and are
corrected. agentSession.restartResume stays: no in-app caller is left,
but it is a published wire method a non-desktop or older client can call.

* fix(native-chat): label the resume button with the number of chats selected

The button read "Resume all" whenever every chat happened to be ticked,
which described the selection rather than the action. It always acted on
the selected chats only. Now it always names that count, with a singular
variant so one chat does not read "1 chats".

* refactor(native-chat): drop the reconnect vocabulary the resume action left behind

Resuming became one action — reattach and ask the agent to carry on — so the
notification helpers no longer need to be told which action they are reporting.
Every caller passed `continue`; the `reconnect` branch, its helper and its
catalog keys are gone.

The dialog and the launch path had grown two copies of the same call: same RPC,
same response shape, same announce-and-settle. That now lives once in the store
module that owns the offer, which also takes the dismiss call, leaving the modal
presentational. The two copies had drifted — only the dialog's caught a
malformed payload — and the unified one keeps the defensive reading.

No behaviour change. `agentSession.restartResume` stays: it is a published wire
method even though nothing in the app calls it.

* refactor(native-chat): derive the resume selection instead of intersecting it

The modal's selection was intersected back against the host's candidate list
before every action, as a guard against naming a chat the host never offered.
That guard could never fire: the selection was already derived from that same
list, so the intersection was the identity. The array of chosen ids is now the
derived value and the lookup set falls out of it, which makes the property
structural rather than checked. The helper had no other caller and is gone,
along with its three tests.

Three tests mocked the resume response in the shape the old API returned. Two
never reached that branch at all; the third only passed because the unreadable
shape happened to exercise the malformed-payload path. All three now use the
real shape, and the malformed-payload behaviour — report an unconfirmed
delivery, leave the offer standing — gets a test that says so.

Also: the candidate reader took two trailing optional parameters, so one caller
passed a placeholder `false` to reach the second; they are an options object
now. `isFolderWorkspaceId` had no caller outside its own module and is no
longer exported. `RestartActionOutcome` only ever describes a continuation row,
so it is named for that. `dismissAll` set a busy flag that nothing could
render, since it closes the dialog first. Several comments repeated an argument
already made in the module they point at.

Settings: the automatic-resume description is one sentence again.

No behaviour change.

* fix(native-chat): make restart recovery explicitly durable

* fix(native-chat): preserve dismissal fence across new interruptions
2026-09-21 16:38:05 -07: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.50 · 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 9.

    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%