Commit Graph
17822 Commits
Author SHA1 Message Date
Brennan BensonandCodex QA b42fcd8a00 fix(runtime): retract Orca's own Codex ready title when the next turn starts (#35547)
* fix(runtime): retract Orca's own Codex ready title when the next turn starts

A Codex that paints no working title of its own (its activity title off) kept the
"Codex ready" frame Orca writes on its Stop hook for the whole next turn, so
tui-idle waits, mailbox delivery and the tab all read it as at rest. The working
hook now replaces Orca's own rest label with one working frame, as #29429 did
for agents whose profiles synthesize working titles.

Tracked in STA-12718.

* test: cover titleless turn wait and mailbox lifecycle

* test: type title recovery write spy

* test: isolate CLI lifecycle exit state across runtimes

---------

Co-authored-by: Codex QA <codex-qa@users.noreply.github.com>
2026-10-29 10:06:42 -07:00
Jinjing e41105b5fc fix(editor): suppress ambiguous Unicode highlights in Markdown (#36009) 2026-10-29 10:05:15 -07:00
Neil 525719789f perf(terminal): read the macOS process table in process instead of forking setuid ps (#35987)
* perf(terminal): read the macOS process table in process instead of forking setuid ps (#31783)

/bin/ps is setuid root, so every exec costs an uncached syspolicyd
assessment and an EDR inspection. The terminal host forked ~7-10 ps/lsof
per second with 4 agent panes, and each stale daemon multiplied that.

node-pty's patched pty.node now exports processTable (sysctl
KERN_PROC_ALL/KERN_PROCARGS2 on a worker thread), processCwd
(PROC_PIDVNODEPATHINFO) and processPath (proc_pidpath). runPsInProcess
renders the same ps column text for the invocations Orca issues, so the
existing parsers and callers are unchanged; anything it cannot reproduce,
non-macOS hosts, and builds without the exports keep running ps/lsof.

Measured with 4 anchored panes polled every 750 ms for 60 s: ~440
ps/lsof children before, 0 after.

* fix(daemon): roll the in-process macOS process table into a fresh v54 daemon (#31783)

* fix(terminal): bound, escape and complete the in-process macOS process table like ps (#31783)

Review follow-ups for the libproc reader:

- Timeouts: every caller passes its ps timeout; a read stalled behind a
  saturated libuv pool resolves timed out (code null, timedOut) at that
  budget, exactly as the killed ps would. Identical native reads share one
  worker, each caller racing its own deadline; past 8 distinct reads in
  flight callers run ps. execFile-shaped callers reject like a failed child.
- tty naming shares one slot with captureDarwinProcessTable's guard, so a
  draining naming pass sends both paths to ps instead of naming again.
- Commands: the addon renders argv with adv_cmds' getproclline and strnvis
  (VIS_TAB|VIS_NL|VIS_NOSLASH) under the caller's LC_CTYPE via a
  thread-scoped uselocale, so newlines print as \012 instead of splitting
  tokens; zombies print <defunct>.
- Other users' processes: unreadable argv is marked commandIncomplete. Only
  -p selections and processes on a terminal under the user's own readable
  process there (sudo in a pane) get a targeted `ps -p ... -o pid=,command=`,
  cached by (locale, pid, start time); root daemons and a pane's root login
  never spawn ps.
- -t selects with KERN_PROC_TTY after ps's addelem_tty path resolution, so
  per-terminal reads skip argv for every other process (~0.3 ms vs 4 ms ps).
- -p deduplicates; rows sort by terminal then pid as pscomp(); stat adds
  ps's < N X V L modifiers. The export is renamed readProcessTable so an
  older pty.node falls back to ps rather than misreading the new query.

* test(terminal): diff the real libproc addon against /bin/ps on macOS (#31783)

Spawns a pty holder whose argv carries a newline, tab, invalid UTF-8 and é,
then compares Orca's ps invocations column by column: pid/ppid/pgid/tpgid/
lstart/tdev/tty exactly, commands for the user's own processes, and stat
modulo running/idle (thread state needs setuid ps). Rows are compared only
when two in-process reads around the ps run agree, so churn cannot flake it.

* fix(terminal): never serve a native process table read begun before the request (#31783)

A timed-out read stayed shareable, so a later descendant capture could join
an earlier poll's abandoned worker and take its rows as fresh; a descendant
that exited meanwhile could have its reused pid SIGTERMed. The addon now
reports when its worker actually began reading (startedAtMs), and
runPsInProcess refuses a shared read that began before its own request and
reads again, so every consumer that signals from rows (descendant
teardown, foreground-group signalling, identity probes) only sees rows
scanned after its stamp. Regression: pty-descendant-termination-libproc.

Also from review:
- argv recovered with setuid ps is keyed by pid, start time to the
  microsecond, comm and executable path (exec changes the latter two) with
  a 10 min TTL and 512-entry bound.
- Recovery covers a sudo use_pty tree: the unreadable chain must start on
  the terminal of the user's own readable process, so the root shell and
  agent on sudo's own pty qualify while root daemons, GUI helpers and a
  pane's root login (own pty, under a terminal-less or tty-inheriting host)
  do not.
- Concurrent tty= readers wait out the single naming pass, and readers past
  the 8-read cap queue behind one, within their deadlines, instead of
  bursting /bin/ps at startup restore (24 panes polled at once: 0 ps).
- darwin-terminal-naming-slot.ts is now darwin-terminal-naming-single-flight.ts.

* test(terminal): require /bin/ps row order in the libproc parity test (#31783)

Parsers that take the first matching row depend on ps's terminal-then-pid
order, so compare the order of the rows both captures share, not just their
contents.

* test(terminal): pin libproc read start times instead of reading the wall clock (#31783)

* fix(terminal): match native process table reads to requests by sequence, not wall clock (#31783)

Freshness compared the worker's gettimeofday with Date.now(), so a clock
step back let an abandoned read's rows through (wrong-pid SIGTERM) and a
step forward could reject fresh reads until the deadline. Each read now
takes a JS-side sequence number when created and is submitted a microtask
later; a caller joins only a read not yet submitted or created after it
registered, so callers in one turn share a worker and nothing submitted
before a request serves it. The addon drops startedAtMs again. Deadlines,
timeouts and the argv cache use performance.now().

Callers past the 8-read cap wait on one slot notifier and each joined read
has one settle notifier; both unsubscribe on timeout, so a stalled pool
retains no per-caller reactions. If the pool is still at the cap when an
overflow caller's deadline passes, the reader is marked stalled for 30s
and callers run ps, as before this branch.

Recovered argv is trusted for 30s: exec can keep pid, start time, comm and
executable (node -> node with another script) and no cheap field shows it,
so a sudo pane costs one targeted ps per process per 30s.

* fix(terminal): keep ps fallbacks off a stuck terminal naming pass (#31783)

- captureDarwinProcessTable's canonical fallback, taken while the naming
  slot is busy, now runs /bin/ps directly instead of re-entering the
  in-process reader and waiting on that same slot (a held lstat timed out
  every strict capture without one subprocess fallback).
- In-process tty= readers wait out a short pass (startup restore) but hand
  a pass older than 500ms to ps, which names terminals out of process.
- The naming single flight keeps one settle notifier per pass; waiters sit
  in a Set and drop off on timeout, so timed-out callers leave no reactions
  on a stuck pass (was one per call). The notifier moves to
  deadline-settle-notifier.ts, shared with the native read scheduler.
- Once the native read breaker has tripped, a caller finding the cap still
  held after the cool-down returns to ps at once instead of waiting out its
  timeout.
- Descendant teardown and foreground-group signalling give the native read
  half their budget and fall back to a fresh ps on a native timeout, so a
  partly stalled pool cannot cost a kill.

* fix(terminal): give signalling ps fallbacks their full baseline timeout (#31783)

Splitting the budget in half shrank ps's window behind a stalled native
read: a 750ms ps lost descendant SIGTERMs and an 80ms foreground ps lost
group SIGWINCH. Descendant teardown and foreground-group signalling now try
the native reader for min(150ms, timeout/4) (inProcessSliceMs), then give
ps the whole original timeout; readProcessTableBeforeDeadline extends the
production reader's outer deadline by that slice, so ps succeeds exactly
when it did on main. The stale-read regression test now also asserts the
fresh rows' real descendant is signalled.

* fix(terminal): carry the in-process deadline allowance through every descendant reader (#31783)

readProcessTableBeforeDeadline granted the native slice only when the
reader was readProcessTable by identity, so the daemon shutdown sweep's
sharing wrapper kept a bare 250ms deadline: a stalled native read plus a
200ms ps missed it on every retry and descendants survived shutdown.
ProcessTableReader now declares deadlineAllowanceMs; the fresh reader sets
it (0 without the addon or with a stalled pool, so Linux and ps-only hosts
keep their exact deadlines), createProcessTableSnapshotReader and the
shutdown wrapper inherit it, and injected readers default to none. The
fresh reader moves to pty-descendant-fresh-table.ts. The real-ps parity
test gets a 30s per-case timeout, since whole-table /bin/ps runs slow
under suite load.

* chore(terminal): move the descendant reader's child_process baseline entry with it (#31783)

* test(terminal): pin the libproc test locale so lstart doesn't follow the runner's LANG (#31783)
2026-10-29 09:51:53 -07:00
Kelvin Amoaba 0c0814e872 fix(browser): keep the chosen default browser profile after a restart or update (#35991)
* fix(browser): keep the chosen default browser profile across restarts

The default profile pick lived only in the renderer browser slice, so every
launch rebuilt it as Default. Store it in GlobalSettings, next to the
profile scope setting, and read it through one resolver.

* test: seed default profile map without a new cast

* fix(browser): drop a saved default profile only once it is gone

Trust the saved pick when reading it and forget it on a delete or a full
listing without it, since partial catalogs and failed fetches prove
nothing. Load this desktop's profiles at startup even when Settings shows
a paired server, so local tabs resolve the saved pick's partition.

* test: drop the default-profile test that only guarded the removed mirror

* refactor(browser): share the profile delete step and simplify the startup local fetch

* fix(browser): load this desktop's profiles at startup without a focus read

* fix(browser): apply overlapping default profile writes in order
2026-10-29 16:49:01 +00:00
OrcaWinandOrcaWin 41bfe43ebb test: remove low-value wrapper and scalar store suites (#35906)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-29 09:40:20 -07:00
Kelvin Amoaba 92f9431416 fix(browser): give agent set device and viewport --mobile touch input and pointer: coarse (#35982)
* fix(browser): turn on touch when an agent sets a mobile device or viewport

* docs(orca-cli): say every page load resets agent device emulation
2026-10-29 16:34:03 +00:00
Neil 89705dbf85 fix(agent-resume): quote desktop-built commands for the host's own OS and shell (#35995)
Cold restore, sleeping-agent wake and account restart on a paired Orca
server quoted the resume line for this client's own OS, so a Mac client
quoted POSIX for a Windows server and a Windows client quoted PowerShell
for a Linux one. Fresh desktop-built launches knew the server's OS but
assumed PowerShell on Windows.

One resolver, resolveHostAgentStartupShell, now decides the shell a
desktop-built command runs in: a tab's override, then the executing host's
own Windows shell (this client's setting locally, the server's reported
terminalWindowsShell remotely), then the platform default. Fresh launches
and all three resume paths use it, with the server's reported OS. A nested
SSH workspace on a server quotes for its SSH target. A host that has not
reported its OS gets POSIX quoting with OS-specific agent flags off.
2026-10-29 09:23:56 -07:00
d527f971cd fix(orcad): keep the browser sidecar's listener on loopback (#31896)
* fix(orcad): keep the browser sidecar's listener on loopback

orcad starts its headless browser sidecar as an `orca serve` child and talks to it
only over the Unix socket / named pipe. Serve mode binds its WebSocket listener to
all interfaces, so every headless host also exposed the sidecar on a random port to
its LAN and VPN. Pin the sidecar's listener to 127.0.0.1, using the owner variable
orcad already sets, so no orcad or argv change is needed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(orcad): pin the sidecar to loopback using the existing sidecar launch marker

orcad already marks its browser sidecar with ORCA_ORCAD_SIDECAR, read by
isOrcadSidecarLaunch() for the foreground policy. Reuse it instead of
remembering the consumed owner variable in a new module flag.

* test: drop the stale desktop pinnedBindHost parity exception and reset the sidecar mock per test

---------

Co-authored-by: Daniel <momo.zhiwang@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
2026-10-29 16:22:55 +00:00
Jinjing 23c146929c Fix mobile terminal overlays after delayed PTY binding (#35842)
* Refresh mobile terminal overlays after delayed PTY binding

- Show phone fit and driver-lock state when hydration precedes pane binding

* test: verify mobile overlays render after delayed PTY binding
2026-10-29 09:14:04 -07:00
Kelvin Amoaba 7c5314196a feat(native-chat): preview cards for web links, fetched with a private-address guard (#35221)
* feat(native-chat): preview cards for web links in chat

Hovering an http(s) link that isn't the repo's own PR or issue shows the
page's image, favicon, site name, title and description. The chat's host
fetches the page with no cookies, refusing private, loopback and
link-local addresses after DNS and on every redirect, and returns images
as data URLs. A Chat > Behavior setting turns web previews off.

* perf(native-chat): cap link preview caches at 20 MB each

Preview images are held as data URLs, so a count limit alone let the
renderer and main caches grow to hundreds of MB in a long session.

* fix(link-preview): keep a > inside quoted attribute values

Page tags ended at the first >, so content="Home > Docs" was cut short
and an og tag whose content came before its property was dropped.

* fix(native-chat): keep link preview caches within budget and keyed by host

A preview that finished after newer ones stopped eviction at itself, so
both caches could stay over their byte limit. The GitHub card's load key
also left out the host its repo lives on.

* fix(link-preview): decode the named entities page titles use

Only five named entities were decoded, so titles like
Docs&nbsp;&ndash;&nbsp;Guide showed the raw codes in preview cards.
2026-10-29 16:12:07 +00:00
Kelvin AmoabaandOliver Mee badab9cc41 fix(browser): import a cookie file into a client-hosted browser under a remote runtime (#35989)
* fix(browser): import a cookie file into a client-hosted browser under a remote runtime

A paired runtime refused every cookie-file import before the picker
opened, even when this desktop hosts the runtime's browser pages. The
file import now asks main, like the native-browser import does, and
imports into the page's route partition; it is still refused when the
pages are not hosted here.

Co-authored-by: Oliver Mee <102673257+oliver-mee@users.noreply.github.com>

* chore(browser): lower the focus-read baseline for the cookie import actions

---------

Co-authored-by: Oliver Mee <102673257+oliver-mee@users.noreply.github.com>
2026-10-29 15:53:48 +00:00
Kelvin Amoaba 75b658c278 feat(native-chat): hover cards for GitHub PR and issue links (#35199)
* feat(native-chat): hover cards for GitHub PR and issue links

Links to the chat's own repo (origin or upstream) open a card with the
state, title, author, update time, and +/- or comment count. Bare #123
references in agent replies now link too. Cards use a light one-call
lookup; older hosts ignore the flag and answer with the full lookup.

* test(github): expect the preview options argument in runtime owner/repo lookups

* fix(native-chat): route GitHub link cards by the chat's own host and retry failed repo lookups

The repo lookup took the first worktree with the chat's id, so a twin on
another host could answer, and the preview cache keyed by repo id alone.
A failed slug lookup also left cards off until the chat remounted.
2026-10-29 15:29:32 +00:00
Neil 482ff4228c fix(ai-vault): resume saved sessions for the OS of the host that runs them (#35988)
Resuming a saved session in an SSH or paired workspace assumed Linux whenever
the session had no recorded host OS, and a dropped session never passed one.
On a paired Windows server that quoted the command for the wrong shell and
could add Codex's Unix-only paste-burst flag. The resume now reads the
host's own reported OS. When the host hasn't reported it, quoting stays
POSIX, as before, and OS-specific agent flags stay off.
2026-10-29 08:27:33 -07:00
Neil 76cb99dc52 feat(automations): label workspaces a host creates with the desktop's own automation run (#35986)
* feat(automations): label workspaces a host creates with the desktop's own automation run

A desktop's own automation could create its workspace on a managed or paired
host, but the workspace lost its automation identity: no badge, and its run
pane closed instead of sleeping. The desktop now sends a capable host its run
as a label, naming itself (`local`) as the automation's owner. The host
records it with origin 'client', only for automations it does not hold, caps
its fields, and never lets a client-origin label grant run ownership, sleep
authority or automation create policy. Older hosts get nothing new; older
desktops still create plain workspaces. A client-origin label whose
automation can't be found reads as unavailable, not removed.

Refs #34433

* test(automations): keep the dropped-label warning test independent of test order

Refs #34433
2026-10-29 08:14:10 -07:00
Kelvin Amoaba 54fa31658c fix(browser): orca console and network print agent-browser's logs and honour --limit (#35981)
The agent-browser bridge cast agent-browser's {messages} and {requests} output to Orca's {entries, truncated} contract and dropped --limit, so text mode crashed and --json returned the raw shape. Map the output once at the bridge, keep the newest --limit entries, and make the contract fields agent-browser never reports optional.
2026-10-29 14:46:56 +00:00
Neil 525d17733a refactor: group native-chat image reference owner (#35807) 2026-10-29 07:46:31 -07:00
Neil 4595be8263 fix(codex): launch Codex without its paste-burst guess outside Windows (#35975)
* fix(codex): launch Codex without its paste-burst guess outside Windows

Codex treats a fast run of keys followed by Enter as a paste and turns the
Enter into a newline. Keystrokes relayed from a remote client can reach the
host bunched together, so the prompt stayed in the composer as a draft.
Orca's terminals bracket every real paste, so the guess can only misfire
there. Orca now launches and resumes Codex with disable_paste_burst=true,
except on Windows, where Codex reads console input records and relies on
the burst for pastes.

Fixes #34496

* test: expect Codex launches to disable the paste burst off Windows

Refs #34496

* fix(codex): keep the paste-burst flag out of commands that end their options

Refs #34496

* fix(codex): decide the paste-burst flag from a paired server's own OS on resume

Resume quoting on a paired server keeps the client's platform, so a macOS
client resuming Codex on a Windows server would have disabled the burst that
Windows Codex needs. The resume target now carries the server's reported OS
and leaves the flag off when it is Windows or unreported.

Refs #34496

* fix(codex): add the paste-burst flag only when the command runs Codex itself

A wrapper override may not forward -c, which would break every launch.

Refs #34496

* fix(codex): leave the paste-burst flag off compound commands and unreported SSH hosts

Refs #34496

* test: leave pass-through resume input untouched in the banner spec

Refs #34496

* test(mobile): expect AI-vault Codex resumes to disable the paste burst

Refs #34496
2026-10-29 07:45:06 -07:00
Kelvin Amoaba c5957e7d4d fix(mobile): notice a silent drop while dictating within seconds (#35834)
An overdue chunk reply now asks the connection for one decisive liveness
probe instead of waiting for the idle watchdog, so the resend lands inside
the host's grace. Native clients probe directly; the OTA page asks the
shell with a new notify that older shells refuse with a diagnostic only.
2026-10-29 14:32:59 +00:00
Neil 770a1b8e6e fix(automations): let a host create workspaces for another owner's automation run (#35978)
A desktop older than #34525 still sends the host its own dispatch token, which
only that desktop can verify, so the host rejected every run with "Invalid
automation provenance request". A runtime host now ignores a provenance
request for an automation it does not own and creates a plain workspace;
requests for its own automations are still verified.

Refs #34433
2026-10-29 07:30:54 -07:00
Kelvin Amoaba 558be9909c fix(mobile): resend dictation audio lost when the connection drops (#35777)
* fix(mobile): resend dictation audio a dropped connection left unanswered

The host feeds chunks in seq order, each seq once, holding audio that
arrives ahead of a resend for up to the reconnect grace, and says so in
its start reply. To such a host the phone resends chunks rejected as
delivery-unknown after each reconnect, so speech sent on a dying
connection is no longer lost.

* fix(mobile): cap held dictation chunks by count as well as bytes

* fix(mobile): skip every gap needed to keep held dictation audio within the cap
2026-10-29 14:25:38 +00:00
Kelvin Amoaba 25bd428976 feat(mobile): stop tap-mode phone dictation after a minute of silence (#35716)
* feat(mobile): stop tap-mode dictation after a minute of silence

A forgotten mic kept recording and held the screen awake until stopped. Hold mode is left alone: a finger on the button isn't a forgotten mic.

* fix(mobile): time dictation silence from the last speech window the meter measured

* refactor(mobile): simplify the dictation silence timer
2026-10-29 14:24:18 +00:00
Neil 5cd7b5d52c fix(telemetry): send PostHog events through the main HTTP client so they honor the proxy (#35972)
* fix(telemetry): send PostHog events through the main HTTP client so they honor the proxy

* fix(telemetry): omit credentials on PostHog sends
2026-10-29 07:13:47 -07:00
Neil ba7b9405d7 fix(wsl): an automation's window create never starts its startup terminal as the user (#35970)
spawnLocalStartupAndSetupTerminals marks wslUserAct only when the create carries no automation provenance, so an automation create that sends a startup command still holds on a stopped WSL distro.
2026-10-29 07:08:32 -07:00
NeilandChad Tolkien b95b11cc6e perf(codex): resolve the Codex CLI once per spawn recheck, off the main thread on Windows (#35967)
Every native terminal spawn schedules a Codex hook reconcile, which resolved
the Codex path twice with a synchronous stat scan of PATH and install dirs
(~50ms main-process block per spawn on Windows with a long PATH and no Codex).

The reconcile now resolves the path once and passes it to both the live
lookup and the saved-answer read. On Windows, where each stat is slow, that
lookup runs every read (PATH candidates, nvm dirs and aliases, install dirs)
through fs/promises; macOS and Linux keep the sync scan, now run once. Sync
and async share one lookup that yields its reads, so they cannot diverge.

Hook rechecks stay immediate on every spawn and nothing is cached. A spawn
that lands before a run's hook-file pass (while it is still finding Codex or
waiting for its answer) gets one trailing rerun, so an install or upgrade
that moved Codex mid-run cannot leave hooks unchecked.

Fixes #35565

Co-authored-by: Chad Tolkien <chad@sodadigital.com.au>
2026-10-29 06:58:26 -07:00
Neil 3f8b15d5a7 test(wsl): stop a distro mid-reply on the real-WSL lane, then Start and Continue (#35826)
* test(wsl): stop a distro mid-reply on the real-WSL lane, then Start and Continue

* test(wsl): read the held partial reply from the chat stream, not history

* test(wsl): close the mid-turn chat streams when a wait fails

* test(wsl): reinstall the guest claude the decoy probe removed before the mid-turn stop

* test(wsl): read the continued turn from the scripted Claude's report log

After Start the restarted server's model discovery relaunches the same scripted
CLI, which overwrites report.json with a discovery-only report after the
continued turn finished. The CLI now also appends every report to an opt-in
log, and the mid-turn step finds the resumed launch there.

* test(wsl): match the continued launch by its session id, after Continue only
2026-10-29 06:55:57 -07:00
Neil 6eebdbbaf8 fix(wsl): keep a stopped distro's worktrees and re-list once it runs (#35733)
* fix(wsl): keep a stopped distro's worktrees and re-list once it runs

After a relaunch with a WSL distro stopped, the E9 gate refused the
worktree scan and the sidebar showed "Worktree scan failed" with no rows
until the user pressed Retry. The listing now answers with the stored
rows (non-authoritative, so nothing is pruned), marks the distro stopped
or unverifiable in a new optional field, and re-lists once a wake or a
settled running read shows the distro running. The re-scan is held once
per repo and dropped when the repo is removed. The sidebar shows a
neutral state instead of a failure.

* fix(wsl): make the stopped-distro Retry a user act that may start the distro

The indicator's click now runs the scan inside runAsWslUserAct for the
repo's distro (new worktrees:scanStartingWslDistro IPC), then re-lists,
so clicking it can start the distro instead of re-running the gated
background scan that never could.
2026-10-29 06:54:51 -07:00
Neil 2a5f6c79c1 test(e2e): on the real WSL lane, background work never starts the stopped distro (E9 guard) (#35590)
* test(e2e): on the real WSL lane, background work never starts the stopped distro (E9 guard)

The Windows real-WSL lane opens a project inside its own imported distro, terminates that distro,
and runs a window of background work elsewhere (focus/blur/reveal, workspace switches, a linked
issue/PR card, Settings panes, Tasks, account switches). Every child the main process starts is
logged; no wsl.exe may run anything in the distro (listings are fine), and `wsl --list --running`,
watched each second, must never show it, which also catches UNC touches. Opening a terminal in the
WSL workspace then starts it. A participation check fails the lane if the guard skipped.

* test(e2e): actually render each Settings pane in the WSL lane's E9 background window

openSettingsTarget alone only set the navigation target, so no pane mounted and none of its reads
(servers, accounts, agents, integrations, CLI status, the repo pane's orca.yaml check) ran. Open
Settings, wait for each pane's section to mount, then close it.

* test(e2e): harden the WSL lane's E9 guard per review

- process-host logs every spawn to ORCA_E2E_SPAWN_LOG when set, so the guard sees worker-thread
  and inheriting-child spawns (e.g. the Codex rate-limit fetch), sync ones included.
- The spawn baseline is taken before `wsl --terminate`, and the running-list watcher starts right
  after it, so work in the stopping window counts.
- A wsl.exe launch counts as starting the distro unless it names another or only lists/terminates.
- The watcher is async with a 5 s timeout and non-overlapping ticks; a failed read is retried and
  fails the guard after three, never reads as "stopped". The recorder throws if it cannot hook,
  and the spawn log must be non-empty. The doc names the watcher as the authoritative assertion.

* test(e2e): bound the terminate grace, keep the spawn log out of shipped runs, tolerate torn lines

- The watcher forgives running reads only within 3 s of `--terminate` and before its first
  not-running read, which the rounds now await; any running read after it fails the guard.
- Main drops ORCA_E2E_SPAWN_LOG outside e2e mode (no ORCA_E2E_USER_DATA_DIR) before anything
  spawns, so no daemon, PTY or agent inherits it; the log holds full argv, tests only.
- readSpawnLog skips a line torn by a concurrent append.

* test(e2e): no blocking wsl.exe in the WSL lane's E9 guard, and a timeline for failures

The first lane run was cancelled at the 30-minute job limit while the guard step had run 25
minutes: past three 5-minute test timeouts, which only a blocked worker survives. Every wsl.exe
call (setup, terminate, cleanup) is now async with a timeout, so a hung one fails the test. The
guard logs a timestamped timeline (each running-list read, terminate, rounds) for diagnosis, and
the job limit goes to 50 minutes for the three added runs.

* test(e2e): let the setup's own WSL terminal launch settle before the terminate; 10 s grace

The lane run failed with "Ubuntu was still listed 3000 ms after --terminate" in all three runs.
The timeline showed why: focusing the WSL workspace in setup is a user act that opens a WSL
terminal, and its launch (codex trust probe, PTY, hook relay) landed about a second after the
terminate and restarted the distro. Setup now waits for that terminal's PTY and for wsl.exe
launches to go quiet for 3 s before terminating. The grace is two running-list reads (10 s).

* test(e2e): interleave wsl.exe launches into the E9 guard's timeline

The lane saw the distro boot about two seconds after the terminate with nothing naming the
launcher; each running-list tick now also prints the wsl.exe launches logged since the last one.

* test(e2e): time and stack every logged spawn and main's WSL UNC touches

The E9 guard printed spawns at its poll time, up to a second late, and with
no caller. Each record now carries when it happened and the caller's stack,
and main's fs touches of \\wsl.localhost paths are logged too, so a boot
with no wsl.exe launch before it can still be attributed.

* test(e2e): keep spawn-log stacks and main's fs hooks strictly to e2e mode

process-host now writes the spawn log, and captures caller stacks, only when
ORCA_E2E_USER_DATA_DIR is set as well, so a stray log variable in a CLI or
daemon environment does nothing. The main-process fs/spawn hook refuses to
install outside an e2e-mode app, and a source scan pins that no shipped code
imports or installs it.

* test(e2e): guard a focused WSL workspace left idle through the stop

Device runs saw a stopped distro restart with its bridge workspace focused
and nothing touched: the stop's own fallout re-ran renderer reads as user
acts. The new case keeps that workspace focused with a folder expanded in
the Explorer and Source Control shown, terminates, and idles. Shared guard
helpers move to tests/e2e/helpers/wsl-boot-guard.ts.

* test(e2e): capture caller stacks deep enough to reach the IPC handler

* test(e2e): give the focused-idle guard's timeout bump its reason
2026-10-29 06:52:46 -07:00
NeilandChad Tolkien 4642bc00d6 fix(telemetry): never deliver held startup events later than an eager client (#35949)
#35798 holds pre-window captures until the first window shows, which
can delay delivery past what an eager client does: 20 quick events
waited instead of tripping flushAt, and the first flush moved from 10s
after the first event to 10s after the client started. Held events now
start the client as soon as they reach flushAt, and a deadline 10s after
the first held event starts the client if needed and flushes. It stands in
for the timer an eager client would have armed, so it retires at the
SDK's first flush attempt (the flushAt-th enqueue, whether that send
succeeds or fails); after that the SDK's own flushAt and flushInterval
alone decide batching. Held
events are dropped when consent was withdrawn before start, and quit no
longer loads the SDK just to drop them.

Fixes #35752

Co-authored-by: Chad Tolkien <chad@sodadigital.com.au>
2026-10-29 06:26:41 -07:00
Neil 0bd7ecf1f3 perf(session-search): append to the index after a restart instead of rewriting grown transcripts (#35941)
Parser resume points live only in memory, so the first time a transcript grows
after a restart it is refolded from byte 0 and the search index deleted and
reinserted every row it held for that file. On a 40MB transcript that cost
2.8-11.9s per file versus about 1s for the parse alone.

The index now stores a SHA-256 of bytes [0, byte_offset) with each commit. The
replacement read the parser already does from byte 0 hashes as it goes; when it
reaches the cursor with a matching digest, the index appends only the messages
after it. A rewritten prefix, a cursor the read never lands on, or a replaced
inode falls back to a full replacement, and read failures still count toward
holding a file out. The parser still reads from byte 0 after a restart; the
saving is avoiding the delete and reinsert, not I/O.

Version 8 indexes gain prefix_hash additively under BEGIN IMMEDIATE, so
concurrent openers add it once; a missing hash costs each file one replacement.
SESSION_SEARCH_SCHEMA_VERSION bumps remain the mechanism for parser fixes.

Refs STA-12516
2026-10-29 05:58:10 -07:00
Neil 1a12ec8864 fix(telemetry): keep the SDK opted out while an env override disables telemetry (#35940)
DO_NOT_TRACK, ORCA_TELEMETRY_DISABLED and CI markers beat the stored
choice, but setOptIn(true) and banner acknowledgement still re-enabled
the SDK, and setOptIn(false) then captured telemetry_opted_out, which
quit flushed. Consent-change paths now skip SDK opt-in and the direct
opt-out capture while an env override is active; the client is already
opted out at creation under the override.
2026-10-29 05:58:05 -07:00
Neil 1263370049 fix(wsl): hold a daemon-off restore of a stopped WSL distro (#35938) 2026-10-29 05:42:45 -07:00
Neil 0ff3fb67c9 fix(wsl): held terminals read as a sentence; a person's worktree create starts its own terminals (#35935)
RPC errors for a terminal held on a stopped or unchecked WSL distro keep runtime_error but carry a readable message and the hold in data. A headless launch the runtime held records the coded wslDistroStopped skip. A worktree create someone asked for (UI, CLI, agent, paired client) starts its setup and default terminals as their act; an automation's create stays background.
2026-10-29 05:32:55 -07:00
Neil 8cc687818e test(e2e): force the stale-title window in the live-reattach mode check (#35936)
The same-process serve restart step relied on timing to land the reattach
replay inside the window where the host still publishes the tab-default
title, which happened about one run in five. Pin the client's published
pane title to the tab default and clear its pane status for the duration
of the restart, so the replayed image's own title alone must prove the
live agent. The check fails 3/3 without #35920 and passes with it.

Refs #35559
2026-10-29 05:30:32 -07:00
Neil 010d0d586a feat(wsl): a pane main held for a stopped distro shows the Start card (#35927)
The renderer maps main's terminal_held_wsl_distro_stopped start failure to #35813's shared stopped card; its Start calls layout.startWslHeldPanes, which starts each pane through ensureRestoredPaneStarted as the user's act.
2026-10-29 05:11:38 -07:00
Neil 434ffbac1b fix(wsl): a passive restore's env prep never starts a stopped WSL distro (E9) (#35858)
* fix(wsl): a passive restore's env prep never starts a stopped WSL distro (E9)

resolvePtySpawnHostEnv runs before #35813's hold. Every spawn but a passive
restore now runs its prep as a user act on its distro, so explicit launches
are unchanged. prepareForCodexLaunchAsync checks blockedWslDistroForExec once
for WSL targets and, when blocked, skips the guest-home probe, the legacy auth
drain, the config and instructions sync and the session bridge, returning the
home without probing. When not blocked, the home is warmed with getWslHomeAsync
so the sync reads hit the cache instead of a 5 s wsl.exe. The state-DB
recovery and the explicit plugin readiness wait are gated the same way.

* fix(wsl): only a launch someone asked for acts on its distro; gate the relay start

A passive restore or a non-WSL launch never runs as a user act, not even on
the default distro (a null distro resolved to the default before). The
daemon-off local provider applies the same rule, receiving wslPassiveRestore
from the spawn options. Starting the hook relay (ensureForDistro from every
WSL spawn's env build) now checks blockedForExec before the state check, so a
passive restore on a stopped distro starts no relay.
2026-10-29 05:10:19 -07:00
Neil 43ed9878b7 fix(terminal): judge live-agent reattach modes by the image's own title (#35920)
After a serve restart where the daemon survives, a paired client could
drop mouse and focus reporting for a still-live agent. The host publishes
the pane with its tab-default title until it seeds the daemon's live title
about 180 ms later. A reattach image replayed inside that window was judged
against the default title, read as a plain normal-buffer program, and got
the reset that disarms its modes, though the image itself carried them.

The snapshot now carries the host's tracked title when the image was taken,
as an additive `lastTitle` field in the snapshot info, gated on the image's
seq. Every reset that judges a replayed image as a live agent's (the
post-replay reset, the focus-in that follows it, the hidden-restore reset,
and the model-snapshot reattach) uses that title when present, so the
decision describes the same moment as the replayed bytes. Local Electron
model snapshots already carried this title and now use it the same way. The reattach policy itself is
unchanged: non-agent normal-buffer panes still shed modes a dead child may
have left (#7329, #7893).

Older hosts omit the field and clients fall back to the published title.
Older clients ignore it.

Refs #35559
2026-10-29 04:45:26 -07:00
OrcaWinandOrcaWin c40f32163e fix(mobile): translate coded generation failures and counted chat copy (#35923)
- Commit-message generation keeps the host's optional errorCode, errorParams and
  errorDetail and translates every known text-generation code, matching desktop's
  rules: unknown codes, older hosts or missing values show the host's English
- Background tasks, subagent groups, the model label and agent-session write
  notices use localized mobile adapters instead of the shared English sayers

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-29 04:39:23 -07:00
NeilandChad Tolkien 594844cf0f perf(ssh): load ssh2 on first use instead of at startup (#35911)
Every value import of ssh2 sat on the main-process startup graph, so
Electron paid for loading ssh2 before any window even when no SSH host
is configured. Route all runtime ssh2 access through a lazy loadSsh2(),
define the BaseAgent subclasses on first use, and read ssh2's default
host-key algorithm list on first connect instead of at module load.

Profiles with saved SSH targets still load ssh2 during startup, right
after the profile store loads and before any window or reconnect, so
SSH users keep the old timing. A failed eager load is logged and
surfaces at connect instead of aborting startup. Profiles without SSH
targets never load ssh2 until first use.

An oxlint no-restricted-imports entry rejects value imports of ssh2
outside tests, and a guard test imports the SSH connection graph to
prove ssh2 stays unloaded. SSH suites mock the loader seam instead of
the ssh2 package, so no global vitest setup is needed.

Fixes #35731

Co-authored-by: Chad Tolkien <chad@sodadigital.com.au>
2026-10-29 04:28:00 -07:00
Neil b58d758599 fix(claude): resume a chat with a saved effort on a Claude Code that predates --effort (#35878)
* fix(claude): resume a chat with a saved effort on a Claude Code that predates --effort

A chat resumed with its saved effort always launched Claude with --effort,
which Claude Code before 2.1.38 rejects as an unknown option, so the chat
could never start again. New chats carry no saved effort and started fine.

Gate effort on the CLI version facts Orca already probes for
--thinking-display and --plugin-dir, which now answer supported,
unsupported or unknown. A chat with a saved effort waits out the version
probe and puts --effort on the command line only where the CLI has it.
Live effort and Fast changes use a control request Claude Code before
2.1.50 never answers, so on those CLIs saved choices stay saved but are not
sent, the chat offers no effort or Fast choice, and a live change is
refused at once instead of after a 30 second wait. Unknown versions keep
today's behavior.

Fixes #34795

* test(claude): give the scripted runtime a current Claude Code's flag support

Production always passes flag support, so a runtime without it now reads
as a CLI whose version is unknown and keeps saved effort off the command
line. The scripted CLI stands for a current Claude Code, so name its
version and keep its saved choices on the launch.

Refs #34795
2026-10-29 04:24:02 -07:00
OrcaWinandOrcaWin 4e9e320e32 fix(i18n): translate remaining renderer leftovers (submodules, agent summaries, filter counts, automations) (#35914)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-29 04:16:11 -07:00
Neil af8117c07a perf(native-chat): stop re-rendering composer controls on every keystroke (#35913)
* perf(native-chat): stop re-rendering composer controls on every keystroke (#35836)

Each keystroke (and each IME compositionupdate) re-rendered the composer's
action bar and TipTap editor, which re-applied editor options and re-decorated
the view, even though neither had changed. The draft store also gave each write
a fresh image-row array, which invalidated every image consumer.

Memoize NativeChatComposerActions and NativeChatPromptEditor (initialValue only
seeds the editor at mount), pass them stable handlers that call the latest
committed closure, and keep the stored image row's identity when it's unchanged.
Draft-store writes, onDraftChange and every consumer still see each edit
synchronously, so composition semantics are unchanged.

CDP-simulated pinyin IME, 4x CPU throttle, median per compositionupdate:
14.5ms -> 6.8ms (PTY composer), 14.3ms -> 7.2ms (structured composer).

* chore(native-chat): note the prompt editor memo's stable-props contract
2026-10-29 04:12:00 -07:00
Neil 211bbbb6d1 fix(wsl): main's background terminal starts never start a stopped WSL distro (#35865)
* fix(wsl): main's background terminal starts never start a stopped WSL distro

Every start main makes through the runtime controller is background work unless its caller names the user's act (CLI and window creates and splits, paired-client creates and tab opens, agent launches and sessions, conversation resumes). #35813's hold now also holds those fresh and restored starts while the distro is stopped or unverifiable.

* fix(wsl): headless Run now starts as the user's act; update create expectations

A headless automation's Run now names the user's act through launchAgentTerminal, so it may start a stopped WSL distro; a scheduled run stays background and is held. The paired-client background create expectation now includes wslUserAct.

* fix(wsl): main's background start checks a folder workspace's path passively

With #35899's folder stat gate, the runtime preflight passes passiveWslRestore = !wslUserAct, so a background start never stats a stopped distro's folder as a user act before the daemon hold.
2026-10-29 03:54:50 -07:00
Neil a5bb1ae9ce fix(automations): a scheduled run never starts a stopped WSL distro (#35857)
* fix(automations): a scheduled run never starts a stopped WSL distro

Scheduled runs whose target is on a stopped (or unverifiable) WSL distro skip with a coded, retryable wslDistroStopped reason naming the distro, checked at the tick and again at dispatch; repeated ticks fold into one skip. Run now is the user's act and still starts it.

* fix(automations): gate host-path WSL projects by their distro; translate the skip

A host-path project whose Git runs in WSL passes its project distro to the stopped-distro check (as #35778 does). wslDistroStopped is translated into the 10 other locales.
2026-10-29 03:49:40 -07:00
Neil 3ef3123f26 fix(wsl): the port scanner never realpaths a stopped distro's workspace root (#35907)
The workspace port scanner resolves each workspace root's realpath to match
listening ports; for a \\wsl.localhost root that 9P touch started a stopped
distro about 17 s after it was terminated (E9). It now leaves a stopped
root's alias unknown, as a timed-out lookup does, and never wakes.
2026-10-29 03:49:17 -07:00
Neil 31b672e995 fix(wsl): Source Control's PR push-target and Create PR checks never start a stopped distro (#35885)
* fix(wsl): Source Control's PR push-target and Create PR checks never start a stopped distro

Showing Source Control resolved the linked PR's push target
(worktrees:resolvePrBase) and its Create PR eligibility
(hostedReview:getCreationEligibility); both read the repo's git config over
\\wsl.localhost and ran git and gh in the distro, starting a stopped one
(E9). Both now refuse a stopped distro unless the user is creating or
launching from the PR, or pressed Create PR (optional wakeWslDistro), in
the IPC handlers and their runtime twins.

* fix(wsl): gate the GitLab MR push-target lookup the same way

Source Control's push-target ensure also resolves a linked GitLab MR
(worktrees:resolveMrBase / worktree.resolveMrBase), which read the repo and
ran glab and git in a stopped distro. It now refuses unless the user is
creating from the MR, which passes wakeWslDistro.

* test(runtime): let the mocked running gate cover guardWslUncPathAccess too

Runtime specs fake win32 with no wsl.exe and mock blockedWslDistroForPath
to running; the real guard calls its own module's check, so the PR-base,
MR-base and eligibility gates refused there. The guard mock now asks the
mocked check, keeping per-test overrides working.
2026-10-29 03:48:58 -07:00
Neil 557f281780 fix(wsl-host): a chat holds Send while its WSL distro is stopped, until Start (#35864)
* fix(wsl-host): a chat holds Send while its WSL distro is stopped, until Start

* test(wsl-host): find the held Send by the reason it waits
2026-10-29 03:46:05 -07:00
Neil 83909a9b72 fix(wsl): issue-command follow-ups: no-op saves, WSL Git repos, paired hosts (#35900)
- A blur with no edit saves nothing, so it never starts a stopped distro.
- The write gate also covers a host-path repo whose Git runs in WSL (its
  save's ignore check); clearing runs no Git and stays lazy.
- A paired host's refused read is re-asked on focus and every 30 s while
  visible, through a watchRefusedWslRead extracted from the base-ref
  watcher, which now uses it too.
2026-10-29 03:43:42 -07:00
Chad TolkienandNeil 568cd1ba72 perf(telemetry): load posthog-node and start its client after the first window (#35798)
Requiring posthog-node at bundle evaluation and constructing the PostHog
client in initTelemetry put ~58ms on the main-process startup path before
any window showed. The SDK is now required lazily inside
createPostHogClient, and initTelemetry defers client creation until the
first window is shown (15s fallback for headless serve / GPU failures).

Captures tracked before the client starts are held with their original
timestamps and replayed in order on start. Opt-out and shutdown start the
client first so held events flush and telemetry_opted_out still enqueues
before optOut(); a client start failure no longer blocks either path.

Fixes #35752

Co-authored-by: Neil <neil@stably.ai>
2026-10-29 03:36:04 -07:00
Venius24andNeil e08616e95d fix(orchestration): name the provider's words when a worker's preamble is rejected (#35791)
When a structured worker's dispatch preamble is definitively rejected, the
dispatch_preamble_undelivered error now appends the provider's failure detail
("Details: ...") unless the rejection sentence already quotes it. The quoted
text is bounded and normalized by the same quotedProviderDetail helper the
rejection writer uses.

Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-10-29 03:35:49 -07:00
Neil bccdb0254f fix(wsl): never boot a stopped distro to check a folder workspace's path (#35899)
A background read of a \\wsl.localhost folder workspace's status (sidebar
polling, runtime status RPC, a passive terminal restore) stat'ed the folder
and booted its stopped distro. The status now reports 'unavailable' without
touching it; a passive restore holds with Start; a spawn the user asked for
checks the folder as their act, which may start the distro as before.
2026-10-29 03:32:56 -07:00