* feat(acp): ACP agents can run with a private per-process temp folder
* feat(acp): a reclamation primitive removes all but the kept managed runtime release
The installer calls it while it owns the runtime's installs and launches; it answers the entries it
could not remove so that caller can report them. Nothing calls it from adapter construction.
* Recover retired macOS daemon trees during terminal restart
* fix(daemon): never let retired-job recovery failure block restart
A wedged retired job (unkillable descendant, launchctl timeout, recycled
root) previously aborted every untargeted Restart Terminal Daemon, leaving
no way to replace the current daemon short of a reboot. Log the failure
and continue with the ordinary restart instead.
* fix(native-chat): paste clipboard images into a chat on a managed SSH host or paired server
Since SSH hosts moved onto managed Orca servers, a terminal-backed chat on one resolves
to a remote owner, which refused every clipboard image. Save the image on that server
(its own host runs the agent), as a terminal paste already does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(native-chat): let a server-saved clipboard image through the composer's remote-target guard
The composer refuses client files on a remote PTY when it starts the pending chip.
An image the paste saved on that server is not a client file, so mark the chip
server-owned, including when it settles as a file reference. Cover the flow with
the real attachment hook.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore(native-chat): drop an unused paste option type widening
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(orcad): hand macOS opens from the browser sidecar to the desktop app
On macOS 27, LaunchServices registers orcad's Electron browser sidecar even
when orcad was started over SSH, and sends the user's opens to the first
registered instance of the bundle regardless of its activation policy.
Opening Orca on such a host showed onboarding on the sidecar's throwaway
profile instead of the user's own.
orcad now marks the sidecar with ORCA_ORCAD_SIDECAR=1. The app treats the
marker as a background launch and hands app opens, skill share links and
opened documents to a new desktop instance with `open -n` and an empty
environment, one launch per batch. An older installed app ignores the
marker and keeps its current behavior.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(orcad): an automated sidecar never launches the real desktop app
---------
Co-authored-by: jbamlee <dev.jbam@gmail.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(serve): ship the paired web client outside app.asar so a /tmp sweep cannot break it
* fix(serve): unpack the mobile web bundle too, which reads its assets the same way
* 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(session-tabs): keep recent worktree streams warm; skip resubscribing live ones
Switching worktrees on one SSH host tore down and reinstalled the per-worktree
session.tabs.subscribe stream on every switch (a subscribe round trip, an orphan
recovery pass and a snapshot re-apply). A hostContactEpoch bump also reinstalled
both session-tabs streams even when they never ended.
- Keep the three most recently selected worktree streams warm, keyed by
connection identity; a new runtime session, re-pair or hidden window drops them.
Only the selected worktree may seed or respawn a terminal.
- On a hostContactEpoch bump, restart only streams that ended or are waiting on
the retry backoff; live streams are untouched.
* fix(session-tabs): a warm stream seeds only when its host still owns the selected worktree
* refactor(session-tabs): inject the worktree stream installer so the focus-read count stays in the hook
* fix(session-tabs): drop a warm stream when a host that is not selected changes identity
* fix(orcad): orca serve owns its Claude and Codex accounts again (#28962)
orcad never wired account services, so every accounts.* RPC failed once
orca serve moved onto orcad. Build the same account services the desktop
builds (now in provider-account-services.ts), made Electron-free, and
install them in orcad except on a WSL host, whose desktop owns its accounts.
* test(orcad): keep account-services test off the real userData when XDG_CONFIG_HOME is set
* fix(orcad): structured Codex chats and session discovery follow this host's managed accounts
Create orcad's account services before the runtime so its structured Codex launch/read resolvers and catalog-probe prep are constructor deps, as on the desktop. A client-sent account home still wins, and a WSL host keeps them unset so the desktop-pushed CODEX_HOME applies. AI Vault session discovery now includes this host's managed Codex homes.
* fix(orcad): automatic Codex resumes follow the account home their session was recorded in
Share the desktop's resume launch prep as a factory and wire it into orcad's PTY registration and AI Vault resume, so a pane restored after an account switch resumes from its origin home. A client-sent account home keeps resuming from itself.
---------
Co-authored-by: Neil <neil@stably.ai>
* fix(ssh): retry a stalled SSH reconnect when the machine wakes or the network returns
After sleep, an SSH host could sit in reconnection-failed forever: the retry
ladder spent its nine attempts while the Mac was asleep or in dark wakes, and
nothing restarted it. Resume only re-probed managed tunnels, and the window's
online signal never reached SSH at all.
- SshConnection.retryAfterNetworkReturn restarts a spent ladder, a pending
backoff, or a background handshake older than 5s with a fresh budget.
- Resume, unlock-screen and the window's resume/online signal
(runtimeEnvironments:retryConnectionsNow) all call it via the manager and
re-probe managed tunnels, so a transport that still looks connected after a
network change is caught by the probe instead of keepalive.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(ssh): keep a superseded system-SSH probe from clearing its replacement's transport
A wake retry can now replace an in-flight handshake. Closing a system-SSH
probe only signals the process, so its late close used to reset the
transport state the replacement had just set up, leaving a connected host
whose exec failed with 'Not connected'.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(ssh): keep a superseded handshake from killing its replacement's proxy process
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Chat question cards now focus the first answer automatically, let Up and Down move the highlight, and use Enter to choose it. Existing number shortcuts, Other input and explicit multiple-answer submission use the same answer path.
Preserve text-only question focus when combining with current main, and keep arrow navigation inside the focused question card so Conversation retains its keyboard focus.
Validated with 53 focused Windows tests, independent review, and remote Electron QA using a real Claude Code session.
* feat(layout): add the dark layout.apply channel and route pane splits through main behind ORCA_LAYOUT_OWNER
A build made with ORCA_LAYOUT_OWNER=main registers layout.apply: main applies the
window's layout command to the stored partition through the shared applyLayoutCommand,
minting new ids. A user split then waits for main's answer and mounts the pane id main
minted. Every other build substitutes 'window', so neither branch is compiled in.
* fix(layout): write only the records a layout.apply command touched
A load-then-save rewrite also normalizes records the command never touched. The command's
effect is now the diff between the save before and after it, written onto the stored
partition record by record. A split whose main reply says a paired server holds the tab
splits as today.
* fix(layout): write a load-supplied record whole and check that patched partitions reload to the command's result
* feat(layout): close, rename and equalize panes through main behind ORCA_LAYOUT_OWNER
In a layout-owner build the window's pane close, title change and equalize go through
layout.apply and the window shows main's answer. A window pane close keeps the legacy
close's rules: the tab's last pane is refused, a pane restarted since main read the close is
left alone, every partition holding the tab closes it, and the tab's remote session id follows
the remaining pane. The rules sit behind a command field only layout.apply sets.
* fix(layout): close the tab when a racing pane close leaves the window one pane
* fix(layout): leave an unmounted tab alone when main refuses its last pane
* fix(layout): close a user's pane even if its terminal restarted, as today
Claude-Session: 10569c9a-a0a1-4fc9-8469-0edfb7aed125
* test(layout): pin the restarted-pane close directly, not through a made-up profile state
Claude-Session: 10569c9a-a0a1-4fc9-8469-0edfb7aed125
* fix(layout): leave a split to today's path only when the workspace's row names a paired server
The split switch point asked which host to route to with a helper that falls back to the focused
server, so a focused server could claim a workspace whose row names no owner. Use the same owner
test the tab mirror uses.
* test(layout): type the split owner mock without a cast
* test(layout): type the layout-owner command mocks with the real signature
* fix(layout): finish the main merge with this branch's versions of the layout-owner files
When the wake that adopts another version's stopped server found the host
fenced (e.g. another desktop starting it after a reboot), the connect fell
through to its own deploy, which over an active older version can only defer
on the terminal census. Report the fence as a waiting deferral instead, and
count terminals against the record the wake re-read.
Refs #28833
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(remote-terminal): keep live output flowing during resync and retry an unanswered one
Co-authored-by: Claude <noreply@anthropic.com>
* fix(remote-terminal): escalate to stall recovery when a resync is never answered
Co-authored-by: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat(session-history): show the scan's progress in a strip under the search box
One line under the Session History search box says what this computer's
scan is doing: "Scanning N files", "Reading n / T files", "Scanned N
files · K agents", or "Stopped after N files" with a Refresh link, which
replaces the red timeout line. Clicking it opens one row per agent. The
strip is hidden for remote hosts, All hosts and the web client.
* fix(session-history): drop the agent count from the finished strip
* fix(session-history): match an applied list to its progress by scan ID alone
* fix(session-history): keep the scan strip's own icon uncoloured; colour stays in the per-agent rows
* fix(session-history): a push supersedes an in-flight progress fetch for its key, whatever else was pushed
A pane hidden on the host remounts onto the same live PTY, and for a moment its graph leaf has no PTY. Main dropped the handle then and minted a new one, so the paired client's next send was refused as stale and the client remounted the pane, which made the host remount it again. Main now holds the live PTY on the leaf through that gap.
Fixes#23759
* Allow worktree parentage across projects on one host
* Keep sidebar placement and project ownership consistent
* Keep project labels on phone rows outside their own section
* Derive sidebar placement and reveal from shared occurrences
* Preserve project text across workspace card styles
* Resolve saved lineage peers without rebuilding host projects
* fix: preserve independent project deletion progress
* fix: satisfy mobile formatting and test lint checks
* test: declare project fixture arrays without assertions
* Align reveal fixtures with rendered workspace occurrences
* fix: reuse project name for cross-repo child cards
* fix: satisfy mobile section lint after main merge
* fix: retain project label for nested mobile rows
* test: keep nested card owner assertion resilient
* test: assert project badge visibility across card styles
* test: drop lineage tests already covered by the whole-flow suites
The host-peer projection is exercised by the runtime and SSH listing
integration tests, and the simple phone nesting case is covered by the
deep-chain, Pinned and hidden-project flow test.
Raise the remote terminal row bound from 120 to 500 (the bound terminal.subscribe/send already accept) so a pane taller than 120 rows no longer sits over a shorter PTY that leaves stale TUI frames below row 120.
Fixes#27425
- mobile-path-search-expiry: poll full GCs until weak targets clear instead
of one fixed round, which a loaded CI worker can outlast.
- NativeChatCodeBlock: cold grammar + WASM highlighting of 400 lines can
exceed findByText's 10s on a saturated runner; wait up to 25s, under the
30s testTimeout.
* ci: publish an explicit result from the PR aggregate
* test(codex): preserve review text across OSC terminators
* perf(ci): reuse worker fixtures and validated launcher output
* fix(test): use typed orchestration database access
* fix(ci): exclude test fixtures from focus ratchet
* test(launch): pin where the two spawn preflights differ, on main
Row 3 (one spawn preflight for both launch lanes) first pins main's behaviour:
- Row 8, agents started with their workspace (from the earlier row-8 work): a work item's
"start agent" and the folder-workspace composer through the real producer and pane and
pty:spawn: draft and note on argv, a long multi-line note, PowerShell quoting under cmd.exe,
project WSL, terminal shell WSL, and a prompt-less Codex start with no shell wait.
- A deleted folder: the window starts in the workspace root and says so; the host passes the
folder on and the provider refuses it.
- A Windows plain terminal in a folder containing a WSL-project repo, and in a \\wsl$ repo with
no saved runtime: the window uses the runtime the renderer sends; the host reads only the
repo's project.
- A managed SSH Windows host: only the host lane opens the OpenSSH login shell.
- A pane's own agent launch drops the root fallback (TEMPORARY parity).
* refactor(pty): one spawn preflight step for both launch lanes
The window's pty:spawn and the host's runtime controller each kept their own copy of the
first spawn step: resolve the folder a new terminal starts in and the shell/runtime its PTY
uses. resolvePtySpawnStartupTarget now does both for both lanes. Each lane passes the two
inputs it still differs on, so nothing changes for users:
- missingCwdFallback (TEMPORARY, row 13a): only window starts fall back to the workspace root,
with a notice, when the requested folder is gone (including the WSL cwd probe).
- projectRuntimeSource (TEMPORARY, row 13b): the window uses the runtime its renderer sent;
the host reads the repo's project from the store.
The OpenSSH login shell (#9327) now resolves for both lanes; only an SSH-launched orcad turns
it on, and that process has no window, so the window lane never meets it.
Both lanes resolve after fresh-spawn recovery and session-id mint (the WSL distro reads the
session id). Admission, adoption, hidden mark, recovery and mint stay where they were.
* feat(native-chat): one queue header that becomes the pause row; no composer Resume or send dialog
The queued cards now sit under one header row on desktop and phone: "Queued"
and the count normally, and "Paused" with the count, Clear all and Resume
while the host holds the queue after a Stop. A chevron folds only the cards;
the choice is local to the view and resets when the queue empties.
The composer's Send no longer turns into a second Resume, and a message sent
into a paused queue no longer asks "Send message?": it sends, its accepted
turn lifts the pause, and the cards follow it. Clear all deletes every card
through the existing per-card delete. No wire or host change.
* test(mobile): find the background-tasks strip's toggle inside the strip, not the queue header's
* Make queued Clear all atomic across clients
* Show queued message failures in collapsed headers
* Use per-card delete for paused queue Clear all
* Join repeated paused queue Clear actions
* fix(git): keep merge-tree's throwaway trees out of the real object store
The PR conflict summary and the squash-merged branch cleanup both run
`git merge-tree --write-tree` only to read its output, but every divergent
run left unreachable loose objects behind. Run it with a per-call scratch
object directory (inside objects/, alternates pointing at the real store)
that is deleted afterwards, on native, WSL and SSH relay hosts alike.
* fix(git): keep partial-clone downloads and clean up stranded scratch stores
- A partial clone fetches missing blobs during merge-tree; those packs landed in the
scratch store and were deleted, so every check re-downloaded them. Move fetched packs
(index last) into the real store before removing the scratch dir.
- Sweep this app's own scratch dirs older than an hour, once per objects dir per process,
so a crash or failed delete cannot strand one forever in repos where gc never runs.
- Remove with Windows transient-lock retries; the shared core now owns all filesystem
work and host adapters only resolve the objects dir.
* fix(git): quote Windows alternates paths containing ';' and log scratch cleanup failures
* fix(git): sweep a merge-tree scratch dir only when its owner is provably gone
Another Orca process on the same repo (a second instance, dev and release
builds) could delete a scratch object dir still in use once its mtime passed
an hour. Each scratch dir now records its owner (pid, hostname, platform,
Linux pid namespace); the sweep removes it only when that owner shares this
pid space and has exited, or, with no checkable owner, at Git's own two-week
tmp_ expiry.
Also:
- A fetched pack whose later rename fails is rolled back, so the real store
never keeps an index-less pack; a pack already installed is left alone.
- WSL Git gets the /mnt/<drive> spelling of a drive-path common dir (Git
< 2.31 prints it relative, resolved against the C:\ repo path).
- conflict-summary tests no longer create scratch dirs in the cwd.
* fix(git): cap every merge-tree scratch dir at two weeks, live-looking owner included
A reused pid held by a long-lived process could keep a leftover scratch dir
forever in a repo where git gc never runs. The two-week age (git gc's own
tmp_ expiry) now removes any scratch dir; a younger one is still swept only
when its owner shares this pid space and has exited.
* fix(git): sweep merge-tree scratch dirs by age alone, off the check's path
- The stale sweep now removes any scratch dir at least two weeks old, Git's
own expiry for its objects/tmp_* dirs; no merge-tree runs that long. The
owner file and pid-space probe are gone, and with them the cases where a
sweeper misjudged another process's pid.
- The sweep no longer delays the user's check: it starts without being
awaited and can only touch dirs far older than the current run's.
- A fetched pack's .keep never moves into the real store, so a transient one
from an aborted lazy fetch cannot stop gc from repacking that pack.
- Tests: the legacy merge-tree fallback gets the scratch env; the local
quarantine's WSL decisions (distro from a UNC path, drvfs spelling,
host-Git linked-worktree skip, WSLENV hand-off).
* fix(git): let quarantined merge-tree see exactly the object stores Git sees without it
The scratch object store must not change what merge-tree can read, or the
conflict list and the squash-merged branch cleanup differ from main:
- An inherited GIT_ALTERNATE_OBJECT_DIRECTORIES (from Orca's or the SSH
relay's environment) was replaced; it is now kept and the real store
appended after it, as Git's own temporary object stores do.
- An inherited GIT_OBJECT_DIRECTORY is the store Git reads and writes, so the
command now runs unquarantined. On the WSL route a Windows value only
counts if WSLENV forwards it, and then the quarantine is skipped too.
- The real store's own info/alternates entries are listed before it, so an
alternates chain is linked at the same depth as without the quarantine
(a 6-deep chain no longer hits Git's nesting limit). An alternates file
that cannot be read or holds a quoted entry runs unquarantined.
* fix(git): run merge-tree unquarantined when the repo's store has its own alternates
Mirroring Git's alternates resolution could not match it in every shape: a
path back to the real store (mutual alternates, or an inherited alternates
value naming the repo itself) still linked its chain one level deeper, and a
NUL byte in info/alternates broke the spawn. When objects/info/alternates
exists, or cannot be checked, the command now runs exactly as before. Repos
cloned with --reference or --shared keep writing throwaway objects, as they
always have. Inherited alternates are still appended to, and an inherited
GIT_OBJECT_DIRECTORY still skips the quarantine.
* refactor(git): drop the stale-folder sweep and pack-move rollback from merge-tree's scratch store
A scratch folder left by a crash is a tmp_* dir inside objects/, which
Git's own gc prunes after gc.pruneExpire, as it does Git's own leftovers,
so Orca's 14-day sweep and its test hooks go.
Packs a partial clone fetched into the scratch store are moved in Git's
migration order: pack- files only, index last, existing names left in
place, stopping at the first failed rename with no rollback. A failed
move is logged and never stops the scratch dir's removal or the result.
Adds a real-Git test that a partial clone lists its conflicts offline
once an earlier check downloaded the contents.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(git): quarantine merge-tree only once the host is known to support --write-tree
Git before 2.35 cannot prune a leftover scratch folder under objects/tmp_*, so
a crash in the moment one exists makes every later `git gc` warn and stops
automatic cleanup. On Git before 2.38 merge-tree --write-tree is rejected, yet
the first check per host per session still made and removed a folder around it.
Branch cleanup and the PR conflict list now use the scratch object store only
when that host's capability cache already knows merge-tree --write-tree works.
Until then the command runs exactly as it does on main: on a Git that
rejects it no folder is ever created, and every Git that does get one is 2.38+,
whose gc prunes it. Also reword two comments in the quarantine module.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(git): learn merge-tree support from a conflicting branch-cleanup check
Branch cleanup quarantines merge-tree only once the host's capability cache
knows --write-tree works. A conflicting merge-tree exits 1, so the check threw
and support was never recorded; on a host whose only checks conflict (likely
over SSH, where the relay has no conflict summary to teach its cache) every
check kept writing loose objects into the real store.
A failure whose stdout starts with a tree ID proves --write-tree ran (old Git
that rejects the option prints nothing there), so it now resolves to the same
"not merged" result and lets the cache record support. Every other failure is
rethrown unchanged. Also shorten two comments and soften a third.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(git): put WSL Git's scratch object store on its command line
A WSL login profile that set GIT_ALTERNATE_OBJECT_DIRECTORIES replaced the
WSLENV-forwarded value, so a quarantined merge-tree lost the real store and
failed where main succeeds. The WSL runner now passes both variables as
command-line assignments after the profile, appending to its alternates.
* test(git): drop quarantine tests the real-Git and compatibility tests already pin
The cold-capability gating, scratch-folder lifecycle and WSL path spelling
are covered end to end by merge-tree-object-quarantine-real-git.test.ts, the
real-binary compatibility contract (Git 2.25/2.38/2.49) and the
localGitObjectsDirectory tests, so the unit copies only mirrored them.
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix(daemon): skip the ps process-table read on Windows
Windows foreground evidence is unverifiable whatever the table says, yet the
daemon still ran `ps` there. With Git's or MSYS2's Unix tools on PATH that
started a real ps.exe without a hidden window, so agent monitoring could flash
a console. Return the Windows verdict directly instead.
* refactor(daemon): keep all Windows inspection in one branch
Fold the ConPTY foreground confirmation into the new win32 branch, drop the
anchor clear that can never fire there, and remove test stubs for a
process-table read that no longer happens.
---------
Co-authored-by: Orca Worker <orca-worker@localhost>
* Prepare native chat CLI copies from the host record
* Apply staged-file lint and formatting checks
* Cover pairing replacement before queued preparation dispatch
* Reuse CLI failure feedback and complete the pairing test response
* Isolate Codex materialization test user data
* Preserve native chat CLI copy completion and preparation ownership
* test: consolidate native conversation fork contracts
* fix(native-chat): refuse a Codex chat fork whose saved account home has no CODEX_HOME path
* feat(layout): add the dark layout.apply channel and route pane splits through main behind ORCA_LAYOUT_OWNER
A build made with ORCA_LAYOUT_OWNER=main registers layout.apply: main applies the
window's layout command to the stored partition through the shared applyLayoutCommand,
minting new ids. A user split then waits for main's answer and mounts the pane id main
minted. Every other build substitutes 'window', so neither branch is compiled in.
* fix(layout): write only the records a layout.apply command touched
A load-then-save rewrite also normalizes records the command never touched. The command's
effect is now the diff between the save before and after it, written onto the stored
partition record by record. A split whose main reply says a paired server holds the tab
splits as today.
* fix(layout): write a load-supplied record whole and check that patched partitions reload to the command's result
* fix(layout): leave a split to today's path only when the workspace's row names a paired server
The split switch point asked which host to route to with a helper that falls back to the focused
server, so a focused server could claim a workspace whose row names no owner. Use the same owner
test the tab mirror uses.
* test(layout): type the split owner mock without a cast
* feat(layout): draw local and SSH workspace layouts from main's layout stream behind ORCA_LAYOUT_OWNER
* fix(layout): keep the frame sync out of window-owned builds' live code
* fix(layout): keep each row's live terminal binding and pane state across frames
* chore(layout): count the frame applier's layout writes and drop test casts
* fix(layout): hold frames while any save is owed, scope the view read-back to rebuilt workspaces
* fix(layout): keep the frame sync's module init out of window-owned builds
* docs(layout): name the read-back bucket and the failed-save hold
* fix(remote-terminal): reattach a healthy remote pane promptly after a worktree switch (#27178)
A remote pane whose stream ended while the host still reported its terminal
ready waited the whole 15s inventory window before reusing it, and a third end
within 60s demanded a replacement handle that never comes, parking recovery
with no timer until the ~3 min deadline. Unknown-liveness outcomes on the
attach and resubscribe paths also parked for online/resume events that a
healthy link never fires.
- Reuse a still-ready handle as soon as the host reports it, unless a
terminal.send to it is unanswered (it may still come back stale).
- Pace repeated ends on the same handle with growing delays instead of
requiring a replacement; only ends within 10s of a reattach count.
- Schedule unknown-liveness retries on the backoff ladder instead of parking;
the existing deadline still latches Reconnect for a genuinely lost link.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(remote-terminal): fence an endless same-handle flap once its pacing ladder is spent
Each paced reattach marked the pane healthy and reset the recovery deadline,
so a handle that ended after every reattach retried forever without ever
offering Reconnect. After the last rung it now falls back to the replacement
wait, whose deadline latches Reconnect.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: Claude <noreply@anthropic.com>
* 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>
* fix(ssh): a paused move to the managed server says so, servers report their real version, and a gone server's project can be removed (#27267)
Co-Authored-By: Claude <noreply@anthropic.com>
* test: type the held-move fixture without an assertion
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: Claude <noreply@anthropic.com>
* 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>
* feat(acp): ACP agents can declare a managed runtime that a start runs but never installs
A start resolves PATH and the agent's install folders first, then the newest whole release under
Orca's data folder (its completion receipt and member sizes checked); with none it refuses with
runtimeNotInstalled instead of downloading.
* fix(runtime): tell an unreadable runtime release from a broken one, and point a not-installed chat at the chat itself
A transient read error (EIO, EMFILE) no longer counts a working release as incomplete; only
definite absence or a mismatch does. The not-installed refusal no longer sends a remote chat's
user to this computer's Settings.
* fix(acp): a start runs Orca's installed release before a copy on PATH, and a copy elsewhere only beside every file it needs
* fix(native-chat): resume conversations of any size in a new native chat
Resume in New Native Chat refused any saved conversation over 16 MiB, so long
Claude and Codex sessions could not move into a chat. The import now streams
the file, bounds each message as it is read, and keeps the newest messages
within the same 16 MiB budget; one line is still bounded at 16 MiB. The agent
resumes the full conversation itself, so only the chat's earliest messages
can fall outside the window.
* test(native-chat): adopt preflight refuses one line past the record bound, not a large file
* fix(native-chat): bound retained Pi import state and identities
* fix(native-chat): keep Pi branch projection responsive
* fix(native-chat): classify temporary Pi import table
* fix(native-chat): keep Pi resume independent of import scratch cleanup
* test(native-chat): match legacy Pi identity by shape
* feat(session-history): the scanner reports its progress to windows
The Session History scanner now counts each agent's history files and
databases while it lists them, and how many transcripts it has read.
Main keeps one progress entry per local scan, tags list results with the
scan that produced them, tells the scan's own no-progress deadline apart
from other faults, and pushes updates to every window. The preload API
exposes getScanProgress and onScanProgress. Nothing reads them yet.
* fix(session-history): drop the redundant scan key from list results and progress
The scan ID already names the scan a result or progress entry came from, so
the key copy on the list result (sent to paired clients too) and on each
progress entry was never needed. The key stays only where it is the lookup.
* 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.
WebKit reports no source frame for an app-initiated load() before iOS 18.5 (WebKit c28b242), so the iOS shell read its own document load as subframe-initiated and cancelled it, leaving the phone on "Opening workspace" forever. The navigation policy no longer takes the initiating frame as an input: the shell's own document loads when its one-shot load flag is up, the URL is the exact document URL, the target is the main frame, and it is not a download, matching the Android copy.
Verified on an iOS 18.1 simulator (hang before, workspace opens after) and iOS 26.5 (unchanged).
Refs #28626, STA-10545
* fix(jira): match workspace links by site identity, not account label
Jira links saved the account email as accountLabel while the Tasks page
source used the site display name, so sameSource() rejected every link and
the "Has Workspace" view stayed empty (STA-10654, #29055).
Jira connection identity is hostId + providerIdentity.siteId (per login per
site), so link matching, cmd-J workspace lookup and duplicate-link detection
now compare siteId for Jira and treat accountLabel as display-only. All Jira
label producers share getJiraAccountLabel so labels stay consistent.
* refactor(jira): simplify Has Workspace link matching after review
Match Jira workspace links by host only in sameSource, since siteId checks
downstream already separate logins; give getJiraAccountLabel a plain
structural parameter; make the other-login test use a reachable state.
* feat(native-chat): show Claude's /goal in the goal banner
Claude reports a goal only as a synthetic reply in stream-json, so Orca saved it
as assistant text and never showed the goal banner Codex gets. Journal Claude's
goal replies as the typed goal transition, infer completion when Claude ends a
turn on its own, report the goal to clients apart from host goal controls, and
let the banner clear it by sending /goal clear.
* fix(native-chat): keep a Claude goal open when a person stops the turn
A Stop pressed while Claude ran the goal's check ended the turn with a clean
result, which read as the goal being met and removed the goal banner. The
journal's Stop rule already says when a person's Stop decides how a turn
ends; a turn it decides never counts as the goal being met.
* fix(native-chat): count Claude goal time from the turn start and title an idle goal 'Goal set'
* Fix Claude goal clear reveal and result accounting
* Hold Claude goal clear during pending sends
* Preserve Claude goal text with status-like phrases
* Require host goal support for persisted goal banner
* Keep structured session modules within line limits
* test(native-chat): give option-read journal doubles the goal read
* test(claude): give journal doubles the goal source read
The browser client's UI bridge lacked the four Jira filter methods, so the
fallback bridge resolved undefined and filters showed empty and never saved.
Route them to the active host, gated on its capability so older hosts report
that an update is required.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(layout): add the dark layout.apply channel and route pane splits through main behind ORCA_LAYOUT_OWNER
A build made with ORCA_LAYOUT_OWNER=main registers layout.apply: main applies the
window's layout command to the stored partition through the shared applyLayoutCommand,
minting new ids. A user split then waits for main's answer and mounts the pane id main
minted. Every other build substitutes 'window', so neither branch is compiled in.
* fix(layout): write only the records a layout.apply command touched
A load-then-save rewrite also normalizes records the command never touched. The command's
effect is now the diff between the save before and after it, written onto the stored
partition record by record. A split whose main reply says a paired server holds the tab
splits as today.
* fix(layout): write a load-supplied record whole and check that patched partitions reload to the command's result
* fix(layout): leave a split to today's path only when the workspace's row names a paired server
The split switch point asked which host to route to with a helper that falls back to the focused
server, so a focused server could claim a workspace whose row names no owner. Use the same owner
test the tab mirror uses.
* test(layout): type the split owner mock without a cast
* fix(mobile): phones on a host with no window list each terminal once
With no window attached, the phone tab list was built from the stored terminal rows as they
are, so a leftover row from an older build that repeats another tab's terminal showed as a
second tab on the same terminal. Build it from the shared Loader's result instead, as the
window and main's other readers do.
* perf(mobile): load each stored partition once per loop over worktrees
Review: list-all and graph sync hydrate once per worktree, and the Loader reads the whole
partition, so each call repeated it. The loops now share one cache for their duration.
* docs(mobile): say why the per-loop Loader cache ignores the host id
* perf(mobile): run the Loader only for a partition that repeats a row or terminal
A partition with no repeated row id and no terminal bound in two mounted panes is built from
its raw rows exactly as before, so titles, pane ids and cost there are unchanged.
* fix(mobile): compare terminals as the Loader does before deciding to load
Review: a relay terminal id reused by a later incarnation is a different terminal to the
Loader, so it must not trigger it either.
* fix(mobile): a legacy row's terminals repeat by id alone, as the Loader matches them
Review: comparing every binding by incarnation missed a legacy row repeating an SSH relay
terminal with no incarnation recorded; the Loader's legacy-row check matches raw ids.
* fix(mobile): drop only the legacy rows the Loader absorbs, per worktree
Replaces the partition-wide Loader swap and its per-loop cache: a phone on a
host with no window now differs from main only where it then matches the
desktop window, which tests compare across main, the branch and the window.
* test(mobile): compare tab groups too in the phone, main and window views