Commit Graph
15339 Commits
Author SHA1 Message Date
Jinwoo HongandE2E Test 03e9a03e9e feat(layout-owner): main opens CLI and phone files itself, in the window's live group (dark) (#31123)
Behind ORCA_LAYOUT_OWNER_IS_MAIN, a CLI or phone file open is applied by main as an
openEditorTab layout command, so a host with no window can open files. The window
reports each workspace's active group as an optional view fact on syncWindowGraph,
and shows main's tab once the frame that draws it arrives. Editor language
detection moves to shared so main can name a file's language.

Co-authored-by: E2E Test <e2e@test.local>
2026-10-20 08:48:53 -04:00
Neil cf6582caaa fix(renderer): a twin project refuses instead of following the Active Server focus, and a no-row listing is this app's (#31119)
* fix(renderer): a twin repo id refuses instead of following the Active Server focus, and a listing for a repo with no row is this app's

findRepoOwner no longer lets focus pick between same-id repo rows: runtimeTargetForRepoOwner refuses a
twin, as resolveOwner does, and getRuntimeEnvironmentIdForRepo names no server for one. repoHostId
stamps and routes a repo with no row as this app (or a web client's only server), never the focused
server.

* fix(renderer): a fork names its source's host, and GitHub routing keeps its twin rule until it can refuse

* fix(renderer): a fork captures its source's server transport, and a GitHub edit on a twin refuses instead of using this computer

* fix(automations): a new-per-run workspace is created on the run's captured host

* fix(automations): pass only a captured run host, so a twin without one refuses

* test: stamp the server repo row through a store helper, prune the focus-read baseline
2026-10-20 05:38:41 -07:00
Neil 49a2203b0e Remote Orca Servers: one list instead of six sections; SSH Hosts: bulk remove that stays removed (#31082)
* feat(settings): one-list Remote Orca Servers page and bulk-manageable SSH Hosts

Remote Orca Servers: replace the three mode cards, Advanced fold, duplicate
managed/SSH-access lists and troubleshooting accordion with a single list
(one row per server: status, one action, overflow menu), an always-visible
'New projects open on' row, and Share / Cloud VM cards that open dialogs.

SSH Hosts: group by source, filter, checkbox + shift-range selection, one
confirm for bulk remove (skipping hosts with a managed server or
workspaces), bulk Test. 'Sync ~/.ssh/config' no longer clears removal
tombstones; 'Restore removed hosts (n)' is a separate confirmed action.

* fix(settings): address review findings on servers and SSH hosts redesign

- SSH filter stays clearable when the list shrinks under the threshold
- Cmd/Ctrl-A only selects hosts when pressed in the list itself
- group legacy no-source hosts with main's import predicate (now shared)
- capture the shift-range anchor before queuing the selection update
- blank server names de-duplicate instead of colliding
- Share dialog Done refreshes link count and resets name editing
- Install / SSH-route dialogs reload SSH hosts when opened
- default-host row shows the selector when the default is a WSL host
- managed 'Not running' no longer masks connecting/revoked/update states
- servers without a dropped transport can be re-checked

* test(e2e): WSL host spec matches repo.rm ending a removed project's shells

repo.rm now stops the project's terminals on the host (#29964), so the
'orphan left by older builds' setup can no longer be built; use a shell
only the host knows of instead. Resolve the project path so macOS
/private/var matches what the host reports.

* fix(settings): carry 'Orca on <host>' naming and paired-twice warning into the new server list

Ports #31099 and #31100 into the redesigned rows, install dialog and SSH
host dialogs.

* test(e2e): keep the WSL spec's project path as created
2026-10-20 05:22:39 -07:00
Jinwoo HongandE2E Test a2e0f20b1e Main sends one layout update per action, with only what changed (dark, layout-owner build only) (#31109)
* A respawned terminal's pane rebinds in one write, and the window shows a binding-only frame without a rebuild (dark, layout-owner build only)

Main: when the window's spawn for a restored pane finds its process gone, the spawn that holds
the pane's reservation keeps the dead binding and its fresh bind swaps it (the restart swap), so
the pane is never published unbound: a same-id respawn publishes no frame, a new id one. A failed
spawn still retires the dead binding. Other callers and window-owned builds retire early as before.

Window: a frame that differs from the shown one only in panes' terminal bindings updates the
store's pane trees as a rebuild would, falling back to a rebuild for holds and shared terminals.

* Dead-owner respawn tests expect the layout-owner build's one swapped bind instead of the early retire

* Main publishes one records frame per layout operation, and the window resubscribes on a missed frame (dark, layout-owner build only)

Main: a durable mutation (a layout.apply, the renameTabs batch, a binding write, a repair) is
one operation. It names the partitions it wrote, and at its end the layout stream publishes
once: one `records` frame holding only the changed records (tab rows, workspace groups, a pane
tree per tab, a binding record per pane, editor files, browser tabs, removed workspaces), each
keyed by main's own ids. Writes no operation scopes (window saves) still publish when their
task ends. The snapshot and every records frame carry a stream epoch. Each pane's binding
carries `ptyGeneration`, which rises on every binding write in this process, so a same-id
respawn is one frame with one binding record. Readers beside the stream get the end of each
operation and the stream's own per-partition loaded model (`workspaceLayoutReads`), so they
need no second Loader pass, with or without a window.

Window: the cache applies records frames to its full layouts, so the existing flush and the
binding-only path are unchanged. A records frame of another epoch or out of sequence, a
malformed frame, or one the cache cannot apply resubscribes at once for a fresh snapshot.

Tests install main's model check, which applies each frame to what subscribers held and
compares it with a fresh projection; dev builds log a mismatch, release builds run none.

* E1 review fixes: keep a swapped binding, bound the window's resyncs, guard operation listeners (dark, layout-owner build only)

- A spawn whose swap bind persisted no longer retires that pane's binding when a later commit
  step fails; a pane on an SSH host keeps the early retire, since its dead owner lives there.
- The window resubscribes at once at most three times before a frame applies cleanly, then takes
  the loss path's backoff. A skipped frame of a newer type still advances its position.
- A throwing operation listener is logged; it no longer fails a committed mutation.
- Tests freeze the shared partition model a reader gets; the dev-build model check skips frames
  that only change live titles. Generation and model-check comments say what is true.

* Keep the layout stream's backoff reset after a single resync; only a resync storm holds it

* A layout stream that recovers from a resync storm reopens on the normal delay again

* Move the replaced pane owner into its own module so the stable owner no longer imports its replacement (dark, layout-owner build only)

---------

Co-authored-by: E2E Test <e2e@test.local>
2026-10-20 08:19:53 -04:00
Jinwoo Hong f11f1b8e06 fix(sidebar): a read Done no longer hides a pane that is still Working in Smart (dark switch) (#31114)
* fix(sidebar): a read Done yields to a pane still Working, re-tiered on any unread write (dark switch)

* fix(sidebar): re-tier through the merged producer's settled change
2026-10-20 08:07:36 -04:00
Jinwoo Hong 35f50726b6 fix(sidebar): same-id workspaces on two hosts sort, nest and stay retained on their own; nested groups and project order hold in host sections (dark switch) (#31111)
* fix(sidebar): key the shared list's order, lineage and retention by host identity (dark switch)

* fix(sidebar): repeat a nested project group's path in each host section (dark switch)

* fix(sidebar): hold Recent project order under the pointer on the desktop (dark switch)

* refactor(sidebar): keep the inline-lineage cast where it was
2026-10-20 08:06:17 -04:00
Neil 0848dbfcc4 fix(editor): disk-checked save follow-ups: repeatable stale reloads, Markdown whitespace, Undo of Reload (#31113)
* fix(editor): follow-ups to disk-checked saves: repeatable stale reloads, Markdown whitespace, Undo of Reload

- A stale cached copy can reload again once the disk baseline moves or a mount sees fresh text.
- A clean Markdown tab keeps a trailing-whitespace-only draft across a reload of the same text.
- Undo of Reload re-reads the file's tabs, so typing in a tab that joined after the reload wins.

* fix(editor): Undo of Reload skips tabs that left the model; keep whitespace drafts only when disk is unchanged
2026-10-20 05:00:00 -07:00
c2b3364445 fix(sidebar): one agent phase history, so a compaction raises no unread and a title-started turn pushes its finish (#31103)
* fix(sidebar): one agent phase history in the transition detector, so a compaction raises no unread and a title-started turn pushes its finish

The detector now reads hookless agents' titles as well as hook rows, so a pane
has one phase. The sort-stamps producer consumes the detector's classified moves
instead of rebuilding phases, and raises unread from the detector's news: a
session boundary's done still orders as a finish but is no news. A turn a title
started and the first hook row finished is now a moved finish, which the
headless phone push announces.

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

* fix(sidebar): a pane leaves its old workspace before it is seeded in a new one

The detector emitted the seed before the leave, so the stamps producer moved
the pane into the new workspace and then deleted it there. Leave first, drop the
producer's own leave, and delete the now test-only nextAgentPaneState.

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

* test(sidebar): narrow the seeded workspace in the detector order test

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

---------

Co-authored-by: E2E Test <e2e@test.local>
Co-authored-by: Claude <noreply@anthropic.com>
2026-10-20 07:35:59 -04:00
NeilandClaude 8b11d90905 fix(editor): refuse saves that would overwrite unnoticed disk changes (#30417)
Every editor save carries the disk version its text came from and the host
refuses the write when disk moved on; only the banner's explicit Overwrite
writes without that check. Older hosts compare from the client first.

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-20 04:31:45 -07:00
Jinwoo HongandE2E Test 444064105c build(orcad): a dark test template opt-in, marked so packaging refuses it (#31097)
build-orcad-template.mjs takes --dark-test-template to let ORCA_LAYOUT_OWNER=main
through for test servers; the template and out/orcad carry the switch marker, and
packaging refuses a marked template. The e2e template steps build a switch-on server
when the workflow runs with layout_owner=main.

Co-authored-by: E2E Test <e2e@test.local>
2026-10-20 07:22:34 -04:00
Jinwoo HongandE2E Test 085cc38e75 fix(sidebar-inputs): republish workspace rows on visibility settings saves; fence pre-gap paired-server answers (#31106)
A settings save that changes what the visibility filter reads (external/agent
defaults, workspace folder and its history, nesting) now moves the workspace
rows revision, whichever path wrote it. A paired-server answer to a fetch that
started before a contact gap keeps its rows but no longer marks them live.

Co-authored-by: E2E Test <e2e@test.local>
2026-10-20 07:19:14 -04:00
Neil 41b219d541 fix(renderer): say which saved servers are the same machine paired twice, and how to fix it (#31100)
* fix(renderer): name both entries when a refusal comes from one server paired twice, and mark them in Settings

* fix(renderer): name twins only when they are the workspace's only publishers; generic text when a host is named

* fix(renderer): name twins only when they publish the workspace at one place
2026-10-20 04:18:37 -07:00
NeilandClaude 0ee13f831d fix(ssh): say "Orca on <host>" instead of "managed server" in the UI (#31099)
* fix(ssh): say where Orca runs instead of "managed server" in the UI

Users read "managed server" as hosting run by someone else, so they declined
moving their SSH hosts. Every user-facing string now says "Orca on <host>" or
"this host's Orca server", in all six locales; Settings > "Orca on your SSH
hosts" and the setup line say the code and files stay where they are and only
Orca's list of projects, tabs and terminals moves to the host. CLI command names,
logs and identifiers are unchanged.

Fixes #30880

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

* fix(ssh): reassure only when something moves; name the host's label in its row

Review follow-ups: the reassurance shows only while Orca sets itself up or
moves a host, not on every reconnect; the converting line no longer repeats it;
the row names the SSH host's current label; the section description and the
Move anyway dialog stop repeating themselves; translate the Restart dialog's
buttons; drop "managed" from the remaining raw causes and the external
automation refusal.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-20 04:18:08 -07:00
Neil d9a89dcb0c fix(editor): a rename retargets only editors whose own owner is the renaming host (#31091)
* fix(editor): a rename retargets only editors whose own owner is the renaming host

Each open tab is matched to the move by the owner it captured when it opened (its
server and SSH host), not by looking its worktree id up again, so a same-id twin's
editor on another host keeps its path and its unsaved draft.

* fix(editor): a server route that names no machine never strands the renaming host's own tab

* fix(editor): only an SSH host a tab names excludes it from a rename; a local route can be this app's view of one

* fix(editor): a rename's fallback host check reads both sides at once, never a stale pre-rename host

* fix(editor): a rename matches tabs by the rows' owners, never the Active Server focus

* fix(editor): a tab from another workspace moves only when that workspace is on the host the rename went to

* fix(editor): a rename's cross-workspace check uses the File Explorer's own host resolver on both sides
2026-10-20 03:58:23 -07:00
Jinwoo HongandE2E Test 4e998861e1 test: shared switch-on fixtures so 71 layout tests pass with main owning the layout (#31076)
* test: shared switch-on fixtures so 71 layout-owner test files run with the switch on

- layoutApplyTestTransport answers layout.apply (accept, apply to main's model with its frame, pending, refuse)
- main's loaded layout as snapshot frames, shared from the frame-sync test
- tab-layout maps for the editor store harnesses; missing store methods in the main harnesses
- workspaceRepos/registerWorkspaceRepos so main's load keeps seeded layouts
- ORCA_LAYOUT_OWNER=main runs vitest with the switch on
- the 4 rollback-build tests stay switch-off, labelled

* test: keep switch-off assertions exact, settle only after layout commands, reset the transport per test

* test: parse the transport's command without Reflect.get; name the staged-session type

* test: pin the test transport in the layout.apply census; route the autosave fixture as unit support

---------

Co-authored-by: E2E Test <e2e@test.local>
2026-10-20 06:50:11 -04:00
94e55a2f3d fix(mobile): phone list resends lost option changes, trusts the feed's row facts, and retries a failed feed read (#31088)
* fix(mobile): resend lost Workspace option changes on reconnect, draw feed-owned row fields from the feed, retry a failed feed read

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

* fix(mobile): end a feed retry streak only on install, draw the feed's comment

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

---------

Co-authored-by: E2E Test <e2e@test.local>
Co-authored-by: Claude <noreply@anthropic.com>
2026-10-20 06:47:40 -04:00
Jinwoo HongandE2E Test e01a5392d4 test(ai-vault): pin the appended transcript's mtime so the cold-authority test stops depending on the clock (#31098)
The append reset old-0's mtime to wall-clock time. Once real time passed the
busy fixtures' date (2026-10-20T10:00Z), old-0 sorted newest and was read by
the unfiltered first pass, failing the no-ordinary-read assertion.

Co-authored-by: E2E Test <e2e@test.local>
2026-10-20 06:36:22 -04:00
Neil 63246eba3a fix(pairing): adding a server you already saved updates it instead of saving it twice (#31080)
* fix(pairing): pairing a saved server again refreshes its entry instead of saving it twice

* fix(pairing): only Settings refreshes a saved server in place; other add paths refuse the duplicate

* fix(pairing): a same-key refresh keeps the server's workspaces and tabs; raw add refuses; match any endpoint key

* fix(pairing): refuse a code for a hidden saved server; one choke point publishes verified status only for the pairing still saved

* refactor(pairing): verified-pairing publish in its own module; an unreadable store skips the stamp instead of failing a saved pairing

* fix(pairing): match a host key however its Base64 is spelled

* fix(pairing): a refresh keeps the saved key spelling so the host fingerprint stays put
2026-10-20 03:09:31 -07:00
Neil 956a6aadcb fix(ssh): retry with the saved Node.js mirror once a running connect fails the same way (#31089)
* fix(ssh): retry with the saved mirror once a connect already dialing fails

* fix(ssh): note why the status is current when the lock clears
2026-10-20 03:06:27 -07:00
Neil 917c577433 fix(renderer): no owner route reads the Active Server focus, so a local row never routes to the focused server (#31058)
* fix(renderer): no route resolver reads the Active Server focus, so a local row never routes to the focused server

The worktree route's single-focused-runtime branch, the folder owner's focus fallback and the
worktree host default's focus fallback are gone. The client stamps every server row on arrival, so
an unstamped row is this machine's: terminal spawn and teardown, browser tabs, quick-open, chat and
path links now agree on that. resolveOwnerWithLegacyFallback folds into resolveOwner, and the
focus-free write route becomes the one operation route. Setup terminals keep their explicit
focused-runtime choice.

* fix(renderer): setup terminals keep their focused-runtime host, unstamped repos stay local for the composer, and fixtures stamp server rows

* chore: merge duplicate route imports, drop new fixture casts, prune focus-read baseline
2026-10-20 03:04:03 -07:00
Neil abd1fcf217 fix(cli): worktree create sends the host --project/--host selected (#31066)
* fix(cli): worktree create sends the host --project/--host selected

Codex review 6 #6: the CLI picked the right setup but sent only its repo id,
so a repo id set up on two hosts failed as selector_ambiguous; and
--project-host-setup ignored --host when setup ids repeated.

worktree.create gains optional repoHostId (older hosts drop it and keep the
old behaviour); the runtime narrows the repo selector to that host.

* refactor(runtime): the repo selector carries its host through every lookup

Review follow-up (Codex + Opus on #31066): a separate repoHostId was dropped
by the startup-agent lookup, the CLI's GitLab link check and the create
dedupe key. RuntimeRepoSelector now carries the host with the selector, so
showRepo/resolveRepoSelector and startup-agent resolution keep it; the
create dedupe key includes it; repo.show gains the same optional
repoHostId; the CLI spreads one CliRepoTarget into every repo lookup.
Handler-level tests cover worktree.create (plain, startupAgent,
startupDraft), agent.launch, repo.show and the dedupe key.

* test(runtime): drop a needless cast in the agent.launch host test; fold an import
2026-10-20 02:57:14 -07:00
Neil 6211a5ea8f fix(ssh): a legacy relay restarted at the same socket never takes over an open tab (#31068)
* fix(ssh): a legacy relay restarted at the same socket never takes over an open tab

A pane's owner is now its relay endpoint plus the incarnation that relay listed. A
relay that restarts at the owner's socket and reuses pty-N under another incarnation
is never served for the tab, its exit and output never reach it, and its silence is
not read as the original shell's exit: the pane stays unverifiable.

* fix(ssh): keep an exit a reconnected relay reported before its listing bound the pane

* fix(ssh): a legacy relay's exit report counts when it arrives on the connection that listed the pane

* fix(ssh): a pane waiting on a replaced legacy relay keeps its connection

* fix(ssh): never hold or retake a replaced legacy relay's owner role for a pane it can't resolve

* fix(ssh): an epoch-scoped pane's exit reported during a reconnect counts without a listing row

* refactor(ssh): the owner ledger holds the connection that speaks for each legacy relay pane

The connection a listing verified stays bound to the owner until it closes, so its
exit reports count even after a later listing missed the pane.

* refactor(ssh): every legacy relay pane route asks the owner ledger; a replaced pane is never bound

Attach, input and stops go through liveRouteOf, which reaches only the connection the
ledger says speaks for the pane; exit and output reports use the same check. A pane
whose relay was replaced at its socket is never bound to the replacement, and a stop
owed to it is abandoned instead of being fenced against the stranger.
2026-10-20 02:56:29 -07:00
Neil a439dfa451 fix(runtime): detect Linux libraries reliably across SSH and WSL (#31077)
* fix(ssh): detect glibc on Synology hosts without ldd

* fix(runtime): share robust Linux library detection across SSH and WSL

* docs: remove Synology reproduction notes
2026-10-20 02:39:41 -07:00
Neil 41f15805b4 fix(runtime): PR/MR worktree base refuses SSH projects named only by executionHostId (#31054)
* fix(runtime): PR/MR worktree base refuses SSH projects named only by executionHostId

Codex review 6 #4: the guard read the legacy connectionId, so a row stamped
executionHostId 'ssh:<target>' without it ran git fetch on this machine.

* fix(runtime): one gate for local Git on a repo row; base resolution agrees with create

Review follow-up: a local row with a stale connectionId and runtime: rows now
refuse too (create already refused runtime: rows), through
requireRepoLocalGitRoute in repo-execution-host.ts.

* fix(runtime): keep base resolution for runtime-stamped rows this server owns

Codex follow-up: older servers keep a client-minted runtime:<id> stamp on
rows whose files are here, and base resolution worked for them before.
requireRepoLocalGitRoute reads the stored host (getStoredRepoExecutionHostId),
so they run locally again; SSH rows and any row with a connectionId still
refuse.
2026-10-20 02:36:11 -07:00
e3ad4acaa3 fix(ssh): a managed tunnel whose forward can't start takes the stdio bridge, and reclaims a port an old ControlMaster holds (#31062)
* fix(ssh): recover a tunnel port a ControlMaster still holds

A forward made through a ControlMaster leaves its local listener on the
master: the `-N` client exits at once and the master keeps the port. No
process of Orca's holds it then, so the orphan sweep finds no argv to
match and a managed tunnel's persisted port stays bound - every later
connect fails with `Local port 127.0.0.1:<port> is not available:
EADDRINUSE`, until the user finds and stops that master by hand.

Ask the master to drop just this forward (`ssh -O cancel -L`) when the
orphan sweep comes up empty, then rebind. OpenSSH refuses a spec no
master registered, so another forward on the port, and a port held by
anything but a master, are left alone - as are the master's own
sessions and its other forwards.

* fix(ssh): read a refused forward cancel off stderr, not the exit code

`ssh -O cancel` exits 0 whether or not a master held the spec, reporting
a refusal only as `master cancel forward request failed` on stderr. So a
spec no master registered read as a release that freed the port: the
recovery logged a cancel it never made and let the caller rebind a port
still held, turning a clear refusal into a second bind failure.

Treat a request failure on stderr as nothing released, so a port held by
anything but a matching master keeps reporting the original bind error.

* docs(ssh): document the port reservation contract and its recovery limits

State on each entry point what it returns and what it will not do: that
reservation only ever recovers a port already in use and only from what
the caller supplies, that the bind reports a busy port through the
error cause, and that a forward on port 0 carries no recovery at all
because the port it lands on was just free.

* fix(ssh): any managed-tunnel forward that fails to start falls back to the stdio bridge

* fix(ssh): skip the master cancel on Windows; note the forward's bridge fallback

* test(ssh): an abandoned forward's late exit uses a real close reason

* fix(ssh): cancel only Orca's forward on the master, and bridge a forward that never listens

Codex: -O cancel also cancels every forward the loaded ssh_config declares, so the
cancel now reads the control socket from ssh -G and sends the request with no config.
Opus: a forward still waiting on its own login never binds; that now takes the bridge.

* fix(ssh): pass the master's ControlPath through -S so a path with spaces stays whole

* fix(ssh): keep ssh's own exit reason when the forward also never listened

* test(ssh): the real sshd fixture waits for its processes before removing its temp dir

---------

Co-authored-by: zzzpyyy <167217540+zzzpyyy@users.noreply.github.com>
Co-authored-by: zhangpengyun <zhangpengyun@zpydeMacBook-Pro-2.local>
2026-10-20 02:26:46 -07:00
Neil 43b2d95af9 fix(browser): an SSH host's capture is admitted at each capture and at its answer (#31072)
* fix(browser): an SSH host's capture is admitted at each capture and at its answer

Page captures, PDFs and every guest CDP answer are now checked against the host's
admission at the moment they run and again when they answer, so a page that moves
to a desktop-local document mid-command is never returned to the host.

* fix(browser): a host's input that landed still counts after a later revocation

* test(browser): a host input that landed resolves after a later revocation
2026-10-20 02:12:55 -07:00
Neil 2d41a26a38 fix(runtime): new local projects never dedupe onto an SSH project at the same path (#31055)
* fix(runtime): project registration dedupes only against rows on this machine

Codex review 6 #7: create() matched paths across hosts, so creating a local
project at a path an SSH project used returned the SSH row and made no
directory. add() with no host had the same gap. One predicate,
runtimeRepoIsRegistrationCandidate, now backs add, create and clone.

* test(runtime): cover nested-SSH rows and clone reuse in host-qualified dedupe

Review follow-up (Opus P3s): runtime:<env> rows carrying a nested SSH
connectionId, add() for a runtime host, the clone controller, and a stricter
create() assertion.

* test(runtime): clone dedupe fixture commits without signing
2026-10-20 01:59:18 -07:00
Neil e0cf91c62a feat(ssh): one-click Node.js mirror when neither machine can download Node (#31069)
* feat(ssh): offer a Node.js mirror when neither machine can download Node

* fix(ssh): put the mirror action beside Details on the host row

* fix(ssh): offer the mirror only where this computer's setting applies, and word main's error by mirror state

* test(ssh): type the refusal case

* test(ssh): one checked settings fixture for the mirror dialog
2026-10-20 01:55:13 -07:00
Neil c8962bd529 perf(build): check CLI with Bun before TypeScript emission (#31043) 2026-10-20 01:51:32 -07:00
Neil 8802b55db7 fix(orcad): ship node-pty's winpty backend so Windows 1809 / Server 2019 hosts can deploy (#31056)
* fix(orcad): ship node-pty's winpty backend so Windows 1809 hosts can open terminals

node-pty selects winpty below Windows build 18309, but the Windows server
slot carried only the ConPTY addon, so the candidate's PTY self-test failed
on Windows 10 1809 / Server 2019 and the host was recorded as unsupported.

Reported by @lksr202 in #30906.

* test(orcad): smoke the winpty backend in the Windows prebuild slot
2026-10-20 01:43:46 -07:00
Neil e2c52a9aa0 fix(projects): a moved SSH host lists once in a project's Available Hosts (#31064)
* fix(projects): a moved SSH host's hidden setups stay hidden, so its hosts list once (#30859)

Reported by @matheusrenov.

* fix(projects): hide a moved folder-only host's setup placeholders too

* test(projects): cover a WSL-moved setup; read the move journal once

* test(projects): type the setup catalog stubs instead of casting
2026-10-20 01:41:57 -07:00
Jinwoo HongandE2E Test 30d32d3fcf fix(relay): let the same-cap trust probe outlast load-balancer warm-up (#31067)
A freshly restarted cell can be ready while the global load balancer still
answers 503 for it. The probe retried once after 2 s, so c10's 2026-10-20
roll failed after its drain and was left isolated. Retry wrapped source
5xx every 10 s for up to 2 minutes.

Co-authored-by: E2E Test <e2e@test.local>
2026-10-20 04:40:49 -04:00
Neil 14131cd068 fix(editor): markdown preview of a terminal-opened file reads with its source tab's grant (#31060)
* fix(editor): markdown preview reads its file with the source tab's grant and host

A preview of a terminal-opened file outside every workspace on a managed
server dropped the source tab's one-file read grant, so the editor refused
the read as an external local file. A client-local file in an SSH workspace
lost its readOnly flag the same way and was read from the SSH host instead.

Reported-by: @quzard
Fixes #31034

* fix(editor): a retargeted preview keeps its read authority after the source closes

* fix(editor): reload an open preview when its source gets a fresh grant
2026-10-20 01:38:44 -07:00
Jinwoo HongandE2E Test b822132a75 Agent titles from one status batch reach main as one rename (dark, layout-owner build only) (#31048)
* Send a status batch's generated tab titles to main as one rename (dark, layout-owner build only)

* Share the window's as-today refusal rule with the title batch; cover a failed batch and a paused workspace

* Move the renameTabs request schema to its own module, under the file's line limit

---------

Co-authored-by: E2E Test <e2e@test.local>
2026-10-20 04:38:28 -04:00
Neil 536a32ae82 fix(updater): a live orcad's browser helper pauses for macOS desktop installs (#31059)
* fix(updater): a live orcad's browser helper pauses for macOS desktop installs

orcad starts its browser helper from the installed Orca.app, so ShipIt waits
for it, and the installer refused it as an unregistered blocker with no
supported way out. orcad now registers the helper in the existing serve
install park: it stops the helper for the install and starts it again from
the new bundle once the hold ends. The refusal card for a helper an older
orcad still runs says what to do instead of "stop the server".

Reported-by: @routesmith

* fix(orcad): retry a failed helper restart, hold through a mid-swap bundle, catalog the guidance

* fix(orcad): a new park cancels a slow helper restart; reset backoff per install
2026-10-20 01:34:52 -07:00
Jinwoo Hong 46d0a00548 fix(relay): move director traffic in two steps so a deploy does not land on one instance (#31051)
* fix(relay): move director traffic in two steps so a deploy does not land on one instance

Cloud Run routes a new director revision's floor instances their first request
roughly 60-110 s after they start, while a traffic move reaches clients in
15-30 s. The one-step 100% move therefore put every assignment on the single
instance started with the revision, which sits at its own admission limit at
peak; the 2026-10-20 deploy returned ~550 assign 503s in two minutes that way.

With a floor above one, the deploy now moves 20% first, waits until Cloud Run's
request log shows every floor instance serving (six-minute deadline), then
moves the rest. A timeout or failure at that stage returns all traffic to the
previous serving revision, which still holds its warm floor. The staging deploy
identity gains log read access for the same check.

* fix(relay): bound the director spread log read, log the instances seen at timeout, and document split recovery

A hung gcloud log read could hold the 20/80 split past the six-minute deadline;
each read now times out after 30 s and counts as an unread poll. A timeout logs
the distinct instances seen, so a partial share packed onto fewer instances than
the floor shows up. The runbook covers restoring a split left by a cancelled job
and the missing selector-rollback tag after a timed-out deploy.
2026-10-20 04:13:17 -04:00
Neil e534527407 perf(checks): use Bun for end-to-end typechecking (#31046) 2026-10-20 01:00:32 -07:00
Jinjing 52eea6dd12 docs: update WeChat QR code for group 13 (#31052) 2026-10-20 00:59:21 -07:00
764e09bf2b No empty extra terminal tab from an old saved terminal record (dark, layout-owner build only) (#31045)
* fix(layout): main sweeps a stored terminal row the window would not reattach, so no empty extra tab shows (dark, layout-owner build only)

A stored terminal row with no tab-bar entry became a tab in main's layout even when nothing
would reattach it, so with main owning the layout the window showed an empty extra terminal tab
that today's build never shows. Today's window drops such a row at startup: it keeps one only
when its startup reconnect plan names a terminal for it (the row's own terminal in a workspace
live at shutdown and not on SSH, where that id is only a wake hint; an SSH relay session id; or a
split row's own pane), and removes the rest with their pane layouts.

With the switch on, main's Store load now applies that same sweep once, beside the existing
tab-level binding repair, so main's layout, frames and saves never hold the row. Sleeping agent
records stay, as they do in today's window. A selection naming a swept row moves as the window's
sweep moves it.

The window-parity tests now restore with today's window as the oracle and load as main does with
the switch on (its startup sweep, then the Loader), so they hold under either build; the ghost-tab
entry is removed and each kept and dropped shape has a profile.

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

* fix(layout): the startup sweep keeps an SSH row whose own terminal the window may reattach (dark, layout-owner build only)

The window ignores an SSH row's own terminal id only when it finds the worktree listed; an SSH
folder workspace or an unlisted SSH worktree reattaches by that id. Main cannot see the listing
at load, so the sweep keeps such a row rather than risk deleting a terminal the window keeps.

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

* fix(layout): the startup sweep keeps a row any partition's shutdown list names live (dark, layout-owner build only)

The window reads one shutdown list merged from every partition, so a stale list in one partition
can mark another's workspace live. The sweep now keeps a row live by its own partition's rule or
any partition's list, so it never drops a row the window keeps. A session from before the tab
bar is swept as the window sweeps it (parity profile added).

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

---------

Co-authored-by: E2E Test <e2e@test.local>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 03:57:46 -04:00
Jinwoo Hong 54f1417af7 fix(relay): give region-correction reconnects and roll re-placements the in-region reserve fallback (#31042)
* fix(relay): give region-correction reconnects and roll re-placements the in-region reserve fallback

Every desktop session open carries a region-correction request, and those
skipped the reserve plan entirely, so with the region's database cells full they
still spilled abroad while an in-region reserve cell had room. The plan now runs
for them in a mode that never answers itself (the database lane must reply so the
window exchange follows) and offers only the in-region reserve fallback.

A host re-placed off a roll-isolated cell went straight to the other-regions tier
when its own region's tier was full. With an in-region reserve fallback it now
books that first, and the roll canary event is emitted for the reserve booking.

* fix(relay): never pace a roll drain on reserve intake, and count every reserve move off an isolated cell

A host leaving a roll-isolated cell whose region was full could get a paced 503
from a throttled reserve cell, stretching the drain and re-running the failed
in-region transaction on each retry. For those hosts a pace is now no room, and
they spill as before. Sticky now says when it declined an isolated pin, so a
share-draw booking off that cell also emits the roll canary event.
2026-10-20 03:33:13 -04:00
NeilandKAPUIST fadcb8a988 fix(sidebar): keep project names short and show their machine (#28250)
Keep same-name projects short across execution hosts and show their machine beside host-owned headers, including when collapsed. Reuse existing host identities and filtered section membership; retain global ownership for shared projects.

Includes tooltip, narrow-sidebar, alias and filtering regression fixes with visual proof in the PR.

Co-authored-by: KAPUIST <thsxornjs12@gmail.com>
2026-10-20 00:25:15 -07:00
Neil cf6bbd30da perf(release): overlap Windows builds and gate signing on Mac success (#31032) 2026-10-20 00:21:25 -07:00
509f19867c [mem] Stream saved transcripts and stage large search writes (#30702)
* fix(memory): stream transcript documents and spill anonymous index writes

* fix(memory): declare read-local search staging in WSL storage inventory

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-10-19 23:58:33 -07:00
Neil 4d76e8e76c fix(renderer): editor writes and unqualified removals route by the rows, never the Active Server focus (#31036)
* fix(renderer): an editor file write and an unqualified removal route through the rows, never the Active Server focus

resolveWriteRouteResult is the focus-free owner route for writes that name no host. Removal (and
its mid-flight guard) and the editor's catalog-owner lookup use it; tests that relied on focus for
unstamped server rows now stamp them, as the client does when they land.

* fix(editor): retained-text owner keys use the same focus-free route as the file's writes
2026-10-19 23:41:54 -07:00
Neil 16fa2a844f fix(release): keep Mac qualification runs out of the release build's concurrency group (#31031) 2026-10-19 23:30:17 -07:00
Neil db6b3fd04f fix(web-client): a damaged browser file list disables only the browser client (#31025)
A corrupt or truncated .orca-web-client-files.json threw while the remote
server built its HTTP handler, which could stop the server from starting.
Catch it, warn, and answer browser requests with 404 instead.
2026-10-19 23:13:50 -07:00
Jinwoo Hong f884b9ff05 ci(relay): run cell-flags and reserve-watch on the paid runner pool (#31024)
Both sat 10-20 min in the shared ubuntu-latest queue on 10-19/20, leaving
reserve cells unwatched between segments. The rollout workflows already
run on this pool.
2026-10-20 02:07:56 -04:00
Neil ba6599eabb fix(ssh): pair again over SSH when a managed server drops this desktop, instead of reconnecting forever (#30775)
* fix(ssh): say the host dropped this desktop's pairing, not that another Orca holds the port

* fix(ssh): pair again over SSH when our own managed server refuses this desktop's token

* fix(ssh): key re-pairs by the refused revision, cap attempts, retry on explicit connect

* fix(ssh): keep a newer pairing stored mid-repair, count only completed reads, and offer Connect, not an access link, for managed servers

* fix(ssh): judge a re-pair only against the pairing still stored; Check connection retries a managed server's pairing

* fix(ssh): a pane whose managed server refuses this desktop says why and offers Reconnect, instead of spinning

* test(e2e): live failure path for a refused managed pairing

* fix(ssh): a Connect that re-paired answers from the new pairing, not the retired one

* fix(ssh): only an explicit Connect lifts a managed re-pair's hold and cap; say SSH access can pair again

Startup restore reaches ssh:connect with automatic: true and went through the same
ensureTunnel that cleared the hold, so repeated automatic reconnects could bypass the
three-attempt cap. The reset now lives in connectTarget's explicit branch.

The Shared Server Access footnote now says revoking there doesn't stop someone with
SSH access to this computer from pairing again.

* ci(e2e): route the managed pairing-repair spec with the managed-host remote sources

* fix(ssh): the repair gate decides whether a managed tunnel builds, not the build

A refused shared-control open rebuilt the tunnel (SSH execs, forward, handshake, wake)
before the held failure or the attempt cap was consulted, so reopens on a blocked
server rebuilt without limit. buildWithPairingRepair now asks the gate first: a failure
stands for the refused pairing revision for 15 s, or through the window once the cap is
spent, and every caller gets it without reaching the host.

- A connect during an in-flight repair keeps its reset (per-environment reset epoch).
- An unpaired answer fails at once; it is not a moved port, so no re-read or wake.
- A refusal that only rebuilt the tunnel no longer reports "pairing changed".
- WSL and cloud VMs no longer say Orca tried to pair over SSH, and Check connection
  only re-pairs servers managed over SSH (runtimePairingRepairRoute).
- Translate the new strings; route the pane-banner files to the managed-SSH e2e lane.

* fix(ssh): fence a status re-pair follow-up to the same server, and hold a refused repair under its own revision

- getRuntimeEnvironmentStatus followed any pairing change mid-probe, past a caller's
  expectedEnvironmentPairingRevision fence. It now follows only for an unpinned caller and only
  a new pairing of the same SSH-managed server under its pinned host key.
- recordRefusedAfterRepair stood under whatever revision was stored now, so a newer pairing
  stored mid-refusal was blocked untried; it stands under the repaired revision.
- The WSL restart test expects the refused-pairing classification main's WSL path now reaches.

* fix(ssh): a re-pair hands its callers the new credentials, and a failed one judges only the pairing it tried

- ensureOrcadManagedTunnel returns the environment as stored once the tunnel stands. The
  terminal census and a deploy's already-current result use it, so after a re-pair they
  no longer authenticate with the refused token, which read as an unverifiable census and
  refused restarts, updates and stops.
- The shared-control refusal handler blocks the status owner only while the failure stands
  for the pairing stored now (managedOrcadPairingFailureStands). A newer pairing stored
  meanwhile keeps its owner and retries.

* fix(ssh): migrations commit with the pairing the tunnel stands on, and a Connect mid-handshake stays reset

- Conversion binds the environment ensureOrcadManagedTunnel returns, so the destination, the
  server project reads and the result use the re-paired credentials (the migration mark is kept).
- A delta move gets its destination only from connectDestination, which opens the tunnel and
  builds the destination from what it returns; a pre-ensure destination can no longer exist. A
  tunnel that cannot open keeps the delta journaled and the host marked for the next move.
- buildWithPairingRepair takes the reset epoch when it starts, so a refusal after the re-pair
  cannot reinstate a failure that a Connect pressed during the handshake cleared.

* refactor(ssh): one module owns a shared control socket's environment policy, keeping request-connections under its line limit
2026-10-19 22:45:25 -07:00
Neil e278bc9a17 feat(orcad-migration): unsaved drafts stay on this desktop and are renamed to the server on commit (#30894)
* feat(orcad-migration): unsaved drafts stay on this desktop and are renamed to the server on commit

* fix(orcad-migration): refuse clashing drafts from the rename's own collection; stricter live spec

* fix(orcad-migration): stamp renamed drafts with the server, rename at startup after a cut-off commit, keep draft refusals

* fix(orcad-migration): retain only after a startup rename is durable; startup survives an unreadable journal

* fix(orcad-migration): a cut-off commit is renamed and retained at startup, so the rename runs once

* fix(orcad-migration): one retain per migration at a time; no rollback that could drop newer edits

* test(e2e): check the visible draft in whichever editor a large file opens in

* fix(orcad-migration): startup retains only migrations of hosts this profile owns

* ci(e2e): route the editor-drafts migration spec to the sources it covers
2026-10-19 22:44:43 -07:00
Jinwoo HongandE2E Test d47e3de088 Three small fixes in the layout-owner build: repo reads, dragged-out pane folder, cancelled chat reader (dark, layout-owner build only) (#31003)
* Main routes a floating terminal or removed project without reading every project (dark, layout-owner build only)

In a layout-owner build, finding a worktree's save partition checked for a
project registered on two machines by listing every project on every call,
including ids that cannot belong to a project. Skip that list for ids with no
registered project, and reuse the list a hydration pass already read.

* A dragged-out pane with no shell yet opens its new tab in the pane's folder (dark, layout-owner build only)

In a layout-owner build, main builds the dragged-out pane's new tab and the
window shows main's tab, so the window's folder for the pane never reached it
and the new shell started in the source tab's folder. The window now sends the
pane's folder with the move as an optional field, and main uses it when the
moved pane has no terminal yet. An older main drops the field and keeps today's
folder.

* A chat cancelled while it is being created stays closed when the late reply arrives (dark, layout-owner build only)

In a layout-owner build, cancelling a new chat on this computer closes it on
the host without writing a cancellation record, and the host still answers
the in-flight create as successful. That answer then rebuilt the closed chat's
conversation reader, which nothing would ever close. The cancel now notes the
closed chat in memory, and the create answer skips the reader for it. No
message between the window and the host changes.

---------

Co-authored-by: E2E Test <e2e@test.local>
2026-10-20 01:39:20 -04:00