* feat(runtime): let a paired desktop register its phones as delegated mobile devices
A desktop paired to this runtime can now call pairing.delegatedMobileDevice.sync to make each of
its phones an ordinary mobile-scope device it owns, so the phone mirrors that host through the
desktop. Children are never pending, inherit the parent's reach, and die with the parent's grant.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* refactor(runtime): move the mobile device revoke primitive below dispatch
Dispatch binds the caller-bound delegated-device context, so the revoke path it needs now lives in
a layer under it as a pure move; this drops the abstract method seam. No behaviour change.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(runtime): keep a runtime grant until its delegated phones are revoked
A failed child cleanup now leaves the parent in place so a retry can still find the children, and
revokeRuntimeAccess lives beside the revoke it cascades to. Adds restart, rename and bounds coverage,
shares credential minting, and drops the timing-dependent census.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(runtime): fail a delegated sync whose dropped phone cannot be revoked
Matches the parent revoke: a child whose cleanup could not be saved is kept and the sync errors
before minting anything, so the desktop retries instead of believing the server matches its list.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(runtime): refuse pairing admin to phones delegated through a paired desktop
A delegated child phone could call the mobile-allowlisted pairing-admin methods
(push registration, relay provisioning) that its parent desktop is never granted.
The caller scope now marks delegated phones and denies them that permission.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* feat(mobile): show SSH host health on the workspace row host badge
Rows on an SSH host the desktop cannot reach looked healthy on the phone. The badge now
adds the desktop sidebar's own word ("devbox · Disconnected"), derived by the same shared
function from the connection status ssh.listTargetSummaries already sends; old desktops keep today's badge.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* feat(mobile): name an unhealthy SSH host even in a single-host list
The desktop card shows SSH status on any host count, so the phone now reads target summaries
whenever a repo is on SSH and badges an unhealthy host alone too. Labels and health share one
target-to-host keying, and a hook-level test pins the reply-to-row wiring.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(mobile): clear stale SSH host badges and announce them without the dot
A catalog that no longer spans hosts or holds an SSH repo now clears the health and label maps,
so a host that left cannot keep badging rows. The badge gets a screen-reader label ("devbox,
Disconnected") instead of reading the middle dot.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* test(mobile): type the single-host health fixtures inline so the test tsc passes
Fixtures hoisted into consts widened their health maps to string, which the now-required
hostHealthById rejects under the tests-typecheck ratchet; passing them inline keeps the union.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* test(mobile): re-record the repo-metadata goldens for SSH host health
The settled metadata state now carries hostHealthById, and an SSH-only catalog now reads the
target summaries so a lone unhealthy host can be badged. Scoped re-record via rpc-recording.mts.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
Normalize the incoming host slice and the saved partition before retirement and
membership rebasing read their terminal maps.
Fixes#25924
Co-authored-by: m4air <m4air@Mac.localdomain>
One hosted runner tops out near 2,048 connections to one cell (4 shards each stalled at 512 with
control_open_timeout), short of the 2,840 the Asia flip-back test needs.
The relay server deletion removed src/relay/ from the cross-host qualification
prefixes, but the scope tests merged alongside it still used src/relay/ paths
as their cross-host example. Use src/main/ssh/, which still carries that rule.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
The Asia load runner is registered to the old repository, so asia-east2 load could not run.
Where the load comes from does not change what c4 does with it.
A desktop relays alerts for agents on its paired servers, but the push named
only the server's worktree, so a tap opened it on the desktop and bounced.
The gateway accepts an optional runtime execution host and forwards it.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
The headless split and move-to-group handlers answered {moved:true} when the
target group did not exist, hiding desktop/server group-id drift (#26522).
They now refuse with target_group_not_found, as reorder already does.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* perf(ci): skip unnecessary alias scans and compare preflight compilers
* perf(ci): overlap measured Bun typechecking with static checks
* perf(ci): use measured Bun lint runtime and strengthen compiler parity checks
* fix(agent-status): keep nested models off the parent pane
Preserve the parent's model when the existing identity fence retains an
active parent for a nested CLI hook. Exercise native SessionStart and Stop
bodies alongside model updates and legitimate pane takeovers.
Adapted from PR #25687, with real native-hook and hidden renderer proof.
Co-authored-by: Regis <92858615+Regis-RCR@users.noreply.github.com>
* chore: record original authorship for Parent-pane model ownership
Records the retained adapted contribution from #13602 in replacement PR #26846.
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* test: drop redundant single-agent model case
---------
Co-authored-by: Regis <92858615+Regis-RCR@users.noreply.github.com>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
Co-authored-by: m4air <m4air@Mac.localdomain>
* 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>
* perf: avoid retransferring unchanged paired-runtime session history
Keep selected finite-depth history refreshes content-conditional while preserving scans, full old-peer replies, force refreshes and the existing cache lifetime.
Co-authored-by: mmarabel <166927047+mmarabel@users.noreply.github.com>
* perf(ai-vault): keep full host list behind unchanged replies for cache readers
---------
Co-authored-by: mmarabel <166927047+mmarabel@users.noreply.github.com>
* perf(worktrees): skip unchanged lineage after sidebar order saves
Mark order-only remote events and skip redundant lineage downloads only
after a successful current-host read. Preserve independent cold, failed,
pending and structural refreshes, including reconnect and re-pair.
Addresses #27268.
Co-authored-by: mmarabel <166927047+mmarabel@users.noreply.github.com>
* fix(worktrees): notify after recording SSH-created worktree lineage
Paired clients now skip lineage refetch on sort-order-only events, so the
pre-lineage create notify on the SSH route could leave lineage stale until
the next structural change.
---------
Co-authored-by: mmarabel <166927047+mmarabel@users.noreply.github.com>
* 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>
* test(e2e): add a paired phone client helper and phone-mirror baseline specs
Later phone-mirror slices need one oracle that drives a real mobile-scoped device with the
phone's own capabilities against a serve host or a paired desktop. Pins #26022 across a serve
restart and today's gap: a desktop's phone does not list the desktop's server workspaces.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* test(e2e): compose the phone-mirror topology and check the cold snapshot
Slices need one composer for host, desktop and phone with a single teardown, so specs stop
copying setup. The serve-restart spec now reads the first tab-stream snapshot after the cold
start, which is what the phone builds its tab strip from.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* test(e2e): keep phone-mirror teardown errors from masking test failures
Teardown failures become a test attachment instead of replacing the real failure, and the
cold-snapshot poll reports stream errors together with the PTY list.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(vm): a recipe VM's SSH root runs on a managed Orca server
A recipe VM's hidden SSH target was counted as owned by another runtime, so
every connect refused to deploy a managed server and fell back to the relay.
Its runtime owner only records when the VM is created and destroyed, so it no
longer blocks the deploy. The VM's workspace then lives on that server: the
server adopts the provisioned root as its main worktree, and removing the
project destroys the VM and forgets its server.
* fix(vm): release the server of a relay-era VM that converted on connect
* fix(vm): resume reconnects an SSH VM's server; removal stays on its own VM
* fix(vm): removal and the remove dialog find a VM's server before the catalog loads
* fix(vm): a catalog refresh failure never blocks a VM create
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
* fix(clipboard): paste a macOS image copied beside its file URL
When a screenshot tool saves a file and also copies the image, the
pasteboard holds public.png and public.file-url, and Chromium reports
only text/uri-list from availableFormats(). readClipboardImageSource()
then saw no image and Cmd+V dropped the paste, although readImage()
returns it. On macOS, also check public.png and public.tiff with
clipboard.has() before giving up.
Fixes#27345
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HzCzr11hHmaEhiUoGJCrDy
* test(clipboard): cover macOS file URL image paste safety
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Neil <neil@stably.ai>
* fix(claude): register a short PostCompact hook command
Claude Code echoes every PostCompact hook's full command into the
post-compact transcript, so Orca's ~3KB portable wrapper landed in the
model's context after every /compact. On POSIX hosts (local macOS/Linux and
SSH remotes) PostCompact now gets only the POSIX branch of the wrapper;
every other event, and Windows, keep the full wrapper.
Fixes#25209
* fix(claude): guard runtime home and verify hook process identity
---------
Co-authored-by: Neil <neil@stably.ai>
* perf(worktrees): preserve Git scans when saving sort order
Credit @mmarabel for identifying the repeated scans and the host-only approach in #27271.
Co-authored-by: mmarabel <166927047+mmarabel@users.noreply.github.com>
* perf(worktrees): skip Git scan invalidation for order saves on desktop hosts too
The desktop window notifier ran the worktree-change invalidators unconditionally,
so a desktop (or paired-host) Orca still cleared Git scan caches on every sidebar
order save. Carry the invalidateGitScans flag through the notifier; every other
caller keeps the default.
---------
Co-authored-by: mmarabel <166927047+mmarabel@users.noreply.github.com>
The renderer imports quick-open-listing-limits for the result cap, but the
same module also held a Buffer-backed accumulator that imports the CommonJS
@orca/process-host dist. Vite dev serves that dist as raw ESM, so the named
import failed and the workspace workbench and right sidebar crashed. Move the
accumulator to its own main-only module.
Follow-up to #27344. isTerminalPreviewLineControl now gates on
isTerminalPreviewLineControlFinal, so the normalizeTerminalChunk
pre-filter cannot drop a final the predicate accepts. Add a test over
every CSI final 0x40-0x7e and replace String.prototype.charCodeAt spies
with behavior assertions.
The macOS helper serves each sidecar connection on a dedicated thread
that lives as long as the connection, and the sidecar keeps one
connection open while the helper runs. Encoding a screenshot with
NSBitmapImageRep leaves autoreleased objects that keep the captured
image alive, and that thread never drained its pool, so every
screenshot kept a window-sized buffer in WindowServer until the helper
restarted.
Drain an autorelease pool around each request.
* [CI] Fix E2E folder and catalog fixtures
* [CI] Strengthen folder readiness and reuse composer fixtures
* [CI] Repair E2E catalog owners and retain stronger unit checks
* [CI] Defer optional unit deletions to avoid extra CI jobs
* [CI] Forward Monaco test mocks to the real editor API
* [CI] Make agent launch fixtures handle probes and terminal resize
* [CI] Avoid unused test initialization and flush version probe output
Credit to outof-place for identifying the retention and proposing the focused fix in #27335. Independently validated 15.19 MiB of collectible startup allocations.
Co-authored-by: outof-place <contact@outofplace.space>
Adapted from outof-place normalization work in #27170. Preserve tail behavior while avoiding repeated CSI scans and newline-density regressions.
Co-authored-by: outof-place <contact@outofplace.space>
* fix(computer-use): clean up completed permission helpers asynchronously
Stop the helper belonging to each finished status check without waiting
for a process scan on the main thread. Keep concurrent probes, setup
prompts and agent helpers running.
Adapted from #22451; fixes the cleanup gap reported in #22436.
Co-authored-by: Rishabh Jain <18290665+jrishabh55@users.noreply.github.com>
* test(computer-use): derive cleanup expectations from host paths
---------
Co-authored-by: Rishabh Jain <18290665+jrishabh55@users.noreply.github.com>
An explicit Actions, Open URL, or Leave to terminal choice now also writes the
legacy terminalLinkActionPopoverEnabled switch, so an older opt-out no longer
snaps the Actions selection back to Leave to terminal.
Fixes#25576
Co-authored-by: drakeo338 <paranoyouz@gmail.com>
* feat(layout): record a display-only snapshot of each paired server's tabs
Records the session.tabs.subscribeAll frames main already relays to the window
into a new remoteLayoutSnapshots profile domain (design 6.3, D3). No new server
requests; written only by the recorder; dropped when the server is removed.
Nothing reads it yet.
* fix(layout): keep only what the snapshot draws and save it on structure changes
Drop retirement proofs (sent as deltas) and one-shot navigation from recorded
workspaces; save immediately only when tabs or groups change, letting title
ticks ride the next save; drop the protocol field.
* 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>
* fix(ssh): restore saved port forwards on managed hosts; show Ports for managed workspaces
* test: annotate the mock connection casts
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
A rendered hook left mounted can commit after happy-dom is torn down, which
fails the shard with "window is not defined" from React's scheduler.
Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: Claude <noreply@anthropic.com>
* List the cold-relaunch tab-order swap as known on main
The cold-daemon-restart oracle scenario intermittently finds the tab rows in swapped order after
the relaunch. The relaunch's exits for the killed daemon's terminals retire both tab rows, the
respawns re-add them in the order they bind, and the window's next save keeps main's row order.
Co-Authored-By: Claude <noreply@anthropic.com>
* Wrap the known-on-main comment to the line limit
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix(projects): create on a server's SSH project; Add Project lists the server's SSH hosts
* fix(projects): derive server SSH states; drop redundant casts
* fix(composer): a server's SSH project gets its agents from the server
* fix(composer): only agent detection treats a server's SSH project as the server's
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
* Write runtime-started panes with their tab bar entry and group before they spawn
Co-Authored-By: Claude <noreply@anthropic.com>
* List the headless reorder's row-order finding and mark the headless group writer for the switch
Co-Authored-By: Claude <noreply@anthropic.com>
* Admit runtime-started tabs into the targeted or focused tab group
A phone, CLI or orchestration create on a headless host with two tab groups was written into the
first group, so after a restart the tab moved groups. The phone's targetGroupId, else the host's
focused group, now reaches the pane admission.
Co-Authored-By: Claude <noreply@anthropic.com>
* Admit an untargeted runtime-started tab into the first tab group
Defaulting to the host's focused group moved a CLI-created tab to the right group after a restart
and in the desktop window, while main and the live view keep it in the first group. Only the
phone's explicit targetGroupId now picks the group.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
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>
* test(e2e): pass the catalog owner to the remaining project-group and folder fetches
* test(e2e): pass the owner to folder-workspace and project-group creates
---------
Co-authored-by: m4air <m4air@Mac.localdomain>