Offer confirmed permanent deletion when moving an item to Trash fails.
Preserve authorization and workspace ownership checks, cancellation behavior,
text-snapshot undo, remote compatibility, and all supported translations.
Fixes#33944
Co-authored-by: nori <dosukoicrypto@gmail.com>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* fix(wsl): background Git and the roots catalog never start a stopped WSL distro
git status ran on the UNC project root 0.45 s after a terminate, next to the
explorer read; either boots the distro. Background Git (status, conflict
operation, upstream status, branch compare, the upstream-ref watch) now asks the
shared running gate first and refuses for a stopped distro, including host
paths whose Git runs through wsl.exe. The authorized-roots rebuild skips a
stopped distro's repo and leaves it dirty, and the detected-worktree scan fails
as unavailable, so rows are kept and the next scan after the distro runs lists it.
* fix(wsl): the active workspace's first status and a wake read's roots rebuild may start WSL
- Drive-path projects whose Git runs through WSL had no wake path: the active
workspace's first git:status after it becomes active now passes
wakeWslDistro, so Source Control starts the distro; later polls stay gated.
- A user act runs in an async scope naming its distro; gated work inside it
(the authorized-roots rebuild an authorization miss triggers, the detected
scan, the Git) may start that distro and no other. External worktrees on a
stopped distro no longer fail a wake read with access denied.
- After a user act wakes a distro, background reads stop trusting the older
'stopped' read and probe again.
* fix(wsl): explicit WSL launches refresh the running read; wake only on tab change; gate the home probe
- Any wsl.exe Git/CLI run (commit, push, fetch) or WSL terminal spawn for a
distro last read as stopped records that it started, so background reads probe
again instead of refusing for up to 60 s.
- The active editor tab wakes only when the active tab changes; effect re-runs
driven by git status or overview views read without waking.
- The authorization roots' WSL home probe no longer runs wsl.exe in a stopped
distro when the home is uncached.
- The settle window no longer imports the orcad liveness module (it pulled main
code into the web typecheck); a test pins it to EXIT_SETTLE_MS.
* fix(wsl): deferred work and running-list listeners never inherit a user act
A user act's async scope could leak into work it triggers indirectly: a drain of
deferred tasks it caused, or listeners of a running-list probe started inside
it. Both now run outside the scope, so only the act's own work may start its
distro.
* fix(wsl): keep shared modules free of main-only gate imports
worktree-logic.ts and wsl-running-distro-cache.ts are also compiled by the web
project, which cannot see main-only modules (AsyncLocalStorage, wsl.exe).
getAllowedRoots now injects the gated home lookup into computeWorkspaceRootWithin,
and the background gate's own cache subscriptions run outside the user-act scope
instead of the cache knowing about it.
* fix(wsl): background file reads and watches never start a stopped WSL distro
Any Windows touch of a \\wsl.localhost path boots a stopped distro. After a
terminate, the dying explorer watcher's overflow refresh ran realpath on the
project root 0.23 s later and booted the distro again. Background reads, stats
and watch installs now ask a fresh running list first (shared, with a 5 s lease
and a stop signal from the watcher exit and the hook relay link) and refuse when
the distro is stopped. Opening the workspace and Retry still start it. A stopped
watch waits for the distro and resyncs the explorer when it runs again.
* fix(wsl): trust a confirmed running read for 60 s without a stop signal
* fix(wsl): user acts wake; unverifiable is not stopped; settle after a stop
- Opening a workspace, expanding a folder, opening a path (explorer, quick-open,
links), the active editor tab's load and every Retry pass wakeWslDistro on
readDir, readFile, stat and readFileChunk. Background reloads, restored-tab
scans and watchers stay gated.
- A failed running-list read is 'unverifiable', not 'stopped': the gate re-reads
once past the failure backoff (once per failure window) and otherwise refuses
with 'WSL didn't answer', never touching the path.
- After any stop signal, a running read that started within EXIT_SETTLE_MS of it
never counts, in isWslDistroRunning itself, so neither the watcher's own
overflow refresh nor the hook relay's restart check can read a stale listing.
- A confirmed read that sees a distro stop records a stop signal, resetting the
60 s lease.
* test: user-opened stats now carry the WSL wake flag
* fix(wsl-host): uninstall and startup release the WSL distro holders Orca left running
The detached wsl.exe holder outlives the app, so an uninstall left its distro,
orcad and shells running. The uninstaller now runs Orca with
--orca-uninstall-release-wsl-holders after killing the app and its daemon; it
ends only the holders this profile recorded, each verified by pid, start time
and command line, and keeps any it cannot verify.
At startup the WSL host service now releases holders whose distro is no longer
a host (a failed add or remove), and every holder while WSL hosts are off; a
closed gate releases them too.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(wsl-host): a Remove that can't prove the server stopped keeps the host, pending
A partial Remove dropped the host anyway, leaving a server whose live
terminals block the next add's activation. Remove now drops the host only on a
proven stop; otherwise it records a pending stop on the WSL record, keeps the
host listed and says why (live or unverifiable). Remove again retries without
ever booting a stopped distro (it says to start it); Connect or Add on the same
distro keeps the host; a distro that is no longer registered finishes. Nothing
reconnects a pending host on its own.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(wsl-host): E11 review — keep holders when records are unreadable; finish removals a stopped or renamed distro proves
- Startup keeps every holder when the WSL host records can't all be read
(unverifiable), and no longer re-adopts a pending host's holder.
- Remove matches a renamed distro by GUID (isWslDistroRegistered), and a
stopped distro finishes a pending Remove: the cold probe proves the stop.
- Only the user's Connect or Add withdraws a pending Remove; other connects
are refused, and re-adding respects startIfStopped.
- Settings' Remove carries the kept result, so it warns instead of 'Removed'.
- A holder record without a start time is never killed on its command line
alone, except the one this process just spawned.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(wsl-host): E11 review 2 — a closed gate keeps live hosts' holders; stop a renamed distro by its current name
- With the gate closed, a launch releases only orphan holders (no host record,
or a pending Remove), under the same keep rule as startup; a user with no
holder records costs one folder check, nothing else.
- The keep rule re-reads host records per holder and keeps an Add in flight.
- Remove resolves the distro's current name from its GUID and stops it there.
- The uninstall command arms its exit timer first and exits 1 if setup throws;
NSIS launches it only when a userData holder folder exists.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(wsl-host): never boot a host whose Remove is pending at the next launch
Early wake built its host list from every record, so a host focused when its
Remove ended pending was booted at each launch, undoing the stop's proof. It
now skips pending records, and Remove forgets the host's early wake whatever
the outcome.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(wsl-host): no wake for a pending host; the e2e fake stops an unbridged server like the real one
- mayWake skips a host whose Remove is pending, so a due automation can't
boot it (focus already couldn't).
- The e2e fake connector read a host with no bridge as stopped. Since E9 a
re-enable connects nothing, so Remove after off/on left the host's shells
running. Like the real connector, it now reconnects the always-running fake
distro to stop its server.
- Status bookkeeping moves to WslHostStatusBoard, and the pending-host connect
rule to admitConnectToPendingHost, to keep the service under max-lines.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix(browser): honor preview link destinations and modifiers
* Clarify the source browser provider contract
* test(browser): check preview guest before reporting modifiers
* fix(browser): report system link failures and cover preview routes
* test(browser): combine HTTP link destination imports
* refactor(browser): require source ownership for preview links
* test(browser): type the forbidden Active Server getter
* fix(i18n): retain required preview error catalog entry
* perf(wsl-host): start booting the distros the last run had open work on as the app launches
The WSL host connect can only start once the store has loaded, about 1.5-2 s into
startup on a slow PC, and its first command then waits for the distro to boot.
The service now remembers which distros had open work, and the next launch starts
booting just those right after taking the single-instance lock.
* perf(wsl-host): wake only the focused WSL host early at launch (replaces #33141's list)
open work on, which conflicts with E9 (a launch boots only the focused host). Each
focus entry now records just that host's distro for the next launch's early boot,
and focusing a workspace off WSL (reported as a null focus entry), turning WSL hosts
off, or a host that can't run clears it.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(wsl-host): the early-wake list is authoritative: deleted when off, forgotten on Remove
Review of #33924: the early wake runs before settings load, so it can't read the WSL
hosts setting. Turning WSL hosts off now deletes the list synchronously (emptying it
if the delete fails; a missing or unreadable list wakes nothing), and removing a host
drops it from the list. There is no pendingStop field on host records yet (E11); the
existing registered-host filter covers removed records until then.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix(wsl-host): boot only the focused WSL host, and keep a stopped one down
E9 connect policy, part 1. A stopped distro now boots only for an explicit user action
or when its host becomes focused:
- Visible work is the active workspace plus workspaces focused earlier in this window
that still have tabs. Saved tabs alone no longer count, so a launch with two hosts
boots only the focused one.
- After a confirmed stop (wsl --shutdown, WSL update) the host is parked. Liveness no
longer reboots it even when it is visible, and polls get "<distro> was stopped"
instead of a boot. Re-focusing the host, Connect, or the distro running again lifts it.
- Liveness recovery always needs a confirmed-running distro, so a distro that stops
mid-recovery is never booted again.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(wsl-host): launch boots one of two hosts; polls never boot; lost contact never parks
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(wsl-host): only a focus entry or a user action boots a WSL host (E9 review)
Review of #33838:
- Re-focus never lifted a park: a host focused earlier stayed in the visible set, and
the renderer skips unchanged reports. The renderer now sends a separate focus entry
(focusHost) each time the user switches to a workspace on a WSL host, including the
launch restore; only that, or Connect, lifts a park and may boot.
- Runtime calls could boot: ensureForRuntimeCall connected any visible, unparked host.
It now reads the host's reachability first: a confirmed-stopped distro parks at once,
an unreadable running list refuses, and only a distro confirmed running is
reconnected (for visible work). Visible work itself never connects anything.
- Turning WSL hosts on no longer boots every visible host, and focus isn't recorded
while they're off; the window re-reports its focused host instead.
- Each window's visible work is kept apart and unioned, so one window can't drop
another's.
- wakeForAutomation/isConnected move to the automation PR, which uses them.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(wsl-host): a poll waits for liveness's verdict right after an exit (E9 re-review)
Right after `wsl --shutdown` WSL can still list the stopping distro as running (the
supervisor waits and re-reads for exactly this). A poll's cold probe then read
'unreachable', reconnected, superseded the pending distro-stopped verdict and booted
the distro again. The supervisor now reports when it is settling (a verdict in
progress, or a watch/holder exit within the last few seconds), and a runtime call
refuses instead of reconnecting then; the verdict parks the host as before.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(wsl-host): a new supervised entry starts with no recorded exit
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
A throwing ui-changed listener (e.g. webContents.send on a destroyed
secondary window) propagated out of store.updateUI() during
app:stage-before-unload-sync and cancelled the shutdown checkpoint,
leaving the user unable to quit. notifyUIChanged now isolates each
listener, the ui:stateChanged broadcast skips destroyed webContents, and
the staging failure cause is returned to preload so the error toast
names it.
Fixes#20897
Co-authored-by: Neil <neil@stably.ai>
* fix(terminal): skip an escape tail an abandoned hidden-output restore would print (#33766)
Main drops a hidden pane's renderer delivery after its model ingests the
bytes, so the first chunk sent on reveal can begin inside an escape whose
start was dropped. When the restore is abandoned, the renderer resets its
parser and writes the queued chunks, and xterm printed the tail (';242m',
the rest of a Codex OSC title).
Main tracks each PTY's escape state over every accepted payload and labels
the first delivery after a hidden or pending-cap drop with the escape it
resumes inside (gapEscapeState). Only the abandon path strips that
continuation; a successful restore keeps every byte so the snapshot's
pending escape tail can complete. No label means no change.
* fix(terminal): make the escape-continuation scan's switch exhaustive (#33766)
`orca environment add` writes the saved-server file directly. Main already
watched that file but only repaired the Active Server preference, so every
renderer surface listing saved servers (the Agent Session History host
picker, host menus, the status bar) kept the old list until something
else refreshed it. Main now tells every window when the file changes, and
the renderer refreshes through the existing coalesced catalog refresher.
Co-authored-by: Tian-yi Liang <tianyilt@qq.com>
* fix(agent-resume): record a background agent launch's recipe so resume keeps its flags
* test: narrow the reveal's notifier so the test needs no cast
* refactor(agent-resume): one recipe registration path; skip adopted panes
* test: add the launch-recipe listener to the remaining window mocks
* Guard managed server restarts with host work reservations
* Protect legacy server update requests with host admission
* Reserve chat-safe restarts in SSH and WSL server updates
* Keep activation host fixtures within the module line limit
* fix(orcad): preserve session queue timing during admission
* Explain chat-safe update deferrals and superseded restarts
* Clarify managed server Force upgrades and retry instructions
* Separate chat entry delegates from conversation creation
* Report child work through main's child evidence in the restart drain test
* Drive main's snapshot-room tests through the restart reservation
Main's room tests deployed over a serving incumbent with a census that has no
chat work count and no restart admission, so the update deferred before the
room probe ran. Give them the same idle census and slot admission as the other
activation tests, and pin the probe before the reservation instead of before the
recount it replaced.
* fix(native-chat): an update keeps waiting on a chat start after its conversation is closed
The restart check counted a start only while its conversation stayed open,
so an idle close during a stalled start let a managed update through.
Every tracked attach, create, start and open now counts as restart work
in the same tracker that counts the session queue.
* fix(native-chat): keep the structured session view within its line limit once formatted
Main's copy sat at 400 lines only because one line was left unformatted; the
merge's formatter pushed it to 401. Inlining the one-use question list keeps it
at 400 with identical behavior.
* fix(orcad): a recovery stop request stops the server; only an older client's empty request waits for idle
Stopping a slot and restarting it for an older client's update were both an empty
.orcad-stop-request file, so routing that file through restart admission let a
crash recovery, stranded-setup release or rejected-candidate rollback leave the
server running. New clients now write "stop-now" into the request (renamed in
whole), which stops at once as before; an empty or unrecognized request takes the
chat-safe restart, and a deferred restart logs why. Also gives the restart
admission activation fixtures the now-required recount.
* docs(orcad): say a directory at the stop-request path is refused, not restarted
* docs(orcad): the plain stop request's comment states both of its meanings
* style: format the restart admission files for the format check
* test(wsl): count the incumbent's recount bridge after the reservation's
* fix(orcad): a Windows stop request retries its rename while antivirus holds the new file
Renaming the just-written stop-now request can briefly fail with EPERM, EACCES
or EBUSY on Windows; the stop now retries those with the same bounded delays as
record-publish instead of answering SIGNAL_FAILED and leaving the server up.
* test(orcad): a real Windows host stops on the slot's stop-now request and relaunches
* test(orcad): recovery stops a real server whose restart admission refuses
* test(orcad): the recovery-stop cell tunnels over the cell's SSH connection
* test(orcad): the recovery-stop cell names itself in the CI log
* fix(settings): a deferred WSL update shows its full reason below the row
* test(orcad): fixtures pass releaseIncumbentAdmission; the census test calls its own method
* test(orcad): the capable-server census in the fence test reports idle chat work
* Record server chat restart offers and desktop creation history
* Offer interrupted chat recovery per managed runtime
* Ship the remote chat resume action labels in the runtime catalog
* Update removal contracts for desktop chat history cleanup
* Scope remote chat recovery to each server occurrence
* Preserve recovery choices and uncertain continuation outcomes
* fix: preserve uncertain restart action outcomes
* Fix restart recovery CI imports and fixtures
* test: align resume assertions with unconfirmed outcomes
* test: bound restart recovery work across scaled populations
* fix(native-chat): title the remote resume dialog with the restart
The dialog can appear out of nowhere when another desktop updates a shared
server, so its title now says what happened: "<machine> restarted".
* fix(native-chat): one ownership index with link roles, and a chat refuses to start while a terminal writes its conversation
Each chat link now carries its role (head or earlier) and key; Session History names
the chat continuing a conversation first, duplicate adoption checks only head links in
both the pre-commit check and the store transaction, and terminal resumes stay refused
exactly as before. The terminal-writer check now also runs inside acquireOwner before
the spawn, refusing with the typed pre-spawn reason conversationOpenInTerminal.
* fix(native-chat): hold a terminal start Orca issues until its terminal reports it
An in-memory registry of managed terminal starts per conversation, registered in the
same step as the resume admission (terminal.send with a resume command, createTerminal
with a command), bound to the PTY execution that runs it, and ended only when that
terminal's hooks report the conversation or the execution is gone. A chat's agent
start refuses while an entry may hold its conversation.
* fix(terminal): an automatic pane restore never resumes a conversation an open chat continues
Resume recipes built from a pane's saved facts (cold restore, deferred and late reattach,
sleeping wake, host mobile materialization) carry resumeOrigin 'saved'. The execution host
decides before any command is issued: while an open chat continues the conversation, or a
chat may be writing it, the pane starts its plain shell with the started-fresh notice and
its saved claim is forgotten; with unreadable records only a saved restore is refused and
its record kept. An admitted resume registers its managed start in the same step, so the
chat's agent start refuses until the terminal reports it or exits. Clearing the last saved
record now reaches disk.
* Fix saved terminal resume ownership and effective launch results
* Preserve conversation case and clear refused cold restore identity
* fix: publish effective terminal launch results on attachment
* fix: preserve terminal launch callback and candidate ownership
* fix: keep existing terminal attachments observational
* fix: consolidate terminal launch imports
* test: document snapshot runtime fixture boundary
* fix: preserve terminal launch identity types
* fix: accept reattach payload policy sessions
* fix: preserve terminal result validation contracts
* refactor: separate terminal create contract
* test: document partial terminal runtime fixtures
* test: align sleeping resume store fixture
* Fix terminal create type contracts
* fix: return the terminal main started to its waiting view
* fix: register reattached terminals before publishing their handles
* Check unavailable-resume test fixture types
* Complete sidebar resume test store fixture
* test: bound saved resume ownership and refusal work
* Settle refused saved resume claims from actual launch results
* Fix saved resume test type contracts
* test(e2e): capture exact terminal parking diagnostics
* test: verify parked terminal with fresh host inventory
* Consolidate agent session imports in remote terminal transport
* Clarify terminal conversation refusal guidance
* Admit note-only saved conversations without hiding live writers
* Retain terminal writer proofs through final admission
A slept agent whose tab disappeared now resumes in a fresh tab (with --resume, through the existing duplicate/host guards) when its workspace opens, and main's stranded workspace-sleep record is then dropped (only if no tab still lists that pane), instead of the record sitting forever while the workspace opens a bare shell.
Fixes#22657
* feat(ssh): list saved port forwards that couldn't start, with why, and let you remove them (#32484)
A saved forward whose local port is taken, or that keeps failing, stayed
saved but invisible, retried in the background with no way to remove it.
The port-forward lists now show each saved forward that isn't open, with
the last start error, and Remove deletes it by its mapping.
* fix(ssh): a forward whose ssh exits after starting backs off like a failed start
* fix(ssh): re-register the new forward channels, close a forward a restore reopened before removing it, and update IPC fixtures
* fix(ssh): a flapping forward keeps its backoff streak until it stays up
* fix(ssh): translate the saved-forward row, say Stopped for a forward that died, toast a failed remove, and reset backoff on manual edits
* fix(ssh): keep saved forwards listed and removable while the host is offline; label the remove button; test the healthy-uptime reset
* fix(ssh): mirror saved forwards from main with a stale-read guard, including hosts that never connect
* fix(ssh): scroll the offline ports panel when saved forwards overflow it
* test(e2e): give the saved-forward restart case's timeout its reason
* feat(managed-server): offer Update and Update anyway on a managed SSH server's row
A managed SSH server only updated on connect, and every connect deferred while
a terminal ran there. Its row now offers Update when this desktop bundles a
newer build than the Orca that runs the host. When running terminals defer it,
Update anyway restarts the server past them; the terminal daemon carries them
across the restart.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(managed-server): Update anyway passes only counted live terminals and keeps Update's version admission
Update anyway now sends the incumbent build it confirmed over; under the host
fence it is refused if another desktop activated a build since, keeps the
newer-host downgrade refusal, and never passes an uncounted terminal census.
The offer also covers hosts activated before the Orca version was recorded,
for the build this desktop already deferred there.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(managed-server): Update anyway also confirms a menu Update the row does not advertise
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(managed-server): the row shows one confirmation at a time
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* ci(e2e): route managed-server update sources to the Update anyway Docker spec
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat(orcad): run --reclaim-stale-lock on a managed host's active slot
The desktop can now dry-run and confirm the stale instance-lock recovery
on a Linux managed host, serialized with launches, then start the server
again and reconnect the way the restart action does.
Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>
* feat(settings): offer Recover server for a managed host refused on a stale lock
When a managed orcad start names --reclaim-stale-lock, the SSH host row
offers Recover server. The dialog dry-runs the check, shows the host's
evidence, and clears the lock only after the user confirms no other
machine sharing the home runs Orca.
Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>
* feat(mobile): point a host refused on a stale lock to the desktop's Recover server
Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>
* fix(settings): mount the Recover server dialog per opening
A fresh mount starts each opening from a new check instead of resetting
state in an effect.
Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>
* fix(orcad): end the readiness wait once the host proves the launch exited
A managed orcad refused at start (such as on a possibly stale instance
lock) left the client waiting out the full readiness deadline. Each
host-side wait now also checks the launch's PID file with kill -0 and
ends early on a proven exit, so the caller reports the same pending
readiness, with the orcad.log tail, within a poll. No PID, a denied
probe or a reused PID keeps the old deadline.
Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>
* fix(orcad): refuse a managed slot without --reclaim-stale-lock before running it
An older orcad ignores the flag and starts serving in the foreground. The
same remote exec now checks that the slot's server bundle names the flag
and answers unsupported otherwise, without invoking orcad.
Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>
* fix(settings): show the manual stale-lock command as literal mono text
The rendered dialog ran the command into the sentence, so it was hard to
read or copy. It now sits in its own mono block, like Details.
Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>
* fix(settings): offer only a manual delete when the host can't reclaim, and format the lock's age
An unsupported host was told to run the very command it lacks. Main now
names the lock path for that case, and the dialog shows only a copyable
rm of it with the shared-home caution. The lock's age uses the UI's
relative-time formatter instead of a hand-rolled minutes count.
Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>
* fix(orcad): pin a confirmed stale-lock reclaim to the record the user approved
--yes reclaimed whatever record held the lock at that moment, so a lock a
same-named machine wrote while the dialog sat open could be cleared.
orcad now takes --expect-nonce and refuses unless the lock still holds
it; the dry run reports the nonce, the desktop sends it back on confirm,
and the managed slot gate requires a build that knows the pin. A
reclaimed result carries the evidence it acted on.
Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>
* fix(settings): keep the Recover server dialog open through its own reclaim
The dialog's mount was gated on the status that offered it, and the
reclaim clears that note before its answer arrives, so the result was
never shown and the card kept the dialog marked open. Only the triggers
are gated now; an open dialog keeps the server it opened for until the
user closes it.
Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>
* fix(settings): put the Recover server confirmation on the security surface
It asks permission to run a remote command that clears an ownership lock
and starts server code, which STYLEGUIDE puts on that surface.
Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>
* fix(orcad): trust a gone launch PID only on the machine that launched it
The PID file sits in a home other login nodes may share, so a wait exec
that lands on another node read a starting server as exited. Both launch
paths now record the launching machine's boot id and node name beside
the PID, and the readiness wait ends early only when its own exec reads
the same record. No record, a host without a boot id, or another machine
keeps the deadline. The PID path is named once in the launch command so
it stays within its sent-byte ceiling.
Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>
* fix(orcad): keep a slot's unreadable stale-lock answer from offering a manual delete
A slot that passed the gate but gave no JSON, or a status this client
does not know, read as unsupported and offered rm of the lock. It now
reads as no-answer, which points to the slot's own reclaim command, and
unsupported is left to a slot without the command, a non-Linux host or
no active version.
Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>
* style(settings): use the text-2xs step for the stale-lock command block
Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>
* fix(mobile): offer the desktop-recovery hint on the same refusal the desktop does
Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>
* test(orcad): name the extra fields a fake stale-lock answer carries
Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>
* fix(settings): end the Recover server flow once the lock is cleared, and keep an unpinned reclaim manual
After a reclaim whose restart failed, the dialog kept the old evidence,
the enabled confirmation and manual recovery for a lock that was gone.
It now shows only that the lock was cleared and why the server did not
start, with Close. A slot that has the reclaim but not the nonce pin is
now told apart from one without the reclaim, and is pointed to its own
reclaim command rather than rm.
Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>
* fix(orcad): keep a stale-lock reclaim's result when the restart after it throws
A wake or tunnel rebuild that threw after the lock was cleared discarded
the reclaimed result, so the caller skipped its reconnect and note
clearing and the dialog kept a confirmation for a lock that was gone.
The restart's failure now becomes the result's notRestarted reason.
Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>
* fix(orcad): report a reclaimed server as started when only its forward failed to follow
A tunnel rebuild that failed after the wake started the server was
reported as the server not starting. The rebuild now goes through
followLaunchedServerPort, as other relaunches do, which warns and leaves
the forward to the next connect. Also drops a caller test that only
covered behaviour the fix did not change.
Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>
* test(mobile): re-record the execution-target goldens for the recovery hint field
The SSH gate's projected state gained `needsDesktopServerRecovery`, which
is false for every recorded reply (no recorded host sends a managed
server refusal). Re-recorded with `pnpm --dir mobile rpc:record` for the
two pilot recordings and the four execution-target reply partitions; the
only change is that field, plus the value pool reordering around it.
Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>
---------
Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>
* refactor(worktrees): remove unreachable legacy worktree list IPC
The desktop and web preloads both always provide `worktrees.listDetected`,
so the renderer's fallback to `worktrees.list` never ran, and nothing calls
`worktrees.listAll`. Remove both IPC handlers, their preload/web wrappers,
the renderer fallback, and helpers only they used. Tests that observed
behaviour through the removed handlers now go through `worktrees:listDetected`.
* test(worktrees): drop removed list channels from SSH listing matrix
Address review: the canonical SSH listing test built channel names dynamically
and still invoked worktrees:list/listAll. Also fold the detected-result reader
into the shared worktree test fixtures, refresh stale comments, and assert the
backfill no-op test listed authoritatively.
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* test: remove redundant cases and repair behavioral assertions
* docs: reconcile audit records with current main
* fix(deps): apply security updates from #32101
Apply the six-file DOMPurify 3.4.17 and Next.js 16.4.1 patch from PR #32101 to resolve the dependency security gate for this test audit. Original patch by Brennan Benson.
* test: satisfy changed-line quality gates
Add required braces and replace filter indexing with find. Rerun both CI quality gates and the 27 affected relay tests; refresh audit hashes and record the corrections.
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* Track Agent Dashboard adoption and daily visible use
- Record enablement, board opens, and per-surface daily visibility
- Persist daily limits across restarts and respect consent and window visibility
* Track dashboard telemetry at surface roots
* Add Orca's own bundled-skill planner and installer (not wired in yet)
One planner decides what installing Orca's bundled skills does to each
target folder; execute re-plans under per-path locks and runs only a plan
whose digest still matches; a shared summary derives badge, wording and
exit code. A copy counts as Orca's untouched one only when its SKILL.md
bytes match a version some build shipped (resources/skills/released-digests.json,
generated from git history and checked add-only).
Nothing calls this yet; the npx path is unchanged.
* Report left-alone folders as warnings and keep backups in the home
A leave is no longer a failure: the outcome line says what happened plus
"N left alone: <reason>", and the exit code is 1 only for a failed or
changed op. Backups for every scope go to <home>/.agents/.orca-skill-backups
on the executing host, so a project Replace never writes into the repo;
moving there falls back to copy, verify, then remove across filesystems.
* Update Orca's skills with Orca's own installer instead of npx skills
The app's Update skills dialog, badge, nudge and status pills now read the
bundled-skill plan and summary, and Update executes the reviewed plan's
digest. `orca skills install|update` runs the same planner in-process with
--dry-run, --force, --json and per-summary exit codes. Removes both npx
spawn sites, the freshness inventory modules and the npx round-trip CI.
* Update only where the skill already is; one provider-root rule for app and CLI
installedOnly now drops every location that holds nothing (keeping the shared slot
only when an existing agent link points at it), so Update never adds a skill for
another agent. The app and CLI resolve the Claude skills folder through the same
rule in localBundledSkillInput; a test asserts both plan identically.
* Compact Update skills dialog: one row per skill, locations named by agent
summarizeBundledSkillPlan now returns each skill's locations, labelled by
agent (or "Shared folder") with a ~-collapsed path; the dialog shows one row
per skill that needs something, collapses up-to-date skills into one line, and
lists every location behind "Show all locations". The result reads "Updated N
skills · M left alone". Under installedOnly a Replace with no shared copy
replaces in place instead of adding one.
* fix(skills): keep location paths readable by putting the state under the path
* fix(skills): hold the last plan in state, not a ref mutated during render
Split the update dialog's rows, headline and footer into components, move the Update label
next to the other plan copy, and use z.strictObject for the request schemas.
* Escape non-ASCII characters in bundled-skill regex and wording
The orcad bundle rejects a literal non-ASCII character in a regex.
* fix(cli): escape non-ASCII apostrophes in the skills install help text
* Plan and update Orca's skills on paired servers, SSH hosts and WSL
* Show the skills pill for paired hosts; real-WSL installer test
* test(orcad): check the bundled CLI plans a skill install without npx
Drop the deleted roundtrip workflow's group from the closed-PR cancellation fixture.
* Let the WSL installer create the backups folder itself
* Review fixes: follow the active workspace's host, keep hosts' plans and runs apart, never boot WSL for a background check
* test: execute requests carry the plan request; CLI pill follows the local target
* Write large WSL skill files in chunks that stay under the command-line limit
* Make bundled-skill test fixtures link folders the way the product does on Windows
Directory links in fixtures are junctions on Windows, which need no
Developer Mode; the one file-link fixture is skipped there.
* Let a client ask a host to install named Orca skills, not only update installed ones
* Plan an SSH workspace on the managed server that serves its host; ask to connect otherwise
* Translate the remote skill states into every locale
* Resolve legacy skill agent keys canonically
* Reject stale skill replacements when auxiliary content changes
* Remove the Windows-hosted WSL bundled skill installer
* Wait for active workspace ownership before planning bundled skills
* Keep ordinary bundled skill classification shallow
* Leave unreadable skill replacements alone without aborting plans
* Pass force-all through shallow planning and describe unreadable copies
* Clarify skill update statuses and folder details
* Keep selected skill replacements in place with one status
* Report completed skill replacements with their backup confirmation
* Clarify available skill updates and skipped copies
* Keep skill update results in preview order
* Show each skill skip reason once
* Count skipped skills instead of skipped folders
* Make installer input and link tests work on Windows
* Bound bundled skill classification reads and parse names linearly
* Keep workspace skill roots inside the resolved workspace
* Compare workspace skill roots with native path semantics
* Restrict bundled skill backups to their owner on POSIX
* Keep bundled skill updates on the proven local workspace host
* Keep recovered skills needing attention until they are updated
* Count completed skill updates in the status tooltip
* Sync the runtime localization catalog
* Add Orca's own bundled-skill planner and installer (not wired in yet)
One planner decides what installing Orca's bundled skills does to each
target folder; execute re-plans under per-path locks and runs only a plan
whose digest still matches; a shared summary derives badge, wording and
exit code. A copy counts as Orca's untouched one only when its SKILL.md
bytes match a version some build shipped (resources/skills/released-digests.json,
generated from git history and checked add-only).
Nothing calls this yet; the npx path is unchanged.
* Report left-alone folders as warnings and keep backups in the home
A leave is no longer a failure: the outcome line says what happened plus
"N left alone: <reason>", and the exit code is 1 only for a failed or
changed op. Backups for every scope go to <home>/.agents/.orca-skill-backups
on the executing host, so a project Replace never writes into the repo;
moving there falls back to copy, verify, then remove across filesystems.
* Update Orca's skills with Orca's own installer instead of npx skills
The app's Update skills dialog, badge, nudge and status pills now read the
bundled-skill plan and summary, and Update executes the reviewed plan's
digest. `orca skills install|update` runs the same planner in-process with
--dry-run, --force, --json and per-summary exit codes. Removes both npx
spawn sites, the freshness inventory modules and the npx round-trip CI.
* Update only where the skill already is; one provider-root rule for app and CLI
installedOnly now drops every location that holds nothing (keeping the shared slot
only when an existing agent link points at it), so Update never adds a skill for
another agent. The app and CLI resolve the Claude skills folder through the same
rule in localBundledSkillInput; a test asserts both plan identically.
* Compact Update skills dialog: one row per skill, locations named by agent
summarizeBundledSkillPlan now returns each skill's locations, labelled by
agent (or "Shared folder") with a ~-collapsed path; the dialog shows one row
per skill that needs something, collapses up-to-date skills into one line, and
lists every location behind "Show all locations". The result reads "Updated N
skills · M left alone". Under installedOnly a Replace with no shared copy
replaces in place instead of adding one.
* fix(skills): keep location paths readable by putting the state under the path
* fix(skills): hold the last plan in state, not a ref mutated during render
Split the update dialog's rows, headline and footer into components, move the Update label
next to the other plan copy, and use z.strictObject for the request schemas.
* Escape non-ASCII characters in bundled-skill regex and wording
The orcad bundle rejects a literal non-ASCII character in a regex.
* fix(cli): escape non-ASCII apostrophes in the skills install help text
* test(orcad): check the bundled CLI plans a skill install without npx
Drop the deleted roundtrip workflow's group from the closed-PR cancellation fixture.
* Make bundled-skill test fixtures link folders the way the product does on Windows
Directory links in fixtures are junctions on Windows, which need no
Developer Mode; the one file-link fixture is skipped there.
* Resolve legacy skill agent keys canonically
* Reject stale skill replacements when auxiliary content changes
* Keep ordinary bundled skill classification shallow
* Leave unreadable skill replacements alone without aborting plans
* Pass force-all through shallow planning and describe unreadable copies
* Clarify skill update statuses and folder details
* Keep selected skill replacements in place with one status
* Report completed skill replacements with their backup confirmation
* Clarify available skill updates and skipped copies
* Keep skill update results in preview order
* Show each skill skip reason once
* Count skipped skills instead of skipped folders
* Make installer input and link tests work on Windows
* Bound bundled skill classification reads and parse names linearly
* Keep workspace skill roots inside the resolved workspace
* Compare workspace skill roots with native path semantics
* Restrict bundled skill backups to their owner on POSIX
* Keep recovered skills needing attention until they are updated
* Count completed skill updates in the status tooltip
* Sync the runtime localization catalog
* fix(sidebar): land flip-a's switch-on fixes on main, still dark (no flip)
The fixes found while the sidebar ran from main's feed, without turning
that on: ORCA_SIDEBAR_INPUTS_IS_MAIN stays false, and none of flip-a's
stop-gaps come along.
- Main stops counting a closed tab's terminal once it was asked to stop
and no pane shows it; a failed or refused stop takes the request back,
and a host out of contact stays unverifiable.
- Main fetches a paired server's rows with the transport's normal bound,
so a server slower than 5 s still lists.
- A deferred unread raise flips the folder row it was decided for, and a
read while the owner check is out cancels it.
- Phone: no worktree.ps flash before the feed; a host's last installed
feed stands in while a fresh read is out; it highlights its own
last-opened workspace and a fold never scrolls; projects file under
their project group with the desktop's indent; the collapse spinner
shows only after 500 ms; "Reopen to update" keeps the rows on screen.
- e2e helpers and journeys for the switch-on suite.
* fix(sidebar): keep an owed relay stop marked, clear a thrown stop, and prune pending unread raises
- A stop-and-wait that throws takes its stop request back, like one that fails.
- A final stop the legacy relay still owes stays marked, since the relay retries it.
- Pending unread raises are tracked only while one waits, so the map no longer grows.
- The paired-server fallback poll test gives its third fetch a reply and asserts live.
* feat(sidebar): main publishes cached snapshots and announces its own stores; detected, view and live-content domains (inert)
* fix(sidebar): keep links confirmed only by a new git run, no rescan on metadata writes, no phone wait on detected checkouts
* fix(sidebar): rescan the workspace rows when the Windows runtime default changes
* feat(sidebar): one lifecycle queue per workspace in main, with in-progress hints (switch-gated)
Create, delete, terminal.closeAll, terminal.sleep and worktree.sleep now run through one
first-in, first-out queue per host-qualified workspace: strict order, no merging, delete
cancels what has not started and waits for what has, an unconfirmed outcome pauses until
the host's own answer. Hints publish in a new inProgress feed domain (no reader yet); the
creations domain is its projection. A finished create or delete keeps its hint until a scan
started after it applies, in the same revision. Adds the paired-server metadata serializer
(saving domain, no caller yet). Replaces the creation registry.
* fix(sidebar): a create never queues behind the previous occupant's delete
* feat(sidebar): main runs sleep; the window follows (P3b)
* fix(sidebar): keep electron out of the headless server's sleep path
The desktop registers how sleep reaches paired servers; a headless serve has none.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(sidebar): type-check the sleep e2e specs; drop the removed VM suspend stub
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(sidebar): resume a paused workspace by re-observing, never replaying
- A command issued while paused waits behind the pause; when the owner answers again main
re-reads terminal liveness and the listing, then carries on. A forced delete runs past it.
- Commands keep their arrival order across a workspace lookup; deletes by name key by the
resolved id, so they wait for that workspace's running sleep.
- A second delete with different options is refused as already in progress.
- Close and delete read lost contact as sleep does.
- A create always frees its own queue entry, even after a retry took over its id.
- Drops the unused opId, and the paired-server write serializer and its feed section,
which land with their first caller.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(sidebar): main is the only writer of a sleep's wake records; sleep classifies only lost contact as unconfirmed
- The window no longer captures wake records for a sleep main runs; it takes main's records
from main's answer, which its wake and tab-open resume then read.
- A workspace reopened during its sleep keeps its VM running (the window and a phone's
activation tell main).
- Only lost contact pauses: a definite chat-stop failure or a server's still-live answer is a
plain failure with today's toast.
- A paired server's chats are asked with the Sleep mark, and again without it when an older
server refuses the field, instead of reading an unprobed status.
- A phone's worktree.sleep is answered on acceptance and its activation no longer waits on a
queued wake, so neither can outlast the phone's request timeout. The web client passes
markChatsAsleep and keeps the receipts.
- One desktop adapter, passed where the desktop builds the runtime, replaces the two module
slots; a headless server gets none.
Co-Authored-By: Claude <noreply@anthropic.com>
* chore(sidebar): share worktree.sleep's params with the RPC catalog; lint fixes
Co-Authored-By: Claude <noreply@anthropic.com>
* test(sidebar): test the paired-server sleep where main runs it; drop the window's copies
The window no longer sleeps a paired server's terminals, so its convergence and order tests for
that path move to main (remote-workspace-terminal-sleep.test.ts) or follow main's answer.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(sidebar): retry the sleep spec's liveness read while terminals stop
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(sleep): main resumes every workspace-sleep wake; the window only attaches
- Main claims and starts each slept pane through its windowless resume path, with or
without a window: the "window present" check is gone.
- A phone's open queues main's wake, which finishes only once the resumes have started,
and always notes the reopen so a running sleep leaves the VM up.
- The window no longer relaunches a workspace sleep's records in fresh tabs; opening a
slept pane asks main to resume it and the pane's spawn attaches to main's terminal.
- A window save cannot overwrite main's workspace-sleep records.
- A VM that would not suspend no longer reads as "kept open": the terminals slept, and
the toast says the machine is still running.
* test(e2e): main resumes a slept pane once, in place, and keeps its record
* chore(i18n): add the VM-still-running sleep message
* fix(sleep): a phone's or the CLI's failed sleep shows the desktop toast again
* fix: restore main's final P2 test fixes a merge reverted
* fix(sleep): window saves never write or clear main's sleep records; headless wake report counts only real sleeps
* test: a same-id twin's grace follows the rows' change, not the removed catalog hook
* fix(sleep): drop the 1 s settle wait after an observed exit, and wake a slept workspace's mounted agents on phone open
A workspace sleep stopped each terminal through main's stop-and-verify, which polled for a
further second after the exit it had already seen. And a phone opening a slept workspace no
longer reached the window's mounted panes of agents that had finished before the sleep, so
they stayed asleep until the window opened their tab.
* test: give the workspace-sleep wake record its prompt field
* refactor: match slept-agent records to the workspace with worktreeIdsEqual
* chore(sleep): one import from sleep-record-pane-claim
* test(sleep): route the disconnected-sleep SSH spec, declare the desktop sleep adapter, and open the workspace after its sleep starts
* test(sleep): delete a workspace the spec creates, not the seeded one later specs share
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat(resources): end local terminals with checked identity checks
- Add a PTYs view to the Resource Manager that lists local terminals and can end one. Before stopping, the host confirms the terminal's incarnation, agent ownership, and attachments. This prevents ending a same-ID replacement that started after the list was read.
- Bump the daemon protocol to v50 and gate the new fenced kill on it. Older daemons ignore the extra fields, so the client refuses End instead of sending an unchecked kill.
- Admit diagnostic reads (PTY listing and memory snapshot) only while the Resources view is visible, so a hidden window does no background scanning.
- Report incarnation and host on spawn and exit events so the renderer can match them to the terminal it showed.
- Replace the popover's terminal-session list with the new inventory and move memory polling into the Resources view.
* Show unrouted daemon PTYs in the Resource Manager
* Simplify PTY controls and clarify safe termination
* Format local build compatibility protocol lists
* Add PTY resource management localization strings
* Harden PTY resource manager test fixtures
* Use typed IPC event in PTY kill test fixture
* Refine local PTY resource view model typing
* Refine local PTY resource view model typing
* Include local host identity in PTY lifecycle event tests
* Offer Resolve with AI for paused merge, rebase, and cherry-pick
A paused operation can need recovery after every conflict is staged, but the Resolve with AI action only appeared when unmerged paths remained. The operation banner now offers it whenever AI actions are visible and an operation is in progress.
Starting an AI conflict-resolution agent is also blocked while a Git operation is running. The composer, the banner and card buttons, and the agent launch itself check the running-operation state, so a launch that began during agent detection cannot start while Git is still running.
* Block paused-operation recovery only on Orca's own Git work
The previous gate scanned the host's process table and refused "Resolve with AI" whenever it could not prove no Git process was running. That refused recovery on older paired hosts, on Windows whenever any Git process ran anywhere on the machine, and while a paged `git reflog` or `git grep` was open in a terminal.
- The host tracks Orca's own mutating Git commands per worktree (including ones still queued) and reports them through `git.mutationInFlight`. Reads and fetch/push/ls-remote are not tracked.
- Recovery is refused only when the host reports that work. Older hosts without the method keep today's behavior.
- The gate applies only to launches from the paused-operation banner and conflict card, not every resolveConflicts launch, so the Checks panel's PR-conflict flow is unchanged.
- The agent prompt now tells the agent to stop if index.lock exists or another git command is running, which covers Git started outside Orca on every host. With no unmerged files, the prompt asks the agent to find why Git paused instead of listing zero conflicts.
* Preserve Git mutation state until terminated commands exit
- Cancel stale recovery checks and agent launches when their dialog or scope closes
- Guide rebase recovery agents to amend fixes after failed exec steps
* Cover failed rebase exec recovery with real Git
* Recognize global Git options in worktree mutation tracking
* fix(native-chat): attach a long paste as a text file, and say a message is too large before it is sent
A paste of 32 KB or more (or one that would push the message past the 256 KB
send limit) into a structured chat is saved as a file the agent reads, through
the same pending-chip and @path route a dropped file takes: Orca's paste folder
here, or the chat's attachment store on its paired server. Claude gets the paste
folder as an allowed directory. A message still over the limit is handed back
before it is sent, on desktop and the phone, with words naming the limit instead
of the generic 'not sent, send it again'.
* test: Claude launch parity snapshot grants the paste folder
* fix(native-chat): a long paste replaces the selection at once and settles in place
A long paste now puts an inline Pasted text pill where it went, replacing any
selected text immediately, and the saved file (or inline text, or the original
selection on failure) replaces that same pill when the save finishes, in its own
chat's draft. Nothing reads the caret later, so order and position hold. The
paste never joins the image row, so image numbers are unchanged. The size check
measures the real message body with the selection replaced and images included.
A server that keeps no files takes the paste inline when the message still fits.
A pending paste a restart lost shows Paste not saved, and Send says so.
* fix(native-chat): type the pasted-text parser and the composer's missing draft scope
* fix(native-chat): admit paste references before goals and commands
* fix(native-chat): resolve staged paste paths without editing the draft
* fix(native-chat): consolidate paste imports for code quality checks
* fix(native-chat): retain checked string types for saved paste paths
* fix(native-chat): retain completed paste facts through history and delimit paths
* fix(native-chat): keep structured question rendering within file limit
* fix(native-chat): pasted or inserted text that looks like a paste token stays literal
* fix(web): keep the browser's UI bridge under the file length limit
* style: format the files #29982 merged unformatted
* fix(native-chat): retain oversized drafts before send admission
* fix(native-chat): keep the composer handle's merge resolution to main's destructure