* feat(managed-server): offer Update and Update anyway on a managed SSH server's row
A managed SSH server only updated on connect, and every connect deferred while
a terminal ran there. Its row now offers Update when this desktop bundles a
newer build than the Orca that runs the host. When running terminals defer it,
Update anyway restarts the server past them; the terminal daemon carries them
across the restart.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(managed-server): Update anyway passes only counted live terminals and keeps Update's version admission
Update anyway now sends the incumbent build it confirmed over; under the host
fence it is refused if another desktop activated a build since, keeps the
newer-host downgrade refusal, and never passes an uncounted terminal census.
The offer also covers hosts activated before the Orca version was recorded,
for the build this desktop already deferred there.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(managed-server): Update anyway also confirms a menu Update the row does not advertise
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(managed-server): the row shows one confirmation at a time
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* ci(e2e): route managed-server update sources to the Update anyway Docker spec
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(ssh): move a managed tunnel off an unbindable saved local port
When the saved local port of a managed SSH server's tunnel can no longer
be bound (EADDRINUSE from another listener, or EACCES from a Windows
excluded port range), pick a free port, save it with the pairing and the
deployment link together, and build the tunnel again. The pairing, server
identity, remote port and projects are unchanged.
A move interrupted between its sidecar and store writes reads the moved
port from the stored endpoint, so the deployment link is never dropped.
Fixes#33182
Co-authored-by: SahilZ0810 <sahil@zamp.ai>
* test(e2e): drop squatter connections so teardown never waits on them
* fix(ssh): move the tunnel port before pairing repair and retire old transports
Move outside buildWithPairingRepair so a refusal on the moved port is
repaired against the revision the move saved, and retire transports and
status owners still dialing the old port, as a repair does.
* fix(ssh): refresh maintenance pairings on the current tunnel port
Update, rollback and restart reread the deployment's local port when they
save the new pairing, since the census before them may have moved it.
* fix(ssh): let concurrent tunnel builds follow a port another caller moved
---------
Co-authored-by: SahilZ0810 <sahil@zamp.ai>
* fix(sidebar): land flip-a's switch-on fixes on main, still dark (no flip)
The fixes found while the sidebar ran from main's feed, without turning
that on: ORCA_SIDEBAR_INPUTS_IS_MAIN stays false, and none of flip-a's
stop-gaps come along.
- Main stops counting a closed tab's terminal once it was asked to stop
and no pane shows it; a failed or refused stop takes the request back,
and a host out of contact stays unverifiable.
- Main fetches a paired server's rows with the transport's normal bound,
so a server slower than 5 s still lists.
- A deferred unread raise flips the folder row it was decided for, and a
read while the owner check is out cancels it.
- Phone: no worktree.ps flash before the feed; a host's last installed
feed stands in while a fresh read is out; it highlights its own
last-opened workspace and a fold never scrolls; projects file under
their project group with the desktop's indent; the collapse spinner
shows only after 500 ms; "Reopen to update" keeps the rows on screen.
- e2e helpers and journeys for the switch-on suite.
* fix(sidebar): keep an owed relay stop marked, clear a thrown stop, and prune pending unread raises
- A stop-and-wait that throws takes its stop request back, like one that fails.
- A final stop the legacy relay still owes stays marked, since the relay retries it.
- Pending unread raises are tracked only while one waits, so the map no longer grows.
- The paired-server fallback poll test gives its third fetch a reply and asserts live.
* feat(sidebar): main publishes cached snapshots and announces its own stores; detected, view and live-content domains (inert)
* fix(sidebar): keep links confirmed only by a new git run, no rescan on metadata writes, no phone wait on detected checkouts
* fix(sidebar): rescan the workspace rows when the Windows runtime default changes
* feat(sidebar): one lifecycle queue per workspace in main, with in-progress hints (switch-gated)
Create, delete, terminal.closeAll, terminal.sleep and worktree.sleep now run through one
first-in, first-out queue per host-qualified workspace: strict order, no merging, delete
cancels what has not started and waits for what has, an unconfirmed outcome pauses until
the host's own answer. Hints publish in a new inProgress feed domain (no reader yet); the
creations domain is its projection. A finished create or delete keeps its hint until a scan
started after it applies, in the same revision. Adds the paired-server metadata serializer
(saving domain, no caller yet). Replaces the creation registry.
* fix(sidebar): a create never queues behind the previous occupant's delete
* feat(sidebar): main runs sleep; the window follows (P3b)
* fix(sidebar): keep electron out of the headless server's sleep path
The desktop registers how sleep reaches paired servers; a headless serve has none.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(sidebar): type-check the sleep e2e specs; drop the removed VM suspend stub
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(sidebar): resume a paused workspace by re-observing, never replaying
- A command issued while paused waits behind the pause; when the owner answers again main
re-reads terminal liveness and the listing, then carries on. A forced delete runs past it.
- Commands keep their arrival order across a workspace lookup; deletes by name key by the
resolved id, so they wait for that workspace's running sleep.
- A second delete with different options is refused as already in progress.
- Close and delete read lost contact as sleep does.
- A create always frees its own queue entry, even after a retry took over its id.
- Drops the unused opId, and the paired-server write serializer and its feed section,
which land with their first caller.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(sidebar): main is the only writer of a sleep's wake records; sleep classifies only lost contact as unconfirmed
- The window no longer captures wake records for a sleep main runs; it takes main's records
from main's answer, which its wake and tab-open resume then read.
- A workspace reopened during its sleep keeps its VM running (the window and a phone's
activation tell main).
- Only lost contact pauses: a definite chat-stop failure or a server's still-live answer is a
plain failure with today's toast.
- A paired server's chats are asked with the Sleep mark, and again without it when an older
server refuses the field, instead of reading an unprobed status.
- A phone's worktree.sleep is answered on acceptance and its activation no longer waits on a
queued wake, so neither can outlast the phone's request timeout. The web client passes
markChatsAsleep and keeps the receipts.
- One desktop adapter, passed where the desktop builds the runtime, replaces the two module
slots; a headless server gets none.
Co-Authored-By: Claude <noreply@anthropic.com>
* chore(sidebar): share worktree.sleep's params with the RPC catalog; lint fixes
Co-Authored-By: Claude <noreply@anthropic.com>
* test(sidebar): test the paired-server sleep where main runs it; drop the window's copies
The window no longer sleeps a paired server's terminals, so its convergence and order tests for
that path move to main (remote-workspace-terminal-sleep.test.ts) or follow main's answer.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(sidebar): retry the sleep spec's liveness read while terminals stop
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(sleep): main resumes every workspace-sleep wake; the window only attaches
- Main claims and starts each slept pane through its windowless resume path, with or
without a window: the "window present" check is gone.
- A phone's open queues main's wake, which finishes only once the resumes have started,
and always notes the reopen so a running sleep leaves the VM up.
- The window no longer relaunches a workspace sleep's records in fresh tabs; opening a
slept pane asks main to resume it and the pane's spawn attaches to main's terminal.
- A window save cannot overwrite main's workspace-sleep records.
- A VM that would not suspend no longer reads as "kept open": the terminals slept, and
the toast says the machine is still running.
* test(e2e): main resumes a slept pane once, in place, and keeps its record
* chore(i18n): add the VM-still-running sleep message
* fix(sleep): a phone's or the CLI's failed sleep shows the desktop toast again
* fix: restore main's final P2 test fixes a merge reverted
* fix(sleep): window saves never write or clear main's sleep records; headless wake report counts only real sleeps
* test: a same-id twin's grace follows the rows' change, not the removed catalog hook
* fix(sleep): drop the 1 s settle wait after an observed exit, and wake a slept workspace's mounted agents on phone open
A workspace sleep stopped each terminal through main's stop-and-verify, which polled for a
further second after the exit it had already seen. And a phone opening a slept workspace no
longer reached the window's mounted panes of agents that had finished before the sleep, so
they stayed asleep until the window opened their tab.
* test: give the workspace-sleep wake record its prompt field
* refactor: match slept-agent records to the workspace with worktreeIdsEqual
* chore(sleep): one import from sleep-record-pane-claim
* test(sleep): route the disconnected-sleep SSH spec, declare the desktop sleep adapter, and open the workspace after its sleep starts
* test(sleep): delete a workspace the spec creates, not the seeded one later specs share
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix(remote-terminal): repaint an idle program when a reconnect can't image its pane
After an SSH reconnect the pane is repainted from the host's screen
snapshot. When the host cannot give one (too large for the replay limit,
no buffer, unanswered, overflowed), the pane only printed a loss banner and
an idle full-screen program that writes diffs kept its pre-disconnect frame
until the user resized.
The two places a remote pane declares it has no host image now ask the
program to repaint through one choke point: a one-row viewport claim and
back, which every TUI honours (Node's tty ignores a same-size SIGWINCH).
It respects a phone or another desktop holding the size, skips hidden
panes, and puts the row back even if the pane unmounts mid-nudge.
Fixes#26887
* test(e2e): idle full-screen program frame after a managed SSH outage
* test(e2e): wait for the pane before typing in the idle-frame spec
* fix(remote-terminal): pick the redraw height on the host's clamped grid
* fix(remote-terminal): wait for the host's grid before putting the redraw row back
* test: give transport mocks a connection status for the redraw gate
* test(e2e): keep the orcad idle-frame proxy typed for the e2e ratchet
* fix(remote-terminal): let a new PTY's redraw supersede a swapped-out nudge
* test: keep new transport-mock lines free of type assertions
* ci(e2e): route the managed-SSH idle-frame spec to its sources
* fix(ssh): an old SSH host converts while a relay shell named only on its tab still runs
The conversion check counted legacy pane-key aliases as terminals waiting to
reconnect. An alias only routes hook events to a pane whose terminal the lease
check, the bridge or the terminal gate already accounts for, so it no longer
blocks the move.
* Tighten the alias census comment
* Register the tab-only relay spec: typed deployment lookup, relay fence baseline, PR route
---------
Co-authored-by: E2E Test <e2e@test.local>
* 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
* 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
* wip: orcad client-hosted browser pages
* wip: tests and renderer placement
* wip: engine-free client tab queries; keep paired server intent
* wip: one logical tab list for orcad engine tabs and desktop-hosted pages
* wip: one selection rule for engine tabs beside desktop-hosted pages
* refactor: one owner per mode for orcad tab selection; hide reopen-on-server without a server browser
* fix: an engine tab create or switch takes workspace selection from desktop-hosted pages
* refactor: one logical tab list and one selection rule for orcad engine tabs and desktop-hosted pages
* fix: keep the engine's per-workspace current tab beside desktop-hosted pages
* fix: an unscoped current tab matches unscoped automation routing
* fix: retained desktop-hosted pages stay listable and closable after their desktop leaves
* 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>
* refactor(ssh): split ssh-connection-utils into concretely named modules
- classifiers -> ssh-reconnect-error-classification.ts
- retry/timeout constants and sleep -> connection/ssh-connection-timing.ts
- buildConnectConfig -> connection/ssh-connect-config.ts
- credential callbacks -> connection/ssh-connection-callbacks.ts
- exec options and abort error -> connection/ssh-exec-options.ts
- wrapRemoteCommandForPosixShell -> ssh-posix-command-wrapper.ts
- shellEscape and shared/wsl-login-shell-command quotePosixShell (identical) -> shared/shell-argument-quoting.ts quotePosixShell
No re-exports are left behind.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix: point the WSL git benchmark and the WSL lane route at shell-argument-quoting
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* refactor(ssh): move the ssh-connection and ssh-target cluster into ssh/connection/ and ssh/targets/
Pure git mv plus import and path-literal rewrites. ssh-connection-utils stays put; the next PR splits it.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(e2e): keep the release-checkout import at the v1.4.218 path
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
orcad-managed-*, orcad-runtime-*, orcad-stdio-bridge-*, orcad-decommission-* and orcad-rollback-*
leave src/main/ssh. git mv plus import and path-literal rewrites; two e2e route regexes learn the
new folder. orcad-runtime-maintenance stays for now because open #29834 edits it.
orcad-remote-*, remote-install-*, remote-node-runtime-store-*, remote-versioned-install and
remote-deploy-timing leave src/main/ssh. git mv plus import and path-literal rewrites; one e2e route
regex learns the new folder. orcad-remote-deploy and orcad-remote-node-runtime-report stay for now
because open PRs #29834 and #29829 edit them.
Moves legacy-relay-bridge/ plus the relay-only census and endpoint files into ssh/legacy-relay/,
so the 2027-01-15 retirement deletes one folder. Live install, exec and framing modules that only
carried the relay name become remote-*. git mv plus import, path-literal, routing and workflow
path-filter rewrites only; no remote file name or path changes.
Gate evidence prose and evidence runs record what ran then, so the check reads only each gate's
commands and test files (baseline 53 -> 14). Four evidence-pinned historical commands that named
deleted tests are removed from their gates' commands and their runs point at the successor via
currentCommand, the manifest's existing mechanism. One live command drops a deleted updater test.
The managed-SSH catch-all route is renamed remote-sources and its test joins the route contracts.
* fix(ssh): a tab closed while a managed host is disconnected stays closed after Connect
#30130 made a user's Disconnect mark the host's managed server manually
disconnected, so the desktop now answers its calls itself with
runtime_manually_disconnected. The tab close path read that as the host's
answer and dropped the close instead of holding it for replay, so the tab
came back on reconnect. Treat it as an undelivered close, and route the
Disconnect sources to the managed-orcad Docker lane.
* test(e2e): close the disconnected host's tab the way a user does
The journey called the store's bare closeTab, which sends only terminal.close
and holds nothing; it passed only because the disconnected host used to be
redialed in the background, which #30130 stopped. Click the tab's X instead,
the path that holds and replays an undelivered close.
* test(e2e): the shared-home spec tells a host's own server from a PID that only collides
Each Docker host has its own PID namespace, so the PID host A records in the
login-home slot can belong to an unrelated live process on host B; kill -0
alone then read it as B running from A's slot. Count a recorded PID only when
its process runs from that slot.
* fix(ssh): an opted-in desktop serves its browser to a managed SSH host's orca CLI again
* fix(ssh): scope bridged pages through the renderer, render them as desktop pages, and route the e2e
* fix(ssh): bridge refuses file URLs and clipboard reads, revokes on disconnect, closes desktop tabs
* fix(ssh): judge bridged paste chords as the page sees them; accept long workspace ids
* fix(ssh): bridged clipboard denial follows popups; revoked commands never leave the browser queue
* fix(ssh): refuse option-like host values bound for agent-browser argv; report undeliverable results
* fix(ssh): bound bridged error results and fall back when the host refuses one
* fix(ssh): bridge only routed host tabs, and bound bridged page scripts by a deadline and revocation
* fix(ssh): cap waiting bridged commands below the desktop socket's result capacity
* fix(ssh): bridge revokes on routing changes, checks each page's route at run time, and cancels host-driven downloads
* fix(ssh): name the bridge registry's owner type
* fix(ssh): bridge refuses pages showing a local document, and never starts a command past orcad's deadline
* fix(ssh): bridged commands may leave Chromium error pages, and stop starting before orcad's deadline
* fix(ssh): ask for a fresh opt-in to host CLI control saved before it drove the desktop browser
* test(ssh): type the bridge registry deadline test
* fix(browser): managed SSH terminal links open in the Orca Browser again
A managed server reached over SSH has no browser backend of its own, so
links from its panes were offered only the system browser. They now open
in a desktop tab whose page egresses through the SSH host, the same route
new browser tabs in that workspace already use.
Fixes#29077, #27349
* test(e2e): keep the managed workspace host selected for the Cmd/Ctrl-click step
* refactor(browser): keep owner-route checks private; split tab presentation instead
Moving the owner predicates to their own module made their callers count as
new focus reads. Keep them in workspace-browser-tab-open and move the
title/error presentation helpers out to stay under the line limit. The SSH
deployment check also reads the workspace's owner route instead of the
focus-falling-back host lookup.
* test(e2e): assert the managed SSH link page renders in the visible browser tab
* fix(browser): keep a desktop-hosted link tab owned by its runtime workspace
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: Neil <neil@stably.ai>
* Run clipboard file copy and TLS openssl through process-host
* refactor(clipboard): inject runProcess, pass the Windows path as data, add an e2e
- clipboard-file-copy builds the process spec itself, so the stdio policy sits
next to the wl-copy/xclip knowledge it exists for; the IPC handler passes
process-host's runProcess straight through and keeps no wrapper.
- A helper's exit is read as data (clean exit or not) instead of being turned
into a throw and caught again.
- Windows runs a constant Set-Clipboard script and reads the path from the
environment, so no path is ever parsed as PowerShell.
- Drop the dead tls-certificate rewrite; nothing in the app imports it.
- e2e: right-click Copy on Windows, then a real shell paste of the file.
* ci(e2e): run the Windows explorer file-copy spec on the Windows runner
Route it from the clipboard sources, keep it out of the Linux lanes, and run it
in the existing Windows e2e job beside the other Windows-only specs.
* refactor(clipboard): tighten the helper seam, tests and e2e after review
- runClipboardHelper takes runProcess and a plain ProcessSpec.
- One handler test pins the process-host wiring on every host; spec details
stay in the unit tests, which now also pin the inherited environment.
- e2e: native realpath so an 8.3 runner temp path still matches, paste via
process-host, and no toast check that could never fail.
- Route the renderer caller to the e2e too.
---------
Co-authored-by: Orca Worker <orca-worker@localhost>
A connect that found its own build already active launched nothing, so a
server killed or stopped by a reboot failed on its stale record (#27284).
A stopped server whose recorded port another runtime took failed the
tunnel's identity check before any serving check could start it (#28013).
Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: yuzhichang <153784+yuzhichang@users.noreply.github.com>
* perf(ci): reduce test overhead and reuse expensive build work
* test(ci): keep worker startup outside failure-case deadlines
* test: isolate push fixture advisory locks
* test(ci): preserve fixed drain retry allowance
* Align CI contracts with current triggers and guide content
* Stabilize consolidated reveal scenarios and retain behavioral CI contracts
* Fix upstream lint blockers while retaining behavior and assertions
* Classify global Jira preferences in the WSL migration inventory
* Align Jira test fixtures with startup and build safety checks
* Include package-test routing dependency in CI checkouts and fixtures
* Keep native stream classes intact in transcript dependency tests
* wip: legacy relay bridge client
* Revert "fix(ssh): end an older Orca's relay terminals from the connect refusal (STA-10166) (#27748)"
This reverts commit d4a55f583e.
* feat(ssh): bridge terminals an older relay still runs into their old tabs
* test(ssh): cover the legacy relay bridge client, target bridge, transport and gate
* test(ssh): type the bridge test fakes without assertions
* test(e2e): a 1.4.224 relay's live terminal survives the upgrade on a Docker SSH host
* ci(e2e): route legacy relay bridge changes to its Docker spec
* fix(ssh): bridge delta moves too, and never read a non-exhaustive relay listing as exit
* fix(ssh): keep retrying a bridge for panes that mount before connect; end panes proven gone
* fix(ssh): bridge 1.4.224's detached leases through export preflight; publish every leased pane before the first look
* test(e2e): build the released relay out of process so Playwright never loads ESM build tooling
* fix(renderer): drop the recovery hook main removed from PtyTransport
* test(e2e): show the converted worktree before reading the bridged tab
* test(e2e): reconnect and wait for the converted host before opening the bridged tab
* fix(renderer): match bridged leases to tabs mirrored from the managed server
* test(e2e): find the mirrored relay-era tab under any worktree key
* test(e2e): open the converted worktree before looking for its mirrored tab
* test(e2e): a retired lease is terminated or pruned
* fix(ssh): back off attaches a listed terminal refuses; one pane per bridged shell
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
* ci: the layout oracle gates merges
An oracle-only job joins verify's needs. It runs only the two layout oracle
specs through the existing e2e workflow, routed by layout writer paths; a PR
outside the route skips it and verify treats the skip as a pass. The route is
widened to the terminal store, terminal pane, graph sync, frozen mirror and
compatibility folders while the layout-holder stack is open. The oracle specs
leave the advisory e2e job so a PR does not run them twice.
* ci: the layout oracle tests the merge commit and routes its harness and stack writers
Review fixes: run on the merge commit so a stale-base PR cannot pass on an old
main; route the oracle's own e2e harness and runtime client (a change there broke
main's oracle without running it), and the window-side layout writers the stack
edits; move the route into its own module.
* ci: route the remaining layout writers the stack edits to the layout oracle
* wip: delete the SSH relay server and its build, assets and tests
* wip: drop main/relay plugin conformance; WSL reader lookup test
* fix: drop the emptied relay bench; prune baselines
* test: keep the native halves of tests that also covered the relay
* chore(ci): drop shard timings for the deleted relay server tests
* fix: keep the directory listing budget that darwin terminal names now import
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
* wip: connect fails for hosts the managed server can't serve; remove relay session, deploy and relay UI
* wip: delete relay-only client modules left unreachable, prune their tests and gates
* wip: format
* wip: exhaustive unserved message
* wip: drop relay attestation test for the deleted ownership proof
* fix: prune stale baselines; type the test transport stub
* fix(ssh): census proves leftover relay leases; name the remedy for older terminals; translate every connect-failure surface
* test(ssh): leftover leases defer to the host census
* test(e2e): remove relay-path SSH e2e specs and their CI lanes
* test(e2e): drop the auto-convert case that built its relay era through a live relay
* test(e2e): mark relay-era browser and catalog journeys fixme until they seed a profile
* test(e2e): seed relay-era browser and catalog journeys from a saved profile
The three journeys built their old-version state through a connect without an orcad template, which now refuses the host. They now start from a seeded relay-era profile, so the first connect converts the host as an upgrading user's would.
* chore(ci): drop shard timings for the deleted relay tests
* test(e2e): carry a relay-era login cookie across the browser conversion; own every launch
* test(ci): drop the deleted SSH layout oracle from its route
* test(e2e): open the retained browser after conversion, retrying a pre-connect card
* test(e2e): bind the relay-era browser partition before seeding its cookie
A relay-era build always recorded the partition binding before a page stored
data in it. Seeding the cookie into an unbound partition made the binding store
refuse it (browser_route_partition_binding_store_invalid), so the retained tab
showed the generic routing card and Retry could never clear it.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: Claude <noreply@anthropic.com>
* fix(files): a managed terminal links and opens a host file outside its workspace
A paired-server terminal checked out-of-workspace paths on this computer, so a
host file such as /tmp/screenshot.png never became a link, and a click was
refused. Link detection now asks the host the same question a click asks, with
the printing terminal's handle, and a granted file opens read-only through the
host's existing terminal-artifact grant.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(files): a restored server-granted tab is never this computer's file
Co-Authored-By: Claude <noreply@anthropic.com>
* test(e2e): keep the external-image journey's /tmp outside every workspace
The default folder workspace is /tmp, so the image resolved into it instead of
exercising a host file outside all workspaces.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: Claude <noreply@anthropic.com>
* test(e2e): cover SSH terminal and workspace journeys on a managed orcad host
* test(e2e): port the provisioned-root recipe journey; read ports from the host scan
* test(e2e): fix the provisioned-root spec's catalog refresh call
* test(e2e): wait for the shell prompt, open file links directly, and mark forward restore fixme
* test(e2e): record launches for teardown, match the prompt robustly, park the link test
* test(ci): list the journeys job in the workflow permission matrix
* test(e2e): run the terminal image-link journey again
* test(e2e): park the external-path link journey on its product gap
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
* ci(e2e): run the layout oracle on PRs that change layout writers
Co-Authored-By: Claude <noreply@anthropic.com>
* ci(e2e): narrow the layout oracle triggers to the modules that write layout
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* refactor(wsl): move the WSL guest bundle sources out of src/relay
The WSL hook relay and WSL browser network relay are built from src/relay but are
not part of the SSH relay. Move them, and the 46 relay modules the hook relay
bundles, to src/wsl-guest so the SSH relay can be deleted on its own. Pure
renames; the built bundles are byte-identical.
Adds a boundary test so nothing outside src/relay imports it except relay tests.
* fix(ci): exempt renamed files' existing lint debt in the changed-lines gate
* test(relay): keep the cast suppression on the wrapped line
* test(relay): only parse files that mention relay in the import boundary check
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
* Keep terminal workspace root links on their execution host
* Resolve terminal root-link source host from the owner, not a missing connection id
A direct SSH pane whose worktree id also exists on the desktop has no
connectionId (ambiguous owner), which was read as local and offered the
desktop workspace. Resolve the source via the owner-route resolver and
skip the root shortcut when the source host is unknown.
* Resolve terminal file links against the source owner's host
A same-id SSH workspace left connectionId unset, so a rejected root link,
the default-app row and Shift-click fell through to this machine's files.
The terminal file context now names the resolved direct SSH host and
refuses file access when no owner can be determined.
* Keep an ambiguous terminal link owner unresolved instead of local
The connection lookup ignores detected worktree rows, so a registered local
row could override an owner the full resolver found ambiguous (registered
local, detected SSH) and send link stat/open to this machine. Only a missing
route may now defer to the connection lookup.
* Re-resolve a held chat link's owner before rechecking it
Native chat rechecks held link targets when a turn ends. A target resolved
while its owner was local kept that context after a same-id worktree was
detected on an SSH host, so the recheck still asked this machine. Each
existence ask now re-resolves the workspace owner and refuses when it is
unresolved or names a different host.
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
* Invalidate shared file watch targets when the selected host changes
* Watch the selected host's own workspace root when hosts share a workspace id
* Watch each open editor on its own execution host instead of the selected host
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
* Restore remote explorer updates through the recovered app watcher
* Keep the remote Explorer watched when it is shown as a fallback tab
---------
Co-authored-by: m4air <m4air@Mac.localdomain>