* fix(terminals): make redundant tab activation idempotent (React #185) setActiveTab always reallocated activeTabIdByWorktree, even when the tab was already active for that worktree. Terminal's active-terminal repair effect depends on that map, so when the repair cannot converge activeTabId -- which happens when an earlier-scanned worktree reuses the tab id -- the effect re-triggers itself every commit until React throws #185. Crash cluster A: 12 reports, boundary terminal.workbench, 1.4.162/1.4.163. Co-authored-by: Orca <help@stably.ai> * fix(terminals): converge activeTabId when a tab id is owned by two worktrees Prefer the active worktree when resolving a terminal tab's owner. First-match ownership left activeTabId permanently unconvergeable under a duplicated tab id, so the active-terminal repair effect re-triggered itself into React #185. Breadcrumb the duplicate-ownership state (once per tab id) so a crash bundle can prove or kill the production origin of the precondition. Co-authored-by: Orca <help@stably.ai> * fix(crash-reporting): coalesce the duplicate-tab-owner breadcrumb Its renderer guard is once-per-tab-id, so the stale worktree map it exists to diagnose duplicates every tab id at once and could evict the whole 30-entry ring. Also drops two keyed re-reads of tabsByWorktree that would throw for a prototype-named worktree id, and pins the activeTabIdByWorktree guard with a test that fails without it. Co-authored-by: Orca <help@stably.ai> * fix(crash-reporting): key the duplicate-tab-owner crumb on its convergence flag Name-only coalescing keeps only the newest payload, so a resolvedToActiveWorktree false sample — the one value saying the activation still could not converge — was erased by any later benign true in the same 30s window. Keys on the flag instead, mirroring the WebGL name:kind branch; two keys still bound the burst. Also: the previous coalescing commit had no test at all (removing the name from both sets broke zero of 2701 tests), the resolver's activeWorktreeId truthiness check was a hole rather than a guard for a '' active id, and the resolver test file failed oxfmt --check. Co-authored-by: Orca <help@stably.ai> * fix(crash-reporting): keep the non-converging duplicate-tab verdict The two earlier commits contradicted each other. Splitting the coalesce key existed so a `false` verdict could not be erased by a later benign `true` — but the renderer guard was keyed on the tab id alone, so for any one id only the first verdict was ever emitted. A duplicated id that first resolves benignly, then stops converging when the user switches worktrees, dropped the `false` sample at the source. That sample is the whole reason the breadcrumb exists: it is the only value saying the activation could not converge activeTabId. Key the guard on id plus verdict. At most two crumbs per tab id, and the main process still folds each verdict into its own ring entry, so the flood bound is unchanged. * fix(crash): correct the duplicate-tab verdict rationale, pin and cap the guard Three comments said `false` is the verdict that matters because it is the only one showing the activation could not converge. That is backwards. The repair effect activates a tab drawn from tabsByWorktree[active], so the React #185 path can only ever emit `true`; `false` is what a deliberate jump-to-agent into a background worktree emits from a fully converged state. A reader of the next bundle would have discarded the exact sample the breadcrumb exists to capture. The mechanism was right, only its stated reason was wrong: the real justification for keying on the verdict is symmetric, since coalescing keeps only the newest payload and either verdict would erase the other. The suite also did not pin the "at most 2 per tab id" bound - a guard keyed on `${tabId}:${activeWorktreeId}` passed all 11 tests while emitting once per worktree, the storm the guard exists to prevent. Adds a count-pinning test that kills it. Caps the never-pruned guard set at 256 distinct verdict keys (~85KB), mirroring MAX_COALESCE_KEYS. Measured 330 B/entry; a realistic thousand duplicated tab ids is ~0.6MB, negligible but unbounded in principle. * perf(crash): scan worktree tabs by key, and soften the verdict rationale Round 6 corrected my own round-5 comment. I had written that `true` is the repair-loop signature and `false` covers a deliberate background activation. The repair effect can emit `false` too: its closure holds the worktree from its render while the guard runs against live state, so a worktree switch landing in between reattributes the tab. The verdict hints at the caller; it does not prove it, and neither value should be discarded. Comment-only. Also take the free scan win the perf review measured: Object.entries allocates a pair array per worktree on a path that runs per tab activation. Own keys are safe to index by, so Object.keys plus an indexed read is behaviour-identical (16.1us -> 5.0us at 170 worktrees x 10 tabs). * fix(terminal): keep a duplicated tab id from re-sorting the active worktree setActiveTab now prefers the active worktree when a tab id is held by more than one, but terminals.ts has a second, older owner resolver: getTerminalTabOwnerWorktreeId, a memoized map built last-writer-wins. Two of its callers — setRuntimePaneTitle and clearRuntimePaneTitle — use the result for the same "is this pane in the active worktree" gate, so under a duplicate the two resolvers disagree: the cache names whichever worktree it saw last, which can be a background one for a pane the user is looking at. The gate then fails open and every classified OSC title frame bumps sortEpoch, reinstating the click-driven sidebar re-sort #209 removed — 20 title frames measured 20 bumps, each one a store write that re-renders every sortEpoch subscriber. isTabInActiveWorktree answers from the active worktree's own tab list instead of a tie-break. It stays behind the cheap id equality so the common non-duplicated path is unchanged, and it is a hasOwn lookup plus one scan of that worktree's tabs rather than resolveActiveTabOwnerWorktreeId, whose full scan would run per title frame and whose breadcrumb would fold a second caller into one verdict. Leaves updateTabTitle and clearTabLaunchAgent on the cache: they pick which copy of a duplicated tab to mutate, where no answer is defensible until the duplication itself is fixed. * test(terminal): pin the SSH-hydration origin of the duplicate tab id Drives the duplicate from real hydration rather than constructing it: a direct-SSH snapshot is keyed by worktree path, so renaming the worktree on the host (or re-adding the repo, which mints a fresh id) re-resolves it to a new worktree id while replaceHydratedRecordKeys retains the old key verbatim. Nothing de-dupes across keys. Fails on unfixed origin/main with converged=false after 200 passes; the two precondition assertions pass on both sides, so the red is the non-convergence itself and not a setup divergence. The reconnectPersistedTerminals stub is load-bearing and marked as such: with no registered PTY the orphan sweep cleans the duplicate up before the repair effect sees it. * docs(test): record the end-to-end #185 reproduction method on the regression test Co-authored-by: Orca <help@stably.ai> * test(terminal): pin the null-active-worktree guard in isTabInActiveWorktree Dropping the `activeWorktreeId === null` early return was killed by nothing: `Object.hasOwn(map, null)` coerces to the string key 'null', so a worktree literally named 'null' would answer for "no active worktree". An untested guard reads as dead code and gets deleted. * rm triage context * rm triage context * rm context files * refactor(terminal): extract repair logic into reusable hook and guard ag Extract the active terminal repair effect from Terminal.tsx into `useActiveTerminalRepair` hook to enable reuse in tests and clarify responsibilities. Replace falsy coercion guards (`obj[id] ?? []`) with explicit `Object.hasOwn()` checks to handle edge cases: empty-string worktree ids (valid but falsy), prototype-named ids like 'toString', and duplicated tab ids across worktrees. Remove the now-unused `isTabInActiveWorktree` helper. Simplify the isActive logic in terminals.ts to rely solely on owner-equality since the repair now uses proper membership checks. --------- Co-authored-by: Orca <help@stably.ai> Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com> |
||
|---|---|---|
| .github | ||
| .husky | ||
| Casks | ||
| build-plugins | ||
| config | ||
| docs | ||
| examples/plugins | ||
| mobile | ||
| native | ||
| resources | ||
| skill-guides | ||
| skill-stubs | ||
| skills | ||
| src | ||
| tests | ||
| .gitattributes | ||
| .gitignore | ||
| .npmrc | ||
| .oxfmtrc.json | ||
| .oxlintrc.json | ||
| AGENTS.md | ||
| CLAUDE.md | ||
| LICENSE | ||
| README.md | ||
| components.json | ||
| electron.vite.config.ts | ||
| orca.yaml | ||
| package.json | ||
| pnpm-lock.yaml | ||
| pnpm-workspace.yaml | ||
| tsconfig.json | ||
| vite.web.config.ts | ||
README.md
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.36
Community & Support
-
Discord: Join the community on Discord.
-
Twitter / X: Follow @orca_build for updates and announcements.
-
WeChat: If group 5 is full, you can join group 6.
-
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.
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.








