orca orchestration send --to dispatch:<id> now works whatever the worker's state: a working worker reads it now; a finished worker still open gets it as a follow-up Task in place; a closed worker is reopened in its saved conversation, or started fresh with the earlier task and report when no conversation is saved. Unread mail at worker_done becomes a follow-up automatically. An older paired server refuses send to a finished worker with an update message.
worker-start and task-create take --spec-file <path> (or - for stdin). worker-start --keep-open keeps a worker open after it finishes in one step, and worker-keep-open replaces worker-retain (kept as a hidden alias). Concise text output escapes terminal control characters in worker-written values.
* Keep CLI-removed worktree branches unless requested
* Bind branch policy checks to removal connections
* Remove redundant local default removal test row
* Negotiate worktree branch policy on the mutation connection
* Fix removal integration compiler boundaries and type imports
* Use checked annotations in paired removal fixtures
* Reset CLI integration exit status across test runtimes
* fix(orcad): materialize the app's agent-browser driver into the local slot
Replaces #34111's ORCA_AGENT_BROWSER_DRIVER hand-off, Windows profile copy and
claimed snapshot store. Packaging now references the app's own
resources/agent-browser-<target> from the orcad template manifest
(packagedBrowser, like packagedRipgrep), so the materialized slot carries it and
every local orcad launch finds it beside orcad-server.js. macOS signs the driver
before hashing; Windows reseals it after SignPath. The resolver now also tries
the musl driver name on Linux.
Refs #33846
* build(orcad): let a checkout keep template drivers with ORCAD_OMIT_AGENT_BROWSER=0
* test: remove 37 low-value cases and 3 suites
* test: delete ten more renderer wiring and copy suites
* test: drop deleted suite from its contract entry point
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* fix(orcad): find the app's bundled agent-browser driver for orca serve
Desktop orcad templates omit the driver for size, so orcad materialized from an
installed app reported driver_missing with ORCA_BROWSER_EXECUTABLE set. orca serve
now hands orcad the app's resources copy via ORCA_AGENT_BROWSER_DRIVER; the
slot's own driver still wins for standalone artifacts.
Fixes#33846
* test(serve): expect the app's agent-browser driver handed to orcad
* fix(serve): run a profile copy of the driver on Windows so app updates stay replaceable
* fix(serve): never lend the installed Windows driver when staging fails
* fix(serve): keep another launch's in-progress driver copy
* refactor(serve): share claimed snapshot publication for the Windows driver copy
`orca terminal send --text … --enter` now uses the bracketed paste + Enter agent-prompt path for every recognized agent (not just Claude/Codex), so Pi's #/@ completion and Grok's y/n trust question no longer swallow or answer typed prompts. Agents whose process names collide with ordinary tools (orca, acli, cn, vibe) still need title/hook evidence. A send into a permission/trust menu is refused with a hint to send --text and --enter separately.
Refs #20695
Refs #18399
* fix(orcad): add --reclaim-stale-lock for a lock no start may judge
On a Linux host with no machine id whose ~/.orca is on a network or FUSE
home, a lock left by the host's own previous boot names a PID that is
evidence only on the machine that issued it, so every start after a
reboot refused it, and the only way out was deleting orcad.lock by hand.
Starts still refuse it: losing contact is not proof of exit, and a home
other machines may share must never be taken over automatically. Now:
- `orca serve --reclaim-stale-lock` (and `orcad --reclaim-stale-lock`)
checks that the lock was written on this host, in an earlier boot, at
least 2 minutes ago, and that nothing answers at its holder's
endpoints. It prints the pid, host, boot and age and warns about other
machines sharing the home, and clears the lock only with --yes,
through the normal race-safe reclaim of that exact record.
- Lock records now name their host, as evidence for that command.
- The refusal has its own code, orcad_instance_lock_holder_unverifiable,
names the command, and exits 3, so `orca serve --supervise` waits on it
instead of giving up, and the desktop refuses on it like a held lock.
- orcad advertises orcad.stale-lock-reclaim.v1 so a client can offer
the command on hosts that have it.
Fixes#32177
Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>
* fix(orcad): let --reclaim-stale-lock clear a reclaimer cut short by the reboot, and probe IPv6
A reclaimer from the same earlier boot on this host that the reboot cut
short left a recovery owner the command could never judge, so --yes
always failed. The confirmation now covers such an owner too, and only
one from this host's earlier boot. An IPv6 WebSocket endpoint is probed
by its bare address.
Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>
* fix(cli): ship the stale-lock reclaim module with packaged builds, and refuse an unreachable lock
The CLI loads main modules from out/main after the main build replaces
it, so the reclaim module becomes a stable plain-Node entry and is
unpacked from the asar. A lock that cannot be stat'ed for any reason other
than absence (a permission error, say) is now a refusal, not "nothing to
reclaim".
Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>
* fix(orcad): make --reclaim-stale-lock name the real data root and report a status
- The refusal prints a runnable command for the data root it refused:
an orcad names its own runtime and entry plus `--data-root <dir>`,
since managed hosts have neither `orca` nor `orcad` on PATH and their
data root is not the default. The hint is Linux-only.
- Both entry points take `--data-root` and `--json` and report one of
none, eligible, refused or reclaimed, each with its own exit code
(4, 2, 1, 0), so a missing lock never reads as a recovery.
- The evidence says nothing answers on this machine, and that the host
name and boot checks cannot rule out another machine with the same
name; --yes stays the gate.
- wss endpoints default to port 443, a same-boot branch that could never
run is gone, and the supervisor's standby line points at the command.
Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>
* fix(orcad): point the eligible message at the same command plus --yes
Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>
* fix(orcad): refuse an empty --data-root, and name the right program in reclaim hints
- `--data-root` with no usable value (a trailing flag, an empty or blank
string) prints usage and exits 1 on both entry points, never falling
back to the default profile.
- A refusal raised while the reclaim command itself runs names
`orca serve --reclaim-stale-lock`, not the binary the CLI runs under;
an orcad still names its own runtime and entry.
- The supervisor's standby hint includes this profile's --data-root.
- The unreadable-lock test is skipped when running as root.
Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>
* test(cli): reuse the existing client stub for the --data-root refusal test
Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>
---------
Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>
* fix(cli): negotiate agent launches with paired runtimes
* refactor(cli): share paired client capability list across request paths
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix(orcad): voice dictation on headless orca serve hosts
Mobile dictation against an orca serve host answered speech_unavailable
since serve moved to orcad: orcad never installed the speech service
factories, and the model manager and download imported Electron.
The model download now takes a host request opener (Chromium net on the
desktop, Node https on orcad, with redirects mapped to the same event),
the STT worker ships in the orcad slot, and orca serve hands orcad the
installed app's on-device recognizer. A host without a recognizer (an
SSH or WSL managed slot) lists only cloud models and sends a phone that
picked an on-device model to the setup sheet. Mobile names the fix when
an older server still has no speech at all.
The parity test now requires both roots to install speech.
Reported-by: delorean2020 (@delorean2020)
* fix(orcad): retry Node download drops, stage the recognizer on Windows, review fixes
- Node https errors (ECONNRESET, ETIMEDOUT, ...) are retryable, so an
interrupted orcad model download resumes (Codex P2).
- On Windows orca serve hands orcad a per-version copy of the recognizer
in the profile, so a loaded addon never locks the app's files against
an update (Opus).
- Drop a recognizer candidate path that could never match; one SAFETY
helper per test file instead of repeated casts; comments.
* fix(orcad): cloud speech is ready only with a key this host can decrypt; prune superseded recognizer copies
Codex round 2: a desktop-sealed OpenAI key exists on disk but orcad's
secret store cannot open it, so the catalog reported cloud models ready
and dictation failed at the end. orcad's model manager now proves the
key decrypts. The desktop keeps its existence check (no keychain prompt).
* fix(orcad): never prune staged recognizers before the profile lock; copy linked packages as real files
Codex round 3. ref-oss check: versioned copies are cleaned only by the
process that owns them, so the pre-lock prune is dropped rather than
patched.
* fix(orcad): remove a partial recognizer copy when staging fails (Codex round 4)
* fix(speech): never hand out another store's ciphertext as the OpenAI key (Codex round 5)
* refactor(orcad): no recognizer copy on Windows; Windows orcad keeps cloud dictation only
Design check after review round 5: every remaining finding came from the
Windows copy that dodges addon file locks. Dropping it shrinks the
surface to a path lookup; Linux and macOS serve keep on-device models.
* fix(speech): name the fix when an old server refuses a mic tap; always set the recognizer env; retry ENOTFOUND (final Opus review)
* fix(managed-server): probe install completeness relative to the slot dir
CI: shipping stt-worker.js added one more absolute slot path to the
install-complete probe, pushing it further past the exec-size budget.
Testing each artifact relative to the slot (inside a subshell cd) cuts
the probe from ~12.2 KB to ~3.6 KB, so it drops off the over-budget
list instead of raising a ceiling.
* fix(mobile): pin host.platform's read to a platform type, independent of which zod copy resolves
With mobile's own dependencies installed (which CI does for any PR that
touches mobile/), the e2e program resolves two zod copies, and the
salvaged platform arm from src/shared inferred as {}. The schema now
narrows through an explicit platform guard.
* fix(mobile): keep the host.platform read line intact for its RPC mutant; narrow in a second step
Mobile verify: the recorded-RPC mutant anchors on the read line, which
the previous guard rewrote, so the mutant could not apply.
* refactor(cli): app-executable resolution in its own module; drop the platform guard now zod is aligned
launch.ts went one line past max-lines with main; resolveAppRoot,
resolveForegroundOrcaExecutable, getExecutableAppArgs and
stripElectronRunAsNode move to orca-app-executable.ts (re-exported, so
callers are unchanged). With mobile and root on one zod (#31260), the
host.platform schema no longer needs the extra narrowing step.
* refactor(cli): import app-executable helpers from their module; no re-exports from launch.ts
* refactor(serve): move the update handoff file reads and writes into their own module
Move-only: the supervisor module is at its line budget, and the handoff file
IO is a separate concern from the child supervision loop.
Claude-Session: serve-supervisor-reexec
* fix(serve): restart the macOS serve supervisor from the updated app
After a remote update on macOS, the node-mode supervisor kept running the
old Orca executable, which the installer moved aside and deleted. Under a
LaunchAgent the system charges every descendant's privacy permissions
(Accessibility, Screen Recording, files) to that supervisor, so once its
path no longer resolved, screen capture and computer use failed for the
whole served tree while the permission check still reported them granted.
Once the new bundle is verified on disk and no stop is pending, the
supervisor now re-runs itself in place (same pid) from the installed
executable instead of spawning the replacement itself. The handoff stays
install-requested, so startup's existing resume path spawns, verifies and
completes it. A --supervise supervisor that runs from its copy outside the
app restarts through the app's own CLI entry, so it copies the new release
aside again rather than mixing old scripts with the new executable. If the
re-run fails it logs and starts the replacement as before. Only applies
when serving our own executable on macOS; Linux, Windows, orcad and
overridden executables are unchanged.
Claude-Session: serve-supervisor-reexec
* fix(serve): restart a supervisor whose executable the install replaced
Three review fixes on top of the update restart:
- Decide the restart by file identity instead of which path observed the
install. The supervisor records the device and inode of its executable
at start; after any install wait (including resuming an interrupted
update at startup) it restarts only if the installed executable is a
different file. A supervisor that started on the old app before the swap
now restarts too, and the process after a restart never restarts again.
- A relocated --supervise supervisor releases its marker on the copy it is
leaving, so the next relocation can delete that copy. Before, every
update kept one more full copy of the app's scripts for the life of the
service.
- A failed process.execve aborts the process natively rather than
throwing, so the fallback never ran. The restart now checks first that
the executable can run and the script entry exists, and otherwise logs
and starts the replacement directly as before.
Claude-Session: serve-supervisor-reexec
* fix(serve): restart a parked serve service whose app a desktop install replaced
When the desktop app's Update & Restart parks an `orca serve --supervise`
service for another profile, it writes no update handoff for that profile.
After the install the service's outer loop just starts its next server, so
neither update restart point ran and the supervisor kept the deleted
executable.
Every server start now runs the same identity check right after the
park/stop gate and before spawning, so a supervisor whose executable was
replaced restarts in place first. The check is a no-op when the executable
is unchanged, so first starts and the process after a restart do not loop.
The test drives the real park and commit functions, so the CLI typecheck
project now includes that updater module (it imports only shared code).
Claude-Session: serve-supervisor-reexec
* refactor(serve): check for a replaced app in the one place every server start passes
The restart check ran at three call sites and was threaded through the update
loop as a hook, and each review round found another start path it missed. Every
path (first start, resumed update, after an install, after a park or crash)
spawns its server through the function spawnElectronServe builds, after its stop
and park gates, so the check now lives in that spawn and nothing else carries it.
The restart moves to its own module, the hook and the handoff-file move it
forced are gone, and the tests drive serveOrcaApp instead of the hook.
Claude-Session: serve-supervisor-reexec
* perf(serve): copy the supervisor's code aside with one cp clone on macOS
orca serve --supervise copies the app's unpacked code and node_modules out
of the app before re-running itself from the copy. Node's fs.cp clones those
~6,000 files one at a time, which took 8-15 s in the foreground and around
35 s under the service manager's throttled I/O. One /bin/cp -cR per tree
does the same copy in about 1.5 s.
The clone-then-plain-copy fallback the terminal host copy already used now
lives in src/shared/macos-tree-clone.ts, and both copies call it. Linux keeps
fs.cp with reflink cloning, and Windows does not support --supervise.
Claude-Session: serve-snapshot-clone
* fix(serve): let the macOS tree copy run without a time limit and throw on failure
The shared cp helper ran under runProcess's 30 s default time limit, which the old fs.cp copy never had. At background I/O priority a large tree can outlast it, so the supervisor would quietly give up and run from the app itself. The helper now runs cp with no limit; the terminal helper copy stays bounded by its deadline signal.
The helper moves to src/main beside the asar-safe rm and uses it directly, so it no longer takes an rm option. It throws instead of returning a boolean, so copyBundle is gone. The relocation reads process.platform instead of a platform dependency, and its copy test now runs on every platform but Windows.
Claude-Session: serve-snapshot-clone
* fix(build): keep the macOS tree copy module where the packaged CLI loads it
The serve supervisor's relocation code in the CLI now imports the macOS
tree copy module from the main-process sources. The CLI is compiled file by
file, but the later main-process build replaces out/main and keeps only the
files it is told to build as entries, so the packaged CLI failed at load
with "Cannot find module '../../main/macos-tree-clone'" on every platform,
and the CLI runtime verifier rejected the build.
Register the module as a CLI main entry, like the other main modules the
CLI imports, and unpack it from the app archive. Its asar-safe removal
dependency is bundled into a shared main chunk, which is already unpacked.
The build output contract test now covers both.
Claude-Session: serve-snapshot-clone
* fix(build): unpack the asar-safe rm the CLI's tree clone imports
The compiled CLI layout keeps out/main/macos-tree-clone.js requiring
./asar-transparent-fs directly, and that file was still packed, so the
packaged CLI failed to load (caught by packaged-cli-assets in CI). The
bundled build only passed because the main build inlines it into an
unpacked chunk.
Claude-Session: serve-snapshot-clone
* fix(pairing): pairing a saved server again refreshes its entry instead of saving it twice
* fix(pairing): only Settings refreshes a saved server in place; other add paths refuse the duplicate
* fix(pairing): a same-key refresh keeps the server's workspaces and tabs; raw add refuses; match any endpoint key
* fix(pairing): refuse a code for a hidden saved server; one choke point publishes verified status only for the pairing still saved
* refactor(pairing): verified-pairing publish in its own module; an unreadable store skips the stamp instead of failing a saved pairing
* fix(pairing): match a host key however its Base64 is spelled
* fix(pairing): a refresh keeps the saved key spelling so the host fingerprint stays put
* fix(updater): a live orcad's browser helper pauses for macOS desktop installs
orcad starts its browser helper from the installed Orca.app, so ShipIt waits
for it, and the installer refused it as an unregistered blocker with no
supported way out. orcad now registers the helper in the existing serve
install park: it stops the helper for the install and starts it again from
the new bundle once the hold ends. The refusal card for a helper an older
orcad still runs says what to do instead of "stop the server".
Reported-by: @routesmith
* fix(orcad): retry a failed helper restart, hold through a mid-swap bundle, catalog the guidance
* fix(orcad): a new park cancels a slow helper restart; reset backoff per install
* refactor: de-barrel remote-runtime-client and runtime-terminal-stream (D2)
importers name the defining modules; the two vi.mock factories that named a re-export move to
the defining module (pty-id spy) or drop the dead key (RemoteRuntimeClientError)
* refactor(renderer): de-barrel runtime-terminal-inspection (D3)
importers name runtime-terminal-input-recording, runtime-terminal-verified-input and
shared/remote-runtime-pty-id; 22 vi.mock factories move the re-exported keys to those modules
(pty-id keeps its other exports via importOriginal)
* test: drop the duplicated verified-input mock (Opus P3)
* fix(serve): orca serve on orcad serves the browser client and answers plain HTTP
orcad, the default local orca serve host since the compatibility launcher,
was never handed the app's web client directory, so its HTTP server had no
request listener: every GET hung and no Web client URL was printed (#30100).
The CLI now passes the app's unpacked out/web to orcad via --web-client-root,
a server without a web client answers plain HTTP with 404 instead of hanging,
and packaging fails when the unpacked web client or a file it loads is missing.
* test(serve): pin orca serve handing orcad the app's web client root
* fix(terminal): log a failed pane reveal instead of rethrowing it
The bridge already replies false when the reveal throws, so the rethrow only
surfaced as an uncaught renderer error after the caller had its answer.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(terminal): refuse terminal.reveal from paired clients
It moves this host's window. terminal.focus lets a paired client do that only
by asking for host navigation, and the callers this method serves (the CLI and
companion apps on the same machine) never arrive as paired clients.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat(terminal): show a paired server's terminal in this desktop on terminal switch
A paired `orca serve` host has no window, so `orca terminal switch --environment <name>`
resolved the handle there, answered `navigated: false`, and the desktop stayed put (#24688).
Add `terminal.reveal`, a desktop runtime method that names a pane by worktree, tab and leaf, as
the owning host knows them, and runs the Agent Dashboard's click-to-focus sequence before raising
the window. When a paired server cannot navigate, `terminal switch` asks the desktop on this
machine to reveal the terminal instead.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test(e2e): terminal switch on a paired server moves the desktop on this machine
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test: update terminal manifest count after main and assert the revealed pane renders
Main added a terminal method since this branch was cut, so the characterization
count is 39 with terminal.reveal. The e2e now proves the target pane is visible
in the DOM, not only selected in the store.
Co-authored-by: Velm14 <Velm14@users.noreply.github.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Velm14 <Velm14@users.noreply.github.com>
* fix(serve): reap orphaned processes when orca serve is PID 1
* fix(serve): keep AppImage, eval launches and exit status intact under the PID 1 reaper
* fix(serve): pin headless Linux serve to X11 so an inherited Wayland session can't abort startup
Fixes#25400
* fix(serve): put --ozone-platform=x11 on the launch argv for Linux headless serve
Chromium caches its Ozone platform in PreSandboxStartup, before main JS runs,
so app.commandLine changes cannot move it off Wayland. Pin X11 on the CLI and
orcad serve spawns, and re-exec in-process serve (same pid) when Electron
already resolved Wayland.
* fix(quick-commands): save agent prompts whole, bounded by what a chat message carries
Agent quick-command prompts were silently cut to 6,000 characters on save:
the editor accepted a long paste, settings normalization trimmed it, and
reopening showed the shortened copy. The 6,000 was a Windows launch speed
threshold, not a limit on prompts.
- Storage no longer trims. A save is refused, never trimmed, when a prompt's
chat text block is over 256 KiB of JSON (measured on its real escaped size,
the measure the chat send schema applies) or the list is too large for one
paired reply. Host and desktop store both check before writing.
- The editor shows the size against the limit and blocks Save past it, and
stays open with the text when the host refuses the save.
- terminal.quick-commands.long-prompts.v1: hosts withhold prompts over 6,000
characters from clients that do not advertise it (older clients reject a
whole list holding one); editors keep an older host's 6,000 cap.
- The phone editor no longer drops pasted text past 6,000 via maxLength.
Launch delivery of long prompts is left to the launch-prompt series
(#24257, #23962, #24080); this branch's earlier paste-after-ready delivery
duplicated it and is removed.
* fix(quick-commands): drop a stale duplicate doc comment
* fix(quick-commands): advertise long prompts to this build's own host too
* fix(quick-commands): keep submitted editor immutable until save settles
* test(quick-commands): keep storage and save lifecycle coverage focused
* fix(quick-commands): keep portalled choices within pending form ownership
* Preserve retained credentials when settings validation refuses a save
---------
Co-authored-by: OrcaWin <alpha-eng@stably.ai>
* fix: refresh host environments before structured agent command resolution
* fix: ship the owning server CLI for managed remote agents
* test: include the shipped CLI in remote install fixtures
* Bind packaged agent CLI calls to their execution host
* Keep owning-server CLI binding scoped to managed agents
* Serve the last shell environment to chat starts and refresh it in the background
Only the first chat start waits for the login shell; later starts get the last
snapshot and refresh it in the background once it is 10 s old. On Windows hosts
without the registry addon (managed server slots), the saved PATH is reread
with reg.exe. The Windows server CLI launcher stager now checks the target
architecture, and the CLI help says ambient selectors are ignored inside a
managed server.
* Keep the last good shell environment when a refresh fails, and load the Windows profile once
A login-shell capture now says whether the shell produced the env or Orca fell back
to its own, and only a real capture replaces the snapshot. Windows loads the
PowerShell profile once per process and refreshes only the saved PATH. The reg.exe
fallback queries the whole key so a missing user Path no longer drops the saved PATH.
The first capture starts when the resolvers are created.
* Keep the last good login-shell env for other readers while a forced refresh runs
* Mark the Claude launch resolver as a reviewed router-aware module
It only types the chat command resolver it injects; main's launch-routing guard (#26801) matched the name.
* Keep a settled login-shell env for readers during a refresh and share the first run
- Any settled cached entry, a fallback included, now serves other readers while a forced capture
runs; only a real capture replaces it.
- Native chat's first capture shares a run a startup reader already began, so the login shell
runs once per process at startup on macOS/Linux.
- Type the injected Claude resolver without naming resolveClaudeCommand.
- Cover the runtime's Claude command options, the owning-host --host check, and the PTY flag drop.
* Drop an inherited owning-host flag from daemon terminal panes
The persistent terminal daemon merges its own environment into every pane, so an inherited
ORCA_CLI_OWNING_HOST reached desktop panes even though main strips it from the spawn env.
Drop it with the other per-launch CLI variables.
* Route agents to their own server through ordinary launch defaults; start the first chat after an install
The native-chat agent environment now sets the owning server's ORCA_USER_DATA_PATH and
empty ORCA_ENVIRONMENT / ORCA_PAIRING_CODE / ORCA_REMOTE_PAIRING, which the CLI already
reads as unset; empty rather than deleted, because the spawn merges the launch overlay
over the server's own env. The ORCA_CLI_OWNING_HOST flag, the CLI's skip of ambient
selectors, the launchers' export of it, the terminal clean-up, the help-text exception
and the WSL launcher guard are gone, so a person's terminal follows their own settings
as on main.
A launch that finds no program in the shell snapshot waits for one shared refresh (at
most one shell timeout) and resolves again; create support rereads the shell once
before refusing. On Windows a failed PowerShell profile load is retried on the next
refresh instead of being kept until restart.
* One shared shell reread when an agent's program is missing
The pre-create check for OpenCode, OMP and Pi forced its own login-shell
capture on every refusal: not shared with launches, one per concurrent
check, triggered by an installed-but-too-old version too, and an older
capture finishing last could overwrite a newer cached environment. On
Windows, every launch miss also reran a failed PowerShell profile load,
bypassing the 10-second limit.
- One saved shell environment per host: chat launches, their misses, the
pre-create check and AI commit messages read it and share one refresh.
- The pre-create check resolves the agent's program the way a launch does,
so only a missing program waits for the refresh; registrations declare
how to resolve their launch and check its version.
- A forced login-shell capture joins one already running for the same
shell, so captures cannot land out of order.
- On Windows, a miss within 10 s of a failed PowerShell load rereads only
the saved PATH.
* One owner for the cached shell environment
Review found two more races in the reread-on-miss machinery, both from the
host wrapper keeping its own snapshot beside the login-shell cache:
- A launch that found no program could join a reread that started before the
agent was installed, and spend its one retry on the old PATH.
- On Windows, saved-PATH rereads renewed the snapshot's age, so a failed
PowerShell profile was never retried while launches kept missing.
The login-shell cache is now the only owner of the captured environment, the
run in progress and which result wins (the newest run started; a fallback
never replaces a captured env). It takes three options instead of `force`:
- startedAfter: a lookup that found nothing waits for a run that started no
earlier than the lookup did, joining one if it has, else starting one.
- refreshAfterMs: serve the last result and rerun in the background once the
last attempt is older.
- keepCaptured: a captured env is kept for good; only a fallback is rerun
(Windows' PowerShell profile).
The host wrapper keeps only policy: the 10 s interval, on Windows the saved
PATH laid over the profile env on every read (a miss forces a fresh registry
read), and the settings overlays. Its snapshot, pending promise, staleness and
Windows "loaded" bookkeeping are gone. A lookup's start time travels with the
miss (resolveAgainAfterRefreshOnMissingProgram passes it).
* Read an injected shell environment once per runtime, as main does
The previous commit read an injected environment on every acquisition, which
broke main's 'caches shell exports but re-reads configured overrides for a
resume' contract. It is read once when the resolvers are made again.
* Model lists reread the environment on a missing program, as launches do
Live QA: the first new chat after installing Claude showed "Claude wasn't
found on the computer running this chat". The chat launch itself reread the
login shell and started the new binary, but Claude's model-list check ran
first, with the environment read before the install, and never retried:
catalog probes were deliberately built without the miss refresh.
The retry now lives in each agent's one shared lookup of its binary and
environment, which a launch and a model listing both call:
resolveCodexStructuredInvocation, resolveAcpLaunchInvocation, a new shared
resolvePiRpcInvocation, and resolveClaudeChildEnvSources (which already had
it). Each catalog probe is given the runtime's refreshAfterMissingProgram,
and the catalog context no longer strips it, so a listing and a launch
reread the same single-owner environment the same way.
* Pass optional launch lookups through directly instead of conditional spreads
* Read the host shell env through resolveLoginShellEnvironment; drop the extra wait cap
CI on the merge with main failed main's new codex-workspace-relative-path
test (#26684), which mocks the login-shell module with only
resolveLoginShellEnvironment: the wrapper called captureLoginShellEnvironment
and imported the spawn-timeout constant. The wrapper only needs the env, so
it reads through resolveLoginShellEnvironment again, as on main.
The miss wait no longer races its own 5 s timer: the shell run it waits for
is already killed at the spawn timeout, so the second cap was a guard over
the same bound. The constant goes back to private, as on main.
* Test that a hung login shell holds a launch miss for at most one shell timeout
The bound lives in the one owner: the shell run is killed at the spawn
timeout (5 s) and settles to a fallback, which never replaces a captured
snapshot. A mutation raising that timeout fails this test.
* A program found through a relative PATH entry is not missing
Main's #26684 runs Codex through a workspace-relative PATH entry (`bin`,
`node_modules/.bin`). The lookup searches such an entry against Orca's own
directory and returns the bare name; the spawn then finds the program in the
chat's directory. Counting that bare name as missing made every such launch
wait for a login-shell reread.
The retry now fires only when the lookup truly found nothing: a bare name back
from a PATH whose entries are all absolute. Each agent's one lookup tells it
which PATH it searched.
* Count a relative PATH entry as found only when it holds the program; Windows miss waits for a saved PATH read started after it looked
A bare name with a relative PATH entry is checked from the launch's working
directory, so an agent in neither entry still gets its one reread. On Windows the
miss no longer joins a registry read that began before the lookup.
* fix(linux): a supervised AppImage serve restarts from a replaced AppImage
Swapping the AppImage file never reached a supervised serve: it runs the copy it unpacked, and
even a full service restart went back through the stable launcher to that same copy. The
extraction cache now records which AppImage each namespace came from. Before each start the
supervisor compares that file's identity with the build it runs; once a settled new file is
there and runs on this host, it execs the new AppImage with its own arguments (same pid), whose
serve unpacks the new build and re-points the stable launcher.
Fixes BUG-4 from the real-host Linux test run.
* fix(linux): try a replaced AppImage once, and let it past an old build's give-up
- A give-up recorded against the old build no longer stops the supervisor before it checks
for a replaced AppImage.
- The supervisor records the AppImage identity it is about to exec in the profile. If that
build dies before its own crash brake, later starts of the old build keep running it
instead of exec'ing the same broken file until systemd's start limit disables the service.
A supervisor running that build, a further replacement, or --force clears the record.
* test(cli): compare the namespace listing order-independently
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
Since a direct `Orca.AppImage serve --supervise` now runs the CLI supervisor (#28227), the
supervisor's relocation re-exec ran from the AppImage mount. An exec from node mode closes every
inherited descriptor, including the AppImage runtime's keepalive pipe, so the runtime unmounted
the AppImage during the exec and the new image failed with 'libffmpeg.so: cannot open shared
object file' (exit 127) on every start. Relocation exists so an update cannot swap the app under
a running supervisor; an AppImage's mount cannot change during the run, so it stays in place.
Co-authored-by: m4air <m4air@Mac.localdomain>
* fix(linux): run a direct AppImage serve through the CLI so it keeps its profile and supervisor
A direct `Orca.AppImage serve` stayed in the Electron process and skipped the CLI's serve
front end, so it ignored ORCA_USER_DATA_PATH and silently dropped --supervise. It now execs
into the bundled CLI (same pid, so service managers and signals still reach it), translating a
leading --user-data-dir into ORCA_USER_DATA_PATH. Also lists terminal-daemon and reference as
CLI commands so a direct binary launch of them reaches the CLI.
Fixes BUG-1, BUG-3 and BUG-10 from the real-host Linux test run.
* fix(linux): drop desktop switches anywhere in a direct serve launch
* fix(linux): keep a direct serve's --no-sandbox for the Electron server the CLI starts
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
- A headless serve with no X display told the user to run `orca-ide serve`, which they just
ran. Serve now gets its own message: it needs an X display, Wayland alone is not enough, and
which X virtual framebuffer package to install (now including Arch's xorg-server-xvfb).
- The supervisor's restart line read 'Orca serve Orca serve exited via SIGKILL.; restarting'.
- The supervisor and 'serve service status' gave different advice after giving up; both now
give the same, correct advice.
Fixes BUG-2 and BUG-9 from the real-host Linux test run.
Co-authored-by: m4air <m4air@Mac.localdomain>
* feat(serve): run the --supervise supervisor from a copy outside the app
* fix(serve): record supervisor copy users beside the copies so a cleanup never races a fresh copy
* fix(serve): remove a partial supervisor copy when copying fails
* test(serve): skip the unreadable-copy case where mode 000 does not block reads
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
* fix(updater): clear a cancelled macOS install request before staging the next update
* fix(updater): match only Orca's ShipIt and recheck owners right before clearing
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
* fix(serve): explain what to run when an SSH host's orca cannot start or install a server
* fix(serve): keep an app launcher on PATH usable from a server terminal; share the app-CLI check
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
* feat(serve): orca serve service install|status|uninstall for launchd and systemd user units
* fix(serve): redact pairing secrets in the supervisor instead of an inherited env, validate the port and launcher at install
* fix(serve): keep the redaction module off a direct child_process import
* fix(serve): redact the JSON readiness offer and survive a closed log pipe
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
* feat(updater): auto-park registered serve supervisors during a desktop install
* test(serve): drop a type assertion in the bundle park test
* fix(updater): stop any server started during an install hold and re-list blockers right before commit
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
* feat(serve): supervised mode for orca serve --supervise
Restart the server with jittered backoff instead of exiting when it dies,
with a crash-loop brake whose exhaustion is durable per profile and build,
an install hold while the executable is missing or ShipIt replaces the
bundle, and standby while another Orca serves the profile. Only an
explicit --supervise turns it on; Windows refuses it for now.
* fix(serve): let a supervised orcad serve wait for a live peer instead of counting it
* fix(serve): gate update replacements on the install hold, honor stop during the bundle wait, keep a give-up while the executable is missing
* fix(serve): hand a removed executable back to the service manager and key the brake on the app archive
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
Desktop, CLI, web and runtime transports now take a PairedRuntimeConnection
(endpoint, pinned host key, credential) instead of a PairingOffer. Offers stay on
the pairing input path (codes, QR, links, environment add) and are converted with
pairedRuntimeConnectionFromOffer. The credential is token-only today; request
envelopes still carry the same deviceToken, so the wire is unchanged.
This lets device-key and ticket credentials replace the token later without
touching every connect call site again.
Co-authored-by: m4air <m4air@Mac.localdomain>
On macOS a second Electron main aborts inside _RegisterApplication
before any Orca JavaScript can apply the one-Orca-per-profile rule, so
supervisors and agents retrying 'orca serve' loop on SIGABRT (#14541).
The CLI now checks the profile before it execs Electron serve. A runtime
that answers, or whose socket accepts, is live; one whose socket neither
accepts nor refuses, or that refuses this CLI's credentials, is
unverifiable; both refuse with exit code 3, the single-instance code
systemd units already key off. Only a definitive refusal (ENOENT or
ECONNREFUSED) lets it start. A fresh profile keeps the old synchronous
launch. A macOS startup abort is remembered per environment, so a later
separate 'orca serve' from the same launchd session refuses for 30
minutes with exit 78 instead of aborting again; --force overrides, and
a serve that reaches readiness clears it.
Adapted from #14768; the Launch Services diagnostic wording and the
exit code 3 contract come from #14542.
Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: thiagomsoares <5190162+thiagomsoares@users.noreply.github.com>
* fix(serve): keep the isolated profile off the login keychain
The E2E/pairing runtime hands the app a fresh HOME, so there is no login
keychain for OSCrypt to read. On the serve path the secret-protection report
runs inline before readiness and probes safeStorage on the main thread, where
Chromium's keychain lookup creates the item and waits on an authorization
reply that never arrives, so `orca serve` never advertises (#25453).
The sidecar already launches with --use-mock-keychain for exactly this
profile; share that decision and give the foreground serve process the same
switches.
* test(serve): read spawn args without type assertions
Co-authored-by: Paolo <paovalletta@hotmail.it>
* fix(serve): only an isolated userData profile drops the keychain
The sidecar always runs on a fresh profile, so either E2E variable is
enough there. The foreground serve process opens the selected profile's
sealed secrets, and a HOME override alone still selects the user's real
profile, so it now needs ORCA_E2E_USER_DATA_DIR.
Co-authored-by: Paolo <paovalletta@hotmail.it>
---------
Co-authored-by: Paolo <paovalletta@hotmail.it>
Co-authored-by: m4air <m4air@Mac.localdomain>
orcad now checks its flags with the same validator Electron serve and the
CLI use, so contradictory pairing flags fail with the same message instead
of one silently winning. The CLI launcher drops its duplicate recipe check
(the CLI handler already ran the shared validator).
Removes the daemon-init and cli/runtime/index re-export barrels and the
parsePtySessionId / OrcadInstanceLockError re-exports; callers and mocks
import the defining modules.
Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(cli): extract the serve supervisor's after-exit decision into a pure policy
* refactor(cli): type the exit signal without a child_process import
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
Nothing mints an SSH-bridge credential since the relay client was removed
(#27214), so the 'ssh-bridge' caller kind, its host binders, result filter,
the CLI bridge-mode policy and the ORCA_SSH_BRIDGE_CREDENTIAL env read could
never run. The allowRemoteCliControl target field stays for older peers.
Co-authored-by: m4air <m4air@Mac.localdomain>
A CLI launched from an Orca terminal now sends, with each request, the
runtime it verified before sending (expectedRuntimeSource: the launching
profile's installation id plus the verified runtime id). The host checks
it before parsing params or running any handler and refuses a mismatch
with runtime_source_mismatch, so a runtime that took over the endpoint
between the CLI's check and its send cannot apply the request.
Optional field: an old CLI sends none and an old host ignores it (the
CLI's pre-send probe stays the fence there). Hosts advertise
runtime-source-fence.v1.
Co-authored-by: m4air <m4air@Mac.localdomain>
* fix(cli): check the runtime source before sending
A terminal Orca launches now carries which Orca launched it
(ORCA_RUNTIME_SOURCE_ID = the profile's installation id,
ORCA_RUNTIME_SOURCE_INCARNATION = the launching runtime id). Before a
request is sent, the CLI compares that with the runtime the profile's
runtime record names. Same runtime: sent as before. Different runtime:
a read-only status.get asks which profile it serves; the same profile (a
restart or update) is accepted, anything else is refused before the
request reaches it.
The vars cross into WSL through WSLENV. The SSH bridge stamps the
bridging runtime itself rather than trusting the remote env. The
existing orchestration compatibility stamp is untouched.
* fix(orcad): drop an inherited runtime source at startup
* fix(cli): an explicit switch to another profile is not fenced
The stamp also carries the launching runtime's userData path. A CLI that
dials a different profile than the one that launched its terminal, and
not the platform default an unpinned terminal falls back to, was pointed
there on purpose (orca-dev from a packaged Orca's terminal), so the
fence does not apply.
* refactor(cli): reuse the shared userData path comparison
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
Extract subprocess execution and process contracts into @orca/process-host with explicit public entry points, independent checks, and one Node 18 compatible CommonJS runtime. Migrate application consumers and integrate package builds, dependency selection, import enforcement, and runtime resource staging.
Preserve daemon startup ownership and profile isolation, and qualify development rebuild recovery and concurrent output publication. Remove unnecessary aliases, test forwarding exports, and a duplicate output scan.
Validation: full typechecks, changed-code quality, package and artifact checks, native Windows builds/types plus 306 runtime tests, and independent native reviews.
Matched full E2E controls on original main and unchanged reviewed head both pass all 17 applicable tests (nine skips). Earlier intermittent E2E failures are documented in the PR; no assertions were weakened. The final combined-tree audit preserves both branches with no import-boundary violations.