Commit Graph
86 Commits
Author SHA1 Message Date
Brennan Benson 24a2accc3c fix(hibernation): reap restored subagent rows with no live agent process (#11219)
* fix(hibernation): reap restored subagent rows with no live agent process

A pane whose Claude session had a subagent in flight can be locked out of
agent hibernation for good. A PTY that dies while Orca is down never runs
the teardown that clears pane state, so hydrate rebuilds a subagent roster
that nothing can retire: the existing reap needs the parent to emit a
complete `background_tasks` inventory, and a parent that went idle before
the restart never emits one. The restored row keeps gating the pane
'working', and hibernation only accepts 'done'.

Observed locally: six panes parked at SubagentStop in state 'working' for
17 to 145 hours, each still holding a working child row.

Adds a second reap path. Hydrate seeds are marked `restoredFromSnapshot`,
cleared by any live lifecycle event or an id-exact running inventory entry.
One post-restore sweep drops the rows still unconfirmed when the pane's PTY
is absent from the live local inventory, then re-derives the child-gated
'working' to 'done'.

The scan is local-only by construction: panes with a relay connection id are
skipped and SSH-scoped PTY ids resolve as live, since a remote agent runs on
the far host and could never appear in a local listing. An unreadable
inventory is not evidence that anything exited, so it is a no-op. Panes that
have reported to this runtime are left alone.

`stateStartedAt` and `stateHistory` are untouched, so a draft typed while
the pane was working still blocks hibernation.

* fix(hibernation): prove local ownership before restored reap

* fix(hibernation): require authoritative restored PTY absence

* fix(hibernation): probe restored PTY liveness authoritatively

* fix(hibernation): restart idle window after restored reap

* fix(hibernation): type restored reconciliation timing

* fix(hibernation): respect worktree host ownership

* fix(hibernation): preserve same-id restored PTY rebinds

* fix(hibernation): fence batched restored PTY probes
2026-07-29 16:30:44 -07:00
OrcaWinandOrcaWin 363e478909 fix(orchestration): preserve active workers across updates (#11271)
* fix(orchestration): preserve active workers across updates

* test(ssh): model absent legacy adoption

* test(orchestration): align compatibility contracts

* fix(windows): escape updater PowerShell booleans

* fix(windows): restore stock uninstall process check

* fix(orchestration): keep recovery off renderer startup barrier

* fix(orchestration): harden legacy recovery migration

* fix(orchestration): close recovery review gaps

* fix(orchestration): complete legacy worker cutover recovery

* fix(orchestration): preserve legacy workers across updates

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-07-29 11:31:35 -07:00
Brennan Benson d3681f6306 fix(runtime): surface desktop RPC startup failures (#11037)
* fix(runtime): surface desktop RPC startup failures

* fix(runtime): isolate RPC failure telemetry

* fix(runtime): satisfy the changed-code quality gate and kill vacuous dialog tests

The `no-floating-promises` label span covers the whole `app.whenReady().then()`
callback, so adding lines inside it made a long-standing finding overlap changed
code. `void` is the linter's own suppression; no `.catch()` on purpose.

The startup-failure tests were vacuous: mutation runs showed the wait-for-show
deferral, the destroyed-window guard, the `closed` companion event, listener
cleanup, the cause walk, the cycle guard, and the truncation bound could all be
deleted with every test still green. The "not called yet" assertion ran before
any microtask, so it passed either way.

* test(runtime): de-brittle the desktop RPC-failure source assertions

Anchoring the slice on the full destructure and matching the whole dialog
call expression made an innocuous rename break the test with a cryptic
'expected -1'. Match the shape that is actually the contract instead.

* test(runtime): repair the silently-unbounded desktop startup slice

The desktopEnd anchor comment lost a word in 98b00d3a64, so indexOf returned
-1 and slice(start, -1) covered index.ts to EOF. Moving the dialog call to a
path that never runs at startup still passed. Anchor on code instead, and
assert both bounds so a future reword fails loudly.

* test(runtime): bound the attach anchors in the startup ordering slice

Round 3 bounded the desktop pair but left attachStart/attachEnd unguarded in
the same test: deleting the PTY startup barrier from attachMainWindowServices()
and breaking the rateLimits.attach(window) end anchor still left the case green.

* test(startup): bound the last two unguarded slice anchors in this file

Rounds 3 and 4 fixed the desktop and attach pairs; two instances of the same
class survived in the same file, both proven vacuous by mutation:

- it #3 never bounded readyEnd. Renaming the `pairing:` payload key makes it
  -1, widening readyPayload from 372B to ~52KB. Moving the reconciliation
  status out of the serve-ready payload (its whole point) but leaving it later
  in index.ts then kept all 6 cases green.
- it #2 bounded desktopWindowStart against reconciliationStart rather than
  serveEnd. An earlier `Promise.resolve(openMainWindow())` steals the anchor,
  collapsing desktopStartup to '' while every existing guard still passes, so
  its only assertion — a negative — succeeds against an empty string.

Both mutants now fail. `src/main/ipc/pty-startup-barrier-ordering.test.ts:11`
has the same latent shape; left alone as out of scope for this PR.

* fix(runtime): keep walking the cause chain past an unmapped code

getErrorCode returned the first code it found, so an outer wrapper carrying
an unrecognised code masked a nested EACCES/ENOSPC and classified it unknown.
Only a mapped code ends the walk now; every other input classifies as before.

Unreachable today (writeSecureFile rethrows raw fs errors with .code intact),
but the classifier's job is surviving whatever error shape reaches it.

* fix(runtime): tell the user what to fix, not just to restart

The dialog's only advice was "Restart Orca to try again", which is true for
address_in_use and wrong for the rest: permissions, a full or read-only disk,
and a missing data folder all survive a relaunch, so the user restarted, hit
the same failure and had no next step.

Route the error class we already compute into the copy so each cause names the
thing the user has to change. Guidance and telemetry now derive from the same
classifier, so they cannot drift apart.

* fix(runtime): guide users through long RPC paths

* fix(runtime): avoid false window listener warning

* fix(runtime): guard destroyed window before web contents
2026-07-28 16:05:05 -07:00
NeilandBrennan Benson a8126a0a92 fix(macos): explain the TCC prompts, and surface Full Disk Access only to users macOS is prompting (#9756) (#9910)
* fix(macos): add a Full Disk Access nudge to reduce recurring TCC prompts (#9756)

macOS shows the "Orca wants to access other apps' data"
(kTCCServiceSystemPolicyAppData) prompt and it can keep reappearing. The
reappearing loop is not a fixable app bug: it is TCC identity churn — an
unsigned local rebuild mints a new code identity each build, so macOS treats
each as a new app — and Orca's other-app reads are already gated behind opt-in
settings or explicit user actions.

The durable remedy for the population we can help (release users) is Full Disk
Access, a superset macOS grant that stops these prompts for a stable identity.
Surface it with an ambient, dismissable sidebar card that reuses the existing
developer-permissions IPC. macOS-only; probes FDA status at most once per
renderer session (the probe itself reads protected data, so it must not repeat
on focus/remount); "Open System Settings" opens the Full Disk Access pane;
permanent localStorage dismissal.

* fix(macos): stop the FDA nudge promising macOS will stop asking

The card said Full Disk Access makes "macOS stop asking", but the grant
covers this app while terminals are spawned by the detached PTY daemon
(daemon-init.ts forks execPath with ELECTRON_RUN_AS_NODE + detached:true,
reparented to launchd), which macOS treats as its own TCC identity. A user
who followed the card would grant FDA and still be prompted from terminals.
Scope the claim to reducing prompts and name the terminal caveat.

* fix(macos): drop stale focus refreshes in the FDA nudge

refreshFullDiskAccessStatus() applied whichever getStatus() round-trip
resolved last. Rapid blur/focus puts several in flight, so an earlier
pre-grant 'unknown' landing after a newer 'granted' un-hid the card and
also wrote 'unknown' into the module-level session cache, re-nagging a
user who already has Full Disk Access for the rest of the session. The
adjacent FullDiskAccessSetupPrompt already guards this with a refresh
sequence; mirror it here.

Also unmount React roots in afterEach: clearing document.body left them
mounted, leaking each test's window focus listener into later tests.

* test(macos): unmount the StrictMode FDA nudge root between tests

The afterEach unmount added in 5a0f717 only covers roots created through
renderNudge(). The StrictMode probe test builds its own root, so it was
never unmounted and its component stayed live for the rest of the file.
Today that component has no window focus listener, so nothing breaks; add
a CTA click to it and the same contamination 5a0f717 fixed comes back —
the two tests after it see extra getStatus() calls and fail. Register the
root so the fix covers every mount site.

* fix(macos): attribute the FDA prompts to agent activity, not Orca's own reads

The card said the prompts happen "when this copy of Orca reads protected app
data", but Orca's own reads are small and gated; #9756's trigger is agent
find/grep sweeps into ~/Library/Containers, which macOS bills to Orca because
Orca is the responsible process for every terminal child. Blaming Orca reads
as an accusation and hid why FDA works at all — the grant attaches to Orca
rather than to each churning child binary.

Name agents as the trigger, keep the "reduce" hedge and the terminal caveat,
and drop the "this copy of Orca" dev-build hedge that cost a clause. Assert
the causation wording so it can't silently regress.

* fix(macos): explain the TCC prompts on the settings row, drop the sidebar card

The sidebar nudge added in 344d466b was premised on FDA being reachable
"only inside onboarding". It isn't: Settings > macOS Permissions has had a
full-disk-access row all along (searchable), the Setup Guide hosts the same
prompt from both a settings pane and a re-openable modal, and the sidebar
already links to that modal via the "Onboarding checklist" entry. The card
added a fifth affordance to the same sidebar that already had the fourth,
so it bought prominence rather than access - shown to every macOS user
without FDA, most of whom never hit #9756.

Keep the part that was actually new. The settings row still described the
prompts as something projects and worktrees trigger, which is the same
misattribution the card carried: the reads come from the agents Orca runs,
and macOS names Orca only because it is the responsible process for every
terminal child. It also never mentioned that the grant has to cover Orca
Helper, or that the preserved daemon keeps stale TCC state until restart.

Non-English catalogs get the English string as a placeholder; the bootstrap
translators key their cache on the English value, so a changed string is
re-translated on the next run.

* feat(macos): nudge Full Disk Access only after macOS repeatedly prompts

The FDA hint is only worth showing to users macOS is actually prompting.
tccd emits one AUTHREQ_PROMPTING line per consent dialog it displays,
carrying the service and both identities, so a narrow log-stream predicate
detects the real thing without correlating across lines or guessing whether
a dialog appeared. Verified against a captured dialog: the predicate matched
1 line out of 1436 TCC lines in ~28s, because routine preflight checks - the
overwhelming majority of TCC traffic - do not emit it.

Count dialogs where Orca is the responsible process, persist across launches,
and tell the renderer on the third one. The event separates the accessing
binary from the responsible app, which is the crux of #9756, so the toast can
name the tool that triggered it rather than blaming Orca generically. One
toast per user, with a permanent opt-out; it deep-links to the FDA row in
Settings > macOS Permissions rather than restating the guidance.

macOS-only: the watcher no-ops elsewhere, the web client stubs the API, and
the child is killed on before-quit since log stream ignores a closed stdout.

* test(macos): pin the platform so the TCC watcher tests exercise the darwin path

start() is darwin-gated, so on Linux CI it no-opped and the stream/kill
assertions passed vacuously against a watcher that never spawned. Pin
process.platform per the existing convention (shared/secure-file.test.ts),
and cover the gate itself with an explicit non-darwin case.

* fix(macos): start the TCC watcher from app bootstrap, not the window wiring

attachMainWindowServices is called directly by its own unit test, so wiring
initTccPromptNotice there made `vitest src/main/window/` spawn real `log stream`
children that outlived the run - two orphaned watchers were left behind by a
single test session. Only the IPC handler registration stays there; the spawn
moves to the real app bootstrap in index.ts, which tests never execute.

Verified: running the suite that leaked now leaves the watcher count unchanged.

* fix(macos): clarify repeated permission notice

* fix(macos): keep TCC notice lifecycle safe

* fix(macos): retain pending TCC notice delivery

* fix(macos): acknowledge TCC notice delivery

* fix(macos): release failed TCC notice claims

* fix(macos): retry transient TCC notice display

* fix(macos): contain TCC notice IPC failures

* fix(macos): harden TCC notice renderer lifecycle

* fix(macos): contain TCC notice dismissal failures

* test(macos): satisfy promise executor lint

* fix(macos): detect helper-attributed TCC prompts

* fix(macos): align TCC watcher lifecycle and helper identity

* perf(macos): defer TCC log reader until first paint

* fix(macos): recover deferred TCC watcher startup

* fix(macos): recover TCC watcher from deferred quit

* fix(macos): localize recurring file access notice

* fix(macos): preserve TCC watcher and localized guidance

* fix(macos): avoid duplicate TCC watcher recovery

* fix(macos): wait for locale before TCC notice

* perf(macos): isolate TCC notice subscriptions

---------

Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
2026-07-28 15:34:52 -07:00
OrcaWinandOrcaWin 380034edf9 fix(macos): avoid scene deadlock on app reactivation (#11055)
* fix(macos): avoid redundant focus on app activation

* test(macos): cover passive app activation

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-07-28 01:45:59 -07:00
Neil 4681edb520 fix(terminal): limit pre-paint WebGL resume to macOS (#10794)
Run terminal visibility transitions pre-paint only on macOS. Restore passive disposal and recreation on Windows/Linux, remove the Windows retained-context LRU machinery, and preserve the normal 128-context startup ceiling.
2026-07-26 15:24:26 -07:00
NeilandOrca 8f5a45401f fix(terminal): stop switch bold flash and Windows lag (#10692)
* fix(terminal): stop bold flash on worktree switch

Worktree hide disposes WebGL and falls back to xterm's DOM renderer.
On reveal, resume ran after paint and flushed backlog against DOM first,
so the first frame used heavier CSS-AA glyphs before WebGL settled.

Resume in useLayoutEffect and reattach WebGL before backlog flush so the
first painted frame stays on the GPU path. No cold-park policy change.

Co-authored-by: Orca <help@stably.ai>

* fix(terminal): fit WebGL grid before backlog flush on resume

Adversarial review: resume-before-flush alone wrote TUI backlog onto the
transient DOM↔WebGL one-column-off metrics window. Order is now
resume → fitAllRevealedPanes → flush on heavy reveal and window wake.

Co-authored-by: Orca <help@stably.ai>

* fix(terminal): latch viewport intent before WebGL wake resume

Adversarial review: wake path synced intents after resume/fit, which can
re-latch a pinned viewport as followOutput. Capture before reattach and
drop the post-resume re-sync on heavy reveal (outer path already latched).

Co-authored-by: Orca <help@stably.ai>

* fix(terminal): complete visibility bookkeeping before PaneManager exists

useLayoutEffect runs before the passive lifecycle creates PaneManager, so
the mount-visible path never set hasCompletedVisibleResume. The first
intra-worktree hide then wrongly suspended WebGL. Bookkeep completion
even when managerRef is still null (extracted helper for max-lines).

Co-authored-by: Orca <help@stably.ai>

* fix(terminal): re-sync pin geometry after resume backlog flush

Keep the pre-resume intent latch (reattach must not re-latch pins as
followOutput), then re-sync after flush with preservePinnedAtBottom so
scrollback trim updates absolute pin lines before enforce.

Co-authored-by: Orca <help@stably.ai>

* fix(terminal): drop same-tick post-flush intent re-sync

flushTerminalOutput only queues terminal.write and returns before parse, so
a same-tick re-sync read pre-parse resume/fit geometry and could overwrite
pre-resume pins. Keep pre-resume latch + enforce only.

Co-authored-by: Orca <help@stably.ai>

* fix(test): expect default worktree card properties to include cli

#10712 added 'cli' to DEFAULT_WORKTREE_CARD_PROPERTIES, but the fresh
default-profile assertion still omitted it and fails verify.

Co-authored-by: Orca <help@stably.ai>

* perf(terminal): retain Windows WebGL across worktree hides

* perf(terminal): bound retained WebGL contexts

* fix(terminal): harden retained WebGL lifecycle

* fix(terminal): preserve healthy WebGL on wake

* fix(terminal): preserve reveal recovery ordering

---------

Co-authored-by: Orca <help@stably.ai>
2026-07-26 05:29:38 -07:00
NeilandOrca bc2bdfc52e test(gpu): pin the win32-only fallback invariant the macOS Graphite fix relies on (#10646)
Co-authored-by: Orca <help@stably.ai>
2026-07-25 19:49:29 -07:00
NeilandOrcaWin 2e6467710a fix(mac): disable unstable Skia Graphite renderer (#10643)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-07-25 18:31:40 -07:00
NeilandOrca aab112933e Revert "fix(memory): bound OOM-prone accumulators (#10179)" (#10255)
Co-authored-by: Orca <help@stably.ai>
2026-07-23 18:35:31 -07:00
Brennan Benson 6eb70d8370 fix(main): stop unhandled rejections from silently killing the app during startup restore (#9970)
* fix(main): log unhandled rejections durably and stop them killing the app (#9441)

Main had no unhandledRejection handler — Node's default killed the process
with a clean exit code and no macOS crash report, which is exactly the
silent startup-restore exit in #9441. Rejections now record a durable crash
breadcrumb (flushed to the trace sink) and the app stays alive; uncaught
exceptions still terminate but record the same breadcrumb first so field
exits stop being undiagnosable.

* fix(main): harden process error guard diagnostics

* fix(main): cap fatal-error records to stop a rejection storm stalling main

One broken resource can reject hundreds of concurrent restore chains, and
each durable record does a synchronous trace flush. Cap records at 20 per
60s window and carry the suppressed count into the next recorded event.

* fix(main): never rate-limit the fatal uncaught-exception breadcrumb

A rejection storm that exhausted the 60s record window would suppress the
one main_uncaught_exception record taken right before the re-throw kills
the process, recreating the undiagnosable death from issue #9441. Scope
the fixed window to main_unhandled_rejection only; the uncaught path
records at most once per install (handler removes itself, EPIPE/EIO never
record), so it cannot storm.

* fix(main): reset fatal-error window on backward clock jumps
2026-07-23 18:31:02 -07:00
Neil 8f40ddf328 fix(memory): bound OOM-prone accumulators (#10179) 2026-07-23 06:22:56 -07:00
OrcaWin 34c160442f Fix headless Linux serve pairing readiness (#9785) 2026-07-21 18:23:20 -07:00
OrcaWin 1fef1e1ddd Relaunch macOS orca serve safely after updates (#9634) 2026-07-21 17:44:40 -07:00
Brennan BensonandOrcaWin e58de71f5e feat(codex): real-home routing + self-contained multi-account homes (#9501)
* feat(codex): backfill managed-home sessions into the real Codex home once per host

Orca-launched Codex sessions currently land only in the Orca-managed
runtime home, so the user's own `codex resume` picker and app history
never see them (#4444, #8612). Backfill the managed sessions tree into
the real ~/.codex/sessions/YYYY/MM/DD layout once per host:

- hardlink first (one physical rollout log), copy as the cross-volume
  fallback; existing target files are always skipped, nothing in either
  home is deleted or moved
- idempotent; per-file failures leave the completion marker unset so the
  next startup retries cheaply
- JSONL audit log of every link/copy/failure under
  <userData>/codex-session-backfill/
- honors the custom Codex session source home override, mirroring the
  existing system->managed bridge

WSL managed homes are distro-local and need an in-distro variant; that
is a follow-up.

* feat(codex): flag-gated system-default real-home routing scaffolding

Staged internal flag (default OFF, no settings UI): route the SYSTEM-DEFAULT
Codex account at the user's real ~/.codex instead of Orca's managed runtime
home. Flag OFF is byte-identical to today; managed (multi-account) selections
are unchanged in either state.

Routing (flag ON + host system default = no managed account):
- CodexRuntimeHomeService.prepareForCodexLaunch / prepareForRateLimitFetch
  return null so the PTY/env layer injects no managed CODEX_HOME and the
  rate-limit fetcher + auth-presence gate fall back to ~/.codex (the background
  poller stops spawning Codex against the managed home — the #5370 auth war).
- buildPtyHostEnv strips only a nested-Orca-inherited Orca-owned override
  (CODEX_HOME matching the private ORCA_CODEX_HOME marker), preserving a
  user-set CODEX_HOME. Shell-ready re-exports already no-op without the marker.
- The headless commit-message Codex path strips the same inherited override.

Hook install for the real-home lane (append-last into ~/.codex/hooks.json,
trust via the app-server client) lands with the trust plumbing; the managed
hook install is skipped for this lane meanwhile.

Credit @jellychoco (#8606) for the native-home routing direction.

Depends on the codex trust-rpc-grant plumbing for the real-home hook installer.

* fix(codex): strip the daemon-inherited Orca CODEX_HOME override for real-home routing

The daemon spawns PTYs from its own inherited environment and honors only
spawnOptions.envToDelete, so mutating the sparse env object was not enough to
strip an Orca-owned CODEX_HOME the daemon already carries. Add the strip to
envToDelete for both daemon host-spawn paths, preserving a user-set CODEX_HOME.

Verified live via CDP against a sandboxed dev instance (flag ON): an
Orca-spawned pane reports empty CODEX_HOME/ORCA_CODEX_HOME, so Codex resolves
its own ~/.codex. Adds daemon-path unit coverage (strip Orca-owned, preserve
user-owned, no-op when flag OFF).

* fix(codex): harden one-time session backfill

* test(codex): cover staged cross-volume install

* feat(codex): app-server trust-grant client, capability cache, and grant ledger

Short-lived codex app-server JSON-RPC client (hooks/list + config/batchWrite,
the same pair the Codex TUI 'Trust all' flow calls), run in a bundled
ELECTRON_RUN_AS_NODE entry so synchronous launch prep can block on it with a
hard deadline and guaranteed child reap. Capability cache modeled on
GitCapabilityCache, scoped per execution host (native vs each WSL distro),
with a narrow unknown-method/missing-subcommand unsupported predicate. The
grant ledger records verified grants so steady-state launches skip the RPC.

* fix(codex): grant managed hook trust via codex app-server RPCs in install/refresh

Host and WSL installs now grant trust for Orca's managed status hooks through
codex's own hooks/list -> config/batchWrite -> re-list verify, scoped to
exactly the managed entries; the previous computeTrustedHash lane is the
unchanged fallback for incapable/erroring CLIs. getStatus and the removal
paths recognize ledger-recorded codex hashes so drift between codex's real
algorithm and the replica no longer misreports or strands trust. SSH remote
install is untouched by design.

* test(codex): cover app-server trust grant client, cache, ledger, and lanes

* test(codex): cover commit-message real-home override strip/preserve

Adds the two cases for the headless commit-message Codex env under real-home
routing: a nested-Orca-inherited Orca-owned CODEX_HOME is stripped, and a
user-owned CODEX_HOME is preserved.

* test(codex): WSL grant-lane coverage — in-distro invocation and fallback parity

* feat(codex): real-home hook installer trusted via the codex app-server grant client

With the real-home flag ON and the system-default selection, install Orca's
status hook into the user's real ~/.codex before any pane spawns:

- entry APPENDED LAST per managed event: codex hook trust keys are positional
  (source:event:group:handler), so appending keeps every user entry's position
  and trust record intact; user entries and unknown top-level hooks.json fields
  are preserved verbatim
- trust is granted exclusively through the codex app-server client
  (hooks/list + config/batchWrite, verified by re-list); Orca never writes
  [hooks.state] into the user's real config.toml itself
- if the grant lane is unavailable (old binary, unsupported RPC, verify
  failure), the appended entry is rolled back byte-exactly and the host keeps
  the managed-home lane end to end (PTY env, rate limits, commit messages)
  via a lane gate on the runtime-home service
- one-time pristine backup of the user's hooks.json under Orca's userData;
  a rolling .bak sits next to the file (existing atomic writer)
- hook opt-out sweeps Orca entries from the real home and drops Orca-owned
  trust records; flag-off downgrade re-arms the existing legacy system-home
  sweep, which removes the entry and its trust keys cleanly
- the legacy system-home sweep is suppressed only while the real-home lane
  owns ~/.codex/hooks.json, so managed installs cannot delete the entry

* fix(codex): resolve the trust-grant entry without requiring electron

The grant bridge is reachable from plain-Node CLI entries, where the
plain-node entry guard rejects any chunk containing require("electron").
Resolve the bundled session entry from __dirname (root chunk and chunks/
layouts) with an app.asar -> app.asar.unpacked rewrite for packaged runs,
instead of electron's app path APIs.

* fix(codex): keep session backfill off main thread

Use asynchronous, sequential filesystem operations for the one-time rollout backfill, and avoid repeated target-directory probes. Treat inaccessible managed session roots as retryable failures instead of writing a false completion marker.

* fix(codex): harden app-server trust grant fallback

* fix(codex): install cross-volume session backfill copies atomically

On a real Codex home whose filesystem supports no hardlinks (exFAT/FAT,
some network mounts), the staged cross-volume copy was installed with a
non-atomic copyFile(..., COPYFILE_EXCL) straight into the final
rollout-*.jsonl name. An install interrupted mid-copy (app quit, crash,
ENOSPC during the deferred run) could strand a truncated rollout that the
next run then skips as already-present, defeating the staging design's own
guarantee that a failed copy never leaves a partial session behind.

Install the fully-staged copy with an atomic rename instead, guarded by an
existence re-check so it keeps the never-overwrite contract (and the rename
source is the same immutable managed rollout, so any clobber would be
byte-identical). Cover the no-hardlink-support target and an interrupted
install that must leave no partial in the user's sessions tree.

* fix(codex): resolve grant entry from __dirname so plain-node CLI entries stay electron-free

The build guard rejects any electron require reachable from plain-node
entries; the bridge now maps app.asar to app.asar.unpacked by string
replacement instead of consulting electron app paths. CLI typecheck project
lists the new trust-grant module graph.

* fix(codex): harden trust grant reconciliation

* fix(codex): restore trust config permissions on rollback

* fix(codex): harden real-home routing cleanup and retries

* fix(codex): preserve unicode trust RPC responses

* fix(codex): preserve remote env and complete real-home cleanup

* fix(codex): preserve real-home lane invariants

* test(terminal): isolate replacement idle reset assertion

* fix(codex): preserve real-home dotfile links

* fix(codex): preserve verified trust grants across launch prep

* fix(codex): preserve dangling config symlinks on rollback

* fix(codex): don't revoke a just-granted WSL home on a false 'missing' probe

The async wsl.exe canonical-path settlement could report the runtime home
'missing' immediately after a verified RPC grant (a false negative — codex
had just written and re-listed trust there), which drove the reconciliation
'remove' branch to delete all six granted [hooks.state] tables, leaving a bare
[hooks.state] the launching pane read as 'hooks need review'. A 'missing'
settlement now revokes only when no successful install ran this generation; a
genuinely moved home still resolves to a different path and reinstalls.

* test(codex): model codex config/batchWrite faithfully on Windows

The grant-lane stub simulated codex by calling Orca's upsertHookTrustEntries,
which writes both separator variants for a Windows key (a fallback-lane compat
shim real codex never does) — fabricating duplicate tables and whitespace the
RPC path never produces, so the byte-stable and no-duplicate assertions failed
on win32. Replace it with a single-variant, blank-line-separated writer that
matches the real 0.144.x binary's output.

* feat(codex): collapse duplicate session listings across Codex roots

Backfilled/bridged rollouts are hardlinked into both the real ~/.codex and
Orca's managed runtime home, so AI Vault listed each session once per root
(#7521). Dedup candidates by rollout file name pre-parse and parsed sessions
by session id post-parse, keeping the canonical root: host real home first
(unprefixed resume), then the managed runtime home, then other homes. Applies
to local, WSL, and SSH-remote scans.

* feat(codex): background sqlite index heal for backfilled sessions

Codex's own state-DB metadata backfill is one-shot, so rollouts hardlinked in
by Orca's session backfill never become visible to Codex's DB-driven surfaces.
Extract the app-server stdio JSONL transport into codex-app-server-session
(shared with the trust-grant client) and add a bounded, resumable background
pass that drives Codex's lazy indexing via thread/read per backfilled session:
recent-first, batched onto one short-lived server per batch with small
concurrency, ledger + marker so steady-state startups are a no-op, stop-aware
on quit, and capability-aware on CLIs without the app-server surface.

* fix(codex): preserve session identity during dedup heal

* fix(codex): preserve user trust during real-home cleanup

* fix(codex): harden real-home heal boundaries

* fix(codex): fail closed on unsafe backfill install

* fix: harden real-home hook cleanup

* fix(ai-vault): preserve execution boundaries and reap children

* fix(codex): narrow app-server unsupported detection

* fix(codex): bound user hook trust rebase retries per host

The rebase lane ran a codex app-server session on every launch prep while a
host was stuck (CLI without app-server support, or keys hooks/list cannot
match). Gate the transaction on the shared capability cache and add the same
5-minute transient cooldown the grant lane uses, so sweep and legacy-cleanup
retries cost plain fs reads instead of a codex session per pane spawn.

* fix(codex): enforce real-home resume and heal boundaries

* fix(codex): establish real-home lane before cleanup

* fix(codex): stop index heal before delayed spawn

* fix(codex): protect symlinked rolling backups

* fix(ai-vault): preserve resume env deletion through drag

* fix(codex): strip inherited Codex homes on mobile real-home resume

The mobile resume surface types a bare real-home codex resume into a
freshly created pane, but never asked for CODEX_HOME/ORCA_CODEX_HOME
deletion at pane spawn, so an agentDefaultEnv-pinned or daemon-inherited
Codex home rerouted the resume away from the user's real ~/.codex while
the same session resumed correctly on desktop. Share the deletion helper
from the AI Vault resume builders and forward it through the mobile
launch and session.tabs.createTerminal call.

* fix(codex): gate session migration on real-home lane

* fix(codex): stop session backfill after opt-out

* fix(codex): keep session heal failures retryable

* fix(codex): keep session migration state recoverable

* fix(codex): retry republished missing session heals

* fix(codex): preserve hook symlink trust path

* fix(codex): disambiguate POSIX trust paths

* fix(codex): align hook trust source paths

* fix(codex): harden trust grant lifecycle

* fix(codex): restore envToDelete on client invocation type after base reconcile

* test(codex): type child.stdout as PassThrough for oversized-output write

* Assemble RC: reconcile app-server transport API across PRs

Unify on the object RPC surface from the index-heal transport (#8921) while
preserving the default-home env strip (#8828) and the narrowed missing-app-server
capability signal (#8847): adapt the user-hook-trust-rebase consumer + tests,
port envToDelete stripping into the shared session, and route stderr
classification through the canonical capability-signal module.

* RC: enable system-default real-home routing by default (flag ON)

Flip codexSystemDefaultRealHomeEnabled to default ON for this RC's staged
rollout (a user can still opt out by setting it false, which stays byte-identical
to managed-home behavior). This is the only intended behavior difference between
the RC branch and the individual PRs. Updates the two tests that assumed the
prior OFF default.

* fix(codex): snapshot hooks.json bytes+parse in one read to close real-home clobber race

The install/sweep/legacy-cleanup paths parsed hooks.json, then did a separate
later read to capture the previous bytes for the pre-write generation guard.
A concurrent save (second Orca instance or the user editing the file) could
land between the parse and that second read and be silently overwritten.
readHooksJsonWithRaw returns the raw bytes and parse from a single read so the
guard compares against exactly what it parsed. Adds a regression test that
mutates hooks.json mid-RPC and asserts the sweep aborts without clobbering.

* fix(codex): sanitize managed account config trust

* fix(codex): guard OAuth add for custom providers

* fix(codex): persist outgoing managed tokens before real-home lane takeover (PR-C)

prepareForCodexLaunch returns null early for the real-home / system-default
lane before syncForCurrentSelection runs. If a managed account is still
recorded as synced when the selection has dropped to the system default
(nulled without a sync pass, or auto-deselect on missing managed auth), a
Codex-refreshed token stranded in the shared runtime home is never persisted
to its canonical per-account home -> token loss.

Read the outgoing managed account's refreshed token back before the real home
takes over. The real-home lane implies host === null, so running the
managed->system-default transition restores only Orca's runtime mirror from
~/.codex and never writes the real ~/.codex. It is a no-op once the selection
has already been reconciled, so the normal select path does not double-write.

* fix(codex): preserve refreshes across all default transitions

* feat(codex): show system-default/real-home account identity in switcher (PR-B)

The account switcher modeled the system-default Codex account as
activeAccountId:null with no identity fields, so the null row rendered
blank ("System default" / generic subtitle) even though its effective
login is whatever ~/.codex/auth.json currently is.

Add a CodexSystemDefaultIdentity descriptor {hasAuth, authKind, email,
providerAccountId, workspaceLabel} to CodexRateLimitAccountsState,
resolved live and READ-ONLY from ~/.codex by the accounts service and
returned from listAccounts()/getSnapshot(). The settings switcher now
renders the null (system-default) row as that real identity: the OAuth
email when signed in, "Custom provider — no usage tracked." for
env-key/custom-provider logins (auth.json with OPENAI_API_KEY, or an
OPENAI_API_KEY env with no auth.json), and the generic fallback when
signed out. Identity is host-scoped (per-distro WSL keeps the generic
label). Orca never writes ~/.codex; managed-account switches only touch
Orca-owned homes, so the system-default identity stays a stable,
displayed source of truth. Usage already routes to the real home via
getSystemCodexHomePath, so the switcher now attributes it to a real face.

Tests (sandboxed temp homes only): OAuth email/provider resolution,
api-key auth.json and env-key (no auth.json) as custom-provider,
signed-out, and select/deselect of a managed account never mutating
~/.codex/auth.json.

* fix(codex): parse multiline provider pins in OAuth guard

* fix(codex): harden managed trust sanitization

* fix(codex): harden system-default identity rendering

* feat(codex): give each managed account a self-contained CODEX_HOME; retire shared mirror (PR-E)

With the real-home flag ON, a host managed account now launches directly
against its own codex-accounts/<id>/home instead of the shared runtime
mirror + auth.json hot-swap:

- codex-home-paths: syncSystemCodexResourcesIntoManagedHome links system
  resources into any managed home (ownership-marker discipline; never
  symlinks into / mutates ~/.codex).
- runtime-home-service: prepareForCodexLaunch / prepareForRateLimitFetch /
  syncForCurrentSelection route the per-account home directly and skip the
  shared-home hot-swap + token read-back; each home keeps its own auth in
  place (fixes GAP-5 concurrent auth race). Session discovery scans every
  per-account home.
- hook-service / hook-trust-promotion: install/getStatus/refresh accept a
  runtimeHomePath so hooks + RPC-granted trust land in the per-account home.
- service: config mirror into a self-contained home uses the trust-
  preserving merge so granted hook/project trust survives account switches.
- codex-session-root-dedup: rank codex-accounts/<id>/home as canonical
  managed alongside the shared runtime home.

Flag-OFF and the system-default real-home (null) lane are unchanged; the
nested-Orca CODEX_HOME===ORCA_CODEX_HOME daemon strip (#5370) is preserved.
Sandboxed tests only; ~/.codex is never mutated.

* fix(codex): validate per-account home ownership

* fix(codex): keep managed rollouts discoverable across real-home opt-out

WI-4 lossless migration/rollback validation for pre-E shared-mirror managed
accounts. Session discovery gated the per-account home scan on the real-home
flag, so opting back out (flag OFF) hid every rollout an account accumulated
while the flag was ON — the data stayed on disk but vanished from the AI Vault
until the flag flipped back on.

Scan a managed host home whenever it holds a sessions/ tree, independent of the
flag; a never-enabled install keeps its homes credential-only so opt-out stays
byte-identical to today. Forward migration was already lossless (the shared
mirror is always scanned) and the opt-out credential read-back already refuses
to overwrite a fresher per-account token; add tests locking all three
invariants. Sandboxed tests only; ~/.codex is never touched.

* fix(codex): migrate stranded shared auth on E takeover

* test(e2e): isolate Electron from developer Codex home

* test(codex): add real-account validation harness

* fix(codex): finish C and E matcher composition

* fix(codex): bound validation harness shutdown

* test(codex): isolate hook lifecycle user data

* test(codex): cover realistic account-home migration

* fix(codex): keep standalone home tripwire active

* test(codex): fingerprint system auth in validation reports

* fix(codex): bind managed homes to account ownership

* fix(codex): normalize Windows trust source identity

* fix(codex): make Windows trust upgrade transactional

* test(codex): use TypeScript pipeline for validation scripts

* test(codex): run validation modules through native node

* test(codex): allow slow Windows tripwire startup

* fix(codex): survive lingering Windows codex login processes in add-account

On Windows, codex login can keep running (with descendants) after it has
written auth.json, holding OS handles on the per-account managed home
(log/codex-login.log). That made doAddAccount's post-login cleanup fail
with ENOTEMPTY (rmSync) and left an orphaned codex-accounts/<id>/home.

- runCodexLogin now watches for auth.json on Windows and force-kills the
  login process tree (taskkill /t) if it lingers past a short grace
  period; the forced exit is treated as a successful login. The 120s
  timeout path also kills the whole tree instead of only the direct
  child. macOS/Linux behavior is unchanged.
- safeRemoveManagedHome now removes homes with rmSync maxRetries /
  retryDelay (mirroring the local-worktree-filesystem Windows policy)
  and no longer lets a cleanup failure mask the original add error.
- run-codex-real-account-validation.mjs accepts --temp-parent /
  ORCA_CODEX_VALIDATION_TEMP_PARENT so the disposable root can live
  outside %USERPROFILE% on Windows, and fails with an actionable message
  before creating anything when the temp parent is inside the primary
  home. The real-home guard is unchanged.

* fix(codex): preserve managed-account MCP .credentials.json on per-account-home migration (#8440)

Codex file-mode MCP OAuth tokens live in $CODEX_HOME/.credentials.json,
keyed by MCP server URL with no account identity of their own. The legacy
shared-mirror -> per-account-home migration only carried auth.json, so an
existing managed account with authed MCP servers had its tokens stranded on
upgrade and silently needed re-auth.

Carry the shared mirror's .credentials.json into the same identity-proven
per-account home alongside auth.json: only into the single uniquely-matched
active account (no cross-account leak), only when the destination has none yet
(never clobber a newer file the account authed in its own home), atomic 0600,
absent-source no-op. New MCP auth already lands in the per-account home since
that home is CODEX_HOME.

* fix(codex): preserve Windows reauthentication login flow

* test(codex): build real-account validation harness cross-platform on Windows

The harness built its app with execFileSync('npx', ['electron-vite', ...]),
but npx resolves to a .cmd shim on Windows that execFileSync cannot launch
(ENOENT), so the harness could not build its own app there and required
--skip-build with a prebuilt out/main/index.js.

Extract resolveElectronViteBuildCommand(repoRoot): it runs the repository-local
electron-vite JS entry (node_modules/electron-vite/bin/electron-vite.js) with
the current Node binary (process.execPath), which resolves identically on
macOS, Linux, and Windows with no shell. It throws a clear error if the local
entry is missing (install deps or pass --skip-build). --skip-build behavior is
unchanged.

Add regression coverage asserting the build command uses process.execPath and
the repo-local JS entry (not npx), and that a missing entry fails clearly.

* fix(codex): version the MCP creds migration independently of the auth marker

The auth carry and the MCP .credentials.json carry (#8440) shared one
existence-only v1 marker, so any build that stamped the auth-only marker
first would strand the MCP store forever. The MCP carry now concludes via
its own per-account-mcp-creds-migration-v1.json marker and runs even when
the auth marker is already present; ordering is code-enforced instead of
landing-discipline-enforced.

Also isolate per-account read failures: one stale or deleted account home
no longer aborts the whole migration. The broken account stays in the
unique-identity ambiguity gate via its stored fields but is never read or
written, so the active account still migrates.

* fix(codex): fail corrupt managed auth.json without echoing credential bytes

A raw JSON.parse SyntaxError from loadOAuthCredentials could carry auth
file fragments into logs and the add/reauth error surface. Throw a
sanitized error instead; filesystem errors still propagate unchanged.

* fix(mobile): give the pairing runtime a disposable home for the E2E boot guard

The main-process guard now refuses to start with ORCA_E2E_USER_DATA_DIR
set but the real user home, and this was the one caller not updated —
the temporary pairing runtime crashed before emitting its pairing URL.

* test(codex): canonicalize harness containment guards and retry cleanup

Resolve symlinks before the disposable-root containment checks so a
symlinked temp parent cannot smuggle the throwaway home inside the
primary home, and give the final cleanup rm Windows retry/force so a
briefly lingering codex handle cannot strand the credential-bearing
root.

* test(codex): add lane-aware containment mode to the real-account harness

The Windows gate-D run proved strict zero-event whole-profile containment
is structurally unreachable with the real-home flag ON: system-default
spawn sites deliberately delete CODEX_HOME so native codex resolves the
real ~/.codex, and on Windows the binary ignores the USERPROFILE sandbox.
Its own volatile runtime churn (root sqlite/WAL/SHM, tmp/, log/) is the
shipped Phase-1 design, not a candidate defect.

--lane-aware-containment records those designed events without aborting
while every other real-home write — auth.json, config.toml,
.credentials.json, hooks.json, sessions/, anything unknown — remains a
hard violation and still aborts the run. Default behavior is unchanged
(strict); the absolute zero-event claim stays carried by macOS runs,
where HOME does sandbox native codex.

* test(codex): allow the real-account harness to pin the real-home flag off

--system-default-real-home off seeds and env-pins the flag OFF so every
codex spawn gets an explicit managed CODEX_HOME and native codex never
resolves the OS profile. This is the only Windows configuration where the
strict zero-event whole-profile tripwire is reachable, and it matches the
stable-rollout default; flag-ON runs keep lane-aware classification.

* test(codex): correct the flag-off harness comment to kill-switch rationale

The rollout ships all codex-home changes at once (no phased rollout), so
flag OFF is the emergency kill-switch lane, not the stable default.

* test(e2e): canonicalize the isolated E2E home path

The disposable HOME lives under os.tmpdir(), whose spelling is an alias
on CI (macOS /var symlink, Windows 8.3 RUNNER~1). Git canonicalizes
worktree paths, so worktrees created under the aliased home never
matched the app's listing — golden core flows and the packaged
crash-survival harness failed with 'worktree created but not found in
listing'. Resolve the home to its canonical spelling at creation in
both the e2e helper and the packaged-app driver.

* fix(codex): address CodeRabbit review on the landing PR

- carry envToDelete through the mobile agent-resume startup plan so a
  real-home Codex resume cannot inherit an ambient CODEX_HOME
- strip Orca-owned Codex overrides in the commit-message WSL fallback,
  matching the host fallback
- strip ELECTRON_RUN_AS_NODE in the computer-e2e driver like every other
  home-isolation caller
- drop the unused hooksEnabled parameter from isRealHomeCodexHookLaneUsable

* feat(codex): ship real-home routing unconditionally, remove the rollout flag

The codexSystemDefaultRealHomeEnabled setting is gone from types and
constants and the helper no longer consults settings — the system-default
real-home lane and per-account homes ship for everyone in one release.
This also un-strands profiles that rc-era builds stamped with false (the
setting had no UI, so every stored false was a seeded artifact that would
have silently kept those users on the legacy mirror forever).

The ORCA_CODEX_SYSTEM_DEFAULT_REAL_HOME env override survives strictly as
a test-rig control: the containment harness pins the legacy lane for
strict zero-event Windows runs, e2e home isolation pins lanes inside
disposable homes, and the legacy-lane test suites now route their
per-test lane selection through it.

---------

Co-authored-by: OrcaWin <alpha-eng@stably.ai>
2026-07-20 14:34:53 -07:00
NeilandOrca 98b00d3a64 refactor(comments): slim verbose comments in main core (runtime/ipc/daemon/pty) (#9542)
Collapse multi-line explanatory comment blocks into single-line "why" statements
per AGENTS.md ("Document the Why, Briefly"): drop restatements of the code and
mechanism narration; keep the non-obvious reason, external refs, and directives.

Comments-only — verified no code changed via a Babel/esbuild comment-strip
token-equality gate against origin/main; typecheck and oxlint clean.

Area: main — core runtime, ipc, daemon, pty, providers. 73 files changed, 3206 insertions(+), 10475 deletions(-).

Co-authored-by: Orca <help@stably.ai>
2026-07-20 03:18:24 -07:00
NeilandOrca e90dc38f01 fix(dev): stabilize macOS safeStorage Keychain key name across dev branches (#9400)
Co-authored-by: Orca <help@stably.ai>
2026-07-19 21:31:36 -07:00
NeilandOrca 74e2cb702c perf(window): re-enable macOS main-window background throttling (#9395)
Co-authored-by: Orca <help@stably.ai>
2026-07-19 21:30:01 -07:00
Brennan Benson 0532765b7f fix(pty): stop daemon reattach/kill race from orphaning restored terminals (#7742 sub-issue B) (#7836)
* fix(pty): stop daemon reattach/kill race from orphaning restored terminals

Two cold-start defects let a PTY kill silently orphan a live daemon session
after a renderer/app restart — the PTY-orphaning-on-restart half of #7742:

- DaemonPtyAdapter.shutdown() was the only session op that skipped
  ensureConnected(). The daemon client connects lazily, so a kill arriving
  before the first spawn (startup sleep/hibernation sweeps, closing a
  restored tab) threw "DaemonProtocolError: Not connected" against a
  HEALTHY daemon; the renderer swallows the rejection and the live session
  survives detached. Fix: connect first, like every other session op.

- pty:kill had no startup barrier while pty:spawn did. A kill issued before
  initDaemonPtyProvider swapped the provider resolved against the pre-daemon
  LocalPtyProvider, whose shutdown() silently no-ops on daemon ids; the
  handler then reported success and sent a synthetic pty:exit, permanently
  clearing the persisted tab->ptyId binding so the session never reattaches.
  Fix: pty:kill awaits the same getLocalPtyStartupPromise gate as spawn.

Both reproduced red-first (real DaemonServer socket rig for the adapter;
deferred-barrier IPC rig for the handler) and pass after the fix.

Scope: this restores the reattach/teardown invariant. It does NOT address
the renderer crash trigger or the daemon-process-death path that produces
the Windows PowerShell FailFast symptom (separate follow-ups).

* fix(pty): gate runtime shutdown on daemon startup

* fix(pty): keep shutdown independent of hook startup
2026-07-17 20:10:59 -07:00
Jinjingandbbingz 1d2aaf1bf5 Fix recipe serve desktop promotion (#8646)
* fix(runtime): preserve terminals during headless desktop activation

* rm design doc

* Fix desktop activation launch ordering and blocked-window status resolut

- Check desktopWindowStatus before spawning the Orca app so a blocked
  runtime no longer launches a doomed second instance.
- Reuse resolveDesktopWindowStatus for remote runtime status so it
  honors the same authoritativeWindowId fallback as local status.
- Re-check the authoritative window at spawn time instead of trusting
  a possibly-stale snapshot, since it can be destroyed mid-await.
- Harden the e2e activation spec against silent spawn failures.

---------

Co-authored-by: bbingz <zzb@gxsmjx.com>
2026-07-13 19:41:24 -07:00
JinjingandOrca 13ed697cc2 Fix windows serve wsl barrier (#8559)
* fix(startup): bound serve WSL reconciliation wait

* test(startup): cover WSL barrier fail-open on early reconciliation rejection

Co-authored-by: Orca <help@stably.ai>

* feat(serve): surface managed WSL reconciliation status to headless clients

Expose reconciliation state ('pending'|'settled'|'failed') in the
orca_server_ready payload and a wsl-cli-barrier startup milestone, so
headless/SSH agents can tell the fail-open barrier outlived its budget
and a WSL PTY launch may still race an un-migrated registration.

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Orca <help@stably.ai>
2026-07-13 05:40:09 -07:00
Jinjing 34a9df7795 Fix wsl cli rc4 migration (#8471)
* fix(cli): migrate managed WSL launchers to native exe

* refactor(cli): reconcile managed WSL registrations

* fix(cli): harden WSL registration reconciliation

* fix(cli): serialize and harden WSL CLI registration lifecycle

- Cap per-distro concurrent repairs and add a filesystem lock so two
  Orca installs can't race the same distro's launcher/bridge files
- Skip re-adopting a legacy managed launcher when its bridge is user-
  owned, avoiding a doomed install retried on every startup
- Track per-distro reconciliation against launcher target + app
  version so unchanged registrations aren't re-probed each boot
- Replace command install with a single atomic rename instead of a
  move-based backup, closing a window where no orca-ide exists
- Remove the registry invalidate-on-persist-failure path now that
  writes are atomic and repair is disk-authoritative
- Add brief negative caching to WSL distro discovery to bound
  wsl.exe respawns on persistently broken hosts
- Extract keyed-promise-queue as a shared serialization primitive
2026-07-12 23:19:15 -07:00
Neil ee82d66a35 fix(cli): preserve multiline arguments on Windows (#8374)
* fix(cli): preserve multiline Windows arguments

* test(cli): run Windows launcher regression in CI

* fix(cli): support Windows Framework C# compiler
2026-07-12 02:13:41 -07:00
NeilandOrca b41cab1a9e fix(renderer): raise renderer V8 heap toward the 4GB pointer-compression cage (#7531)
* fix(renderer): raise renderer V8 heap toward the 4GB pointer-compression cage

Renderer OOM ('renderer crashed'/'oom', exit 5 / 0xE0000008 / SIGTRAP) is the
dominant crash in the crash channel: the renderer JS heap reaches Chromium's
default V8 old-space ceiling (~RAM/4) and V8 aborts. Two adversarial leak hunts
(13 agents across every renderer subsystem) found no unbounded GB-scale leak, so
this is a capacity ceiling, not a leak.

Chromium sizes the renderer heap at ~RAM/4, leaving 8-15GB machines well under
V8's ~4GB pointer-compression cage (an 8GB machine caps near 2.2GB). Reclaim that
unused headroom via --max-old-space-size in a focused startup module, gated on
physical RAM (>=8GB, ~40% of RAM, floor 3072MB, capped at the real 4096MB cage).
16GB+ machines are already at the cage so this is a no-op for them; low-RAM
machines keep the default to avoid trading a clean OOM for OS memory-pressure
kills.

Overridable with ORCA_RENDERER_HEAP_MB (number to force, default/off/0 to opt
out). Verified on Electron 42.3.3: the main-process js-flags switch propagates to
the renderer V8 and is honored up to the 4096MB cage (5000/12288 -> 4096).

Co-authored-by: Orca <help@stably.ai>

* fix(renderer): address CodeRabbit — floor-to-0 override + Linux 8GB gate

- parseRendererHeapOverrideMb: a fractional override in (0,1) floored to 0 and
  emitted an invalid --max-old-space-size=0; treat floored-to-0 as an opt-out.
- Lower the RAM gate from 8 to 7.5 GiB: os.totalmem() on Linux reports MemTotal
  (excludes kernel/firmware-reserved RAM), so a real 8 GB box reports ~7.7 GiB
  and was wrongly excluded from the headroom — the exact crashing population.
  7.5 still cleanly excludes 6 GB machines (report ~5.7 GiB).

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Orca <help@stably.ai>
2026-07-06 02:13:10 -07:00
NeilandOrca e33b2006f4 Remove stale max-lines lint disables from files under the limit (#7548)
110 files carried an eslint/oxlint-disable max-lines directive but are
already under the default max-lines budget (300 .ts / 400 .tsx / 600 .mjs
/ 800 test), so the suppression is dead. Removing it restores real
max-lines coverage on these files with zero behavior change.

Each removed directive had max-lines as its only rule; verified via a
full oxlint run (0 max-lines violations, 0 new errors). Diff is pure
deletions (200 lines, 0 additions) — no code touched.

Co-authored-by: Orca <help@stably.ai>
2026-07-06 02:12:32 -07:00
Neil ce687221d3 lint(unicorn): enable prefer-number-properties, prefer-array-find, prefer-array-index-of (#7516)
Enable three unicorn rules — one correctness, two performance — and fix every
existing violation repo-wide so the rules pass as errors.

prefer-number-properties (76 sites)
- parseInt/parseFloat/NaN -> Number.* : safe aliases (autofixed).
- isNaN -> Number.isNaN (12 sites, hand-converted): global isNaN coerces its
  argument, Number.isNaN does not. Verified every call site already passes a
  number (Number.parseInt results, number-typed fields, Date.getTime()), so the
  conversion is behavior-preserving today and guards against a future non-numeric
  argument silently coercing.

prefer-array-find (26 sites)
- .filter(pred)[0] -> .find(pred); .filter(pred).at(-1) / .pop() -> .findLast(pred).
  Drops the intermediate array and short-circuits.

prefer-array-index-of (5 sites)
- .findIndex(x => x === v) -> .indexOf(v).

Verified: typecheck (node/cli/web) clean, 53 affected suites pass (1679 tests),
oxlint clean repo-wide. mobile/ uses findLast safely (already ships ES2023
.toReversed()); config scripts and e2e helpers run on Node 24.
2026-07-05 23:56:37 -07:00
rubin dc1c1e9fdd Honor agent settings for bare CLI terminal launches (#6750)
Bare repo-backed terminal creates now reuse Settings agent defaults for exact agent commands while preserving raw commands, disabled agents, and already-managed launches.

Validation:
- PR Checks / verify passed
- Local runtime tests, typecheck, lint, CLI build, and Electron CLI/runtime smoke passed
2026-07-04 08:20:58 -07:00
Rod BoevandJinjing 840bedc90d fix(automations): start scheduler in headless serve mode (#7296)
* fix(automations): start scheduler in headless serve mode

* Trigger initial catch-up check for headless scheduled automations

Headless serve never receives a renderer-ready IPC, which previously
prevented the initial catch-up pass of due scheduled automations from
running on startup. Checking for the presence of a headless dispatcher
allows starting the evaluation pass immediately.

Additionally, refactor the corresponding test to use fake timers for
better reliability and deterministic assertions.

---------

Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
2026-07-04 01:30:04 -07:00
Jinwoo Hong 2f3c7e8660 Fix Windows slow startup and multi-minute UI freezes (#7225) (#7266) 2026-07-03 19:40:45 -04:00
Jinwoo HongandOrca c29bf6f3da Raise WebGL terminal context budget and recover from context loss (#7064)
Co-authored-by: Orca <help@stably.ai>
2026-07-01 23:43:32 -07:00
Jinwoo HongandOrca 39964149c8 Per-Workspace Environments (on-demand disposable runtimes) + Add Project remote host setup (#6320)
Co-authored-by: Orca <help@stably.ai>
2026-06-30 11:31:55 -07:00
Brennan BensonandNeil 48f0e54c03 fix(win): fall back to software rendering after repeated GPU crashes (#6857)
* fix(win): fall back to software rendering after repeated GPU crashes

On old/flaky GPU drivers the GPU child process crashes
(STATUS_BREAKPOINT / ANGLE-D3D init failure) within seconds of launch,
repeatedly — Windows crash clusters F0BDNADU79Q (Win11) and
F0BDNRZ5MDG (Win10). GPU child deaths are intentionally suppressed as
recoverable churn, so Orca never reacted and the GPU kept crashing every
launch with no self-healing path.

Track GPU child crashes in a post-launch window (3 / 30s). On the
threshold, record a gpu_fallback_engaged breadcrumb, persist a marker
file in userData, and relaunch once. The marker is read before
app.whenReady() resolves (the Store isn't available that early) so
app.disableHardwareAcceleration() + --disable-gpu take effect, then it's
consumed/cleared so the fallback applies to exactly one launch and can't
relaunch-loop. Records gpu_fallback_applied on the software-render boot.

Pure logic lives in GpuCrashFallbackTracker and the marker read/write
module, both unit-tested; the index.ts wiring is thin. Desktop only —
headless serve already uses software rendering where needed.

* fix(win): harden GPU crash fallback policy

---------

Co-authored-by: Neil <neil@stably.ai>
2026-06-30 01:21:07 -07:00
Neil f9e18910ae chore(lint): adopt unicorn/prefer-import-meta-properties (error) (#6847)
Migrate fileURLToPath(import.meta.url) / dirname(...) boilerplate to the
native import.meta.dirname / import.meta.filename, then enable the rule
at error so new code stays on the native form.

The oxlint autofix rewrites the expression but leaves the now-unused
node:url / node:path imports behind (which the already-enabled
no-unused-vars=error would then flag), so this commit also removes those
34 orphaned imports — trimming the named import where other names are
still used, deleting the line where it was the sole import.

Scope is build scripts + Node-env tests only (config/scripts, tools/
benchmarks, *.test.{ts,mjs}, vitest configs); zero shipped runtime code.
The native properties are exact equivalents (Node >= 20.11; repo is on
24), so behavior is unchanged.

Verified: oxlint 0 errors tree-wide (root + mobile), oxfmt clean,
typecheck (node+cli+web) + mobile tsc pass, root vitest 22825 passed /
0 failed, mobile vitest 1018 passed. Exercised the rewritten scripts
directly: build:relay (6 targets), ensure-native-runtime,
verify-macos-entitlements all run correctly with import.meta.dirname.
2026-06-29 23:37:30 -07:00
NeilandOrca 46646d7ff1 chore(lint): upgrade oxlint to 1.71 + enable 7 new rules (autofixed backlog) (#6841)
* chore(lint): upgrade oxlint to 1.71 and enable 7 new rules

Upgrade oxlint 1.67.0 -> 1.71.0 (1.72 was blocked by the repo's 3-day
minimum-release-age supply-chain guard; nothing here needs it). The
bump is a no-op on the existing config.

Enable 3 error rules (backlog autofixed to zero in this commit) and
4 warn rules (surface signal without gating CI):

error (autofixed, behavior-preserving):
- unicorn/prefer-node-protocol        (~1531 sites: bare builtin -> node:)
- typescript/no-import-type-side-effects (~36: all-inline-type -> import type)
- unicorn/no-array-reverse            (19: copy-then-reverse -> toReversed)

warn (real signal, current fires are test-only/correct):
- unicorn/no-array-fill-with-reference-type  (aliasing footgun guard)
- typescript/no-unsafe-function-type         (bans bare Function type)
- unicorn/prefer-array-flat-map              (map().flat() -> flatMap())
- unicorn/prefer-regexp-test                 (.match() in bool ctx -> .test())

mobile/.oxlintrc.json extends root, so it inherits all 7; the autofix
ran from root and covered mobile/ too.

Verification (all green): oxlint 0 errors (root+mobile+aux configs),
oxfmt clean, typecheck (node+cli+web), vitest 22795 passed / 0 failed,
builds (electron-vite + web + cli) succeed. node: rewrites confirmed to
skip embedded SSH/CLI string payloads (AST-only); all toReversed sites
verified to operate on fresh copies or write-once locals.

* chore(lint): bump mobile oxlint to 1.71 so inherited rules parse

mobile/ is a standalone pnpm project pinning its own oxlint@1.67, which
lacks unicorn/no-array-fill-with-reference-type (needs >=1.70). Since
mobile/.oxlintrc.json extends the root config, mobile CI's 'cd mobile &&
oxlint' failed to parse the new rule. Bump mobile to match root (1.71).

Verified in mobile/: oxlint 0 errors, oxfmt --check clean, tsc --noEmit
pass, vitest 978 passed / 0 failed.

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Orca <help@stably.ai>
2026-06-29 22:38:29 -07:00
Neil 0976f9427c fix(linux): disable GPU sandbox to stop terminal input freeze on Wayland (#5319) 2026-06-28 19:43:18 -07:00
Brennan BensonandOrca e58a5e9e17 Simplify Appearance settings (#6459)
Co-authored-by: Orca <help@stably.ai>
2026-06-28 11:50:29 -07:00
Brennan Bensonandbrennanb2025 f768e6b503 Run the packaged orca CLI even when an Orca GUI instance holds the single-instance lock (#6542)
On Windows the bundled orca.cmd runs Orca.exe with the unpacked CLI
entrypoint and ELECTRON_RUN_AS_NODE=1. When that env var is dropped, Orca
boots as a GUI, loses the single-instance lock to an already-running
window, and exits silently with no stdout.

Add a packaged-CLI-entry redirect that detects a Windows launch whose argv
carries the known in-package CLI entrypoint and re-runs it in Electron node
mode before the single-instance lock gate can bounce it, then exits with the
CLI's status. Mirrors the existing maybeRedirectAppImageCliLaunch pattern.

Fixes #5774

Co-authored-by: brennanb2025 <brennanb2025@users.noreply.github.com>
2026-06-28 00:58:57 -07:00
Jinwoo HongandOrca 6d6af7af1a Support browser panes on headless remote Orca servers (#5412)
Co-authored-by: Orca <help@stably.ai>
2026-06-18 22:50:11 -07:00
0ec3882cb8 Add project Windows runtime selection (#5519)
* Add project Windows runtime selection

* Fix project Windows runtime selection

Co-authored-by: Orca <help@stably.ai>

* fix: preserve WSL shell variables

---------

Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Neil <neil@stably.ai>
2026-06-17 16:08:14 -07:00
Jinwoo HongandOrca 4df412f18e Keep restored terminals on daemon PTYs when startup is slow (#5234)
The 12s first-window timeout used to abort daemon init and open the PTY
spawn gate, so a slow (but succeeding) daemon start flipped restored
panes onto LocalPtyProvider fallback terminals. Those are killed on app
quit and their numeric ids overwrite the persisted daemon session ids,
permanently orphaning the live daemon sessions (#5232, Bug 1).

The window still fails open at 12s, but the PTY gate now waits for the
service attempts themselves and only fails open (aborting the services)
at a 60s deadlock backstop. Failure fallback is unchanged: a daemon init
that errors still resolves the gate onto the local provider.

Co-authored-by: Orca <help@stably.ai>
2026-06-11 20:17:15 -07:00
Neil 1a22e2d05c UX/copy tweaks (#5142)
* UX/copy tweaks

* UX/copy tweaks
2026-06-10 19:53:03 -07:00
Jinwoo Hong 4daf409776 perf(windows): avoid blocking daemon pid checks (#5137) 2026-06-10 21:32:41 -04:00
Jinwoo HongandOrca 6d7d25f784 Fix packaged shell PATH order (#5125)
Co-authored-by: Orca <help@stably.ai>
2026-06-10 14:03:55 -07:00
Jinwoo Hong 5f6b454c3a perf(windows): fix 60s startup ACL walk and OpenCode streaming freeze, with benchmark harnesses (#5124) 2026-06-10 16:41:22 -04:00
5c31930e0e perf: speed up desktop startup (#4618)
Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
2026-06-09 12:15:04 -07:00
Jinwoo HongandOrca 7ea359bd60 Add Claude Agent Teams native pane launcher (#4892)
* Add Claude Agent Teams native pane launcher

Co-authored-by: Orca <help@stably.ai>

* Fix Agent Teams CI coverage checks

Co-authored-by: Orca <help@stably.ai>

* Fix Claude Agent Teams split direction mapping

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Orca <help@stably.ai>
2026-06-08 16:11:32 -04:00
Brennan BensonandOrca 2756d6f28f Add command palette feature tip (#4718)
Co-authored-by: Orca <help@stably.ai>
2026-06-07 21:07:36 -07:00
Jinjing 60e47944cd Revert "Reduce macOS dev instance identity prompts" (#4721) 2026-06-05 14:07:28 -07:00
Jinjing 39e633c136 fix: address review findings (#4704) 2026-06-05 10:39:18 -07:00
Neil d879cfde37 Add HTTP/1.1 compatibility mode (#4509) 2026-06-02 16:54:11 -07:00