Commit Graph
15492 Commits
Author SHA1 Message Date
Neil ed9c23ad2e test(wsl-mover): read the hidden local row through main's per-host hidden-repo API (D1b-7a) 2026-10-20 22:00:28 -07:00
Neil 4e266cf553 test(wsl-mover): run the moved-project chat runtime test in the Node runtime (D1b-7a) 2026-10-20 22:00:28 -07:00
Neil cac9b7be2d fix(wsl-mover): moved-project chats go through the layout owner instead of writing layout directly (D1b-7a) 2026-10-20 22:00:28 -07:00
Neil 9b863147f9 feat(wsl-mover): a moved project's existing chats stay listed and resumable on this computer (D1b-7a) 2026-10-20 22:00:27 -07:00
Neil 4a7463181d fix(runtime): resolve the scrollback host through resolveOwner and use the shared RPC error check, after main moved both (D1b-6d-1) 2026-10-20 22:00:18 -07:00
Neil 54541d790f fix(runtime): route the scrollback read by the tab's own host, never the focused one (D1b-6d-1) 2026-10-20 22:00:18 -07:00
Neil b6a594ca5c fix(runtime): hold a remote pane only when its host is known to serve scrollback, and never read bytes in render (D1b-6d-1) 2026-10-20 22:00:18 -07:00
Neil 5fe2dc3736 feat(runtime): clients read a migrated pane's scrollback from its host (D1b-6d-1) 2026-10-20 22:00:17 -07:00
NeilandClaude c9acece257 fix(wsl-mover): remove only the newest move of a distro, so its chain stays readable
Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-20 21:59:52 -07:00
NeilandClaude 7d9f5d89c5 fix(wsl-mover): cap a new move's manifest below the request limit, and move forward on a by-reference commit only once the host flushed it
Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-20 21:59:52 -07:00
NeilandClaude dd58ce3dc6 fix(wsl-mover): abort a move by its identity when its manifest is too large for a request
Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-20 21:59:52 -07:00
NeilandClaude 26a4bc6ec8 fix(wsl-mover): reset an interrupted move to zero, or finish it once the host proves a commit
Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-20 21:59:52 -07:00
NeilandClaude 23fff4ed17 feat(wsl-mover): export a local WSL move's signed manifest with its dormant state (D1b-3c)
Collects the scoped projects, their groups and every dormant-state kind through the
local-WSL export scope, rewrites UNC paths to the distro's, and returns blockers for
anything the collectors can't carry instead of dropping it. Automations move disabled,
as the wire requires, and come back as a list to re-enable on the host after commit.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-20 21:53:17 -07:00
NeilandClaude 13e0213087 refactor(orcad-migration): read the dormant-state export through a source-kind scope
A local-WSL scope lives on host local and owns only its projects, so host-wide
matches stay an SSH whole-host rule. SSH bytes are pinned by the collector golden.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-20 21:45:09 -07:00
NeilandClaude 8f26b3f14c test(orcad-migration): pin the SSH manifest through the real dormant collectors
Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-20 21:41:02 -07:00
NeilandWooseong Kim 3ed0bb4dfc fix(mobile): match home-indicator inset to terminal input panel (#31354)
fix(mobile): paint terminal dock bottom safe area
Adapted from community PR #30318.

Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
2026-10-20 21:31:25 -07:00
NeilandWooseong Kim 6537842f0c fix(mobile): blend workspace header into status-bar safe area (#31353)
fix(mobile): match workspace header safe-area surface
Adapted from community PR #30531 with native iOS simulator before/after validation.

Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
2026-10-20 21:31:20 -07:00
Brennan Benson b0d873d86a test(claude): isolate real CLI fixtures and cover chat option requests (#31201)
* test(claude): real-claude suite passes on any signed-in machine and current model aliases

Model ids come from the CLI's own model list instead of dated ids, tests that need a SessionStart hook or an effort bring their own project settings, and the supervised-stop test launches and reads its transcript under one Claude home, as acquisition does.

* test(claude): one real-claude flow for the model, effort and permission pickers and the context meter

Drives the live CLI through the production send path (model list, set model, apply effort, read settings), checks the turn's context report, and relaunches into a new permission mode the way Orca changes it. CI only meets these requests through stand-ins.

* test(claude): accept the ran options a send now reports

Since #30561 a Claude send resolves with the options it ran under; the stop test only needs the send admitted, and the options flow checks the ran options are exactly the per-message pick.

* test(claude): an alias pick's dispatch report may omit the model the CLI resolved
2026-10-20 21:25:11 -07:00
Neil d1ed73ba9b fix(orcad): refuse new terminals while an update stops the server (R32) (#29834)
* fix(orcad): refuse new terminals between an update's last census and the stop

The last census before an SSH or WSL update stops the server now closes the
server's terminal admission first. Every spawn lease (acquireWorktreeTerminalSpawn,
which both spawn paths hold across the provider spawn) passes the fence, the
census waits for leases taken before the close, and an update that leaves the
incumbent serving reopens it. A reattach to a session the daemon still runs is
still admitted.

* fix(orcad): fence-exempt reattaches spawn attach-only; reopen admission when no stop was sent

* fix(orcad): stop nothing once the recount's admission fence may have lapsed
2026-10-20 21:23:41 -07:00
Neil b3e6705337 fix(orcad-migration): a sidebar view setting never blocks moving an SSH host to its server (#31374)
* fix(orcad-migration): a sidebar view preference never blocks moving an SSH host to its server

* fix(orcad-migration): keep repo filters on the desktop, keep later acknowledgements; e2e repro
2026-10-20 21:15:56 -07:00
Jinwoo Hong ce71535bbf Layout groundwork (switched off): main starts every split and new pane, the window only attaches (#31258)
* feat(terminal): the window's store shows main's pane start status (layout-owner build)

A status-only frame changes one store entry and no pane tree, so the pane's view stays mounted.

* refactor(terminal): keep the frame sync within its line budget

* feat(terminal): every pane main starts shows 'starting' from before it is written (layout-owner build)

* feat(terminal): a pane main is starting waits and attaches; a failed start shows why (layout-owner build)

Fixes owner-visible row 15: a failed agent or setup start no longer opens a plain shell.

* test(terminal): iterate store listeners directly

* fix(terminal): an exit that reports no code keeps its pane, as lost contact (layout-owner build)

* test(terminal): a pane main started attaches to main's terminal at its own grid

* test(terminal): detach census tests assert main's move and frame with the switch on

* test(terminal): exited-pane tests assert main's close with the switch on

These 8 have failed switch-on since #31130 moved the exit close to main.

* feat(terminal): main writes and starts every split of a pane it holds; the window attaches (layout-owner build)

The window's split, a startup split with its command, and a CLI split all go through main's
terminal.split; main's frame mounts the pane, with or without a window intent. Deletes the
window's split wait and withdraw, the split appear intents, and the queued-split skip and cancel.
The e2e helper uses the real split. A startup split leaves focus on its source pane.

* fix(terminal): the attach-only wait leaves sleeping, resuming and held panes to their restore path

* fix(terminal): a split pane attaches to the terminal main bound it to; a taken pane id is refused

Also restores the proof that a split closed while it starts gets one answer and leaves no
pane or start status behind.

* fix(terminal): a window split main cannot start still shows its pane, failed, with why

* fix(terminal): check a taken pane id first, keep the start's own error, clear a gone pane's status

Three test stores gain the pane-start field the window's store always has.

* test(terminal): the admission stub answers whether its pane is still written; a closed pane's failed start clears

* refactor(terminal): the rebound workspaces' bindings show through one call in the bindings module

* test(terminal): a pane main wrote and the user closed mid-start drops its failed status

* fix(terminal): a split main starts is listed to phones and session.tabs without a window's report

* fix(terminal): a reconnect shows its pane starting and why it failed; a lost owner race shows no failure; a split no one asked to see keeps the phones' tab

* fix(terminal): a kept create another owner won shows no failure; the window attaches to the winner
2026-10-21 00:14:06 -04:00
Jinwoo Hong 046f29b9e5 fix(runtime): a phone's Markdown save keeps the route it started on (#31376)
forSave captures the workspace's route (and its pairing pin) once; every read, stat,
status.get and write of that save uses it, so a workspace that moves between local and
a paired server mid-save never sends the write to the other host unpinned.
2026-10-21 00:14:01 -04:00
Brennan Benson cd007d4106 fix(session-history): scoped history no longer fails on a very large transcript corpus (#30905)
Scoped Session History built its candidate list by spreading each
discovery's files into push(), which throws RangeError once one root holds
more files than V8 accepts as call arguments. Append item by item instead,
and do the same for the bucket-layout candidates (unbounded on unlimited
scans) and the search index's unchecked-paths hand-off.

Tracked in STA-11092.
2026-10-20 21:06:54 -07:00
Jinwoo Hong a763c1d8b7 style: format pty-connection-split-spawn.spec.ts (main fails oxfmt --check after #30442) (#31378)
Claude-Session: 10569c9a-a0a1-4fc9-8469-0edfb7aed125
2026-10-21 00:00:52 -04:00
Brennan Benson 03f4ce3d6b fix(mobile): show the session title beside the tabs on a landscape phone (#30879)
The served session page (react-native-web) reads flex: 0 as flex-basis 0%,
so the title block collapsed to zero width beside the tab strip. flex: -1
is natural width capped at 160, shrinking only when the row overflows, on
both web and native.
2026-10-20 20:59:19 -07:00
Brennan Benson f7c07409d6 docs(orca-cli): teach agents cross-project parents and re-parenting (#29900)
* docs(orca-cli): teach agents cross-project parents and re-parenting

* chore(cli): regenerate bundled orca-cli guide

* docs(orca-cli): scope the parent host rule and paired-server selectors
2026-10-20 20:47:50 -07:00
Brennan Benson 8a2ec06a22 Start an agent's terminal at its first real size instead of after a 12-frame wait (#30442)
* fix(terminal): start an agent's terminal at its first real size instead of after a 12-frame wait

The startup grid settle waited for the grid to change and then hold for 2 frames;
a grid that was right from the start always paid the 12-frame maximum (~90 ms at
120 Hz, ~200 ms at 60 Hz, seconds when frames are throttled, since it also cleared
the 250 ms fallback timer). Its only case, the setup split, was retired in #29819.
Command panes now connect like plain panes: on the first frame with a real
measurement, or the 250 ms fallback timer.

* fix(terminal): bound startup by completed fit and deadline

* test(terminal): brace startup contract control flow

* fix(terminal): release pending startup when window hides
2026-10-20 20:45:12 -07:00
Neil 411b5c024a Batch macOS signing calls for adhoc releases (#31340) 2026-10-20 20:16:08 -07:00
Jinwoo HongandE2E Test b99552cabc fix(runtime): a phone reads and saves Markdown tabs on a headless server (#31101)
* fix(runtime): a phone reads and saves Markdown tabs on a headless server

With no window to relay through, markdown.readTab and markdown.saveTab answered
renderer_unavailable. Main now reads the tab's file (or its saved draft) from disk and
saves with a version compare plus the disk write precondition, in the shapes the
window's editor relay returns. With a window attached the relay is unchanged.

* Headless Markdown save: keep the draft's disk baseline, honour read-only tabs, re-find the record to clear

* Use the disk baseline only for a draft

* ci: run the phone Markdown spec's orcad case in the orca serve mode-switch job

* Headless Markdown: answer from disk only; a window's saved draft stays its view state

Clearing a saved draft wrote workspace layout from main, which no layout command carries.
Main now never shows or edits a draft; the desktop's restore check reports a disk change.

* Phone Markdown: one path through main and the disk, window or not

A paired server's workspace is read and saved on that server with files.read /
files.writeIfUnchanged, so it answers from its own disk and keeps the precondition.
The window relay (ui:mobileMarkdownRequest and the renderer bridge) is removed.

* e2e: a phone saves a Markdown tab the desktop holds unsaved edits in

The phone reads the disk with the tab marked dirty; its save lands on disk, and the
desktop keeps its draft and shows the changed-on-disk banner.

* Group the Markdown disk host's imports

* Drop an unneeded store cast from the dirty-readers test

* Phone Markdown: refuse a save on a server too old to check the disk; cover the untitled tab

A server before #30605 has no files.writeIfUnchanged, so the save is refused with the
existing update-required message rather than written unchecked.

* Phone Markdown: refuse a file an SSH host holds outside the workspace; pin the untitled answer

* Phone Markdown: untitled tabs stay read-only; older servers save like the desktop editor; a closed-source preview still reads

- The desktop lists an untitled document's tab with isUntitled (optional, additive); main answers
  it read-only with unsupported_untitled and refuses its save before touching the disk.
- A server without the files.write.disk-precondition capability gets the desktop editor's guarded
  save: compare from here, then a plain files.write, or a conflict. Every call of a save is pinned
  to the pairing it probed.
- A preview whose source tab was closed has no saved record; it reads its own file, read-only.

* e2e: read the untitled tab's answer without a cast

* Phone Markdown: pin a save's pairing before its first read

A save captures the server's pairing before it reads, and every call of the save, the read
included, is pinned to it, so a re-pair between the read and the write is refused.

* Test: with no window, a phone saves an existing untitled.md and never creates a deleted one

---------

Co-authored-by: E2E Test <e2e@test.local>
2026-10-20 23:08:47 -04:00
Jinwoo Hong dda855e837 test(layout): pin the layout-owner save format for downgrade, and keep main's touched-records save with an exhaustive guard (dark, layout-owner build only) (#31341)
* test(layout): downgrade probe for the layout-owner session format, and the touched-records patch's guard

Downgrade probe (ledger L8): a checked-in golden of the session main writes in a layout-owner
build (layout commands through the Store plus a window save), matched byte for byte; a
switch-off Store hands it on unchanged and a switch-off window restores its tabs, panes, groups,
editor file and browser tab. Regenerate with ORCA_UPDATE_LAYOUT_OWNER_GOLDEN=1.

L104: for each writer of the touched-records patch (layout.apply, the cross-partition move, the
window save), the patch equals a full save on every partition the load leaves alone, and differs
only on records the load normalizes otherwise. Two concrete legacy cases are pinned, and a
switch-off window restores each differently after a full save, so the patch stays.

* test(layout): type the guard's partition ids and locate the golden without import.meta

* test(layout): pin every legacy fixture's patch difference, assert restored panes and group selection, keep the golden deterministic
2026-10-20 23:01:17 -04:00
Neil 71cf31bf69 Overlap native qualification setup with the mobile bundle build (#31336)
* Prepare server native lanes alongside the shared mobile bundle

* Expect read-only Actions access in template callers
2026-10-20 19:57:29 -07:00
Neil fece03b5ac fix(orcad): voice dictation on headless orca serve hosts (#31217)
* fix(orcad): voice dictation on headless orca serve hosts

Mobile dictation against an orca serve host answered speech_unavailable
since serve moved to orcad: orcad never installed the speech service
factories, and the model manager and download imported Electron.

The model download now takes a host request opener (Chromium net on the
desktop, Node https on orcad, with redirects mapped to the same event),
the STT worker ships in the orcad slot, and orca serve hands orcad the
installed app's on-device recognizer. A host without a recognizer (an
SSH or WSL managed slot) lists only cloud models and sends a phone that
picked an on-device model to the setup sheet. Mobile names the fix when
an older server still has no speech at all.

The parity test now requires both roots to install speech.

Reported-by: delorean2020 (@delorean2020)

* fix(orcad): retry Node download drops, stage the recognizer on Windows, review fixes

- Node https errors (ECONNRESET, ETIMEDOUT, ...) are retryable, so an
  interrupted orcad model download resumes (Codex P2).
- On Windows orca serve hands orcad a per-version copy of the recognizer
  in the profile, so a loaded addon never locks the app's files against
  an update (Opus).
- Drop a recognizer candidate path that could never match; one SAFETY
  helper per test file instead of repeated casts; comments.

* fix(orcad): cloud speech is ready only with a key this host can decrypt; prune superseded recognizer copies

Codex round 2: a desktop-sealed OpenAI key exists on disk but orcad's
secret store cannot open it, so the catalog reported cloud models ready
and dictation failed at the end. orcad's model manager now proves the
key decrypts. The desktop keeps its existence check (no keychain prompt).

* fix(orcad): never prune staged recognizers before the profile lock; copy linked packages as real files

Codex round 3. ref-oss check: versioned copies are cleaned only by the
process that owns them, so the pre-lock prune is dropped rather than
patched.

* fix(orcad): remove a partial recognizer copy when staging fails (Codex round 4)

* fix(speech): never hand out another store's ciphertext as the OpenAI key (Codex round 5)

* refactor(orcad): no recognizer copy on Windows; Windows orcad keeps cloud dictation only

Design check after review round 5: every remaining finding came from the
Windows copy that dodges addon file locks. Dropping it shrinks the
surface to a path lookup; Linux and macOS serve keep on-device models.

* fix(speech): name the fix when an old server refuses a mic tap; always set the recognizer env; retry ENOTFOUND (final Opus review)

* fix(managed-server): probe install completeness relative to the slot dir

CI: shipping stt-worker.js added one more absolute slot path to the
install-complete probe, pushing it further past the exec-size budget.
Testing each artifact relative to the slot (inside a subshell cd) cuts
the probe from ~12.2 KB to ~3.6 KB, so it drops off the over-budget
list instead of raising a ceiling.

* fix(mobile): pin host.platform's read to a platform type, independent of which zod copy resolves

With mobile's own dependencies installed (which CI does for any PR that
touches mobile/), the e2e program resolves two zod copies, and the
salvaged platform arm from src/shared inferred as {}. The schema now
narrows through an explicit platform guard.

* fix(mobile): keep the host.platform read line intact for its RPC mutant; narrow in a second step

Mobile verify: the recorded-RPC mutant anchors on the read line, which
the previous guard rewrote, so the mutant could not apply.

* refactor(cli): app-executable resolution in its own module; drop the platform guard now zod is aligned

launch.ts went one line past max-lines with main; resolveAppRoot,
resolveForegroundOrcaExecutable, getExecutableAppArgs and
stripElectronRunAsNode move to orca-app-executable.ts (re-exported, so
callers are unchanged). With mobile and root on one zod (#31260), the
host.platform schema no longer needs the extra narrowing step.

* refactor(cli): import app-executable helpers from their module; no re-exports from launch.ts
2026-10-20 19:54:55 -07:00
Brennan Benson d98b8eddb4 fix(chat): keep resumed Claude messages deliverable after settings reports (#30982)
* fix(chat): separate applied Claude choices from provider reports

* fix(chat): preserve Max effort without settings readback

* test(chat): retain Max effort confirmation contract

* test(chat): a literal model change no longer pre-flights the catalog

* fix(chat): record observed execution independently of accepted choices

* fix(chat): fence model reports at prompt input before recording

* test(chat): inspect Fast evidence without unsafe outcome access

* test(chat): honor input preparation in turn fixtures

* test(claude): accept dispatch metadata after stopping a turn

* fix: retire deferred chat options at input preparation
2026-10-20 19:50:18 -07:00
Neil 5999642ff1 fix(settings): prioritize sharing in Remote Orca Servers (#31335)
* fix(settings): prioritize sharing in remote server settings

* chore(i18n): remove obsolete remote server settings copy
2026-10-20 19:44:34 -07:00
Neil ecc4a6cacf fix(mobile): show embedded images in Markdown previews (#31304)
Render standalone embedded images in Markdown file previews and open them in the existing full-screen viewer. Resolve relative paths through authorized paired-host reads.

Thanks to @dudupii for reporting and diagnosing #24468 and implementing the broader solution in #24469; related editor integration is preserved in #25594. This independently implemented preview fix leaves editor image serialization, inline images, and pinch zoom as separate work.

Validation: focused tests, iPhone simulator proof, and all CI checks passed.
2026-10-20 19:38:23 -07:00
NeilandClaude 356bda10d1 fix(github): repo cache eviction clears every host's entries, keeping a live twin's (#31312)
* fix(github): repo cache eviction drops every host scope's entries, keeping a twin's own

Eviction and issue-source changes matched only unscoped key prefixes, so
SSH-, runtime- and source-scoped entries survived repo removal. Keys are now
composed by one builder and read back by its inverse, so eviction classifies
each key by host and keeps only a surviving same-id twin's bucket. Hosted
review (GitLab MR) entries are evicted too.

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

* fix(github): read task-source scopes with empty parts back whole

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

* fix(github): an issue-source change evicts only the changed twin's buckets

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-20 19:36:49 -07:00
Jinwoo Hong 379ad0c5cd test(terminal-surfaces): pin why main's pane republish is deferred to a microtask (#31333)
The deferral keeps a terminal's connected flip, noted inside syncWindowGraph's
record update, from re-entering syncWindowGraph and recursing. Layout-owner
(switch-on) builds only.
2026-10-20 22:33:42 -04:00
Neil a237b031df fix(mobile): open relative Markdown file links (#31298)
Open relative Markdown file links in the mobile file preview with workspace and execution-host scoping.

Thanks to @tomasklime for the report and diagnosis in #30368. Covers read-mode links; rich-editor links remain separate.

Validation: focused tests, iPhone simulator proof, and all CI checks passed.
2026-10-20 19:31:10 -07:00
Neil 513c24bc16 fix(mobile): preserve unsent terminal commands across workspace navigation (#31314)
Persist unsent buffered terminal commands per paired host, workspace, execution host, and surface across navigation. Preserve newer typing during storage hydration and clear successfully sent drafts.

Validation: draft/storage/bridge tests, signed iPhone simulator build, and all CI checks passed.
2026-10-20 19:31:05 -07:00
Neil c6becfaf7a fix(github): main finds a twin project's registered row by id and path, and a refresh by its host (#31274)
validateRegisteredRepo took the first row with the requested id, so for a repo id
registered locally and over SSH it read the other twin and refused the second
twin's fetch as a path mismatch. It now matches id and path together, uses the
GitHub source host when one is named, and lets a PR-refresh candidate's host pick
between twins at one path; a lone row's own fields still win over stale candidate
fields.
2026-10-20 19:31:04 -07:00
Neil 4f70d6cee5 fix(mobile): refresh staged files when returning from review (#31295)
Refresh Source Control when returning from review so staged files and commit availability reflect completed mutations.

Validation: focused loader tests, iPhone simulator proof, and all CI checks passed.
2026-10-20 19:31:01 -07:00
Neil 018c6e0c42 fix(mobile): create and edit notes on headless hosts (#31291)
Create, read, and save Markdown notes on headless hosts through authorized file operations and persisted editor tabs.

Addresses the Markdown-note reproduction in #22186, reported by @shota46. Thanks to @Jinwoo-H (#31101), @brennanb2025 (#25436), and @Meapri (#23357) for related community implementations and regression coverage. Those PRs retain broader/distinct scope.

Validation: focused tests, paired-headless E2E, iPhone simulator proof, and all CI checks passed.
2026-10-20 19:30:42 -07:00
Jinwoo Hong 065e6873f6 A paired desktop shows the held-terminal banner and Reopen for a server's held terminals (switch-on builds only) (#31275)
* A paired desktop shows a layout-owner server's held panes on the existing banner; Reopen asks the server (switch-on builds only)

The server's frames carry status 'held' on a pane a newer build's terminal service may still
run. The desktop now lists those panes on its existing newer-build banner, with the same wording
and states. Reopen calls the server's new `layout.reopenHeldPanes` under the server's ids: the
server stops its newer service, as the desktop's own Reopen does, and starts each pane still
held. The desktop starts nothing; the server's next frames clear the status and bind the pane.
A refusal or failure shows the banner's failed state.

Served and advertised (`layout.reopenHeldPanes.v1`) only by a layout-owner build, to paired
desktops (not phones). A server without the capability, or that publishes no status, shows no
banner, as today.

* test(layout): held server pane stays Reopening until the reattach frame, stays failed after a refusal, and shows nothing without the reopen capability

* fix(layout): a held pane's Reopen acts only on its own host and on main's published status (switch-on builds only)

- Reopen needs the clicked pane's key; a pane that already left the banner's store does nothing,
  so a server banner clicked as its frame drops the pane never stops this machine's newer service.
- Local versus server is decided by the pane's host, not by whether it has a release.
- The banner portal for a pane with no terminal handle exists only in a layout-owner build.
- The server reopens only panes its published layout shows held, the status the desktop saw.

* test(layout): Reopen ignores a pane rebound to a new terminal whose held entry is still stored

* test(layout): the dropped-pane Reopen test checks for the reopen method, since the view report shares the RPC client
2026-10-20 22:20:05 -04:00
Neil a1f2094d8c fix(github): work-item cache keys a twin by the row at the request path and its canonical host (#31290)
The fetch picked a twin's row by id first, so the SSH copy's results were cached
under the local copy's key; the task-page reader keyed by the raw executionHostId,
so an SSH row carrying only connectionId read the local bucket; and an unstamped
row was keyed by the focused server while its fetch ran locally. The repo lookup
now prefers the row matching both id and path, and writer and reader both key by
getRepoExecutionHostId.
2026-10-20 19:18:18 -07:00
Neil a308277f9a fix(deps): root and mobile resolve one zod, and CI fails if they drift (#31260)
* fix(deps): root and mobile resolve one zod, and CI fails if they drift

Mobile type-checks src/shared zod schemas. With mobile on zod 4.6.5 and the
root on 4.6.7, TypeScript treated them as unrelated packages, so wrapped
schemas inferred {} (tests/e2e/helpers/phone-feed-list.ts:154) whenever a
job installed mobile deps.

* fix(deps): guard every package mobile's tsconfig remaps, not only zod

* test(ci): pin the shared-dependency version check to the static phase
2026-10-20 19:12:29 -07:00
Neil 0c2b01367e fix(github): GitHub routing takes the caller's host for a twin; the task filters pass theirs (#31263)
* fix(github): GitHub routing takes the caller's host for a twin; the task filters pass theirs

* fix(github): task filters take the host from their source row, not an id+path lookup
2026-10-20 19:11:57 -07:00
Jinwoo HongandE2E Test 6e589bb4ba Layout-owner build: phones see main's terminal and editor rows (A2, dark) (#31276)
* wip(layout): applyDefaultTabs command in the layout model (dark)

* wip(layout): applyDefaultTabs takes the window's proposed ids; main refuses a taken one

* wip(layout): main closes or keeps an exited split pane by the window's rule (dark)

* wip(layout): a create or split sent again after its close is refused (dark)

* wip: typed test doubles

* wip(layout): main writes a worktree's default tabs in one command and starts them, window or none (dark; twin e)

* wip(layout): main writes a worktree's default tabs in one command and starts them, window or none (dark; twin e)

* wip(layout): main provisions setup and default tabs even when the agent fails to start; no window takes over (dark)

* wip(layout): pane start status in frames; a failed start keeps its pane; a terminal close stands after a failed write (dark)

* wip: lint fixes (max-lines, test throws)

* test(layout): a terminal close stands after a real failed profile write; switch off still refuses

* fix(layout): checked types in the close-failure rule and its tests

* fix(layout): a failed start keeps its pane whatever failed; default tabs start once; a tab's last pane keeps its card (dark)

* test(layout): the agent's kept pane counts as the workspace's terminal; main starts it whatever failed

* fix(layout): the switch-off create path gains no await

* fix(layout): a failed start never writes back a pane the user closed (dark)

* fix(layout): a failed start's pane goes through main's create rules, so a closed tab stays closed (dark)

* test(layout): D1a's switch-on close tests assert the intent main receives (both builds)

* test(layout): the close fixture answers main's command without naming the method

* fix(layout): a re-created pane binds as designed; the id-only ratchet reads a shared import (dark)

* fix(layout): a pane's start status may sit on a pane still bound to its old terminal (dark)

* fix(layout): a pane's start status ends when its pane binds again by any path (dark)

* test(layout): same-id restore ends a status; a replayed split brings back a closed pane by design

* test(layout): terminal reply fixtures captured from the window-owned build, and main's switch-on proofs

* wip(layout): main's terminal surface index feeds the runtime graph (dark)

* wip(layout): main's panes stand in for the window's terminal list (dark)

* test(layout): the window sends main-listed panes as mount facts (dark)

* test(layout): surface proofs use public runtime calls

* chore: lint

* feat(layout): close by handle closes in main's layout; the close-time relist goes (dark)

* feat(layout): main's binding outranks a caller's pane claim on the terminal record (dark)

* test(layout): window-list tests stay on the rollback build; switch-on mount-fact cases (dark)

* wip(layout): main's terminal index reads E1's shared projection and follows its operation ends (dark)

* refactor(layout): the main-surfaces host lives beside the session-tabs readers, keeping the graph file under its line cap (dark)

* fix(layout): main's panes skip the window-era membership fence, follow workspace routing, and list a terminal when it registers; window close only hands the graph back (dark)

* refactor(runtime): explicit-close PTY stops move unchanged to their own module, for the file's line cap

* test(layout): the cold-restore table runs in a layout-owner build too: one push per registration

* test(layout): the parked and automation pane tests assert the same window report in both builds (mount facts with the switch on)

* chore: one layout-owner switch import after the restack on D1a

* feat(layout): main's terminal.list rows carry the host's verdict; a pane with a status is never live (dark)

* chore(layout): fit main's surfaces under the lint gates after the D1a restack

* feat(layout): every pane status D1a publishes keeps a row from reading live; remote host answers pass through (dark)

* feat(layout): a drag-out moves main's own binding and refuses the home copy's last pane; the window's terminal claim is not consulted (L127, dark)

* fix(layout): a tab close by a starting pane's handle stops the tab's terminals; a starting pane is never exited on the inventory alone; terminal.list lists every saved pane main owns; the pane claim lookup leaves the output path (dark)

* fix(terminal): keep shipped exit and default-tab behaviour with the layout switch on

An exited pane closes through the legacy retirement unless the keep rule holds; the
default tab's title and color still reach the window and the phone's live tabs.

* fix(terminal): one id guard for every pane write; keep panes on an unvouched -1

Main's taken and closed-id guard now runs where every pane write goes, so a runtime create
replayed after its tab closed is refused before it spawns. An exit code nothing vouches for
keeps the pane, as the window does; a kept pane sends the phone no retirement. Converts the
switch-on tests that asserted the deleted window hand-off and title bridge.

* fix(terminal): keep the closed-tab guard on every pane write, held-id checks on client commands

A paired host starts each pane of a client's tab as a create under the tab's id, so a held tab
id is refused only for a client's own layout command.

* fix(terminal): a provider-reported -1 keeps the pane, as the window does

Only a requested stop or a host-confirmed death closes a pane on a negative exit code. Pins that
the closed-tab guard never fires in the shipped build.

* chore(terminal): satisfy the exhaustive-switch and test typing checks

* test(terminal): give the replay test's live process a cwd

* fix(layout): main's index reads routing only for a workspace two partitions hold, the fence reads ownership once, a legacy pane keeps its claim; switch-on tests assert main's answers after a window closes (dark)

* refactor(runtime): a worktree stop's terminal collection moves unchanged to its own module, for the file's line cap

* fix(layout): main's index routes every workspace as the layout stream does, re-reading routing only on a write or a terminal registration; a worktree stop names every terminal main binds; a starting pane is unverifiable (dark)

* feat(layout): a workspace main owns publishes its terminal and editor session.tabs rows from main's layout; the window still supplies browser, chat and view-only rows until D3b (A2, dark)

* chore: lint

* test: type the row fixtures

* chore(layout): main's session.tabs projection is an old-client entry, imported only by main's terminal surface index

* fix(layout): main's session.tabs rows carry the tab's name, the window's agent status and markdown file id, list rows in tab order, and keep groups and selection consistent (A2, dark)

* refactor(layout): the projection's group order moves unchanged to its own old-client module, for the file's line cap; lint

* feat(layout): every pane status main publishes keeps a row from reading live; a resuming pane is unverifiable like a starting one (dark)

* test(layout): a windowless server's layout change reaches a subscribed phone's session.tabs rows (dark)

* fix(layout): a window-only row keeps its place in the rows, the window's selected row lands in a group that selects it, and a workspace with no main group keeps the window's groups (A2, dark)

* fix(layout): a pane main has no binding for yet keeps the window's terminal in its phone row, so an SSH restore lists it pending (A2, dark; ledger L243)

* fix(session-tabs): phones show the window's live selection, focused pane, dirty state and titles over main's saved view; exited terminals stay gone; stray rows join the active group (switch-on builds only)

* fix(session-tabs): main's groups stand with main's selection; certified exits only; a selected stray row joins main's active group

---------

Co-authored-by: E2E Test <e2e@test.local>
2026-10-20 22:10:25 -04:00
Brennan Benson 8e5992663b A terminal pane that loses its reconnect to another connection attaches to the winner instead of showing terminal_pane_owner_changed (#30448)
* fix(terminal): a pane that loses its attach to another connection attaches to the winner

When a terminal pane reconnected to a saved process that had exited, and
another writer re-bound the pane meanwhile (most often the window's own
session save), main threw terminal_pane_owner_changed. The window showed it
as a raw red banner over a pane that was otherwise working, and nothing
cleared it.

Main now re-resolves the pane's owner once and attaches to it, with fresh
absence evidence even when only the saved binding changed. Retiring a dead
id's binding is fenced on its incarnation so it can never clear a newer
process that reused the id. A second loss is still reported; the window
asks once more for a local pane (never a fresh launch) and otherwise shows
the existing reconnect banner with Retry instead of the raw code.

Tracked in STA-4139.

* refactor(terminal): move the stable-pane attach step into its own file (max-lines)

* fix(terminal): fence an absent owner's retirement on an explicit incarnation, not the binding's own field

* Complete bounded pane ownership recovery through host publication

* Apply repository style rules to pane recovery changes

* fix: preserve terminal adoption type contracts

* fix: complete terminal recovery ownership and acceptance contracts

* fix: keep terminal registration evidence with its candidate

* fix: satisfy terminal recovery lint gates

* fix: consolidate terminal recovery imports

* test: explain bounded terminal fixture assertions

* test: handle optional pane binding maps in assertions

* fix: scope remote pane ownership refresh to recovery epoch

* fix: reject remote pane replies after recovery epoch ends

* Fix static checks for pane recovery

* Reuse session partition assignment in binding retirement

* fix: acquire returned terminal receipt before rejecting early exit

* Release superseded terminal spawn preparation before retry

* test: type preparation cleanup callbacks as functions

* test: initialize telemetry classifier in preparation collision cases

* test: identify preparation resources in reentry assertions

* fix(terminal): let a recovered pane replace the binding its host proved dead

Reopening an agent terminal whose process had exited showed "Orca couldn't
verify this terminal's owner", and the next relaunch lost the tab. The pane
operation proved the old PTY absent, but a window session save wrote that dead
binding back before the fresh commit, and the commit treated it as a rival
owner. The refused candidate was then shut down without an intentional-stop
mark, so its exit retired the whole tab.

The operation now carries its absence proof into the fresh launch, and both the
pre-launch owner check and the commit accept a replay of that dead binding. A
different or unproven owner is still contention. Discarded candidates are
marked as replaced, so their exit keeps the tab.

* test(terminal): type the discard test's runtime mocks

* fix: expose reconnect for retained pane recovery

* test: expect replaced stop for discarded fresh terminals

* fix(terminal): type the absent owner's retirement fence after the main merge

* test(terminal): follow main's Reconnect handler in the pane binding ownership test
2026-10-20 19:09:03 -07:00
Jinwoo Hong d7ef7741d1 fix(serve): restart the macOS serve supervisor from the updated app (#31200)
* refactor(serve): move the update handoff file reads and writes into their own module

Move-only: the supervisor module is at its line budget, and the handoff file
IO is a separate concern from the child supervision loop.

Claude-Session: serve-supervisor-reexec

* fix(serve): restart the macOS serve supervisor from the updated app

After a remote update on macOS, the node-mode supervisor kept running the
old Orca executable, which the installer moved aside and deleted. Under a
LaunchAgent the system charges every descendant's privacy permissions
(Accessibility, Screen Recording, files) to that supervisor, so once its
path no longer resolved, screen capture and computer use failed for the
whole served tree while the permission check still reported them granted.

Once the new bundle is verified on disk and no stop is pending, the
supervisor now re-runs itself in place (same pid) from the installed
executable instead of spawning the replacement itself. The handoff stays
install-requested, so startup's existing resume path spawns, verifies and
completes it. A --supervise supervisor that runs from its copy outside the
app restarts through the app's own CLI entry, so it copies the new release
aside again rather than mixing old scripts with the new executable. If the
re-run fails it logs and starts the replacement as before. Only applies
when serving our own executable on macOS; Linux, Windows, orcad and
overridden executables are unchanged.

Claude-Session: serve-supervisor-reexec

* fix(serve): restart a supervisor whose executable the install replaced

Three review fixes on top of the update restart:

- Decide the restart by file identity instead of which path observed the
  install. The supervisor records the device and inode of its executable
  at start; after any install wait (including resuming an interrupted
  update at startup) it restarts only if the installed executable is a
  different file. A supervisor that started on the old app before the swap
  now restarts too, and the process after a restart never restarts again.
- A relocated --supervise supervisor releases its marker on the copy it is
  leaving, so the next relocation can delete that copy. Before, every
  update kept one more full copy of the app's scripts for the life of the
  service.
- A failed process.execve aborts the process natively rather than
  throwing, so the fallback never ran. The restart now checks first that
  the executable can run and the script entry exists, and otherwise logs
  and starts the replacement directly as before.

Claude-Session: serve-supervisor-reexec

* fix(serve): restart a parked serve service whose app a desktop install replaced

When the desktop app's Update & Restart parks an `orca serve --supervise`
service for another profile, it writes no update handoff for that profile.
After the install the service's outer loop just starts its next server, so
neither update restart point ran and the supervisor kept the deleted
executable.

Every server start now runs the same identity check right after the
park/stop gate and before spawning, so a supervisor whose executable was
replaced restarts in place first. The check is a no-op when the executable
is unchanged, so first starts and the process after a restart do not loop.

The test drives the real park and commit functions, so the CLI typecheck
project now includes that updater module (it imports only shared code).

Claude-Session: serve-supervisor-reexec

* refactor(serve): check for a replaced app in the one place every server start passes

The restart check ran at three call sites and was threaded through the update
loop as a hook, and each review round found another start path it missed. Every
path (first start, resumed update, after an install, after a park or crash)
spawns its server through the function spawnElectronServe builds, after its stop
and park gates, so the check now lives in that spawn and nothing else carries it.

The restart moves to its own module, the hook and the handoff-file move it
forced are gone, and the tests drive serveOrcaApp instead of the hook.

Claude-Session: serve-supervisor-reexec
2026-10-20 22:01:54 -04:00
Neil 60b8eb4a6f fix(browser): keep in-app browser logins when a WSL or managed SSH host re-pairs (#29610)
Client-hosted browser pages for a server Orca deployed (WSL hosts today; managed
SSH desktop tabs use the SSH route) now derive their partition from the
environment and the server's public key, not the pairing revision and device id
that rotate on every re-pair. Existing jars are adopted, and each binding keeps
the shipping build's fingerprint (with the stable one as an alias) so a
downgraded build still opens the same jar.
2026-10-20 19:00:06 -07:00