* feat(sidebar): main publishes desktop focus, pins, project and host order, and SSH/WSL host state, checked against the window in dev and test
* fix(sidebar): read pins through the host-filtered metadata helper; name pins as workspaces
* fix(mobile): edit queued messages in place
* fix(mobile): use the platform input font size for queued edits
* fix(mobile): handle missing host support while probing queued edits
* Keep phone queued edits owned through save settlement
* Keep phone queued edits local to mounted chat
* fix(mobile): retire queued editor when its card disappears
* fix(mobile): retain queued edits while reconnecting
* test(mobile): await reconnect timer inside act callback
* fix(mobile): publish queued edit transport after commit
* test(mobile): await queued Edit inside act callbacks
* fix(mobile): hand a stale queued Save to the composer and reopen on the newer text
* fix(mobile): close an unchanged queued Save locally and keep stale typing in its own chat
* fix(mobile): explain disconnected queued edit refusal
* fix(mobile): combine queued session contract imports
Typed hosts like github.io, vercel.app, or readthedocs.io were treated as bare public suffixes and sent to search. They are registrable domains, so they now navigate directly.
- Suffix check now uses ICANN-only rules. The private-section list made `foo.github.io` a domain, but it also let the bare suffix pass, so the classifier now treats the hosting-provider suffix itself as a navigable domain.
- Website navigation now outranks a name match only when the query equals a worktree, tab, or page name in full. Partial matches such as "project.dev server" no longer block the "Go to" row.
- Browser tab creation from a URL forwards the target group as `clientTargetGroupId`, so older hosts that publish the page before the create response still place the tab in the invoking split.
* feat(sidebar): main produces per-workspace activity facts in shadow mode
* perf(sidebar): agent pings keep last-read surfaces; shadow counts comparisons
* fix(sidebar): settle review findings in the activity-facts producer and shadow check
* fix(sidebar): refresh on inventory verdict clears; resync agents only when tabs move
* fix(sidebar): scope the shadow comparison by each row's owner, not the focused host
* refactor(sidebar): name the shadow scope after what it keeps
* fix(sidebar): classify sidebarInputs as host-owned in the operation ownership census
Partially reverts #27540 and the plain-http part of #27552, which broke web
pairing on upgrade for 1.4.227 users who open Orca at a LAN or Tailscale address:
- The host no longer refuses a first pairing from a plain-http page on another
machine (insecure_origin, ORCA_ALLOW_INSECURE_WEB_PAIRING).
- Such a page keeps its pairing code in local storage and signs in with it, as
in 1.4.227, so reloads, new tabs and bookmarks stay paired; no notice.
- Session-only browser keys (pairing.device-key-ephemeral.v1, the idle sweep)
stay for browsers without Ed25519.
- A web client whose credentials are refused shows the host's reason instead
of an empty workspace and "Session restore failed".
* fix(native-chat): retire acknowledged send echoes after catch-up
* test: follow phone lint rules in reconnect regression
* test: format reconnect message expectation
* Keep current chat requests independent of loaded history
* Read current chat requests in bounded cursor pieces
* Bound desktop catch-up after independent request support
* Fix current request subscription static checks
* Match generated request schema import ordering
* fix(native-chat): correct current request type boundaries
* Accept readonly client capability fixtures
* Exercise current request reads in wire compatibility inventory
* Align current request fixtures with provider commit and RPC surface
* test(native-chat): include current request browser capability
* Keep the mobile chat session hook within its line budget
* Format mobile current request readers
* fix(native-chat): reopen once for a current-request version whose read keeps failing
A deterministic read failure reopened the subscription, whose first frame re-read the same version and reset the backoff, so the pane resubscribed every 750 ms forever.
* test(native-chat): type the current-request snapshot fixture
* test(native-chat): wait for the hydrated open before driving the subscription
* fix(layout): E1 follow-ups: closed waiting splits, paired-server tabs by host, first-tab e2e wait
- A pane main closes before its tab mounts drops that tab's waiting split for it.
- Mounted tabs skip main's pane tree for paired-server workspaces by host kind, as frames do.
- ensureTerminalVisible waits for the activation tab before creating one.
- Main-asked splits write their pane through admitRuntimePane; startInAdmittedPane loses its single-caller `shown` option.
* refactor(layout): paired-server check in its own module, out of the frame sync
* fix(ssh): allow a host's orca CLI to control Orca by default, and stop resetting saved choices
An absent allowRemoteCliControl now means on; only an explicit false turns the
desktop browser bridge and relay-era orchestration commands off. Removes the
one-time reset of saved true values, the consent stamp writes and the reset
notice, which would have silently turned off hosts upgraded from stable.
* fix(ssh): keep the CLI control choice when adding a host from the sidebar
* fix(ssh): read an older client's omitted CLI control switch as off over RPC
* fix(ssh): check the managed fence before routing settings in browser bridge candidates
* fix(native-chat): retire acknowledged send echoes after catch-up
* test: follow phone lint rules in reconnect regression
* test: format reconnect message expectation
* Keep current chat requests independent of loaded history
* Read current chat requests in bounded cursor pieces
* Fix current request subscription static checks
* Match generated request schema import ordering
* fix(native-chat): correct current request type boundaries
* Exercise current request reads in wire compatibility inventory
* Align current request fixtures with provider commit and RPC surface
* test(native-chat): include current request browser capability
* Keep the mobile chat session hook within its line budget
* Format mobile current request readers
* fix(native-chat): reopen once for a current-request version whose read keeps failing
A deterministic read failure reopened the subscription, whose first frame re-read the same version and reset the backoff, so the pane resubscribed every 750 ms forever.
* test(native-chat): type the current-request snapshot fixture
* test(native-chat): wait for the hydrated open before driving the subscription
* Run automated workspace setup in a dedicated tab
* Preserve native-chat setup route coverage after placement retirement
* Preserve Setup tab design and independent review evidence
* Classify activation test fixtures consistently for routing checks
* Sync runtime English catalog after removing setup preferences
* Update listener savings model after retiring setup action
- Specs with their own CI jobs were also being assigned to general E2E shards, so they could run twice or in the wrong job.
- planE2e now drops dedicated specs before balancing and fails if no general specs remain, instead of producing an empty plan.
- The mobile subscribe spec builds a paired runtime connection from the pairing offer before sending requests, matching the updated remote runtime helper signatures.
* Finish structured agent readiness before delivering held work
* Adapt permission fixtures to startup lifecycle
* Keep model discovery for chats before their first start
* Use one host readiness promise for provider operations
* Await provider opening in the execution host
* Await Claude initialization in host-owned open
* Observe Claude readiness through the host status
* Keep optional readback behind host readiness
* Recheck launch permission after host readiness
* Preserve the awaited child during permission revalidation
* Record failed owned opening before the first message
* Abort a matching provider open when its child exits
* Cover provider exit during an unanswered host open
* Cover explicit failure facts on a host-stopped child
* Keep startup waits cancellable and preserve operation order
* Keep live choices and delivery order while agents start
* Apply startup picks and let parked messages release operations [skip ci]
* Save a waiting startup pick after Stop ends its opening [skip ci]
* Save choices when startup ends or a replacement is still opening
* Check saved choices separately from Claude startup reports
* Align startup fixtures and preserve observed Claude exits
* Order startup picks before later messages and retain flow regressions
* Keep reviewed lifecycle contracts while pruning duplicate tests
* Save picks after a proven exit without requiring a child
* Type the startup reply fixture against the Claude control contract
* Honor the before-input callback in the startup reply fixture
* Keep startup option menus independent and show the saved choice
* Exercise startup picks across real menus and the runtime
* Keep saved effort visible when picking a model during startup
* Include the running-turn scope in repeated Stop fixture
* Use the current attach contract in import fixtures
* Align carried-message fixture with startup event contract
* Keep launch-only permission picks usable while prior writes wait
* ci: rerun on main with the locale fix
* Preserve goal retries and chat launch selections
* Give launch regression its request identity
* test(native-chat): type the provisional-launch hook's session prop
* Add website navigation to the Cmd+J palette
Typing a URL or host such as brex.com or localhost:3000 in Cmd+J now
offers a "Go to <url>" row that opens the site in a new browser tab in
the invoking group. Previously the query only went to search, and the
new tab always opened the default page.
- Classify input with the existing tab-create URL classifiers. Blocked
schemes such as javascript: and file:, oversized queries, and plain
words stay on the normal search path.
- Pass an optional URL through openNewBrowserTabInActiveWorkspace. A
requested URL skips address-bar focus so the page can load directly.
- Rank the website row first unless an exact entity match or an
issue/PR URL should lead. Query-bound actions carry sourceQuery and
are ignored on select if the live input has changed, so a stale row
cannot open the wrong site.
* Open typed websites from Cmd+J in a browser tab
Cmd+J now opens a typed site such as brex.com or localhost:3000 in a new
browser tab. Bare dotted names only count as sites when the suffix is a
listed domain, so file names like App.vue or Cargo.lock still resolve as
files. Browser open failures show a toast instead of an unhandled rejection.
* Fix Cmd+J website action availability
* fix(agents): a resumed agent's chat is readable on the phone before its first new turn (#10923)
* fix(agents): skip a dropped Codex resume and keep the seeded identity across restart
* fix(agents): seed resume identity before the spawn's first await and keep it through terminal evidence
* fix(agents): seed after the spawn commits, and name the session on host-built resumes
Headless Linux CI (xvfb, no GPU) falls back to the DOM renderer, so the
WebGL scenario added in #29331 waited for a canvas that never attaches.
Other WebGL specs already skip the same way.
* fix(ssh): keep a host connected for its bridged relay terminals when another desktop already moved their project (STA-10604)
Desktop 2's move onto the server desktop 1 already runs refused to link a
project its own relay-era terminal still runs in, and the connect then failed
outright: this version has no relay to fall back to. The decision now keeps
that host connected and unmoved, starts the legacy relay bridge, and the old
tab reattaches its shell through it. The SSH host card names the terminals and
how to clear them; the next connect after they exit links the project.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(ssh): release a bridged ssh: row pane's claim on detach too
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(ssh): admit the bridged mark in the renderer, and remount bridged tabs even when the connect reply lands first
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(ssh): keep the unverifiable line for a bridged host whose terminal can't be confirmed
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(ssh): keep the linked-project terminal guidance neutral; only the live bridged line reassures
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* ci: remove Windows build and persistence test overhead
* ci: collect unit phase diagnostics during PR checks
* test(ci): seed hook reuse fixture from the canonical prebuild
The hook reuse regression first built into a missing private --output, so it
always reached Cargo (74s warm on Windows). Build through the default output
instead, which reuses a warm canonical prebuild, then copy its executable and
fingerprint into the private fixture root.
* fix(lint): clear inherited max-lines and broad object tab parameter after main merge
Combine the duplicate shared/execution-host imports in runtime-host-services
(301 -> 300 lines) and type the overlap test's tab mapper with the
RuntimeMobileSessionClientTab owner type instead of object.
* fix(ci): align inherited runtime and workflow contracts
* test: establish changed runtime probe signatures deterministically
* test: await asynchronous daemon profile cleanup
* test: shut down owned daemons before deleting profiles
* test: clean up oracle daemons during final app close
* test: recognize verified Linux zombie exit during daemon teardown
* fix: preserve folder SSH ownership when project groups conflict
* test(e2e): prove the held-6768 case really held the port and orcad bound another
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(pty): keep orcad's managed-launch marker out of user terminals
A terminal on a managed host inherited ORCA_ORCAD_MANAGED_ACTIVATION_ROOT, so an
orcad a user started there looked client-managed: --port stopped being strict
and idle exit applied. Scrub it like the other per-launch identity variables.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Pins #29456 end to end: relay-era SSH projects convert to a managed server, a phone paired with the desktop lists them via mobileRelay.hosts.* under the SSH host's name and opens one through the relay, while the desktop's own list keeps the retained copies hidden.
* Bound and sanitize CI failure excerpts with linear ANSI stripping
* Fix excerpt regression checks and reduce bounded marker overhead
* Require signal proof for benchmark timeouts and load consumer fixtures portably
* Validate benchmark timeout framing and run required cases with a pinned CI baseline
* Strip dense generic ANSI escapes with bounded forward scanning
* Use the standard UTF8 encoder for byte-cap test oracles
* Gate CI log performance suites behind a dedicated job
The wall-clock performance suites run about 1k child workloads up to 63 MiB, so they were sharing runners with unit tests and could fail on noise. They now run only in their own `check_log_performance` job, opted in with `ORCA_CHECK_LOG_PERF=1`.
- Remove the pinned-baseline checkout and `ORCA_CHECK_LOG_BASELINE_DIR`. The baseline comparison is now a manual step that reads the pinned ref from local Git history.
- Run the current-implementation checks with `compareBaseline: false`, which skips baseline builds and audits.
* Inline check-log fixture and drop dedicated perf CI job
- Replace the captured failure-log JSON with a synthetic log built by `createCheckLogFixture()`, so no scraped job output or provenance metadata is kept in the repo.
- Remove the `check_log_performance` CI job. The log performance suites now run only when `ORCA_CHECK_LOG_PERF=1` is set, as a manual benchmark.
- Update the benchmark scripts and tests to use the new fixture helper and drop the `fixturePath` parameter.
* feat(checks): add optional job log outcome
* fix(checks): preserve host capture lifetimes and safe outcomes
* fix(checks): retrieve and retry bounded failed-job logs
* test(checks): cover queued snapshots and captured error safety
* test(checks): verify sanitized host excerpts and paired failures
* test(checks): identify measurements and preserve paired cleanup
* Keep check details load failures safe and localized
* Describe failed check logs as bounded excerpt context
* Normalize check log excerpts once at renderer ingress
* fix(checks): prioritize failure excerpts and retain reading position
* fix(checks): show truthful log outcomes and explicit refresh guidance
* fix(checks): fit failure context in narrow panes and skip generic headings
* fix(checks): prioritize captured failure diagnostics over inline loggers
* fix(checks): stop stack association at a new diagnostic
* feat(checks): add optional job log outcome
* fix(checks): preserve host capture lifetimes and safe outcomes
* fix(checks): retrieve and retry bounded failed-job logs
* test(checks): cover queued snapshots and captured error safety
* test(checks): verify sanitized host excerpts and paired failures
* test(checks): identify measurements and preserve paired cleanup
* Use the generated check-log fixture in host retrieval tests
* Keep check details load failures safe and localized
* Describe failed check logs as bounded excerpt context
* Normalize check log excerpts once at renderer ingress
* Repair ingestion test fixture and benchmark names after dependency merge
* fix(checks): prioritize failure excerpts and retain reading position
* Use the replacement generated fixture for check-details presentation tests
* fix(checks): show truthful log outcomes and explicit refresh guidance
* fix(checks): fit failure context in narrow panes and skip generic headings
* fix(checks): prioritize captured failure diagnostics over inline loggers
* fix(checks): stop stack association at a new diagnostic
* ci: rerun UI checks after restack
* fix(checks): classify sidebar job failures consistently
* 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
* [CI] Keep transport-only tests outside the app store graph
* [CI] Advance the updater install clock in failure tests
* [CI] Avoid descriptor formatting in replay clip counts
* [CI] Advance native-chat test observation clocks
* [CI] Restore observation timers after failed test setup
* [CI] Clean up final headless daemons during public close
* [CI] Keep serializer test oracle out of production smoke triggers
* [CI] Keep three unit fixtures out of production smoke triggers
* [CI] Bound frozen-clock close callbacks in tests
* [CI] Avoid the app-store graph in feedback dialog tests
* [CI] Keep hidden popup fixture compositor frames active
* test(ci): reuse mobile runtime fixture for pairing listeners
* test: restore native chat clocks before rig cleanup
Detach the page's debugger before destroying its offscreen window, so a live
screencast closes before its in-flight snapshot can call capturePage on the
half-destroyed page (an Electron segfault). Also wait for the paired workspace's
first terminal in the link-routing e2e so its late focus cannot hide the pane.
* fix(terminal): log a failed pane reveal instead of rethrowing it
The bridge already replies false when the reveal throws, so the rethrow only
surfaced as an uncaught renderer error after the caller had its answer.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(terminal): refuse terminal.reveal from paired clients
It moves this host's window. terminal.focus lets a paired client do that only
by asking for host navigation, and the callers this method serves (the CLI and
companion apps on the same machine) never arrive as paired clients.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat(terminal): show a paired server's terminal in this desktop on terminal switch
A paired `orca serve` host has no window, so `orca terminal switch --environment <name>`
resolved the handle there, answered `navigated: false`, and the desktop stayed put (#24688).
Add `terminal.reveal`, a desktop runtime method that names a pane by worktree, tab and leaf, as
the owning host knows them, and runs the Agent Dashboard's click-to-focus sequence before raising
the window. When a paired server cannot navigate, `terminal switch` asks the desktop on this
machine to reveal the terminal instead.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test(e2e): terminal switch on a paired server moves the desktop on this machine
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test: update terminal manifest count after main and assert the revealed pane renders
Main added a terminal method since this branch was cut, so the characterization
count is 39 with terminal.reveal. The e2e now proves the target pane is visible
in the DOM, not only selected in the store.
Co-authored-by: Velm14 <Velm14@users.noreply.github.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Velm14 <Velm14@users.noreply.github.com>
* fix(remote-runtime): probe sockets and reset backoff on wake, and keep late resubscribes revivable
Rebuilt from #23359 on current main. On OS resume or network return every
remote-runtime socket pings once and is replaced if it stays silent for 8 s,
the shared-control retry ladder restarts from its short delays, and a
resubscribe that fails or opens after the pane's recovery window latched is
parked so resume/online can revive it.
Co-authored-by: Meapri <104259736+Meapri@users.noreply.github.com>
* fix(remote-terminal): revive a latched remote pane when its host answers again
A pane whose ~3 min auto-recovery window ran out only came back on an OS
online/resume event or a Reconnect click. A Tailscale or VPN route that
returns fires neither, so the pane stayed dead over a working link even
though the host shell was alive. The environment's shared control socket
reaching ready again is now the revive signal: the pane advances its backoff
or replays its attach, exactly as Reconnect does.
Adds e2e repros for paired runtimes and managed SSH hosts: 30 s silent and
reset outages, and a silent outage longer than the recovery window.
* fix(remote-terminal): hold the control-ready reviver only while a pane is recovering
* fix(remote-runtime): re-probe a wake check whose deadline fired late, and trim the SSH outage spec
---------
Co-authored-by: Meapri <104259736+Meapri@users.noreply.github.com>
* fix(remote-files): open HTML, PDFs and images too large for one reply on a paired runtime
files.read stops at 512 KiB and files.readPreview at about 3 MB (one 4 MiB
reply, after base64), so larger files on a paired runtime failed to open.
Read them in chunks with the files.stat and files.readChunk methods hosts
already have, and refuse a save the host's 1 MiB frame cap would answer by
closing the connection.
Fixes#29306Fixes#26062
Co-authored-by: sotashimozono <shimozono-sota631@g.ecc.u-tokyo.ac.jp>
* fix(remote-files): keep a UTF-8 BOM when paging a large remote text file
* fix(remote-files): pin a paged read to one pairing and compare ctime
* fix(remote-files): pin the pairing before a remote read's first request
---------
Co-authored-by: sotashimozono <shimozono-sota631@g.ecc.u-tokyo.ac.jp>
* fix(remote-terminal): a headless host keeps a parked terminal at its viewer's grid
Parking a paired pane closes its stream, the PTY's last viewer. The floor then reclaimed the PTY to the host's geometry, which on a windowless orca serve/orcad host is the 120x40 spawn grid nobody sees. Every reveal then fetched a snapshot at 120x40 and reflowed it to the pane, and a TUI redrew twice. With no host pane to give the grid back to, the floor now keeps the last owner's grid; the parked owner's reserved seat resumes it without a resize.
* test(remote-terminal): a phone or another viewer still takes a parked terminal's grid on a headless host
* fix(remote-terminal): a phone leaving a parked headless terminal restores the parked grid
* fix(remote-terminal): keep the parked desktop grid as the restore baseline even while a phone fits
* test(remote-terminal): tie each PTY size probe to its own reply
* fix(terminal): a cold-parked terminal reveal paints once, at the pane's own size
A structural replay (park reveal, reattach, hidden-output restore) clears the
pane, resizes xterm to the snapshot's grid, replays, and only then fits back to
the pane. Every step was presented, so a reveal showed a blank pane, then the
host's grid in the top-left, then the real fit. The screen is now withheld
behind a copy of its last frame until the transaction and its fit finish,
capped at 1 s so a deferred fit can never leave a live pane unpainted.
Pane-level half of #22728, plus a hold cap and a paired-runtime reveal e2e.
Co-authored-by: mmarabel <166927047+mmarabel@users.noreply.github.com>
* fix(terminal): keep a WebGL screen shown when its frame cannot be copied, and keep decorations positioned
Co-authored-by: mmarabel <166927047+mmarabel@users.noreply.github.com>
---------
Co-authored-by: mmarabel <166927047+mmarabel@users.noreply.github.com>
* fix: render Kitty Unicode placeholder images with bounded ownership
* Enable managed Claude plugin images and bounded host file transport
* Preserve isolated Claude evidence and paired restart diagnostics
* Resolve Windows workspace aliases before WSL image reads
* Handle split remote terminal identities and improve WSL file-read diagnostics
* Reuse native drive paths for WSL image files and preserve guest temporary roots
* Apply native Windows path restrictions to mapped drive image sources
* Preserve WSL participation evidence across image checks
* Keep WSL drive directory containment case-sensitive after canonicalization
* Negotiate managed Claude image support with the launching renderer
* Make image repaint checks independent of display profile and shell output
* Capture repaint diagnostics only after the pixel assertion fails
* Allow floating terminal image reads from host temporary folders
* Keep image stack independent of unrelated CI runner and mobile lint settings
* Reuse worktree argument builder for terminal viewer context
Remove duplicated mapping and keep the RPC method table within its existing line limit.
* fix: render Kitty Unicode placeholder images with bounded ownership
* Make image repaint checks independent of display profile and shell output
* Capture repaint diagnostics only after the pixel assertion fails
* fix(ssh): switching back to a quiet remote terminal no longer refetches its snapshot
Co-Authored-By: Claude <noreply@anthropic.com>
* test(cross-version-wire): drop notice negotiates only between current peers
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* 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>
* fix(ssh): give each machine sharing one home its own managed server state (#29059)
* fix(ssh): key machine homes by machine-id, keep data roots short, recover empty claims
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
* 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>
* fix(omp): read the cmd fresh-config path by name so the guard survives a variable hand-over
On cmd.exe, OMP's fresh-session guard referenced %ORCA_OMP_FRESH_CONFIG%,
which cmd only expands while reading the typed line. When the line reaches
cmd inside another variable (the setup gate's
`<wait> && %ORCA_SEQUENCED_STARTUP_COMMAND%`), cmd expands once, the
reference stays literal, and OMP refuses to start with "fresh OMP settings
are unavailable".
The guard now reads the variable by name with a one-pass for-variable,
`for %! in (.) do @if exist "%~$ORCA_OMP_FRESH_CONFIG:!" ...`, so typed and
handed-over lines behave the same. The older cmd prefix stays recognized
for persisted lines and older hosts. A real-cmd test runs both deliveries in
the Windows CI lane.
* test(omp): pin the cmd guard's echo suppression and type the smoke's cmd line on stdin
The real-cmd test now runs echo on, like a pane, and asserts the config
path never reaches stdout, so dropping the guard's @ fails it. The manual
Windows smoke typed the line into a batch file, where the guard's %! would
need %%!; it now feeds cmd.exe on stdin like a pane does. The guard comment
now states the real rule: only %! or %~...! text in the for body changes,
and the quoter keeps prompts from forming it.
* fix(mobile): phone server rows follow the desktop's other-client and sleeping filters
A phone created through a desktop now records whom it acted for, so the
desktop's Hide other-client workspaces filter keeps the user's own phone
workspaces (locally and on servers) and hides only other people's.
The desktop relay marks each server row its sidebar would hide, and the
rows its own browser or chat tabs keep awake; the phone applies both.
* test(e2e): dedupe sidebar row ids in the visibility spec
* fix(mobile): address review: owner naming, old-server relayed phones, scope trim
* refactor: move the sidebar's project section grouping into shared
The project sections the desktop sidebar draws under Group by Project are
built from the project/setup projection, the per-host catalog fold, the
endpoint host stamp, and the section labelling and ordering rules. Move the
pure parts of each into src/shared (no behaviour change) so the paired phone
can call the same code instead of guessing the grouping.
* fix(mobile): group by Project with the desktop sidebar's own sections
The phone grouped Project sections by repo display name, so it merged
checkouts the desktop keeps apart (several local stablyai/orca checkouts,
or a server's copy) and split what the desktop merges. The phone now reads
each host's repo.list, project.list and projectHostSetup.list (the desktop's
own and each server's through the relay), folds them with the renderer's
shared catalog fold and projection merge, and builds sections with the
desktop's shared grouping, labelling and ordering code. Keys, labels and
Recent order equal the sidebar's; a host that predates the project lists
gets the repo-derived projection, as the desktop does.
Header colour and icon come from the section's anchor repo and rows from
their own repo by id, as on the desktop. The repo filter lists every
catalog repo by id per host instead of merging same-named repos. Group-by
labels read Project and None. project.list and projectHostSetup.list are
routed for paired phones (read-only, workspace permission).
* refactor(mobile): record a phone's owner as creator and fold desktop tab activity into the host flag
A phone now records its owner as the creator (this host for a directly
paired phone, the relaying desktop for a delegated one), so the shared rule
is unchanged and onBehalfOf is gone. The relay ORs the desktop's saved
browser/chat tabs into hasHostSidebarActivity, so the phone's status dot
follows too, and local worktree.ps counts the same signals. The old-server
fallback is removed.
* test(e2e): poll the phone and the sidebar together in the visibility spec
* fix(mobile): one Project-section builder for desktop and phone, with the desktop's repo order
Review fixes. The sidebar's buildOrderedGroups now calls the shared
buildProjectSections for Group by Project (notice and pending repos are
optional inputs), so desktop and phone run one function. The phone ranks
manual header order by its folded catalog order, which is each host's
repo.list order with the desktop's own repos first, as the renderer loads
them; only the desktop's cross-host manualRepoOrder (pairing-local) is
unknown to it. The projection selector is now a memo over the shared
resolver. A transient project-list failure keeps that host's last catalog;
only forbidden or method_not_found falls back to the repo-derived
projection. Catalog rows are checked on every member grouping reads, and
the parity test answers the renderer's real catalog fetch, includes a
server's own project rows, and fixes Manual and Recent orders that differ.
* fix(mobile): use the repo-derived catalog until a host's project lists first answer
* fix(mobile): the desktop serves its sidebar's Project catalog instead of the phone fetching it
The phone fetched repo.list, project.list and projectHostSetup.list from
the desktop and every relay-ready server each minute and folded them
itself, so rows from offline or older servers fell back to repo sections
and keys moved on cold start.
The desktop renderer now publishes the inputs its own Project headers use
(state.repos in sidebar order, the projection selector's projects and
setups, projectOrderBy) on the runtime window graph it already sends to
main, only when they change. Main keeps the last one and answers it on a
new paired-desktop read, mobileRelay.projectSections. The phone reads it
with its repo metadata and groups with the shared builder; a desktop that
predates the read (forbidden or method_not_found) leaves the phone grouping
rows by repo from the desktop's repo.list, and a transient failure keeps
the last catalog. The payload carries only grouping and label fields, no
hooks, credentials or uploaded icons. The phone's own catalog fetching and
its project.list/projectHostSetup.list routes are removed.
* fix(mobile): keep uploaded repo icons on the phone, and isolate the snapshot tests
* test(e2e): the desktop window serves its sidebar's Project catalog to the phone
* chore(i18n): regenerate the runtime English catalog (drift already on main)
* fix(mobile): read the Project catalog as its own request, outside the recorded repo-metadata flow
The extra read inside the repo-metadata refresh stalled its recorded RPC
sequence. It now runs beside it with its own throttle. The repo-metadata
goldens are re-recorded for the intended state change: the name-keyed
colour, icon and id maps became desktopRepos.
* refactor(sidebar): one shared Workspace options filter and sort evaluator
The sidebar, the jump palette and the options badge each applied the
Workspace options filters with their own copy of the rules. They now
call one pure evaluator in src/shared/workspace-view, which reads each
row through a small fact reader so the phone can call it next with the
facts its host serves. The sort comparator moves there too, with each
surface supplying its own Agent activity rank.
No behaviour change: a parity test runs the old sidebar chain and the
new evaluator over every toggle combination, and the old comparator
against the new one for each sort mode.
* wip: workspace view descriptors
* refactor(sidebar): address review of the shared Workspace options evaluator
One predicate builder instead of a per-row scope argument; one
exemption-narrowing function taking the hide flag; consumers import the
shared predicates directly; Agent activity ties fall to the label in the
shared comparator. The activation filter lift reuses the shared
predicates. The parity test now covers other-client rows, live agent
attention classes and the sort-label map.
* fix(mobile): fetch the Project catalog at once when the screen moves to another host
The refresh window is now kept per client, so a host screen reused for another desktop doesn't wait out the previous host's 60s window before showing its Project headers.
* wip: desktop option items from shared choices
* refactor(sidebar): the Workspace options menu renders from one shared option list
src/shared/workspace-view/workspace-view-options.ts lists every Workspace
options control (Hosts, Projects, Group by, Sort by, Project order, Card
layout, Show properties or Card display, and each filter) with its
section, its English copy, its choices and when it shows. The desktop
menu walks that list and renders each option through a table keyed on
the option key, so a new option fails to compile until the desktop has
a control for it; the phone sheet will do the same.
The choice lists (sort modes, project order, card layout, agent activity
layout, card properties) are derived from the shared ids, and a test
checks the desktop's English copy matches the shared copy. The unused
classic branch of the card property list is removed.
No visible change.
* wip: row facts
* refactor(sidebar): address review of the shared Workspace options list
The show rules get their own tests with fixed expectations (the cases
the old filter-section test covered, plus the rest), and the menu test
checks each option's own control rather than any matching text. Card
display rows toggle one property each, with the folder-path copy in
shared. The unused Status and Tasks copy left by the removed classic
card list is deleted from the catalogs. Filter rows write through the
store directly; files are named for what they hold.
* feat(runtime): worktree.ps carries the Workspace options row facts
Each worktree.ps row now says whether it is the project's default-branch
workspace, created by an automation, created by the CLI, or on a
detached HEAD, answered by the same shared predicates the desktop
sidebar filters with. The phone has none of these facts today, so its
copies of the filters either guess (default branch) or are missing.
Additive and inert: optional fields that nothing reads yet. An older
host omits them; readers must treat absence as 'not that kind', so the
row stays visible. Server rows relayed through the desktop pass them
through unchanged.
* feat(mobile): the desktop's own phone reads and writes its other-client filter
ui.get and ui.set strip the pairing-local UI fields for every paired
client. That rule exists for clients with their own host catalog (a
desktop paired to a server, a web client). A phone has none: it shows
its desktop's view, so 'Hide other-client workspaces' must be one shared
value. For the host's own phone (clientKind mobile, no relaying parent)
ui.get now always answers the field and ui.set persists it; delegated
phones, paired desktops and web clients keep the stripping rule.
The phone's view-settings schema reads the rest of the Workspace options
values it does not read yet (the automation, CLI and detached-HEAD
filters, project order, card properties, agent activity layout), each
salvaged to absent so an older desktop keeps the phone's local value.
* feat(runtime): address review of the worktree.ps row facts
Name the fact defaultBranch, and say what absence means for it: a reader
keeps its own guess, because 'hide default branch' must not start hiding
or showing rows an older host never described. Folder workspace rows get
the facts too, so every row from a newer host carries them. The
desktopOtherClient comment now says it is a verdict, not a fact. The test
covers the rows where the host disagrees with the phone's current guess.
* wip: phone uses the shared evaluator
* feat(mobile): address review of the phone's other-client filter mirror
One isHostOwnPhone predicate decides both who shares the filter value
(ui.get/ui.set) and who gets the other-client row marks, so the two
cannot drift. The pairing-local field list notes the exception. Tests
cover a paired desktop still being stripped, and a phone write that
leaves the field out not touching the host value.
* feat(mobile): the phone filters and sorts with the desktop's shared Workspace options rules
The phone's own filter chain and comparator are replaced by the shared
evaluator, reading the facts the host stamps on each row. Phone filter
state takes the desktop's setting names and carries every filter; the
badge and Clear filters match the desktop's. The desktop's other-client
marks become facts the phone filters with the shared value.
A parity test runs the same workspaces through the desktop's reading
and the phone's (facts stamped as the host stamps them) over every
filter combination, folder projects included.
* fix(mobile): host scope in shared filters, patch-key writes, live view push
Folds the phone's host filter into the shared filters so Clear and the badge agree,
writes back only the touched filter keys, and pushes workspaceViewChanged so an open
phone list re-reads desktop Workspace options.
* fix(mobile): Clear sends only changed filters; skip a view re-read older than a local edit
An older host's strict ui.set refuses a key it lacks, so Clear names only what differs. A ui.get
that started before a local edit no longer overwrites it; the edit's own change event re-reads.
The change publisher's field list now lives with it and derives the filter fields.
* fix(workspace-options): all hosts and clear reach the desktop; one filter key list drives every surface
Host visibility: the legacy host scope fallback moves to shared code; the phone reads it, and the
host resets the scope whenever a client writes the host list, so All hosts and Clear stick.
An older desktop that keeps the other-client filter to itself has its marks treated as hide.
Badge counts, is-anything-filtered, Clear and reveal derive from the shared filter keys on the
desktop sidebar and the kanban filter menu. The phone uses the desktop's group and sort values,
holds its view in one state value, and reads its UI fields from one shared list.
* feat(workspace-options): Status and PR grouping read the shared tables
The phone groups by status with the desktop's status helpers and default catalog (its own copy
had the defaults in reverse order) and by PR with a shared lane table, so its labels now read
In review / In progress as the desktop's do. The desktop's PR lane decision ends in the same
shared state-to-lane step.
* test(sidebar): folder rows survive restrictive filters; create grace ignores non-finite times
* fix(mobile): write the host scope with the host list, and wait for the first read before forcing the other-client filter
An older desktop narrows back to its legacy single-host scope when the phone
writes only a null host list, so the phone now also writes the scope for a null
or one-host list. The other-client fallback waits for the first ui.get so a new
desktop's marks don't hide rows before its filter value arrives. Inline the
clear-filters key list and note the PR state asymmetry.
* test(mobile): re-record view-settings goldens for the unknown other-client state
* fix(sidebar): reuse the palette's default host instead of reading the focus setting again
* fix(sidebar): read the host options once in Workspace options and pass them to the Hosts item
* fix: parse the ui.get reply into a named type, fix the row memo test's filters, prune the focus-read baseline
* refactor(sidebar): one switch-to-kind table, and fold the leftover helper files back
- The four kind switches come from WORKSPACE_KIND_SWITCH in workspace-kinds;
the shared evaluator and main's phone-push check both apply it, and the filter
type derives its switch fields from it.
- git-identity.ts folds back into git-identity-display.ts, as on main.
- SidebarFilterState moves into sidebar-filter-actions.ts; visible-worktree-kinds.ts is gone.
- buildWorkspaceRowPredicate is module-private.