* refactor(ai-vault): split the session scanner into a transcript reader and consumers The scanner's only output was the Session History summary; a second reader of the same transcripts (a search index) had nowhere to plug in without hooking the parse itself. Extract a reader that owns each file read, keeps the resumable cursor and publishes every decoded message to registered consumers. The session list stays a fold inside the parser and is the only consumer here. Parsers take an optional message sink instead of a scope. Also: read Cursor chats/<md5>/<uuid>/meta.json for cwd, title and timestamps (Cursor transcripts carry only role and message); share the lazily spawned worker-thread host between the OpenCode SQLite reader and the port-scan probe; probe OpenCode's schema before querying; keep the newest-N discovery set with a bounded insert instead of sort+slice. Session list output is byte-identical to main across all 18 providers cold and append-resumed; the one Cursor session gains cwd/timestamps from meta.json. * fix(ai-vault): serialize per-path parses and report unpublished reads Overlapping parses of one transcript share the cached resume point's message channel, so the second beginRead dropped the first read's consumers and the first finishRead handed them the wrong outcome. Two callers really do overlap: a forced refresh restarts a scan while the aborted scan's parse is still in flight, and the title reader parses outside any scan. Restore the per-path lane around the whole lookup-read-store sequence. OpenCode's SQLite sessions are decoded on a worker thread the channel cannot reach, so their reads published no messages while reporting a complete span. Finish those reads as incomplete instead, so a consumer never records a cursor for a stream it did not receive. * fix(ai-vault): degrade a refused cursor chats read instead of dropping sessions A refused WSL read of Cursor's chats tree rethrew, and the per-file catch in discovery then recorded an issue and skipped the transcript. Before the meta.json join Cursor had no content dependency, so a stalled distro could not hide a Cursor session at all. Degrade to no metadata for the scan and report the chats root once. The parse cache stays honest without the throw: discovery stats no meta.json on a refused scan, so the entry's recorded size omits it and the next healthy scan re-reads the transcript. The per-scan index scope covered discovery only, so every Cursor finalize re-read the chats root to validate the module cache. Move the scope to scanAiVaultSessions, which spans discovery and parse. Also drop the unused signal parameters the sink threading added to the Devin and Hermes content parsers, by giving each file parser a private record parser instead. * fix(ai-vault): do not cache a cursor parse whose meta.json read was refused Discovery stats meta.json into the candidate's cache key, so when only the meta.json read is refused the un-enriched session was stored under a key that looks unchanged and reuseCachedSession never re-ran the enrich hook. The session stayed without cwd until Cursor rewrote the file. The enrich hook now reports 'refused', the resumable state exposes isCacheable, and the parse cache drops the entry instead of storing it, so the next healthy scan re-parses. The index-read branch is unaffected: it never stats meta.json, so its key is honest already. * fix(ai-vault): separate the transcript's size from its cache key sizeBytes folds a content dependency's size in, so it is a cache key rather than a file length. The reader compared a transcript byte offset against it and reported it as a whole-file read offset, which for Cline handed consumers an offset past the end of the file it read. Carry the dependency's own size on FileWithMtime and subtract it in the reader. A refused sibling stat rethrew, so discovery recorded an issue and skipped the transcript, the same drop removed for the readdir and read paths. Degrade to no dependency, note the tree once, and mark the key untrustworthy. An untrustworthy key no longer costs the resume cursor: the entry is stored under an mtime no stat can produce, so unchanged is false while the resume point survives and the next scan resumes instead of re-reading the whole transcript. * test(ai-vault): pin the untrustworthy-key mechanism, not just its effect Both refusal tests asserted that a later healthy scan re-enriches, which a plain store would also satisfy once the resume cursor was preserved. Assert the cache entry directly: its mtime is the unmatchable sentinel and its resume point survives. The sentinel is exported so the tests name the contract instead of repeating -1. * refactor(ai-vault): track a session's sidecar file apart from its transcript Folding Cursor's meta.json stat into the transcript's mtime/size made one key mean two things, and every round of review found another consequence: a byte offset could not be compared against it, a refused sibling read took the transcript down with it, and an un-enriched parse cached under it looked current forever. Main already had the answer for a file the transcript key cannot see: Codex titles are refreshed at reuse time over the cached session, not folded into the key. Discovery now records the sibling as its own observation, unknown when it could not be read. A cache hit needs both the transcript key and the sidecar to match. When only the sidecar moved, Cursor re-merges it over the stored un-enriched fold result and never re-reads the transcript; Cline, which reads its sibling as part of the parse, re-parses. Merging over the fold result rather than the accumulator makes enrichment pure, so a meta.json rewritten with a new cwd replaces the old one instead of losing to it. That was unreachable while the merge used ??= on a session it had already enriched. Cline and the remote scanner move to the same field, so the fold is gone from both discovery paths. * fix(ai-vault): tell an absent sidecar from an unreadable one Three places collapsed the two. sidecarUnchanged returned true for any observed 'none' without reading the entry, so a sidecar that was deleted, or one that was unreadable last scan, both read as cache hits. Native discovery mapped every non-WSL stat failure to 'none', so an EACCES on meta.json left a session enriched from a file nobody can see, with no scan issue. Remote discovery could not tell a missing sibling from a failed stat, because statRemoteSessionFile returns null for both. 'none' is now a claim: absent-now is a hit only when it was absent before or the agent never had a sidecar, and only ENOENT/ENOTDIR reads as absent. statRemoteSessionFile grows an opt-in rethrow so its caller can distinguish the two failures it already reports. Also rewrites three comments in the cursor chat-meta reader that still described the deleted fold.
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.












