Commit Graph
15153 Commits
Author SHA1 Message Date
NeilandClaude 36bda5efe5 fix(pty): an exit proof names its incarnation; a spawn is tracked from its reply (#30754)
* fix(pty): an exit proof names its incarnation, and a failed discard keeps ownership

A same-id respawn could be retired by its predecessor's exit proof; finishPtyShutdown now refuses
a proof whose incarnation is not the record's, and retireRejectedPty is scoped to the rejected
spawn's incarnation. A spawn discarded after a failed save no longer drops ownership when its
shutdown failed: it reads unverifiable until a later stop reaches it.

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

* fix(pty): a failed discard leaves a same-id replacement's verdict alone

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

* fix(pty): a lost discard reply does not overwrite an observed exit

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

* fix(pty): a failed discard tracks the PTY it could not stop; test the incarnation refusal

The discard runs before the spawn is published, so it records ownership and incarnation before
stopping; a failed stop now leaves a handle. Every exit proof states its incarnation.

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

* refactor(pty): track a spawn from its reply, so only an exit or a proven stop removes it

Ownership and incarnation are recorded at commit start instead of at publish, before the binding
save. The discard no longer re-records anything, so an exit observed during the save stays exited.

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

* fix(pty): admit a same-id respawn despite a predecessor's verdict; retire an exit seen before commit on its fence

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

* fix(pty): admit a fresh spawn only past the runtime's incarnation-scoped start fence

Replaces the id-only verdict check, so a same-id respawn is tracked and an incarnation that exited
before its commit is not resurrected; registration no longer has to retire it.

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

* fix(pty): a proof that names no incarnation still retires; tidy kill-time capture and adopted re-record

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-19 09:59:24 -07:00
Neil 0ddec6f368 fix(ssh): a network cut during a managed-server install no longer hangs, and says contact was lost (#30732)
* fix(ssh): a network cut during a managed-server install fails promptly and says contact was lost

* fix(ssh): settle an SFTP transfer when its session dies, since ssh2 never answers the requests left

* fix(ssh): only a closed SFTP session reads as lost contact; a dead session also cancels the abandoned transfer

* test(ssh): put the mock's SAFETY note on the cast's own line

* fix(ssh): a channel open the host never answered also reads as lost contact
2026-10-19 09:47:21 -07:00
Neil 40ac8e5f9b fix(remote): keep a paired host's undelivered tab close across a reload (#30758)
* fix(remote): keep a paired host's undelivered tab close across a reload

* fix(remote): show a tab again when its held close expires

* fix(remote): one choke point shows every held close the store drops

* fix(remote): keep held closes while the pairing is unknown; tidy dispositions

* fix(remote): save a held close for a long path-named editor tab
2026-10-19 09:40:17 -07:00
Neil 5cc0f8575e fix(runtime): a project removal stops its workspaces' terminals together under one deadline; every teardown is classified by kind (#30752)
* fix(runtime): a project removal stops its workspaces' terminals together under one deadline; every teardown is classified by kind

* fix(runtime): a workspace the inventory finds mid-sweep is still stopped; the walk test sees const and top-level teardowns
2026-10-19 09:12:26 -07:00
Neil 59a9da22a0 refactor(pane-layout): retire the unread intent tag; renamedLeafIds is the one layout delta (#30734)
No host ever read intent: 'gesture'. The client stops sending it, the params
schema strips it from older clients, and docs/reference records the rule that a
snapshot never carries intent.
2026-10-19 08:01:07 -07:00
Neil 3d91e9a003 fix(projects): removing a project on this computer stops its chats, pages and every terminal on the host (#30728)
* fix(projects): removing a project on this computer stops its sessions on the host, like repo.rm

* fix(runtime): a spent removal budget still asks every project terminal to stop; paired residue rows are no twins
2026-10-19 08:00:37 -07:00
Neil 43abe329c5 refactor(orcad): every snapshot state op fits the gateway exec budget in shell; the state lock sends its script once (#30744)
* refactor(orcad): every snapshot state op fits the gateway exec budget in shell; the state lock sends its script once

* test(orcad): a restore that stops part way reports FAILED and reruns from the archive
2026-10-19 07:54:34 -07:00
Jinwoo Hong 8ff19bc126 perf(sidebar): announce a burst of sort-stamp writes once (dark switch) (#30746)
Each stamp write made every client re-list the repo's worktrees; a pane whose
agent title flips while streaming turned that into hundreds of re-lists a
second. The write stays immediate; the notice is coalesced per workspace
(50 ms quiet window, 250 ms cap) through the existing keyed coalescer.
2026-10-19 10:53:33 -04:00
Jinwoo Hong b358328b7f test(e2e): pane-close specs close panes the way the user does and check main's copy (#30745)
The pane-close consistency spec closed panes with a bare PaneManager.closePane, the
drawing-layer call that never asks main. In a layout-owner build that close reached main
only because killing the pane's terminal made main retire the pane; right after a reveal
the terminal has not attached yet, so main kept the pane. Every user close path already
sends the close to main first.

The helpers now close through the pane close request the menu and shortcut use, and every
scenario also checks that main's stored panes match the window's.
2026-10-19 10:32:01 -04:00
Neil 9a46695f9f test(e2e): mount-authority spec polls through the host's "can't answer yet" terminal list (#30743)
* test(e2e): mount-authority polls retry the host's "can't answer yet" terminal list

First-mount adoption briefly makes terminal.list refuse with
terminal_surface_ownership_unavailable. Thrown out of an expect.poll
callback, that ended the poll instead of retrying it, so the spec failed
whenever a slow runner's probe landed in that window.

* test(e2e): say the refused read returns undefined
2026-10-19 07:29:03 -07:00
Jinwoo HongandClaude 36913fd4f5 test(e2e): a phone lists the desktop's managed SSH workspace as the desktop sidebar does (switch on) (#30718)
* test(e2e): a phone lists the desktop's managed SSH workspace as the desktop sidebar does (switch on)

* fix(sidebar): key folder workspace rows by host in the shared builder so the phone matches the desktop

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

* test(sidebar): pin the SSH folder row key encoding

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

* test(e2e): route the SSH phone feed list spec from its sources

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-19 09:19:39 -04:00
Neil 90e99c48f4 refactor(pty): every PTY retirement names its exit proof; ratchet the unproven ones (#30722)
* refactor(pty): every PTY retirement names its exit proof; ratchet the unproven ones

* refactor(pty): count raw ownership drops, route an abandoned relay stop to unverifiable, honest proof labels
2026-10-19 05:48:19 -07:00
Neil 1bfebad8ec fix(runtime): a new terminal the host can't confirm says why, instead of nothing (#30725)
* fix(runtime): say why a new terminal on a host failed, with a retry

* fix(runtime): never offer a retry or say refused when the host's reply was lost

* fix(runtime): only a host error reply counts as refused; the wake flow keeps its own toast

* refactor(runtime): one 'couldn't confirm' notice carrying the reason, no failure classification

* fix(runtime): one create notice per workspace, so retried seeding can't stack toasts
2026-10-19 05:46:10 -07:00
Neil 5a9e26ef9f fix(ssh): say a noexec home can't run Orca's server, instead of "install an older version" (#30724)
* fix(ssh): name a noexec home instead of calling the host unsupported

* test(ssh): measure the stage exec probe in the host-op byte budget

* fix(ssh): the connect error names a noexec home too

* chore(i18n): add the noexec status line to the English catalog

* fix(ssh): only the shell's exec refusal reads as noexec; other probe failures reach the self-test

* fix(ssh): a stage the probe can't write skips the probe, so a full disk still reads as full

* fix(ssh): read the probe's exit 126 as noexec in any locale
2026-10-19 05:45:39 -07:00
Kelvin Amoaba 2b6077807a fix(native-chat): show the model picker in a new OMP or Antigravity chat (#30713) 2026-10-19 12:32:33 +00:00
Jinwoo HongandClaude 4bc3971b98 fix(sidebar): key folder workspace rows by host so the phone matches the desktop (dark switch) (#30721)
* fix(sidebar): key folder workspace rows by host in the shared builder so the phone matches the desktop

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

* test(sidebar): pin the SSH folder row key encoding

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-19 08:26:12 -04:00
Neil a9011d083c fix(projects): refuse to file a folder under a project its own GitHub remote contradicts (#30717)
* fix(projects): refuse to set a folder up under a project its own GitHub remote contradicts

Setting an existing folder up for a project stamped the project's identity
as the folder's upstream even when the folder's own remote named another
GitHub repository, so the folder was silently relabelled into the wrong
project (fabricated upstream, wrong projectId on its setup and workspaces).

Fixes #30663

* test: type the folder-remote spec store without casts

* fix(projects): judge a registered folder by its current remotes; ignore host aliases

* test: cover the desktop re-read of an already-registered folder
2026-10-19 05:10:00 -07:00
Neil f22aecf1ed fix(runtime): one teardown policy per kind; removal closes host browser pages everywhere, headless sleep stops terminals (#30715)
* fix(runtime): one owned-session teardown policy; removal closes host browser pages everywhere, headless sleep stops terminals

A declared table says what each entry point (worktree, folder and project removal,
sleep, disconnect) does to each owned kind (PTY, native chat, host browser page,
launch record), so a new kind is a compile error until decided. Removal over desktop
IPC and repo.rm now close the host's browser pages and launch records like the
runtime removal; worktree.sleep with no renderer sleeps the host's terminals instead
of doing nothing. Sleep->chat is declared pending #28445; disconnect stops nothing.

* fix(runtime): project removal finds headless pages and failed launches without a client page; stub the new runtime method

* fix(runtime): headless sleep rejects an unproven stop; read headless page owners through the bridge, not Electron
2026-10-19 05:02:21 -07:00
Kelvin Amoaba 162e07cf97 fix(browser): refuse agent clicks while the user is mid-IME composition (#30358) 2026-10-19 11:45:29 +00:00
yi111andYi-111-a ec0c1f115a fix(github): accept SSH certificate org remotes (org-<id>@github.com) (#30659)
GitHub orgs using an SSH certificate authority get an SCP remote whose user is
org-<id> rather than git. The three GitHub remote parsers only matched a
literal git@ user, so those repos were classified not-github and lost PR,
issue and check support.

Co-authored-by: Yi-111-a <yi-111-a@users.noreply.github.com>
2026-10-19 04:43:32 -07:00
Stefan StefanovandClaude Sonnet 5.5 346f6028a3 fix(git): stage and discard files in WSL repos opened by UNC path (#30693)
The Git runner sends a command into WSL when the working directory is a
\wsl.localhost UNC path, even if no WSL distro is set in the options.
literalPathspec only looked at the options, so Windows-style backslashes
in file paths reached Git inside WSL unconverted and stage, unstage and
discard failed to match the file.

literalPathspec now also checks the worktree path, and every caller
passes it.

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-19 04:43:08 -07:00
Neil 25e53f4b0c fix(security): put every trust decision on a host-owned token surface (#30710)
* fix(security): put every trust dialog on a host-owned token surface

Plugin themes write CSS tokens on the document root, and dialogs portal to
body, so any trust surface without plugin-security-chrome inherits them; a
theme could hide what is being approved. The orca.yaml hook trust dialog,
notebook run confirmation, plugin install, remove and rollback dialogs were
missing it. DialogContent gains surface="security", which applies the
host-owned tokens, and a scrollable prop for the plugin dialogs' existing
viewport-height scrolling; every trust dialog uses the surface and a test
lists them. The style guide says when to use each.

* fix(security): keep inline trust decisions on the host-owned surface

The agent command approval card (Allow/Deny) and the browser certificate
override sit outside a dialog, so plugin theme tokens still reached them.
A SecuritySurface primitive puts its child on the same host-owned token set
as DialogContent surface="security"; both use it, and the security surface
test now lists inline trust surfaces as well as dialogs.
2026-10-19 04:21:16 -07:00
NeilandClaude e72d8f926a fix(ssh): say the host's disk is full when a managed-server install runs out of space (#30679)
* fix(ssh): say the host's disk is full when a managed-server install write fails for lack of space

OpenSSH's sftp-server reports ENOSPC and EDQUOT as the generic SSH_FX_FAILURE, so a deploy onto a
full home failed with the bare detail "Failure" under "Try connecting again". After a failed
install write, probe the host's free space where the write was going; when it cannot hold the
write, fail with an error that names the filesystem, how much is free and how much is needed.
Lost or aborted transfers and Windows hosts keep their original error.

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

* fix(ssh): diagnose only SFTP's generic failure on an exhausted filesystem

Codex review: a partial upload may spend the room the rest needed, so compare against an empty
filesystem, not the bundle size, and keep explicit refusals such as permission denied.

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

* refactor(ssh): reuse formatKb and size an upload the way uploadDirectory walks it

Opus review: the shared formatKb already exists, and following symlinks inside the tree could
recurse; uploadDirectory follows the root and skips links below it.

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

* test(ssh): measure the free-space probe in the host-op byte budget

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-19 04:20:09 -07:00
Neil 92ed331184 refactor(plugins): extract the shared content-pack registry and list store (#30697)
* plugins: extract the verified artifact-pack registry from language packs

Pure refactor. Verify, bounded read, parse and per-plugin error handling
move into PluginArtifactPackRegistry; language packs become one instance,
so later data families reuse it instead of copying the file.

* plugins: reconcile content families from one list

Pure refactor. Reconcile and error reporting walk one family list in the
same order as before, so a new family is one entry.

* plugins: one line per family for declared artifacts and contribution defaults

Pure refactor. Declared artifact paths are a list of per-family rows, and
the contributes default comes from the schema (prefault) instead of a
second copy of the field list; the existing defaults test pins the result.

* plugins: extract the renderer plugin content list store

Pure refactor. The fetch generation, startup join window and content-change
subscription move into createPluginContentListStore; language packs become
one instance, and store fields are renamed to entries/fetchEntries.
2026-10-19 04:01:12 -07:00
Neil 148944c020 refactor: de-barrel remote-runtime-client, runtime-terminal-stream and runtime-terminal-inspection (D2, D3) (#30684)
* refactor: de-barrel remote-runtime-client and runtime-terminal-stream (D2)

importers name the defining modules; the two vi.mock factories that named a re-export move to
the defining module (pty-id spy) or drop the dead key (RemoteRuntimeClientError)

* refactor(renderer): de-barrel runtime-terminal-inspection (D3)

importers name runtime-terminal-input-recording, runtime-terminal-verified-input and
shared/remote-runtime-pty-id; 22 vi.mock factories move the re-exported keys to those modules
(pty-id keeps its other exports via importOriginal)

* test: drop the duplicated verified-input mock (Opus P3)
2026-10-19 03:58:26 -07:00
Jinwoo Hong 7cc2859e90 fix(relay-ops): let same-cap rolls run while another cell is reserve; refuse a reserve roll target by name (#30707)
* fix(relay-ops): let same-cap rolls run while another cell is reserve; refuse a reserve roll target by name

The director refuses cell-status for a step-5 reserve cell with a 404
{error: cell_in_reserve_mode}. The same-cap headroom check read it for
every general cell, so any roll failed while c7 was reserve. A reserve
cell that is not being rolled now counts as no free slots (its seats are
not in the database) and is listed with reserve: true. A reserve roll
target, in the headroom check, the same-cap job's first cell-status read
and the candidate tool, now fails with the design's procedure: flip it to
admitMode=db first, roll, then flip it back. Any other 404 still fails.

* fix(relay-ops): pin the headroom gate's imports as evidence code; name both cells in a two-cell reserve refusal
2026-10-19 06:57:06 -04:00
NeilandClaude 41fdd7e6d8 fix(ssh): keep SFTP uploads pipelined so a high-latency link doesn't crawl (#30704)
* fix(ssh): keep SFTP uploads pipelined so a high-latency link doesn't crawl

ssh2's WriteStream acks each chunk before writing the next, so uploadFile moved one 64 KiB chunk
per round trip: about 200 KB/s on a 300 ms link, which made the ~90 MB managed-server deploy (Node
runtime plus orcad bundle) take over seven minutes. A 2 MiB write buffer lets ssh2's _writev keep
that many bytes of WRITE requests in flight, as fastPut's default does.

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

* test(ssh): type the fake session's stream options with ssh2's WriteStreamOptions

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-19 03:48:35 -07:00
Neil cffcef7be0 fix(projects): setup edits and removals name the host, so the twin on another host is never touched (#30664)
* fix(projects): setup edits and removals name the host, so a twin on another host is never touched

Repo-backed project host setups take the repo id, so a project on two hosts has
two setups with one id. Update and delete took the first match by id, so editing
or removing the SSH copy could rename or remove the local one. The args gain an
optional hostId (older peers strip it and keep the first match).

* fix(projects): tolerate only the legacy runtime stamp when a named host misses an independent setup

* fix(projects): refuse a twin edit on a server that would act on the wrong twin; read legacy runtime stamps as local

* fix(cli): refuse setup --host on a server that would ignore it

* refactor(projects): exact host match only; name the host on the wire only for twins on one route

* fix(projects): a named host the catalog lacks refuses; settings remove stays by id

* refactor(projects): name hosts on the wire only for this computer's catalog; paired servers keep the by-id request
2026-10-19 03:47:23 -07:00
Neil ec89fa6099 feat(orcad-migration): host applies a signed editor-drafts blob inside the commit (S10-H) (#30665)
* feat(orcad-migration): host applies a signed editor-drafts blob inside the commit

* fix(orcad-migration): consume received blobs only after the commit is on disk
2026-10-19 03:46:41 -07:00
Jinwoo Hong 218b9a737b The layout drift log names the values that differ (dark, layout-owner build only) (#30678)
* The layout drift log names the values that differ

* Keep the drift log's value copies type-checked and lint-clean

* Give each side of each logged change its own size allowance

* Name the drift log's id length cap apart from other modules' caps

* The drift log names the terminal bound to each pane of a changed tab

* Pin the terminal id length rule in the drift log tests
2026-10-19 06:41:38 -04:00
Jinwoo Hong 9def13dde2 ci: run the layout-owner switch-on specs as an advisory PR check (dark, layout-owner build only) (#30582)
* ci: run the layout-owner switch-on specs as an advisory PR job

Build the e2e app with ORCA_LAYOUT_OWNER=main through the existing e2e.yml
(new layout_owner input, read only by the app and orcad builds), run the
layout oracle, its positive control and the layout-owner acceptance specs
with the save-divergence log on, and upload a summary by kind and spec.
Not in the PR aggregate's needs.

* ci: summarize divergence logs without a test report, and drop file-path ids from kinds

* ci: switch-on job adds the stream-loss spec and the managed-SSH Docker lane, drops diff-word-wrap (fails switch off), and names each looped test in the summary

* test(e2e): editor-model-host-isolation opens both files through the store's open action, so a layout-owner build's main holds the tabs

* ci: the divergence summary skips a cut-short test report instead of failing

* ci: re-run PR checks on a merge ref that includes the main typecheck repair (#30680)
2026-10-19 06:41:26 -04:00
Jinwoo Hong 1cf7472d1e feat(relay): batch a memory cell's resume-confirmed audit rows (#30655)
* feat(relay): add the per-cell phonePath switch, effective only in reserve mode

Step 6 of the relay redesign keys every phone-path change on this switch. The cell
reports the effective value in runtime status, and the flag tool refuses phonePath=memory
unless the cell is reserve, and a reserve flip that would turn phones over unnamed.

* feat(relay): resume paired phones from one device read on memory cells

With phonePath=memory in reserve mode, a phone resume is admitted from one device
read and the cell's own seat, and takes no activity leases. A refused device closes
4401, a failed read 4503, and only a missing device row falls through to the invite
path. A phone whose host has no seat gets one assignment read: 4409 when the row
names another cell or the read fails, otherwise 4404.

* feat(relay): keep a memory resume's basis in the cell and confirm it from one read

A phone resumed on the memory path keeps its connection basis in the cell, not in
relay_connection_bases. Its confirm rereads the device row once, renews it only when
less than 29 days remain, and always answers a current-version confirm renewed:true
with the expiry the database holds. Confirms route by where their basis lives, so a
flip between accept and confirm completes either way. Retries answer from a 10-min
in-memory result map.

* test(relay): roll a memory phone cell back with confirms in flight

Flipping back by admitMode=db, phonePath=db or a dead-man trip leaves in-flight
confirms to finish on their memory basis, writes no phone lease and leaks none, and
evacuation still refuses the cell while Postgres records it as reserve.

* feat(relay): batch a memory cell's resume-confirmed audit rows

Memory confirms buffer their audit rows and the cell writes them as one multi-row
insert every 5 s or 500 rows, idempotent by a per-event id. The buffer holds at most
5,000 rows and drops a row older than 10 minutes rather than retrying it, counting
and logging both kinds of drop.

* fix(relay-ops): store phonePath=db as no key, so old images keep taking writes

An explicit phonePath=db left in the switch file would make the flag tool refuse every
later write after an image rollback, including a watcher's admitMode=db flip back.

* fix(relay): log a failed no-seat read, recheck the switch after the device read

A failed assignment read for a phone with no seat now logs a rate-limited line before
its 4409. A switch that turned off during the device read sends the phone down today's
path. resolveResume reuses the one-read lookup, so the expiry and grace rules have one copy.

* fix(relay): log a failed memory renewal; test flips, replays and basis cleanup

A renewal write that fails still answers the stored expiry, but now logs a rate-limited
line so a persistently failing update is visible before phones age toward re-pairing.

* test(relay): assert a rolled-back memory splice issues no statement when it closes

* fix(relay): flush resume audit early only on crossing the batch size, drop the shutdown flush

A failing database no longer takes a 500-row insert per confirm; retries wait for the
5 s timer. The shutdown flush raced the pool closing, so it is gone and the at most 5 s
loss is documented. A stopped buffer no longer re-arms its timer.
2026-10-19 06:37:35 -04:00
Jinwoo HongandClaude 78bad24d33 test(e2e): nine sidebar specs create workspaces and links through main so they pass with the switch on (#30667)
* test(e2e): create reveal-spec workspaces through main so they pass with the sidebar switch on

The agent-send picker and host-section reveal specs injected fake repos and
workspaces into the window store. With ORCA_SIDEBAR_INPUTS=main the sidebar
lists main's rows, so those workspaces never appeared. The SSH host is now an
unreachable target seeded into the stopped profile, and the paired-server host
is a real headless orca serve host; pins, lineage and notes go through main.

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

* test(e2e): type the seeded hosts as execution host ids

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

* test(e2e): poll the reveal specs' page setup instead of awaiting it across CDP

An evaluate that awaits a preload promise sometimes fails with "Resulting
promise was garbage collected" in switch-on runs. evaluatePolled runs the work
in the page, polls its outcome, and still fails on a rejected or failed write.

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

* test(e2e): parent reorder spec creates its workspaces and links through main

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

* test(e2e): lineage specs create their workspaces and links through main

seedLineageScenario names and orders the two seeded workspaces and links them
with assignWorktreeParent. Extra families and surrounding rows come from a
fresh repo with real worktrees; pins and stale instances are main writes.

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

* test(e2e): default-branch and downward-drag specs create their workspaces through main

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

* test(e2e): reveal and active-delete specs create workspaces through main

The 60-row reveal, the folder-workspace reveal and both active-delete tests
now use real workspaces and folder workspaces added through main, ordered with
metadata writes. The deletes go through the real removal path, so the row
removal sampler waits by time rather than a frame count.

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

* test(e2e): address review on the switch-on spec follow-up

writeWorkspaceMeta now fails unless main lists the written values; the sleep
test's parent is a real worktree instead of a store-faked one; the legacy
inline-lineage case stays window-only and is skipped with the switch on;
the parent-reorder collapse key is built from the row's host.

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

* test(e2e): read-back reports an unlisted workspace and compares values structurally

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

* test(e2e): wait for the added repo before showing its worktrees

updateRepo returns false without writing when the store does not list the repo
yet. On CI the add's own refresh superseded the helper's fetch, so the visibility
write was skipped and every discovered worktree stayed hidden. The helper now
waits until the store lists the repo and fails if the write is refused.

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

* test(e2e): name the repo wait in its failure message

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

* test(e2e): size the setup poll budget to the number of main writes

evaluatePolled waited a fixed 10 s, sized for one write. Linking 53 children takes
about 8 s locally and 36 folder workspaces about 4.5 s, so CI ran past it ("page
work never settled"). Bulk setup now passes a budget of 10 s plus 500 ms per write.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-19 06:29:17 -04:00
Jinwoo Hong b8f7ece200 feat(relay): keep a memory resume's basis in the cell and confirm it from one read (#30651)
* feat(relay): add the per-cell phonePath switch, effective only in reserve mode

Step 6 of the relay redesign keys every phone-path change on this switch. The cell
reports the effective value in runtime status, and the flag tool refuses phonePath=memory
unless the cell is reserve, and a reserve flip that would turn phones over unnamed.

* feat(relay): resume paired phones from one device read on memory cells

With phonePath=memory in reserve mode, a phone resume is admitted from one device
read and the cell's own seat, and takes no activity leases. A refused device closes
4401, a failed read 4503, and only a missing device row falls through to the invite
path. A phone whose host has no seat gets one assignment read: 4409 when the row
names another cell or the read fails, otherwise 4404.

* feat(relay): keep a memory resume's basis in the cell and confirm it from one read

A phone resumed on the memory path keeps its connection basis in the cell, not in
relay_connection_bases. Its confirm rereads the device row once, renews it only when
less than 29 days remain, and always answers a current-version confirm renewed:true
with the expiry the database holds. Confirms route by where their basis lives, so a
flip between accept and confirm completes either way. Retries answer from a 10-min
in-memory result map.

* fix(relay-ops): store phonePath=db as no key, so old images keep taking writes

An explicit phonePath=db left in the switch file would make the flag tool refuse every
later write after an image rollback, including a watcher's admitMode=db flip back.

* fix(relay): log a failed no-seat read, recheck the switch after the device read

A failed assignment read for a phone with no seat now logs a rate-limited line before
its 4409. A switch that turned off during the device read sends the phone down today's
path. resolveResume reuses the one-read lookup, so the expiry and grace rules have one copy.

* fix(relay): log a failed memory renewal; test flips, replays and basis cleanup

A renewal write that fails still answers the stored expiry, but now logs a rate-limited
line so a persistently failing update is visible before phones age toward re-pairing.
2026-10-19 06:27:13 -04:00
Neil e590699e7e refactor: round-3 pass-1 follow-ups (#30699)
- one repoRowOwnerStamp for the SSH and runtime session placeholder builders
- lineage refresh resolves the transport host once; pin that a server reply never appends a copy
- markdown-document-paths tests live next to the shared module
2026-10-19 03:27:09 -07:00
Neil aa739f3395 refactor(renderer): one FileTypeIcon component for file icons (#30695)
Pure refactor. Twelve rows, trees and menus called getFileTypeIcon(path)
and then rendered or createElement-ed the result themselves; they now
render <FileTypeIcon filePath />, so a change to where file icons come from
lands in one component. The tab and chat pill keep getFileTypeIcon because
they hand an icon component to another component.
2026-10-19 03:21:46 -07:00
Neil 224d126133 refactor(composer): split run-target rows and worktree-creation steps into named pieces (#30703)
* refactor(composer): build run-target rows from per-section builders

* refactor(composer): move run-target row actions into their own module

* refactor(composer): render run-target rows in RunTargetRowList

* refactor(worktree-create): dispatch remote-target preparation and share the provisioning log

* refactor(worktree-create): extract the pending create's tab strip

* refactor(sidebar): split the ephemeral VM resume out of worktree activation

* refactor(composer): give the run-target submenu rows an explicit row type
2026-10-19 03:13:56 -07:00
Neil 4f425a2861 fix(cli): refuse server names --environment cannot tell apart, and say how to pick one of twin projects (#30676)
* fix(cli): refuse a paired server name --environment cannot tell apart, and say how to pick one of twin projects

* fix(cli): point an ambiguous --worktree at its host-qualified identity

* fix(cli): keep the ambiguity listing on the paired server the command chose

* fix(cli): compare saved server names the way --environment does, and route cross-host twin projects to --project --host
2026-10-19 03:12:30 -07:00
Jinwoo HongandClaude b50929956f feat(relay-ops): watch a memory phone cell and turn its phone path off on a breach (#30662)
* feat(relay): add the per-cell phonePath switch, effective only in reserve mode

Step 6 of the relay redesign keys every phone-path change on this switch. The cell
reports the effective value in runtime status, and the flag tool refuses phonePath=memory
unless the cell is reserve, and a reserve flip that would turn phones over unnamed.

* feat(relay-ops): watch a memory phone cell and turn its phone path off on a breach

The reserve watch now also judges five phone rules while the switch file says
phonePath=memory, from the cell's own runtime metrics against the 10 minutes before
flipped-at: 4401s, confirm failures, attach-deadline 4404s and accept database errors
over their lines for 3 minutes, and completed accepts under half of background. A
phone-rule trip writes phonePath=db only; any other trip still flips the cell back
whole. Resume audit drops page after 5 minutes and flip nothing.

* fix(relay-ops): store phonePath=db as no key, so old images keep taking writes

An explicit phonePath=db left in the switch file would make the flag tool refuse every
later write after an image rollback, including a watcher's admitMode=db flip back.

* fix(relay-ops): keep phone watch rules off traffic noise, and keep watching after a phone trip

Completed accepts are no longer judged. Rate lines use the background mean plus
max(margin, share, 4 Poisson deviations), with a margin of 3 for attach deadlines. A
phone trip writes phonePath=db mid-watch and the reserve rules go on; both flip backs
remove the phonePath key so a cell rolled back to an image without it takes the write.
The phone background starts where phonePath went memory, and a background broken by a
restart refuses the watch. Audit drops page only while rising; failing renewals page too.

* test(relay-ops): replay the phone rules on 26 recorded hours of c7 and c29

Per-minute counts from the cells' runtime metrics (read-only, 10-18/19) replace the
synthetic quiet window. c29's bursty auth failures tripped the attach-deadline rule at a
margin of 3 in 29% of 300-minute watches, so its margin is now 5 like the others.

* fix(relay): judge the phone rules as a share of attempts, retry a failed phonePath write

The count rule false-tripped busy c28 (33/121 five-hour watches). The share rule (over half of
the minute's attempts, 0.25 over the background, capped at 0.6) trips none of the recorded c7,
c28 and c29 watches and catches two refusals per accept within 3 minutes. A failed mid-watch
phonePath=db write now keeps watching, retries each sample and pages after 3 failures; break
glass also drops phonePath.

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

* docs(relay): cite the replayed c28 count-rule false trips

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

* fix(relay): judge phone confirm failures against completed accepts

Each completed accept makes one confirm, so failures/(failures + accepts) could never pass half
and a total confirm outage never tripped. Confirm failures are now a share of accepts.

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

* test(relay): note why the confirm replay tests only trips

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-19 06:12:03 -04:00
Neil 90236c3bde refactor(browser): one page gate and one owner for a host command's hold (#30652)
The revocation gate, the input cancel list and the hold's lifecycle each lived in two places.
Sinks now call one gate, held input is recorded at the CDP send (and the one native key path),
and runWithRemoteCliPageAdmission owns retire-on-revoke, release and error conversion.
2026-10-19 03:11:54 -07:00
Jinwoo Hong 1a41a23419 feat(relay): resume paired phones from one device read on memory cells (#30649)
* feat(relay): add the per-cell phonePath switch, effective only in reserve mode

Step 6 of the relay redesign keys every phone-path change on this switch. The cell
reports the effective value in runtime status, and the flag tool refuses phonePath=memory
unless the cell is reserve, and a reserve flip that would turn phones over unnamed.

* feat(relay): resume paired phones from one device read on memory cells

With phonePath=memory in reserve mode, a phone resume is admitted from one device
read and the cell's own seat, and takes no activity leases. A refused device closes
4401, a failed read 4503, and only a missing device row falls through to the invite
path. A phone whose host has no seat gets one assignment read: 4409 when the row
names another cell or the read fails, otherwise 4404.

* fix(relay-ops): store phonePath=db as no key, so old images keep taking writes

An explicit phonePath=db left in the switch file would make the flag tool refuse every
later write after an image rollback, including a watcher's admitMode=db flip back.

* fix(relay): log a failed no-seat read, recheck the switch after the device read

A failed assignment read for a phone with no seat now logs a rate-limited line before
its 4409. A switch that turned off during the device read sends the phone down today's
path. resolveResume reuses the one-read lookup, so the expiry and grace rules have one copy.
2026-10-19 06:06:50 -04:00
NeilandClaude 9f37fcdf70 fix(persistence): a commit that runs out of disk no longer stops all later saves (#30694)
* fix(persistence): a commit that runs out of disk no longer retires the profile-state writer

A WAL COMMIT on a full disk fails with SQLITE_FULL after SQLite rolls the transaction back. It was
reported as indeterminate, which retires the writer, so every later write failed until a restart:
on a managed SSH host, a disk that filled once left orcad unable to open a terminal even after the
space was freed. SQLITE_FULL fails a frame write before the commit point, so it is now a known
failure; every other failed commit stays indeterminate.

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

* docs(persistence): say why SQLITE_FULL on COMMIT is safe to treat as not committed

Opus review: the claim holds because powersafe overwrite (on by default) means no padding frames
follow the WAL commit frame.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-19 02:56:37 -07:00
Jinwoo Hong cb0b152409 test(relay): census every activity-lease reader for phones without leases (#30625)
* test(relay): census every activity-lease reader for phones without leases

Step 6 lets a reserve cell accept phones without writing confirmation or
splice leases. This ratchet classifies every reader of the lease table by why
that is safe, and pins the guards that keep completion and idle rehome off a
reserve cell whose host holds no source lease.

* test(relay): scan subdirectories and USING reads, and ban a bare lease table name

The ratchet now walks every production file under src, counts USING as a read, and
fails on a bare table-name value, which could reach an interpolated FROM it cannot see.
2026-10-19 05:54:32 -04:00
OrcaWinandm4air 83c768d1b4 fix(crash-reporting): Windows native crash reports say what the fault touched and which code ran (#30691)
* fix(crash-reporting): record what a native access violation touched and the bytes that ran

Windows renderer AV reports carried only the faulting module offset. Two
recent ones (Orca.exe+0x23d3f88, +0x21674c2) map in official Electron
symbols to stores into memory the preceding instructions had just written,
which an intact binary on a healthy CPU cannot fault on. Without the access
kind, fault address and executed bytes, an engine bug, a corrupted code page
and unstable hardware look identical.

Read them from the Crashpad dump: AV access kind and target, the target's
distance from the faulting thread's stack pointer (x64), and 16 bytes at
the instruction pointer from the captured memory list.

* fix(crash-reporting): capture code bytes only inside a loaded module

They exist to diff against the shipped binary; bytes from JIT or other
anonymous memory add nothing and could carry page contents.

* fix(crash-reporting): capture code bytes only from Windows dumps

On macOS and Linux Crashpad records the touched data address as the exception
address, so bytes read there would be module data, not the faulting code.

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-19 02:49:44 -07:00
OrcaWinandm4air 00d1545a65 fix(diagnostics): crash bundles always keep the terminal daemon's newest log lines (#30690)
* fix(diagnostics): reserve a share of the crash bundle for the daemon log

Trace spans (git.exec, renderer_memory) fill the 4 MiB bundle cap on busy
hosts, and the daemon log was read only after them, so 11 of 13 recent
Windows renderer-OOM bundles carried no daemon lines at all: no session
counts, no daemon deaths, no process-growth level.

Collect the daemon log first with a 1 MiB budget; whatever it leaves
unused still goes to trace spans.

* fix(diagnostics): return unused bundle space to the daemon log

The 1 MiB share is now a floor, not a cap: after spans are collected the daemon
log takes whatever they left, so a chatty daemon beside quiet traces keeps up to
the whole bundle as before.

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-19 02:47:39 -07:00
OrcaWinandm4air da6d823ea0 fix(crash-reporting): renderer crash reports say whether the window was hung or blank (#30692)
* crash-reporting: say whether the main window was hung or blank when its renderer died

Windows killed/exit-1 renderer reports read the same whether Task Manager, an
EDR, or Orca ended the process. Field bundles show several of them follow
minutes of renderer silence or a document that never loaded after an update,
which points at users ending a frozen Orca. Record Electron's
unresponsive/responsive transitions as durable breadcrumbs and attach
rendererUnresponsiveForMs, rendererDocumentLoaded, rendererDocumentAgeMs and the
window's visible/minimized/hidden + focus state to the renderer crash details.

* fix(crash-reporting): a failed load reads as a blank window, and a crash ends the hang

Electron fires did-finish-load after did-fail-load, and a failed load never
commits, so track from did-start-navigation and flag main-frame load failures.
Clear the hang at renderer death so the replacement renderer's responsive is not
logged as the dead one recovering.

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-19 02:45:42 -07:00
Jinwoo Hong a12c592f75 feat(relay): add the per-cell phonePath switch, effective only in reserve mode (#30629)
* feat(relay): add the per-cell phonePath switch, effective only in reserve mode

Step 6 of the relay redesign keys every phone-path change on this switch. The cell
reports the effective value in runtime status, and the flag tool refuses phonePath=memory
unless the cell is reserve, and a reserve flip that would turn phones over unnamed.

* fix(relay-ops): store phonePath=db as no key, so old images keep taking writes

An explicit phonePath=db left in the switch file would make the flag tool refuse every
later write after an image rollback, including a watcher's admitMode=db flip back.
2026-10-19 05:38:03 -04:00
NeilandClaude Opus 5.5 912cb06398 fix(managed-server): repair main typecheck after the byte-budget and snapshot-room PRs crossed (#30680)
* fix(managed-server): import newestStateMtimeCommand from its new module

#30635 and #30618 merged in parallel: #30618 moved newestStateMtimeCommand
to orcad-state-newest-mtime.ts while #30635's byte-budget test imported it from
orcad-state-snapshot.ts, breaking typecheck on main.

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

* test(managed-server): measure the snapshot-space probe and record #30618's capture growth

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 02:29:45 -07:00
Jinwoo Hong 0e1d0ad4a8 A CLI split that times out leaves no pane (dark, layout-owner build only) (#30563)
* fix(layout): a pane main closed stays closed when its terminal starts late (dark, layout-owner build only)

* fix(layout): main takes back a CLI split pane whose terminal does not start within 10 s (dark, layout-owner build only)

* fix(layout): the timeout take-back keeps a pane whose terminal started or is starting (dark, layout-owner build only)

* fix(layout): one take-back call keeps the layout-writer count

* fix(layout): a split take-back is decided in the write queue, and a spawn for a taken-back pane starts nothing (dark, layout-owner build only)

* test(layout): a spawn for a taken-back pane is refused before the provider spawns

* test: typed store fake in the closed-pane spawn test, no casts
2026-10-19 05:21:06 -04:00
Jinwoo Hong 9294b0ae9c fix(sidebar): the window raises the unread flag exactly when its host does not (dark switch) (#30638)
* fix(sidebar): the window raises the unread flag exactly when its host does not

With ORCA_SIDEBAR_INPUTS=main the host raises the workspace unread flag. A host
now advertises workspace-unread.host-raises.v1 in its status; a switch-on window
raises the flag itself only for workspaces whose paired server lacks it, waiting
for that server's status first. The desktop advertises the same capability to the
hosts it pairs with, and a host leaves raising to an older desktop (one that hosts
client pages without the capability) while it is connected.

* fix(sidebar): every desktop socket advertises the unread capability; window answers from cached status

The browser host lease and network tunnel build their capabilities on the shared
remote base, not the Electron list, so a current desktop's lease looked like an
older desktop and silenced the host. The capability now lives in the shared base.
The window reads the cached status first and probes only before the first one.

* test(sidebar): pin the cached-status path and both older desktop socket shapes

* test(e2e): narrow the server row's host before Mark as read
2026-10-19 05:14:00 -04:00