* fix(wsl-host): Remove keeps a WSL host's server that another Orca on this computer shares
A WSL host's server lives in the Linux login's ~/.orca, so two Orca installs on
one Windows account (stable and dev), or a desktop reaching the distro over SSH,
share one server. Remove used to close every terminal there and decommission
it, ending the other desktop's sessions and agents.
The server now answers host.peerDesktops (capability orcad.peer-desktops.v1):
the other desktops paired with it, whether each is connected now, and when each
was last seen. Before tearing anything down, Remove asks. If another desktop is
connected or was seen in the last 30 days, it only closes this desktop's bridge
and reports the server kept. If the server can't answer, the teardown is
refused. An older server without the capability gets today's teardown, plus a
warning.
* fix(wsl-host): a shared Remove releases this Orca from the host, and says the host keeps running
- On a shared server, Remove now revokes this desktop's own device (and its
phones) on the host, through the new host.releaseDesktop, before letting go.
Otherwise its recent row would make a peer's later Remove keep the server
forever. Peers also skip rows of the caller's own installation (same caller
fingerprint), such as an enrollment from before a re-add.
- The renderer says when a removal kept the host for another Orca, and the
Remove confirmation no longer promises to end terminals unconditionally.
- Remove-path files now route CI to the fake transport e2e spec, which
exercises Remove.
* i18n(wsl-host): Russian and Traditional Chinese for the shared-host Remove copy
* fix(wsl-host): tell desktops apart by device key, and release this Orca on every Remove
- Peers are no longer hidden by caller fingerprint. Every device-key fork of
one pairing token row inherits that row's fingerprint, so two installations
that paired through one code hid each other, and one's Remove could tear the
server down under the other. Only a shared device public key now marks the
same installation, as with a re-add under a new token.
- This desktop's own device is released as the last call over the bridge on
every Remove that proceeds (no terminals left), not only a shared one. A
later Add then Remove never meets a stale row of ours.
- When the release fails on a shared Remove, the toast says the host may keep
running for a while. The Russian confirmation copy is corrected.
* fix(wsl-host): move undo refuses a removed host whose server another Orca kept
A Remove that kept a WSL host's server for another Orca (R39) now records
that. Before, undo of a move onto that host read the missing environment as
"its terminals and automations stopped with it", and would have restored the
project here while the host's copies kept running, duplicating its agents and
automations. Undo now reads such a host as unreachable and refuses, as it does
for any host it can't reach. A later Remove that proves the server stopped
clears the record. If the record can't be saved, the host stays listed as
pending instead.
* i18n(wsl-host): Turkish for the shared-host Remove copy
* fix(wsl-host): each WSL desktop splits off its own row from orcad's startup pairing
orcad publishes one startup pairing offer and never rotates it, so every
desktop that enrolled through it shared one token row and device id. Stable and
dev Orca on one distro were invisible to each other as peers, and releasing that
shared row broke the next re-add and pairing repair.
- New host.forkDesktop (same orcad.peer-desktops.v1 capability). A runtime
device on a shared desktop token mints its own token row, forked from it and
named by the caller. Forks share the device-key fork cap, and a released fork
frees its slot.
- The WSL desktop forks right after enrolling through the startup offer, or
after a repair lands back on it, and keeps the forked pairing (named after its
userData folder, e.g. Orca or Orca Dev). On an older orcad, or if the fork
fails, it keeps the shared pairing and warns.
- A token row that desktops forked from counts as a peer only while something
is connected on it. One that nobody forked from keeps the last-seen rule.
* fix(wsl-host): keep host.forkDesktop to the startup offer's holder, over E2EE, with a clean label
host.forkDesktop mints a credential, so:
- only the holder of the desktop pairing offer this server advertised last
(orcad's startup offer) may fork; an older offer, a fork, a phone or any
other row is refused;
- the fork takes exactly the caller's grants and reach; params are just a label
(strict, so a grant request is refused);
- it runs only on an authenticated E2EE socket; the token goes back only in
that reply and is never logged;
- the label is display-only: control, zero-width and bidi characters stripped,
whitespace collapsed, at most 64 characters; an empty result is refused.
* refactor(wsl-host): count only Orcas connected right now as WSL host peers; drop fork and release
Review found the token fork and self-release add more identity state than R39
warrants: orphan forks, a desktop left on the shared token releasing it, and a
changed caller fingerprint. So this drops host.forkDesktop, host.releaseDesktop
and the 30-day last-seen rule, and restores the restart test.
A peer is now another Orca installation with a control socket open on the
server right now. The desktop declares its installation (a hash of its userData
path) with host.declareClient on its shared-control socket after every ready,
including reconnects. The server tags that socket and drops the tag when it
closes. host.peerDesktops takes the caller's installation id, so its own other
sockets (multiplex, one-shot) never count, and two desktops sharing one startup
token stay distinct. Undeclared sockets, phones and browser sessions are
ignored. Peer names go through sanitizeDeviceLabel, and a startup-offer
"CLI <date>" name shows as "another Orca". An older orcad lacks the method:
nothing is declared, and Remove keeps today's teardown with a warning.
Limit: an Orca that's closed while its terminals keep running isn't protected.
* fix(wsl-host): keep a reconnecting Orca as a WSL host peer for 60 seconds
Lost contact isn't proof an Orca left. When an installation's last declared
control socket closes (a bridge exit, an orcad restart, resume from sleep,
reconnect backoff), the server keeps counting it as a peer for 60 seconds, or
until it re-declares. A Remove inside that gap no longer tears down its
terminals.
* fix(wsl-host): say an open Orca keeps the host, and name each peer once
- The Remove confirmation now reads "Unless another Orca that's open on this
computer is using it", since only a connected Orca keeps the server.
Updated in all 10 locales.
- The kept-host toast dedupes peer names, so several unnamed peers read
"another Orca" once.
* test(wsl-host): pin the reconnect grace to the last socket's close, and to the server's own forget
- The unit test advances the clock between the two closes and checks that the
peer still counts when the first close's grace would have run out.
- The integration test waits until the server has dropped the closed socket
before reading peers, so it fails without the grace (checked by setting the
grace to zero).
* fix(wsl-host): sweep expired reconnect graces on every close, on a monotonic clock
Expired graces are dropped at the start of forget() as well as peers(), so the
map stays bounded even when nobody asks for peers. The default clock is
performance.now(), so a wall-clock jump can't stretch or cut the grace.
* i18n(wsl-host): German for the shared-host Remove copy
The layout oracle merge gate keeps Blacksmith via fast_runners: true; the
advisory e2e call, nightly schedule and layout-owner dispatch run on
ubuntu-24.04.
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* test(wsl-host): native chat on a WSL host through the fake transport, including a stopped distro
* test(wsl-host): type the chat spec's focus host id
* test(wsl-host): harden the WSL host chat spec and re-run fake-connector specs on change
* test(wsl-host): scope the chat reply check to the transcript and test the fake's route
Find the prompt only in the transcript so the composer can't stand in for it,
re-check the boot-free stop window after unfocusing, right before the focus,
give the fake's distros.json temp file its own name so it can't collide with the
spec's, and test that the fake connector routes to both WSL host specs.
* fix(browser): focus newly opened image tabs
Focus the window before the webview so Command+W closes the new
image tab instead of the original tab after a plain image click.
Add tests for focus order, image loading, and close-shortcut forwarding.
Fixes#34114
* test(browser): verify image tab focus with native keyboard input
* refactor(ci): keep browser E2E routes within registry size limit
* fix(ci): enable background launch for native browser regression
* test(ci): include browser routes in isolated detector fixture
---------
Co-authored-by: Neil <neil@stably.ai>
* ci: move routine checks and unsigned builds to Blacksmith
* ci: retain GitHub Mac images for isolated SSH fixtures
* ci: keep shared terminal perf preparation on Blacksmith
* test: allow encrypted slow-link fixture to finish on ARM CI
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* 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
* 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.
Deletes remote-layout-snapshot/index.ts and workspace-layout/old-client/index.ts. The compat-boundary
check now lists each folder's entry modules, so deleting the folder plus its listed importers'
import lines still removes the path.
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.
* 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>