Commit Graph
16306 Commits
Author SHA1 Message Date
Neil cc2b6fe6a2 build(mobile): move to TypeScript 7 and Vitest 5, typecheck through the root tsrs runner (#33419)
- Bump mobile typescript to ^7.0.2 and vitest to 5.0.5; keep TypeScript 6 as typescript-api
  for the test-support code that walks the AST, mirroring the root.
- Patch Expo CLI's tsconfig reader to fall back to typescript-api: TypeScript 7 has no
  compiler API, and Metro otherwise crashes reading tsconfig paths.
- Register mobile/tsconfig.json and mobile/tsconfig.test.json with the root typecheck runner;
  locally they use incremental tsrs (Bun elsewhere), CI uses Bun like root CI.
- Route the tests-typecheck ratchet through the same compiler selection.
- Enforce root/mobile parity for typescript, typescript-api and vitest.
2026-10-24 23:09:20 -07:00
Neil fef00e4461 feat(dev): give each worktree its own pnpm dev profile and ports (#33416)
Linked worktrees now get a per-worktree userData profile under
orca-dev-wt/<hash>, a deterministic collision-checked renderer port
(15200+) and CDP port (9333+), so parallel `pnpm dev` runs no longer hit
the single-instance lock. The main checkout keeps the shared orca-dev
profile; --shared-profile / --worktree-profile / ORCA_DEV_PROFILE override.

The runner prints `ORCA_DEV_INSTANCE {json}` on stdout and mirrors it to
out/dev-instance.json so agents can attach over CDP. orca-dev.mjs and the
terminal wrappers follow the profile the worktree's dev app used.

Adds profile seeding (pnpm dev:seed-profile, pnpm dev --seed-profile,
pnpm dev:fresh --seed-from): an allowlisted slim copy of projects,
workspaces, settings, SSH targets, and the account session, written as
orca-data.json for Orca's own import; the source is only read.

Storage reclaim removes per-worktree profiles once their worktree is gone
and no app, runtime, or daemon still uses them. dev-fresh-profile.sh is
replaced by a cross-platform dev-fresh-profile.mjs.
2026-10-24 23:08:52 -07:00
Neil 4b347681bc feat(mobile): skip dev-client rebuilds when the Expo native fingerprint is unchanged (#33410)
Add `pnpm native ensure <ios|android>`, which installs a cached dev-client
build keyed by the Expo fingerprint and only runs `expo run` on a miss.
The cache lives in the user cache dir so every worktree shares it, and
app.config.js registers it as Expo's buildCacheProvider so plain
`expo run` reuses hits too. A report-only PR job flags fingerprint
changes against the base in the step summary.
2026-10-24 23:08:43 -07:00
OrcaWinandOrcaWin 4633fa9d07 fix(sidebar): keep the workspace hover card open behind link actions (#33409)
* fix(sidebar): keep the workspace hover card open behind link actions and system-browser opens

* fix(sidebar): scope the unfocused hover hold to external opens and fix chooser dismissal edge cases

* perf(sidebar): register the refocus listener only while a hover hold is set

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-24 23:07:54 -07:00
OrcaWinandOrcaWin e1066973f2 fix(worktrees): never leave the create-worktree dialog stuck on a stalled preflight (#33400)
* fix(worktrees): never leave the create-worktree dialog stuck on a stalled preflight

Bound the same-repo PR branch fetch with the review-head fetch timeout, and
hand a slow composer submit off to a staged sidebar row so the dialog closes.

* fix(worktrees): harden create-worktree handoff from adversarial review

Dedupe handed-off submits against linked creations resolved during preflight,
bound the staged sidebar row with a deadline, offer Reopen on handed-off
failures, and bound the GitLab MR branch fetch.

* test(worktrees): expect the bounded PR/MR branch fetch timeout

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-24 23:00:47 -07:00
Neilandinnocarpe 10005e5d15 fix(hermes): use the Windows %LOCALAPPDATA%\hermes home for hooks and session history (#33413)
Hermes home resolves like Hermes itself on Windows (HERMES_HOME, else %LOCALAPPDATA%\hermes, else a legacy ~\.hermes that alone exists), shared by hook install/status, skill discovery and session reading; session reading also honors HERMES_HOME. Fixes #24966. Supersedes #25058.

Co-authored-by: innocarpe <2222333+innocarpe@users.noreply.github.com>
2026-10-24 22:56:51 -07:00
NeilandNaveen Chatlapalli 062ebbaf26 fix(agents): pre-trust the folder for Kimi Code so its trust menu does not swallow the prompt (#33411)
Kimi Code workspace pre-trust: Orca writes Kimi's own trust record (<KIMI_CODE_HOME or ~/.kimi-code>/workspace-trust/wd_<slug>_<hash>) for the exact launch folder, so Kimi's "Trust this folder?" menu no longer swallows Orca's prompt. Never pre-trusts the home folder or a filesystem root. Fixes #26680. Supersedes #26738, #26741.

Co-authored-by: Naveen Chatlapalli <54518943+1aifanatic@users.noreply.github.com>
2026-10-24 22:56:46 -07:00
Neil 4ff84bb835 fix(mobile): stop phones losing terminal output sent as output spans (#33405)
* fix(mobile): stop losing phone terminal output sent as output spans

Hosts sent stripped/transformed terminal output to terminal.subscribe as
OutputSpan (opcode 15), which no phone has ever decoded. The phone dropped
it silently, e.g. the reset after a killed full-screen program, leaving
the phone stuck on the alternate screen.

- Host: terminal.subscribe sends spans as plain Output text; only the
  desktop multiplex (which decodes spans) keeps OutputSpan.
- Mobile: decode OutputSpan for older hosts, use the shared protocol
  table, and end the stream with a logged error on any frame it can't read.

* fix(mobile): throttle unreadable-frame resets and log once per stream

* fix(mobile): reset a throttled stream after the window; forget released stream ids

* fix(mobile): defer throttled unreadable-frame resets instead of dropping them
2026-10-24 22:56:11 -07:00
OrcaWinandOrcaWin 3d24e61726 test: remove self-validating checks and exercise intended boundaries (#33412)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-24 22:52:08 -07:00
NeilandClaude Opus 5.5 8e9f86b119 feat(managed-server): Update and Update anyway for a managed SSH server, keeping its running terminals (#33397)
* feat(managed-server): offer Update and Update anyway on a managed SSH server's row

A managed SSH server only updated on connect, and every connect deferred while
a terminal ran there. Its row now offers Update when this desktop bundles a
newer build than the Orca that runs the host. When running terminals defer it,
Update anyway restarts the server past them; the terminal daemon carries them
across the restart.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(managed-server): Update anyway passes only counted live terminals and keeps Update's version admission

Update anyway now sends the incumbent build it confirmed over; under the host
fence it is refused if another desktop activated a build since, keeps the
newer-host downgrade refusal, and never passes an uncounted terminal census.
The offer also covers hosts activated before the Orca version was recorded,
for the build this desktop already deferred there.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(managed-server): Update anyway also confirms a menu Update the row does not advertise

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(managed-server): the row shows one confirmation at a time

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* ci(e2e): route managed-server update sources to the Update anyway Docker spec

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-24 22:49:18 -07:00
NeilandClaude 1592ef1fc9 fix(terminal): a slept WSL pane on Windows wakes in the folder its shell was in (#33398)
* fix(terminal): a slept WSL pane on Windows wakes in the folder its shell was in

Bash in a WSL pane on Windows reports no folder (no OSC 7), so Sleep's final checkpoint kept the
folder the pane was spawned in, and Wake reopened every such pane at the workspace root. Before
that checkpoint, the daemon client now reads the pane shell's folder inside its distro, from
/proc, by the ORCA_PANE_KEY its processes inherit, and stores it in the checkpoint Wake restores
from. The read is one fixed script per distro per Sleep batch, shared while in flight, capped at
3 s and a quarter of the Sleep's remaining budget. It only runs in a distro that is already
running. Any failure keeps today's spawn folder.

After an app relaunch, the client learns each live WSL session's distro and pane key from the
sessions it adopts at startup. A pane slept before the window reopened it therefore keeps its
folder too.

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

* fix(terminal): read only Orca pane processes, skip the running check for live panes, wake at the asked folder when the kept one is gone

Review of #33398:
- Speed: the guest script forked about a dozen times per /proc entry (2.3 s for 405 processes),
  so busy distros spent the whole 3 s budget. One `grep -l` over every environ now picks the
  Orca pane processes first, and only those are read, so the cost no longer grows with the
  distro's process count. A test counts the tool calls for 5 and 400 other processes and
  expects the same number.
- Fewer launches: a live pane's own wsl.exe keeps its distro running, so a Sleep of a live pane
  skips the `wsl --list --running` check and costs one launch per distro instead of two.
- Deleted folder: a pane whose kept folder is deleted before Wake failed to spawn when the
  guest could not answer the folder probe. Wake now retries once in the folder the pane asked
  for, as before the folder was kept.
- Drops the unused sid and argc fields.

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

* fix(terminal): skip the running check only for a distro last seen running; keep the asked folder through the restore re-spawn

Review of #33398:
- A live pane skips the `wsl --list --running` check only when its distro is also in the last
  confirmed running list. The client can still hold a session as live after its distro was
  terminated or across a daemon disconnect, and reading that distro would boot it.
- The probe-to-create race re-spawn now reads back the folder the spawn actually used, so after
  the deleted-folder retry neither `initialCwds` nor the cold-restore payload keep the gone path.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-24 22:47:57 -07:00
Jinwoo Hong 0ae42fe594 feat(relay): cap how many reserve cells a director outage trips to the database (#33415)
<!-- orca-pr-loc -->
<!-- Programmatic LoC summary. Do not edit by hand; rewritten on every commit. -->

| | Files | Added | Deleted | Net |
| :--- | ---: | ---: | ---: | ---: |
| Test | 4 | $\color{#1a7f37}{\Huge{\mathbf{+}}}$​406 | $\color{#cf222e}{\Huge{\mathbf{−}}}$​4 | $\color{#1a7f37}{\Huge{\mathbf{+}}}$​402 |
| Prod | 6 | $\color{#1a7f37}{\Huge{\mathbf{+}}}$​165 | $\color{#cf222e}{\Huge{\mathbf{−}}}$​10 | $\color{#1a7f37}{\Huge{\mathbf{+}}}$​155 |

<!-- /orca-pr-loc -->

## ELI5

Relay servers ("cells") normally get their seat bookings from the front door (the director). If the director goes quiet for a minute or two, each cell switches itself to an older mode that talks to the shared Postgres database for every connection (the "dead-man" trip). Today every cell switches at once, so the database budget must keep room for every cell's full connection pool. This PR adds a per-cell switch that caps how many cells may trip at the same time. A cell that cannot trip yet stays in its normal mode and keeps serving the desktops it already has.

## What Changed

- New cell flag `deadManTripSlots` (integer 0-64). Absent, which is every cell today, keeps the current behaviour: trip as soon as the dead-man window (60-120 s) passes.
- With the flag set, a cell whose window has passed first claims a slot in Postgres. The claim is one short transaction: it takes an advisory lock without waiting, counts the other reserve cells whose own `relay_cell_admit_effective` row says `db`, and writes this cell's row to `db` only if that count is below the flag. Only then does the cell trip (void bookings, re-register its hosts, run on its full pool).
- A cell that gets no slot is "held". It stays in reserve mode, logs `orca_relay_cell_reserve_dead_man_held` once, keeps writing `reserve` to its row, and asks again every 30 s.
- A slot is held while the holder's own row says `db`, its switch still says `reserve`, and the row was refreshed in the last 10 minutes. A holder that restarts or is re-armed writes `reserve` and frees its slot. One that died tripped frees it after 10 minutes. Heartbeats are not used: they reach Postgres through the director, so in a director outage they all go stale and every slot would read as free.
- A tripped cell skips its 15 s row refresh while its pool has waiters. So that a long brownout cannot age its row out of the count, a tripped cell's row older than 5 minutes is refreshed even when the pool is busy.
- A trip stays latched after the director returns, as today. The tripped cells keep their slots until an operator re-arms them with a new reserve write. Until then, a second outage trips nobody new: the other cells stay held. That is the intended bound, but re-arming after an outage now also frees the slots.
- The claim goes through `CellAdmitEffectiveWriter`, which already serialises every write of that row. A `reserve` write still in flight therefore cannot land after the claim and hide the slot from the next claimant.
- Cells keep their existing per-cell window spread (60-120 s by cell-id hash), so the earliest windows claim first.

What a held cell does during a director outage (unchanged code paths, now for longer than the window):
- Desktops already connected keep their sockets. Nothing is closed or re-registered.
- A desktop that drops and redials at the same epoch is admitted from memory (`seatedHereAt`). Any other hello falls through to the normal database check, as it does in reserve mode today.
- The database placement path places no new desktops on it, because its row says `reserve`. It also never re-places the held cell's desktops, because `liveReserveCell` keys on the switch (`relay_cell_admit_modes`), not on this row.
- New placements during the outage go to cells already in database mode (c32, c33 and c34 today) plus the tripped slot holders.

## Why

The database budget (Cloud SQL `orca-cloud-auth-db`, shared with auth, max_connections 500, 490 usable) currently reserves every relay cell's full pool. The only reason for that is the dead-man trip of the whole fleet at once. Spare is 35 on main and about 3 once C36/C37 land, which caps the number of Asia cells.

Budget arithmetic (`relay-cloud-sql-connection-budget.mjs` on orca-cloud main: directors 15, auth 20, API 50, rollout overlap 65, maintenance 5, explicit reserve 10):

| | Cells line | Operating | Spare |
|---|---:|---:|---:|
| Main today: 6 db/legacy cells at 10, 16 US at 8, 7 Asia at 16 | 300 | 455 | 35 |
| Plus C36/C37 at 16 | 332 | 487 | 3 |
| Same fleet, `deadManTripSlots=2`, Asia reserve cells budgeted at 12 (measured peak) | 76 + 128 + 8x12 + 2x4 = 308 | 463 | 27 |
| Plus two more Asia cells at 12 | 332 | 487 | 3 |

So with two slots, two more Asia cells fit in the room C36/C37 leave today. Two caveats keep this honest:
- The bound holds only once every reserve cell runs this image with the same flag value. An older image ignores the flag and trips freely, though its `db` row still takes a slot from the capped cells. With mixed values the cap is the largest value set.
- Counting reserve cells at their measured peak is a softer guarantee than counting their configured pool: the pool is still 16, and a reserve cell could burst past 12. Making it a hard bound needs a pool ceiling that depends on the mode (reserve-mode ceiling 12, the full 16 only for a slot holder or a deliberate db cell). That is a follow-up; this PR does not change pool sizes.

Alternatives considered:
- Longer or ranked trip delays only spread the trips out. A trip is latched until the switch is rewritten, so after the longest delay every cell is still in database mode. That bounds the burst, not the budget.
- A static per-cell "may trip" list in the switch file needs no database coordination, but a listed cell that is down or draining wastes its slot, and an operator has to keep the list current.
- Removing the fallback (cells never trip) is the target design, but it waits on the director stall fix and two weeks of clean running. `deadManTripSlots=0` gives exactly that per cell, so it is the same switch.

Deliberate db flips (for example, flipping reserve cells to db for an image roll) are outside the cap. Budget each Asia cell flipped that way at its full 16. With a two-cell Asia roll batch running during an outage, the slot row above is 316, which leaves room for one more Asia cell rather than two.

Rollout: the flag is dark. Setting it is an `Operate Relay Production Cell Flags` write, per cell, after every reserve cell runs this image.

## Linked Issue

N/A (internal relay capacity work).

## Visual Proof

N/A: relay server behaviour only, no UI.

## Testing

- `cell-reserve-dead-man.test.ts` (new): trips as before without the flag; held until a permit, then latched; slot 0 never trips; a permit for a silence that contact ended, or for an earlier switch generation, is dropped.
- `cell-admit-effective-writer.test.ts`: the claim waits for an in-flight reserve write; a refused claim keeps the reserve row fresh and retries after 30 s; no claim while the pool has waiters or the switch is off reserve; a tripped row is refreshed past a saturated pool after 5 minutes, a reserve row is not.
- `cell-reserve-dead-man-slots.blackbox.test.ts` (new, real cell on SQLite): a director outage with the only slot taken by another cell that writes nothing but its own row: the cell stays reserve across several claims, re-registers nothing, keeps its desktops connected, and lets a redialing desktop back in from memory; once the peer writes `reserve`, the cell trips and re-registers. Also: slot 0 rides out 30 minutes of outage without tripping; no flag trips as before.
- `cell-dead-man-trip-slot-postgres.test.ts` (new, PostgreSQL): eight cells claiming together get exactly two slots; a live holder keeps its slot; a stale one frees exactly one. Removing the advisory lock fails this test.
- Mutation checks: making the slot count always 0, or removing the lock, each fails a test.
- Full relay suite, including the PostgreSQL suites against a local PostgreSQL 17: 173 files, 1564 passed, 3 skipped. The pr.yml static-analysis steps pass locally.
- Two rounds of adversarial review. Round 1 found that the first version counted holders by director-written heartbeats (fixed). Round 2 found the brownout aging (fixed) and the latched slots after an outage (documented above).

- [x] I manually tested these changes locally
- [x] Automated tests added/updated, or explained why not below

## Review

## Agent skill upstream boundary

- [x] Not applicable, or this change copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

## Notes

No new table or column. The claim reads `relay_cell_admit_effective` and `relay_cell_admit_modes` (one row per cell). Under a fleet-wide trip, held cells add one short transaction each per 30 s; the advisory lock is held for about one round trip.

## Checklist

- [x] This PR is small and focused
- [x] I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
- [x] Before/after screenshots or videos attached for UI changes, or `N/A` with reason
- [x] Self-reviewed for correctness, security, and performance
- [x] Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
- [x] `pnpm lint`, `pnpm typecheck`, `pnpm test`, and `pnpm build` pass (or CI will cover; local preferred)
2026-10-25 01:46:23 -04:00
OrcaWinandOrcaWin e7a567b178 fix(worktrees): explain the shallow-clone local base warning and let users stop the refresh (#33260)
* fix(worktrees): explain the shallow-clone local base warning and let users stop the refresh

When a shallow clone hides the shared history, the local base fast-forward
refusal now says so and shows the git fetch --unshallow command, instead of
blaming local-only commits. Every refresh-failure toast also gets a
"Stop updating local main" action that turns off Keep Local Main Up to Date.

* feat(worktrees): add an Unshallow button that runs the fetch in a new terminal tab

* feat(worktrees): rename the opt-out to Disable Check and explain it in a tooltip

* fix(worktrees): harden the Unshallow launch

Reuse createNewTerminalTab (owner-routed, no focus read) instead of a
parallel launcher, report real host-launch outcomes before dismissing,
only offer the button for shell-inert remote names, route activation by
the creation host, ignore double clicks, and treat only a silent exit 1
from merge-base as a missing merge base.

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-24 22:45:21 -07:00
Neil 62d9706c5a fix(agents): start a Claude worker on its composer when its terminal title is off (#33384)
Claude worker-start also becomes ready when Claude's empty composer (❯ + NBSP, only after Claude's DECSET 2031) draws, racing the title-based tui-idle wait, so worker-start no longer times out when Claude's terminal title is disabled. Fixes #22655.
2026-10-24 22:23:08 -07:00
Neil 95df4c74ed refactor: group Parcel watcher process ownership (#33401) 2026-10-24 22:14:52 -07:00
Neil 9017194c0b fix(mobile): explain why a file outside the workspace can't be shown on the phone (#33341)
* fix(mobile): stop publishing plain file tabs outside their workspace that phones can't read

* fix(mobile): keep outside-workspace file tabs and explain why the phone can't show them
2026-10-24 22:01:08 -07:00
OrcaWinandOrcaWin 1d2b5361ef test: remove duplicate assertions and repair misleading fixtures (#33394)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-24 21:52:14 -07:00
Neil 4fa91acb4f fix(repos): drop a paired server's old workspace rows when it relists them under new project ids (#33396)
* fix(repos): drop a repo's worktree rows when its host's catalog no longer lists it

A paired server that relists its checkouts under new repo ids (a project
copied into another profile, then that profile switched to) left the
desktop showing the old rows beside the new ones, the old under Unknown.
Every catalog fetch now drops the rows of the repo registrations it
removed, scoped to the dropped host.

Refs STA-12076

* fix(repos): purge a dropped repo's workspaces like a vanished checkout

Clears selection, tabs and per-workspace state of the dropped rows through
the same bulk purge an authoritative scan uses, flushing delayed editor
text first; the three catalog fetch paths share one finalize step.

* fix(repos): drop rows only, never a registration added after the request

Review: purging tabs and selection could race repo add/remove and lose
state. Keep the change to rows, and fence it to registrations the store
held when the catalog request went out.

* test(e2e): drop the fixed settle wait from the repo id change repro
2026-10-24 21:51:22 -07:00
Neil f3104c80bf fix(daemon): a Windows terminal's output-relay write error no longer kills the PTY daemon (#33391)
* fix(daemon): contain ConPTY output-relay write errors to the one terminal instead of killing the daemon

* fix(daemon): end a Windows terminal whose output relay dies before connecting

* fix(daemon): kill a Windows terminal whose output relay fails before its first output
2026-10-24 21:45:06 -07:00
Neil 0a664b01b6 refactor: group mobile chat composer ownership (#33390)
* refactor: group mobile chat composer ownership

* refactor: use explicit type imports in moved chat adapters
2026-10-24 21:37:52 -07:00
Neil c25fad76df fix(agent-teams): run the tmux shim through the headless host's CLI, never the GUI binary (#33389)
* fix(agent-teams): run the tmux shim through the headless host's CLI, never the GUI binary

On an orca serve host the runtime has no Electron resources directory, so the
shim fell back to PATH and picked the AppImage's Electron binary, which booted
Chromium and died on the SUID sandbox. Prefer the host's profile-scoped CLI
launcher, and route agent-teams-tmux through the packaged CLI redirect so a
direct Electron invocation never initializes the GUI.

Fixes #33354

* fix(agent-teams): prefer a fresh CLI over a shim bin inherited from an older leader pane
2026-10-24 21:35:18 -07:00
Neilandyasyf 1b54aba056 fix(orchestration): hold mailbox pointers while the agent shows an open dialog (#21745) (#33366)
Orchestration mailbox pointers (typing and the delayed Enter) are held while a Claude pane shows an open dialog (AskUserQuestion / permission / plan approval chrome on screen, or a fresh waiting row when the screen can't be read), capped at 10 minutes, so mail no longer answers the dialog. Recheck backs off to 2s while held. Fixes #21745. Supersedes #26928, #21880.

Co-authored-by: yasyf <709645+yasyf@users.noreply.github.com>
2026-10-24 21:27:02 -07:00
OrcaWinandOrcaWin 5efcbf41f7 test: remove redundant checks and strengthen runtime regression coverage (#33385)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-24 21:08:47 -07:00
Neil 4099784d8d test(windows): warm WMI before timing the NSIS capability probe (#33340)
Run 40149801617 timed the SysWOW64 probe out at the 60s watchdog after it
had printed its policy receipt, so PowerShell had started and the first
Win32_Process query on the fresh runner was the slow part; the System32
probe right after took 14.7s. Run each probe once with a 150s bound in
beforeAll so the watchdog measures the probe rather than WMI's first query.
2026-10-24 21:05:51 -07:00
Neil 75ecad41d5 test(git-bash): give ssh-agent a load-tolerant bound in the Windows pane proof (#33287)
The detached ssh-agent proof waited 10s for eval "$(ssh-agent -s)" to print
its pid. On a loaded runner the shell had already accepted and started the
command (OSC 133;C) when the bound tripped; the leading terminal queries are
the pane's normal prelude, not a missing marker. Across eight instrumented
CI runs the marker took 0.5-1.1s, so keep waiting on the marker but cap it
at 30s.
2026-10-24 21:05:19 -07:00
Neil fc508bd19e test(windows): read the grandchild pid from accumulated pane output (#33320)
The ConPTY job-ownership proof matched ORCA_GC=<pid> against each onData
chunk, and once gave up after its 20s poll with no pane output to show why.
Two causes are possible: a chunk boundary inside the marker, which per-chunk
matching never recovers from, or a pane that trails the preceding table
stress test. Match the accumulated output and require a non-digit after the
pid, bound the poll at 40s, and include the pane tail if it still fails.
2026-10-24 20:56:24 -07:00
NeilandLuis Urrutia aa32d9b9d9 fix(terminal): keep fish's startup XTVERSION reply out of the launched agent (#33381)
A complete XTVERSION reply is delivered immediately (past the daemon shell-ready gate) and echo-filtered like colour/private-DSR replies, so fish's startup XTVERSION reply no longer leaks into Codex's composer. Fixes #16489. Supersedes #16491.

Co-authored-by: Luis Urrutia <3771248+LuisUrrutia@users.noreply.github.com>
2026-10-24 20:53:23 -07:00
NeilandMinidoracat b94ad9870e fix(terminal): don't resume an agent whose spawned PTY is still live (#33380)
The daemon records each terminal's spawn pane key and reports it in session listings; the sleeping-agent activation gate uses it as an exact-match fallback to find a live agent PTY after the host re-keys its tab, so paired clients and phone wake no longer start a duplicate `claude --resume`. Fixes #21235, #24385. Supersedes #24386.

Co-authored-by: Minidoracat <11269639+Minidoracat@users.noreply.github.com>
2026-10-24 20:51:22 -07:00
7d0103e8a6 fix(jira): keep the Tasks Board view selected after you leave the page (#30588)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
2026-10-25 03:43:08 +00:00
Jinwoo Hong 407e9ebc33 feat(relay): log the director's zone and round-trip speed on each runtime line (#33372)
<!-- orca-pr-loc -->
<!-- Programmatic LoC summary. Do not edit by hand; rewritten on every commit. -->

| | Files | Added | Deleted | Net |
| :--- | ---: | ---: | ---: | ---: |
| Test | 3 | $\color{#1a7f37}{\Huge{\mathbf{+}}}$​45 | 0 | $\color{#1a7f37}{\Huge{\mathbf{+}}}$​45 |
| Prod | 3 | $\color{#1a7f37}{\Huge{\mathbf{+}}}$​48 | 0 | $\color{#1a7f37}{\Huge{\mathbf{+}}}$​48 |

<!-- /orca-pr-loc -->

## ELI5

Some relay director instances talk to the database about twice as slowly as the rest. Those slow ones are where almost all the single-instance stalls happen. Today the logs can't tell us where an instance is running, so we can't work out why some are slow. This PR adds two numbers to the log line every director already writes every 30 seconds: the zone the instance runs in, and the instance's typical database round-trip time.

## What Changed

- **Before:** the director's `orca_relay_runtime_metrics` line had per-connection statement timings but nothing about where the instance runs. Getting one per-instance speed number meant pooling those connection timings by hand.
- **After:** two new fields on that line.
  - `instanceZone`: read once at boot from the Cloud Run metadata server (`instance/zone`). It's only read when `K_SERVICE` is set, so GCE cells and local runs skip it. If the read fails, the field is simply left off.
  - `stickyStatementMsP50`: the median duration of every sticky-lane database statement in the window, across all connections. It comes from a new window-wide sample of up to 1024 statements, the same kind already kept per connection.
- **Unchanged:** behaviour. The zone read is fire-and-forget with a 3 s timeout and never blocks or fails boot. No request path reads either field.

## Why

In 7 days of director logs (10-17 to 10-25), each instance's median per-statement time sat at either about 1.7 ms or about 3.6 ms. An instance sometimes switched between the two mid-life, with no deploy. 74 of the 81 single-instance stalls happened while the instance was at the slow speed: about 15x the rate at the fast speed.
- CPU, the event loop, the pg pool and database locks were all ruled out. That leaves the network path between the instance and Cloud SQL, and we can't confirm it without knowing each instance's zone.
- With zone and speed on the same line, a couple of days of logs will show whether "slow" follows the zone or the host. That decides between the next fixes: changing the Cloud Run sandbox generation, using a private IP, or cutting round trips per assignment.
- **Alternative considered:** a boot-time-only zone log line. It would force every analysis to join two log streams, and the field on each line costs a few bytes.
- **Alternative considered:** logging a "fast/slow" label instead of the raw median. That would bake today's thresholds into the image.

## Linked Issue

N/A (internal investigation of director single-instance stalls).

## Visual Proof

N/A: log-only change on a backend service, with no UI.

## Testing

- `pnpm typecheck` for `@orca-cloud/relay`.
- The full relay vitest suite: 1348 passed, 201 skipped (postgres-backed suites skip without a database).
- New tests cover:
  - parsing the zone from the metadata answer, and returning nothing on a refused, unreachable or malformed answer;
  - the zone appearing only once it is known;
  - the pooled statement median.
- The floating-promise census lists the zone read as settling its own failures.

- [ ] I manually tested these changes locally
- [x] Automated tests added/updated, or explained why not below

## AI Disclosure

## Review

## Agent skill upstream boundary

- [x] Not applicable, or this change copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

## Notes

- **Wire compatibility:** none affected. These are new optional fields on a log line, and the existing watchers read named fields only.
- **SSH, mobile, desktop:** not affected.
- **Rollout:** rides the next director image. No pin or deploy is part of this PR.

## Checklist

- [x] This PR is small and focused
- [x] I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
- [x] Before/after screenshots or videos attached for UI changes, or `N/A` with reason
- [x] Self-reviewed for correctness, security, and performance
- [x] Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
- [x] `pnpm lint`, `pnpm typecheck`, `pnpm test`, and `pnpm build` pass (or CI will cover; local preferred)
2026-10-24 23:36:08 -04:00
Neil 4e1acc99a3 fix(codex): write folder trust into the managed-account CODEX_HOME the launch uses (#33377)
Codex workspace trust is also written to the launch CODEX_HOME (managed-account home) at spawn, and native Codex chats pick their launch home before writing trust, so managed-account Codex launches no longer stall on "Trust this folder?". Fixes #27235.
2026-10-24 20:31:05 -07:00
OrcaWinandOrcaWin f392c6bd46 test: remove redundant checks and strengthen runtime regression coverage (#33373)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-24 20:24:40 -07:00
Neil ad1b81d53a refactor: group PTY delivery and drain ownership (#33358) 2026-10-24 20:22:31 -07:00
Neil 9f040bcb8b test(mobile-web): act on the HTML preview frame only once input reaches it (#33336)
* test(mobile-web): act on the HTML preview frame only once the browser routes input into it

Chromium runs the sandboxed preview frame in its own process. The rig decided an arm was
ready once the artifact had parsed, but the embedder can still be showing the frame's white
before its surface is composited, and still keep pointer input over the frame for itself
until it holds the frame's hit-test region. A click in that window landed on the
embedder's <iframe>, Playwright reported success, and the arm waited for a navigation
that never came (or read white as pixelBefore).

The rig now waits for the artifact's paint before reading pixelBefore, and before acting
moves the pointer over the frame until the embedder stops receiving it.

* test(mobile-web): name the arm in the input-routing timeout and trim history comments
2026-10-24 20:11:26 -07:00
Neilandinnocarpe fd240408b4 fix(pty): let agents run exit hooks before an immediate local-provider close SIGKILLs them (#33367)
Local (in-process) PTY provider: an immediate close now SIGTERMs descendants and waits up to the shutdown grace before SIGKILL (same as the daemon path since #22232), so Claude's SessionEnd hooks run. Refs #23865, #22039. Supersedes #24314.

Co-authored-by: innocarpe <2222333+innocarpe@users.noreply.github.com>
2026-10-24 20:09:42 -07:00
Kelvin Amoaba 25b9ce5168 feat(browser): show a hovered or focused link's URL in a bubble at the bottom of the page (#32978) 2026-10-25 03:09:30 +00:00
Neilandluisgonzaleznf 0fa161ba42 fix(agents): skip Codex's startup update prompt on Orca-driven launches (#33355)
Orca-driven Codex launches that type their prompt after start (workers, automations, continuation/handoff, pane-spawn and other submit-after-ready flows) pass `-c check_for_update_on_startup=false`, so Codex's "Update now" screen can't block them or be accepted by Orca's Enter. User-typed launches and resumes are unchanged. Fixes #15814, #18153. Supersedes #18156.

Co-authored-by: luisgonzaleznf <47302972+luisgonzaleznf@users.noreply.github.com>
2026-10-24 19:56:41 -07:00
Neil f076731231 fix(omp): replace the ACP child when OMP drops the chat's session (#33359)
Native OMP chat (ACP): when OMP reports its session was closed (-32603 "ACP cancel cleanup timed out" / "Unsupported ACP session: <this session>"), Orca replaces the child so the next send or Retry reloads the session instead of failing forever. Other ACP agents unchanged. Fixes #32684. Refs #32161.
2026-10-24 19:53:58 -07:00
Kelvin Amoaba 76f0f35bad feat(browser): speaker icon on tabs playing sound, with tab mute (#32981) 2026-10-25 02:51:37 +00:00
OrcaWinandOrcaWin b7476f5457 test: remove redundant cases and strengthen regression assertions (#33356)
* test: remove redundant cases and strengthen regression assertions

* test: let upstream empty typecheck baseline take precedence

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-24 19:51:11 -07:00
Kelvin Amoaba 695fc0081c feat(browser): bigger new tab tiles and roomier workspace card (#33178) 2026-10-25 02:44:03 +00:00
OrcaWinandOrcaWin fdcf887bf0 chore(lint): fix suppressed lint violations instead of ignoring them (#33294)
* chore(lint): remove disable directives that suppress nothing

Each was reported unused by the oxlint config that owns its rule (root,
casting, react-doctor, anti-slop, native-plugins), including 17 file-level
react-doctor blanket disables whose rule no longer fires. An unused
react-hooks disable was masking a react/purity finding in
useAgentBucketCounts (compiler rules skip functions with hook
suppressions); suppress that one explicitly where the code explains it.

* test(tab-bar): use typed tab/file fixtures instead of @ts-expect-error partials

* chore(lint): fix misc suppressed lint violations instead of disabling

- consistent-type-imports: the "vi.importActual requires inline import()"
  rationale was wrong; use typed vi.mock(import(...), importOriginal) or a
  type-only namespace import.
- no-control-regex: source-control commit/push failure summaries, shell PATH
  hydration, and ipynb ANSI parsing reuse the shared ansi-escape-sequences
  stripper and control-character pattern instead of private copies.
- no-explicit-any / no-useless-spread / no-this-alias: typed `this` in
  bridge test mocks, drop blanket file-level disables, drop a this-alias.

* refactor(main): replace avoidable type assertions with checked narrowing

17 production cast suppressions in main/wsl-guest removed: untrusted IPC
and RPC payloads (Jira issue update, WSL guest plugin install, handshake
type) now narrow through isRecord/type guards and build typed results;
redundant casts dropped; hand-rolled object checks and errno casts use the
existing isRecord / hasErrorCode helpers.

* refactor(renderer,mobile): replace avoidable type assertions with checked types

13 production cast suppressions removed: RPC failures read through the
`ok` discriminant instead of casting to RpcFailure; react-native-web host
refs narrowed with instanceof HTMLElement; redundant reply/diff casts
become checked annotations; the TUI agent param uses z.custom(isTuiAgent)
like its sibling schemas; the agent-status slice cast that hid missing
actions is gone so the creator's return type checks the whole slice.

* chore(lint): drop dead tiptap wrapper; scope anti-slop mocking rule to test support

- safe-react-node-view-renderer patched handleSelectionUpdate, which
  @tiptap/react 3.31.3 no longer has, so it always returned early. Use the
  stock ReactNodeViewRenderer.
- The anti-slop test override only matched *.test.{ts,tsx}, forcing
  file-level no-module-mocking disables in test harnesses and fixtures.
  Match *-test-harness, *-test-fixture(s), .test-fixture and *.test.mjs,
  then drop those disables and the now-redundant per-file exemptions.

* test: replace repeated cast suppressions with typed fixtures (main/shared/e2e)

Net -119 cast directives. Typed fixtures replace recurring casts:
unusedSshConnection/unusedOrcadHostTransport placeholders, a typed
readPersistedState harness read (exposed a test treating optional
httpProxyUrl as always set), real Response objects in fetch spies, an
updater main-window fixture, a CDP webContents adapter, one shared
releaseModuleExport for cross-version tests, and requireOverlayDir in
place of `as string` on possibly-undefined overlay paths.

* fix(runtime): validate loaded device rows and host scope instead of casting

Two cast suppressions claimed checks that did not happen:
- device-registry-file-read: "normalizeLoadedDeviceRow re-validates every
  field" was false; deviceId, token, name and timestamps were never
  checked. Rows without an identity are now dropped and the file is set
  aside through the existing corrupt-file path (registry reads as
  doubtful, so no revocation verdicts); a non-string token is stripped
  so the row stays listed but cannot authenticate.
- client-ui: "only optionality differs" was false; any string reached
  persisted workspaceHostScope. Narrow it with isWorkspaceHostScope and
  ignore unknown scopes (wire schema unchanged for mixed versions). The
  remaining cast now states what it actually covers.

* test(runtime,ipc): drop redundant cast suppressions; typed runtime store fixture

-98 cast directives across runtime/ipc tests: 61 casts that typecheck
without the assertion; a typed vi.fn for scoped invalidation; a
partialRuntimeStore fixture in place of `store as never` for
OrcaRuntimeService; typed worker terminal/authority builders in the
incarnation-fallback suite (the old stub lacked runtimeId, ptyId,
worktreeId and launchTokenHash) plus removal of stacked duplicate
directives there.

* chore(lint): typed vi.mock imports and explicit listener snapshot in shared-bucket tests

Remaining files from the misc-rule pass that overlapped other buckets:
codex-accounts mocks use the typed vi.mock(import(...)) form instead of
a consistent-type-imports disable; the daemon late-exit fixture copies
its listener set with Array.from instead of a no-useless-spread disable.

* refactor(github,mobile): narrow routing settings type; drop remaining avoidable casts

- work-item-routing cast a one-field object to AppState['settings'] under
  a claim that readers only consume activeRuntimeEnvironmentId. Return the
  existing RuntimeFocusSettings pick instead and narrow the cache-key
  chain's parameters, so the compiler enforces that claim.
- mobile file preview reads refusals through the RpcResponse `ok`
  discriminant rather than a cast.
- terminal document host-notify accepts `unknown` errors, removing the
  message-bridge cast.
- native chat readability drops a redundant repo-list cast.

* test(renderer): replace repeated cast suppressions with typed fixtures

-140 cast directives (and ~150 unsuppressed casts of the same shapes):
- mockPanePtyArgs owns the connectPanePty fixture casts, so a wrong
  argument order or fixture is now a type error; drop binding casts for
  members PanePtyBinding already declares.
- Seed AppState tab rows with the typed makeTerminalTab; this exposed a
  split direction 'row' that is not a TerminalPaneSplitDirection.
- Partial GlobalSettings use getDefaultSettings overrides; Jira-only
  stores use createTestStore; runtime environment rows/statuses come from
  a typed paired-runtime-environment fixture.

* chore(lint): drop no-module-mocking disable now covered by the test-fixture glob

* test(e2e): fix all 221 grandfathered e2e type errors; baseline to zero

The e2e typecheck baseline grandfathered 221 errors in 119 files. About a
third were tests drifted from the current API, several of which would
fail or mislead at runtime:
- terminal-shortcuts called a nonexistent xterm buffer method;
- the daemon-lifecycle fake lacked terminateOwnedTree, hidden by importing
  its type from a module that does not export it;
- arguments in the wrong position (listProcesses(hostId, opts), a dropped
  pty.write inputKind, an extra toWebTerminalSurfaceTabId argument);
- removed enum values ('drawer', 'idle', 'editor',
  'session-insecure-origin') and a launch_source the telemetry schema
  rejects;
- missing required fields (turnScope, childWorkOpen, providerIdentity,
  reviewLookupOutcome, runtimeServiceAccount behind an `as RelayConfig`);
- dead reads (tab.customTitle, bucket.targets, changedPixels).
Also lifts variables that narrowed to `never` after poll-callback
assignment, and adds a typed xterm render-dimensions accessor. Includes
the cross-version suites' shared releaseModuleExport adoption.

* chore(typecheck): gate the now-clean e2e typecheck in local lint

The e2e baseline is empty, so the ratchet now rejects any tests/ type
error. CI already runs it via run-ratchets; add it to pnpm lint's
explicit ratchet list and replace the stale 'not yet clean' note.

* test(mobile): typecheck every mobile test; baseline 105 files -> 0

mobile/tests-typecheck-baseline.txt grandfathered 105 test files (531
errors) whose types CI never checked. Contract changes had landed without
these tests following, and type pins in them silently checked nothing.

- Root cause: salvagedOptional (src/shared/zod-salvage.ts) cast away
  zod's optional marker, so every reply type built on it claimed
  salvaged fields were always present. Return a real ZodOptional;
  runtime is unchanged (the outer optional short-circuits undefined).
- Production now takes the port it uses: RpcOperationSender instead of
  RpcClient for send-only callers, a narrow fetch signature, and only the
  request fields push-receive reads.
- findAllByHostType/findByHostType replace `'Text' as never` lookups;
  stubRpcClient gives tests a typed RpcClient.
- Real test bugs: settings-host-client-lifecycle "relay" hosts were
  direct hosts (relayHostId no longer exists); two toEqualTypeOf pins
  could never hold; a recovery hook ran with an undefined scope key;
  a create-PR test cast to a never-imported type; "no URL" assertions
  passed on null; plus stale required props, args and enum values.

* test(e2e): parse workerStart result instead of casting it

* test(runtime): typed partial runtime stores with production-shaped fixture data

Route every partial store passed to OrcaRuntimeService through
partialRuntimeStore with typed row builders, removing 60 cast directives
that hid fixture data production never produces:
- paired-client-navigation-test-harness keyed setWorktreeMeta writes by
  `[object Object]` (it took an id where the store takes a target), so
  reads never saw them;
- invalid worktree lineage origin/capture and incomplete workspace lineage;
- getWorkspaceSession returning null where the store returns the default
  session; getGitHubCache returning undefined;
- orcad migration states missing migrationId/manifest/receipt, with a
  toEqual pinned to that incomplete shape;
- partial repos, metadata, folders, project groups and notification rows.

* refactor(renderer): resolve suppressed React hook lint instead of disabling it

109 of 187 react-hooks/react-doctor suppressions fixed:
- ~95 exhaustive-deps disables only hid stable refs and setters passed
  through controller objects; list them (identity never changes, so
  nothing re-runs), plus a few real stale closures (tabId/worktreeId).
- Effects keyed on one value that read the latest props now use
  useEffectEvent (WebConnect, skill freshness, Bitbucket dialog seeding,
  Landing preflight, automation catalog, structured-session recovery,
  file explorer).
- The conditional layout-owner hook is selected once at module load from
  the build constant instead of a rules-of-hooks disable that also hid
  the whole controller from compiler and react-doctor rules.
- A hook disable was masking a react/purity Date.now() in the palette's
  recent-tabs memo; sample it when the snapshot is captured.
- Three identical debounced-search effects share
  useTaskSearchDebouncedApply; layout snapshot merge helpers move out to
  stay under max-lines.
Remaining suppressions are mount-once resources, invalidation epochs,
deliberately keyed fields, and test harnesses that mock the dispatcher.

* chore(ratchet): exempt test harnesses and fixtures from the focus-read ratchet

The ratchet skipped *.test-fixture.ts but scanned *-test-harness,
*-test-fixture(s) and *.test-support files. Those seed settings with
getDefaultSettings (which the ratchet discovers as a focus reader) and
never route host work, so 17 baseline entries were false positives, and
replacing a test's `settings: {...} as never` with typed defaults tripped
it. Match the repo's test-support names and prune (which also drops one
stale entry), and rename the frame-ordering harness to *-test-harness like
its siblings. A blanket "-harness" match would also exempt
e2e-terminal-pty-harness, which production imports, so the names stay
explicit.

* test: merge duplicate shared/constants imports

* revert(file-explorer): restore original tree load effects pending SSH e2e investigation

* test(runtime): drop RuntimeStore cast now that the fixture is typed

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-24 19:37:19 -07:00
Neil 219ad610a4 fix(terminal): keep keys typed into a starting agent pane as an unsubmitted draft (#33315)
* fix(terminal): keep keys typed into a starting agent pane as an unsubmitted draft

* fix(terminal): keys typed after the bind always go live; never paste over a driven screen

* test(terminal): a deferred split with late startup work drops its held keys

* fix(terminal): capture only before the first bind; drop drafts on attach or reattach binds

* fix(terminal): leave paired-server panes' type-ahead to their own transport
2026-10-24 19:35:48 -07:00
Kelvin Amoaba 07026d586d fix(keybindings): zoom in with the + key on layouts where it needs no Shift (#33014) 2026-10-25 02:34:32 +00:00
Neil d58849a7d8 test(mobile-web): click the drawer chip once it is actionable, and honour the wait timeouts (#33342)
* test(mobile-web): click the drawer chip once it is actionable, and honour the wait timeouts

* test(mobile-web): say why the drawer trial click stays, and report a failed open with page errors
2026-10-24 19:22:44 -07:00
OrcaWinandOrcaWin 886ac45c67 test: remove duplicate checks and repair weak fixtures (#33353)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-24 19:14:30 -07:00
denchu 672ee2c53e fix(wsl): forward scrub-safe pane identity into WSL (#31205)
Forward the scrub-safe pane identity aliases (ORCA_AGENT_PANE, ORCA_AGENT_LAUNCH) into WSL via WSLENV so hooks inside WSL (DSH, Claude scrub restore, Hermes) can report to the right pane.
2026-10-24 19:13:00 -07:00
Wooseong Kim 3c80fb1193 fix(claude): keep an idle chat idle on background progress ticks (#22879)
Native Claude chat: background task_progress / background_tasks_changed frames refresh task rows without reopening an output turn, so an idle chat stays idle on progress ticks; real wake-ups (task_notification / status-changing task_updated) still open the turn. Fixes #22701.
2026-10-24 19:12:55 -07:00
Neilandggbdpq 456c7bf1f7 fix(runtime): deliver Devin launch prompts via argv instead of stdin-after-start (#33349)
Devin launch prompts are delivered as argv (`devin [flags] -- '<prompt>'`, Devin's documented interactive-prompt form) instead of typed after start, where they were lost before Devin was ready. Fixes #26302. Supersedes #26387.

Co-authored-by: ggbdpq <67929842+ggbdpq@users.noreply.github.com>
2026-10-24 19:06:04 -07:00
Neil dbde82110a test(browser): route input into the baseline sandboxed frame before clicking it (#33330) 2026-10-24 19:02:46 -07:00