* Enable continuing remote CLI conversations in Chat UI
* fix(native-chat): send the host's own pane key; ask older hosts to update
On a paired host the desktop names a pane by its mirrored surface key, but
the host matches cliSource against its own agent-status rows, so every
remote "Continue in Chat UI" create was refused as unreadable. Translate
the key with toHostSessionPaneKey before sending.
A resume launch's join identity now includes its execution host, so a
remote resume never joins an in-flight resume of the same conversation id
on another host.
A verified host that lacks resume-history now shows the pane-header action
disabled, with "Update Orca on this host to continue in Chat UI." in its
tooltip and accessible name, instead of hiding it.
---------
Co-authored-by: Zrzzzz <1160026659@qq.com>
cmd /c strips the first and last quote of a line that starts with a quote
unless exactly one quoted, paren-free executable path is present. A spaced
.cmd launcher plus any quoted argument (or a Program Files (x86) launcher)
was cut at its first space and the spawn silently failed. Prefix the batch
invocation with `@` so the line never starts with a quote, in both the
waiting and detached start /B forms.
Add a real cmd.exe round trip in the Windows CI lane.
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)
* feat(open-in): open SSH workspaces in Cursor and Zed
Cursor shares VS Code's Remote-SSH argv and now opens SSH and SSH-reached
server workspaces the same way, in a new window like its local launch.
Zed opens them through its ssh://[user@]host[:port]/path URL, so manual
custom-port hosts work without a local SSH config alias.
Co-authored-by: zerone0x <soberzml2333@gmail.com>
* fix(open-in): keep manual Zed targets on their saved route
Always send the saved port for manual targets so a matching ssh config Port
cannot redirect them, ask for an ssh config alias when a manual host needs a
key, agent or proxy the URL cannot carry, reject option-like hosts, and accept
the zeditor CLI name.
* refactor(open-in): move the remote SSH editor launch into its own module
Keeps external-editor-launch.ts under the max-lines limit after the main merge,
and moves main's new macOS CLI fallback test onto the remote launch API.
---------
Co-authored-by: zerone0x <soberzml2333@gmail.com>
* feat(ssh): per-host Reconnect on launch toggle
A host with Reconnect on launch turned off starts each launch as if the user
had disconnected it: main records the session user-disconnect for it and the
managed servers reached through it, so neither the startup restore, pane
mounts nor managed-server calls dial it until the user connects it. The
renderer also skips it in the startup restore. The switch lives in Settings ->
SSH -> Advanced for hosts this desktop dials; undefined keeps today's behaviour.
* fix(ssh): hold opted-out hosts before server status probes start
Server status probes start inside registerCoreHandlers, before the SSH
handlers register, so hold launch opt-outs right before it (once per
process). Relay-era panes that hit a held host now wait for Connect instead
of reporting a failed connect.
`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(native-chat): say why Claude refused to start a chat, including Full access as root
Claude Code exits before initialize when Full access (--dangerously-skip-permissions)
meets a root uid, which is common on SSH hosts. Orca reported only that Claude
stopped before it finished starting, and retried four times. Classify that refusal
as fullAccessAsRoot with a next step, and show the CLI's own startup words behind
Details on the failure row and on the unsent message.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(native-chat): keep message Details to start failures; cover the exit path
Route the exit-error module and delivery notices to the managed e2e, explain its
timeout, and drop the exec non-null assertion. Match the zh Full access label.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(native-chat): type the refused fixture so the web typecheck passes
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
floorToMinute recomposed local wall-clock fields via setSeconds(0, 0), and
the FREQ=HOURLY branches used setMinutes. Inside a repeated fall-back hour
both resolve to the earlier offset, so nextAutomationOccurrenceAfter could
return `after` itself and latestAutomationOccurrenceAtOrBefore could return
the first pass's instant.
Run dedupe by scheduledFor prevents duplicate launches, but the second pass
was mishandled: a `30 1 * * *` cron's second run started 30 minutes late (or
was recorded as skipped_missed past its grace window), the 01:00 EST run of
`FREQ=HOURLY;BYMINUTE=0` was dropped, and the scheduler re-evaluated and
flushed state on roughly every tick for the hour because nextRunAt stayed
in the past.
Floor with epoch arithmetic and route cron and hourly schedules through one
absolute-minute walk, so each pass of a repeated minute fires once, on time.
Fixes#20154
Co-authored-by: Neil <neil@stably.ai>
Agent Session History now routes Delete for a session on a paired or
SSH-managed Orca server to that server through a new aiVault.deleteSession
RPC. The host revalidates the path against its own roots, removes the
transcript (into Trash on a desktop host; permanently on a plain-Node host,
which has none), and invalidates its caches. Hosts advertise
aiVault.delete-session.v1; an older host keeps Delete disabled with
"Update <host> to delete its sessions from here."
Co-authored-by: brynnclaw <261708852+brynnclaw@users.noreply.github.com>
The resource panel summed RSS. On macOS that counts the Electron framework's
shared pages once per helper process, so main and renderer read 2-8x too
high, while the GPU process read 3-5x too low because RSS excludes graphics
memory. The detached terminal daemon was also missing from the app total.
On macOS, sample per-process phys_footprint from top (the figure Activity
Monitor shows) at most every 10 s while the panel is open, and fall back to
RSS when top is unavailable. Count the local terminal daemon in the app's
'other' bucket; its PTY children stay attributed to their worktrees. Linux
and Windows keep RSS/working set.
Co-authored-by: m4air <m4air@Mac.localdomain>
macOS ps formats lstart with the user's locale (%c) and vis-encodes
non-ASCII argv when no UTF-8 locale is set. Under de_DE/en_NZ/ja_JP the
process-table parsers dropped tty/startTime and folded them into command,
breaking agent recognition, pane fingerprints, agent-session start times
and daemon identity. Run those ps readers with LC_ALL=en_US.UTF-8 on
macOS; other platforms keep the inherited env.
Fixes#27004
Co-authored-by: outof-place <contact@outofplace.space>
* fix(drops): keep files dropped onto a remote workspace out of git status
Uploads into `.orca/drops` now write `.orca/drops/.gitignore` (`*`) the first
time it is missing, so dropped files no longer block a plain workspace delete
or get committed by `git add -A`. Callers share one constant for the folder.
Fixes#23856
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Re-check the import session after the drop staging ignore step
The ignore step swallows its own errors, including a stale session. When
nothing was staged the loop never asserted again, so the caller got
skipped/failed results instead of the retry error.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(drops): say why a remote drop or file open failed
Co-authored-by: Jae Heo <83424878+jae-heo@users.noreply.github.com>
Co-authored-by: Bing.Z <zzb@gxsmjx.com>
* fix(drops): keep paths out of failure toasts and reuse the too-large classifier
* fix(drops): shorten whole quoted paths in failure toasts
* fix(drops): describe upload failures by category instead of host text
* fix(drops): match failure categories on unquoted text and name manual disconnects
---------
Co-authored-by: Younghwan Chae <chyohw97@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Jae Heo <83424878+jae-heo@users.noreply.github.com>
Co-authored-by: Bing.Z <zzb@gxsmjx.com>
agent_hook_install_failed sent up to 200 chars of the raw installer error,
which embeds home-directory paths (and so the OS user name) and can echo
config contents. Send only the error's errno code or 'unknown', and enforce
that in the event schema. The anchored, capped errno pattern now lives in
src/shared/errno-code.ts and is shared with the renderer launch probe and the
file-write precondition instead of their own copies.
Fixes#21492
Co-authored-by: brynnclaw <261708852+brynnclaw@users.noreply.github.com>
Co-authored-by: Neil <neil@stably.ai>
The RRULE day scan stepped local midnight by a fixed 24h. Walking back
over a 23-hour spring-forward day landed at 23:00 two dates earlier, so
the latest-occurrence lookup used for catch-up runs skipped a date. It
returned a run a day too old for daily schedules, and a week too old for
weekly ones.
Step with setDate via a shared addLocalDays helper, in both the RRULE
scan and the cron feasibility scan. Add a regression test for
America/New_York and Australia/Lord_Howe. The test is skipped on Windows,
where unstubbing TZ doesn't restore the OS zone.
Co-authored-by: liutian <liutian.6000@bytedance.com>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
* fix(ssh): keep machines cloned with one machine-id off each other's Orca server data (#32918)
Machines cloned from one image share /etc/machine-id. On a shared home both
got the same machine key, so both managed servers ran in ~/.orca, and each
judged the other's PID locally and reclaimed its live instance lock.
- Record the claiming hostname in the shared-home claim and machine homes;
a Linux machine whose machine-id matches but hostname differs gets its own
machine home. Older claims fall back to data-root owner records.
- orcad's instance lock no longer treats a holder with this machine-id but
another hostname as local, and refuses to start when an older clone's
profile-state owner record shows it is using the data root.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(ssh): keep a clone's established machine home, and let a host renamed within its boot restart
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(ssh): call a machine a clone only with positive evidence
A renamed host keeps its boot and a local disk has one machine, so only
another host and boot with this machine-id on a shared filesystem counts as
a clone, on Linux only. When both clones already write the login home, stay
and let the server lock refuse the second writer. Drop the machine-home
owner markers.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(ssh): refuse a second server from a cloned host instead of relocating data
Drop the inferred clone relocation. orcad refuses to start when a holder or
profile-state owner on a shared filesystem reports this machine-id from another
hostname and boot, naming that host and the fixes. Add ORCA_MACHINE_NAME, an
explicit host-env name that gives a managed SSH host its own machine home.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(ssh): judge cloned machines by boot and freshness, not hostname
A peer with this machine-id is another live machine only when its record is
from another boot, was written after this boot began, and is fresh. The orcad
lock now heartbeats its mtime. That drops the hostname test, which renames
broke and same-hostname clones evaded. It also drops the participant-owner
precheck that stale records could block forever, and keeps profile-state
owner cleanup from deleting a live clone's owner. stale-lock reclaim uses the
same rule. ORCA_MACHINE_NAME now rejects invalid names and keeps a host on
the home its running server or own machine-id already uses. Hints are
role-specific and aimed only at the clone.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(orcad): stale-lock reclaim waits out the lock freshness window
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(orcad): log a failed lock heartbeat and say a crashed clone's lock frees itself
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(orcad): align reclaim help, age evidence and clone hint with the freshness window
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(ssh): ORCA_MACHINE_NAME always selects its own home
Inferring which home to keep from running owners flipped after shutdown or
reboot and couldn't see macOS boots. A name now deterministically selects
its home, and a multi-line name is refused.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(ssh): refuse a whitespace-only ORCA_MACHINE_NAME, and pin reclaim test clocks
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix(runtime): recover from a NUL-filled server list instead of blocking every save
Fixes#18766
* fix(runtime): recover only crash-zeroed server lists; keep removal proofs fail-closed
* fix(runtime): copy the blank server list aside on write instead of renaming it on read
* fix(runtime): recover a crash-zeroed server list only when saving a server
Plain reads stay fail-closed so startup cleanup cannot treat the zeroed file as
'every server was removed'; saving copies the bytes aside and publishes an empty
store only if the file is still blank.
* fix(runtime): publish the recovered server list only together with the saved server
A failed save now leaves the zeroed file in place instead of an empty registry that
startup cleanup would read as 'every server was removed'.
* fix(runtime): let SSH and WSL server setup save over a crash-zeroed server list
Their duplicate and conflict pre-checks read the list before the add and threw on the
zeroed file. The blank-file backup is now taken only once the write is attempted.
* test(runtime): give the fsynced interleaving property test room on slow disks
* test(runtime): run three guard-killing seeds instead of raising the interleaving timeout
The fsync on every store write made 40 seeds x 40 steps slow; seeds 1, 6 and 8 each
fail when one of the three guards this test protects is removed.
V8 stores a script source as UTF-16 when any character is above U+00FF, so a
single em dash in a hot chunk doubled that chunk's resident source. Emit ASCII
(escaped) output for main, preload, renderer and worker bundles, escape the
CJK sentence marks in the prose URL regex source, and fail the renderer
boot-graph check if a boot chunk regresses to two-byte.
Co-authored-by: m4air <m4air@Mac.localdomain>
* fix(wsl-host): bind a WSL host record to its pairing's server key (R31)
A valid but stale record could overlay WSL hosting onto a different loopback server re-paired
under the same environment id and bridge port. The record now carries the paired orcad's host key;
the overlay ignores a record whose key differs from the pairing's. Add writes the key and a
pairing refresh rewrites it with the ports, so a relaunch that changes the key still matches.
Records written before carry no key and keep port-only binding until their next refresh.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(wsl-host): validate a refreshed pairing before writing, and accept the previous key mid-refresh
Review: the refresh wrote the record's new key before validating the pairing, so a refused or
interrupted refresh could leave a real WSL host without its overlay. Validate first, and keep the
replaced key as previousPairingPublicKeyB64 so the record stays bound across the two writes.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(wsl-host): use a key distinct from the fixture's own in the R31 overlay tests
Main gave the WSL fixture its own pairing key (#30676), the same one these tests used as the
'other server' key, so both cases inverted after the rebase. Production code is unchanged.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* test(e2e): a pane moved to a new tab on a paired server workspace stays moved
* fix(terminal): keep a pane moved to a new tab on paired runtimes
Moving a split pane to a new tab in a paired-runtime worktree changed only
the window store. The host never learned about the move, so the next host
snapshot rebuilt the old tab and the pane snapped back.
Add a session.tabs.movePaneToNewTab RPC that commits the move in the host
session through planTerminalLeafMove and republishes the session tabs.
For a mirrored host tab, the renderer applies the move first under the
mirrored id the host snapshot will use, then asks the host to commit it.
An older host or a refused move falls back to the old local-only move.
Fixes#32939
* fix(terminal): commit a host pane move through the existing leaf-move writer
The layout-writer ratchet allows no new main-process layout writers. The
headless host now hands the move to commitLeafMoveAndRekey, the same
commit a local window uses, and keeps only the window check, validation,
rehydrate and notify.
* fix(terminal): gate the host pane move on a host capability
An older host keeps today's window-only move under a local tab id instead of
a mirrored one it never confirms. Adds a terminal.list check to the e2e.
* test: drop type assertions from the pane-move tests
* fix(terminal): the host keeps a moved pane's place, focus and live terminal
After the host commits the move, the window gives it the drop position and
focus through the existing tab move and activate calls; a refusal redraws the
host's tabs. The host moves the live PTY's surface with its pane and checks the
target tab id. The move lives in its own runtime module.
* fix(terminal): hold a moved pane's drop order until the host has it
The host's snapshot of the commit goes out before its reply and appends the
tab, so the window records the drawn order as a reorder intent at the drop and
sends that order, and the focus read then, once the host answers.
* fix(terminal): route the moved tab's reorder to its owning host
The follow-up names the worktree's host instead of reading the active-server
setting (focus-read ratchet); the e2e narrows host rows to terminal tabs.
* fix(terminal): the host places a moved pane's tab in the move itself; a refusal reverts
The move carries the drop's group and index (optional; an older host appends)
and the host places and selects the tab before its one publish, replacing the
follow-up reorder and the drop-time order hold. A refused move closes the tab
this window drew without stopping its terminal and redraws the host's tabs even
when unchanged. E2e covers a same-group drop and a refusal; host unit tests cover
same-group and cross-group placement.
* fix(terminal): a refused pane move keeps its pane records; drop index in host order
The revert hands the pane's status and launch records back to the source tab
before closing the drawn tab. The host turns a projected client's drop index
into its raw order with translateProjectedSessionTabMove, as session.tabs.move does.
* fix(terminal): only an unknown drop group appends a moved pane's tab
The host catches only target_group_not_found and invalid_tab_order from the index
translation and rethrows anything else.
* fix(terminal): settle a pane move by what the host holds when its reply is lost
A lost reply is not a refusal: the window asks the host whether it holds the
pane in the new tab before undoing anything, and waits for the next snapshot
when it cannot ask. A move the host committed but this window could not draw
takes the host's tabs.
* fix(terminal): a lost transport on a pane move is unknown, not a refusal
runtimeEnvironments.call answers a dropped or overloaded transport with an error
envelope; those codes (the shared recoverable set) now ask the host instead of
undoing a move it may have committed.
* test(e2e): pane-move spec waits on real signals, not delays
The source tab is created on the server and split once its shell is listed, so
the split no longer races a desktop-seeded tab's first spawn (the CI failure).
The stay check follows output from the moved pane; the refusal check waits for
the drawn tab to appear and go.
* test(e2e): read the refused move's tab counts without Reflect (anti-slop)
* fix(terminal): a moved pane's tab joins the order the user had, not a rebuilt one
The host inserts the new tab into the group order captured before the commit,
appending without a drop position, so editors and pages keep their places. The
shared group-move fallback is no longer used here.
* fix(terminal): a moved pane's groups hold the host's current tabs, in the user's order
Placement takes membership from the rebuild after the commit and only the order
from before it, so a tab opened while the commit was pending stays (after the
user's tabs) and no stale id is written.
* fix(terminal): a moved pane's placement keeps a reorder made while it committed
The order is read after the commit, from the host's current tabs, not before it.
* fix(terminal): a moved pane keeps the lone group the user merged into
A rebuild names a single group with the default id; placement keeps the user's
id for it, so the drop position still applies after a split was merged back.
* fix: import error classification from its new shared/remote-runtime path
* fix(terminal): a pane or divider drag survives focus moving inside the window
The drag's window blur listener was registered in the capture phase, so it also
heard every element blur. A terminal attach or host snapshot that moved focus
between panes mid-gesture ended the drag; in the paired pane-move e2e on CI it
cancelled the drag before the drop. Only the window losing focus ends it now.
* test: drive the focus-change drag test through a real pane manager, without casts
* fix(terminal): a headless host moves a pane with its live terminal and never empties its source
The host names the PTY it holds for the pane, so a session copy an SSH respawn
left with an old id no longer refuses the move as a mismatch. A move that would
leave its source tab with no pane (a sibling closed meanwhile) is refused, since
no headless cleanup retires the emptied tab and it would publish as a phantom.
The e2e fences its stays-moved check on a later server rename instead of
sampling on a timer.
---------
Co-authored-by: Ben Grynhaus <ben.grynhaus@wiz.io>
Grok's installer defaults to ~/.grok/bin and only symlinks into
~/.local/bin when that dir is already on PATH. A GUI-launched Orca never
looked in ~/.grok/bin, so the Agents page reported Grok as not installed.
Append ~/.grok/bin after ~/.vite-plus/bin in the three lists that must
stay in step: the packaged PATH seed, the system install-dir lookup
fallback, and the POSIX/WSL guest fallback prelude.
Fixes#17590
Co-authored-by: brynnclaw <261708852+brynnclaw@users.noreply.github.com>
* Fix Codex goal Stop and selected model settings
* Preserve settings evidence and captured Stop outcomes
* Preserve refused goal pauses and bound Stop retry diagnostics
* Use checked settings state in goal contract tests
* Narrow captured Stop child and type its timing fixture
* Coordinate Stop timing fixture with deferred phases
* Fix typed mobile warning icon assertion
* Align Stop tests with reconciled controls and owned shutdown
* Check integration attach parameters without a type cast
* Match restart offer close calls to optional observer contract
* fix(orchestration): refuse run-use by a worker on the Run it works for
A worker holding an active Dispatch in a Run could bind that Run as its coordinator.
That silently unbound the real coordinator, remembered the worker's address as a
coordinator address (so the worker's own mail in that Run routed to the Run mailbox
from then on), and when the worker later bound its own sub-Run, left the parent Run
with no coordinator at all.
bindRun now refuses a caller whose handle holds an active Dispatch in the Run, with
worker_cannot_coordinate_own_run, before any effect. Creating its own Run for
sub-workers is unchanged.
* fix(orchestration): tell a worker naming its own Run where its mail is
Workers that made a sub-Run ran `check --run <parent>`, were told "This
coordinator terminal is bound to X, not Y", and ran run-use on the parent
to "switch back", unbinding their coordinator. That refusal now says the
Run belongs to their coordinator, that their own mail arrives through
plain `check`, and that they report with `send`; it never mentions run-use.
The run-use refusal now matches the caller the way check and send do
(address, then pane, so a reminted terminal handle is still caught),
also covers Runs above the caller's own through recorded creator_dispatch_id
lineage, skips context-only self-dispatches so a coordinator can still
rebind its own Run, and passes its code through the RPC wire.
* feat(orchestration): refuse run-use over a live coordinator without --takeover
run-use silently took a Run from whoever coordinated it, even a chat or
agent that was still running. It now refuses with run_coordinator_live
when there is positive evidence the current coordinator is running (its
chat is still reachable, or the agent hooks prove its terminal agent
live) unless the caller passes --takeover. Anything unproven (closed chat,
exited or unhooked terminal, another host) rebinds as before, so restart
handoffs need no flag. The probe is async in the RPC handler; bindRun
enforces it only if the Run is still bound to the probed party.
A caller working a Dispatch can never take over, with or without the
flag (worker_cannot_take_over_run). The help text now says run-use is
for resuming a Run and that workers never use it.
* fix(orchestration): stop pointing workers at run-use
Workers learned run-use from error text: "No Run is bound. Use run-create
or run-use first", "Rebind with orchestration run-use", and "Rebind this
terminal with orchestration run-use". Each now says what the caller can
do instead. An unbound worker is told its mail arrives through plain
check and it reports with send; a displaced coordinator is told to stop
and take the Run back only if the user asks. The orchestration skill now
says run-use resumes your own Run, --takeover needs the user's word, and
workers never run it.
* fix(orchestration): stop routing mail to addresses that grabbed a Run's seat
Every address that ever bound a Run is remembered as one of its
coordinator addresses, so mail to it routes to the Run mailbox. When a
worker bound the Run it was working for, its own address joined that
list for good, and its direct mail in that Run kept landing in the
coordinator's inbox. The live database has 45 such rows since
2026-08-20.
All four readers of the remembered addresses now skip one that was first
bound while it worked an unsettled, delegated Dispatch in the same Run.
It is derived from existing rows, so no migration or delete is needed and
rows written by older builds stop mattering. The active-Dispatch check
also matches a worker's orca_session_id spelling, not only its handle.
* fix(cli): keep the orchestration spec file within its line limit
* fix(orchestration): read remembered coordinator Run ids without a type assertion
* fix(orchestration): compare seat-grab times at whole seconds
first_bound_at has no milliseconds, so a worker bound in the same second its
Dispatch settled was counted as grabbing the seat. Compare completed_at at
whole seconds with a strict >.
* fix(orchestration): a worker coordinates only Runs it created
The worker refusals depended on proving the target's coordinator alive, so
a worker could still take any Run whose coordinator was closed, unhooked,
remote or headless. Replace them with one rule derived from existing rows:
a caller working a Dispatch may bind only a Run it legitimately
coordinated before (a remembered, non-seat-grab address of that Run, or a
Run it dispatched work into), never the Run it works for, and never with
--takeover. A same-binding no-op is never refused, and workers are
classified before the legacy gate so they never see its
--takeover-legacy hint. The refusal names the Dispatch and how to end it
with worker-abandon if it is stale. The ancestor walk is gone.
run-use now records which coordinator it probed. If the binding moved
before the transaction, it probes again (three tries) and then refuses
with a retryable run_binding_changed instead of binding over a party
nobody probed. run_coordinator_live names the chat by its title and
worktree, or the terminal by its title and worktree, says it is still
open, and that closing it clears the refusal.
* fix(orchestration): say what happens to mail on takeover and stop naming run-use to readers
The help now says --takeover replaces the coordinator and replays its
unacknowledged mail to you. A replaced mailbox reader is told only that
another session coordinates the Run and to stop reading it.
* refactor(orchestration): throw one non-null refusal from the live gate
* refactor(runtime): host the coordinator presence probe beside the hook presence seam
Main moved the orphan-adoption runtime file; keeping the one-line probe next
to checkHookAgentPresenceFn leaves this PR off that file.
* fix(orchestration): use findLast for the worktree name
* refactor(runtime): move the coordinator presence probe beside the pty presence recheck
Main is moving the runtime construction files; the probe now sits with
recheckHookAgentPresenceForPty, which reads the same seam, so this PR stays
off the files being moved.
* fix(orchestration): keep workers out of their own Run seat
* fix(orchestration): clarify worker Run handoffs in skill
* test: align explicit Run gate refusal wording
* Move coordinator presence beside terminal presence checks
* Walk complete worker creator lineage when binding Runs
* Restore main documentation after merge hook formatting
* perf(orchestration): check run bindings without terminal snapshots
* test(orchestration): narrow SQLite instrumentation before access
* test(orchestration): import the explicit RPC methods module
* test: cover orchestration restart and continued Run handoff
* test: place orchestration CLI integration in CLI project
* test(cli): use explicit module extensions in restart handoff test
* test(orchestration): isolate source CLI integration in subprocesses
* refactor(orchestration): leave the live-coordinator takeover rule to #29292#29008 is the worker fix: a worker cannot bind the Run it works for or an
ancestor, cannot abandon itself to get around that, and addresses it held while
grabbing a seat stop redirecting its mail. The separate rule that refuses any
second coordinator while the first is still open (the presence probe, the
--takeover flag and the run_coordinator_live / run_binding_changed refusals)
moves to #29292, which models Run ownership and hand-over and already builds on
it. run-use binds synchronously again, as on main.
* test(orchestration): cover delegated worker legacy takeover refusal
* test(orchestration): explain source CLI integration timeout
* Chat renames are saved as the chat's name, so Session History shows them after the tab closes
Renaming a native chat tab only changed that window's tab. Session History, phones and other windows read the host's saved name, so the rename vanished from History once the tab closed or Orca restarted.
A rename now goes to the host that owns the chat (agentSession.rename) and is saved as the chat's one name, the field History, tabs, sidebars and phones already read. The record remembers that the user chose it, so automatic naming never replaces it. A window's pre-upgrade tab rename is uploaded once and only fills a name Orca chose. Hosts without the new capability keep the old tab-only rename. Every native agent's History row now shows the chat's name once it has one.
* Keep saved chat names consistent across readers and reconnects
* Carry qualified chat names into cached readers
* Reuse the history recorder context across platforms
* Use recorded chat ownership for legacy name transfer
* Transfer legacy chat names only from known catalog ownership
* Correct recorder path matching and History subscription order
* Require complete evidence for bridged parity exclusions
* Preserve sparse array lengths in complete recording comparisons
* Initialize History observation before loading workspace context
* Integrate measured History recordings and scoped parity evidence
* fix chat naming retries and certify both replay modes
* fix ambiguous chat rename labels after navigation
* preserve chat rename intent across tab updates
* type dashboard snapshot fixtures without assertions
* fix: align chat name observation with store contracts
* test: update chat name consumer fixtures
* test: distinguish sender naming metadata from status
* Preserve saved Activity chat names across tab focus
* Clarify Activity tab projection identity
* Remove package-manager lockfile churn from main merge
* Refresh automatic chat names after clearing context
* fix(runtime): validate tab property edits before saving chat names
* Preserve naming eligibility and refresh cleared chat titles
* Fix checked naming projection types and logging metadata
* Supply context floor in status-feed journal fixtures
* fix: refresh History ownership after conversation clear
* fix: commit History request identity before refresh effects
* Fix History scan scope typing and integration test placement
* test: cover saved chat name reader contracts
* fix: bound chat naming work through generation settlement
* Remove duplicate History name-transition refresh
* Restore cache option in tab History test
* Consolidate naming test imports
* test: align naming fixtures with checked contracts
* test: name the discovery child fixture contract
* test: initialize writable child fixture streams
* test: account for ownership head and key reads
A worktree id or project host setup id repeats across hosts. Lookups that
named no host picked the first match, so a delete or setup mutation could
land on the other host's copy. They now go through one sole-copy rule and
refuse when the matches span more than one host; callers that know the
host (settings Remove, ephemeral VM rollback) now name it.
Co-authored-by: fettpl <38704082+fettpl@users.noreply.github.com>
* Unify terminal starts under one host operation
* Pin winning geometry across a late terminal join
* Use terminal creation options type in launch race test
* Check closed panes after acquiring the terminal spawn lease
* Preserve terminal start resource and join boundaries
* Keep terminal attach regression within project boundaries
* Capture terminal spawn authority and preserve image coverage
* Use checked fixtures for terminal image replay regression
* test: expose persisted pane to main layout in attach race
* test: contract terminal spawn scaling and retention
* test: correct spawn scaling fixture types and CI selection
* test(terminal): cover exited prompt claims and adopted team cleanup
* test(pty): consolidate mock registry imports
* fix(pty): retain untyped IPC resume inputs in common contract
* test: remove low-value inventories, passthroughs and redundant cases
* style: format test suites after pruning redundant cases
* test: remove obsolete workflow and flaky-test references
* test: prune deleted builder test from Windows shim allowlist
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.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
* fix(codex): resume a thread with the permissions it was last given, not Orca's launch flag
Codex restores a resumed thread's saved approval/sandbox settings unless the
launch passes a permission flag. Orca replayed its launch-time recipe
(--dangerously-bypass-approvals-and-sandbox, --approve-for-me, or the Ask
flags) on every resume, so a session switched to "Approve for me" came back
as Full Access after a restart, and that override was saved to the thread.
Fixes#10886
Co-authored-by: mys <4546173+wowpotato@users.noreply.github.com>
* test(mobile): Codex AI Vault resume no longer replays Orca's permission flag
The mobile AI Vault resume builds its command through the shared resume plan,
so it now omits --dangerously-bypass-approvals-and-sandbox like desktop does.
Co-authored-by: mys <4546173+wowpotato@users.noreply.github.com>
---------
Co-authored-by: mys <4546173+wowpotato@users.noreply.github.com>
Adds cloud/packages/connector-contract: the shapes the desktop and the cloud connector service exchange (hello, held poll, received/done acks, events with per-hello seq, replies), limits, error codes and the contract version. Nothing calls it yet.
* fix(agent-resume): resume Claude sessions at their own effort level
* fix(agent-resume): keep effort through persistence and /clear; simplify ensure shaping
* fix(agent-status): keep Codex's hidden title-thread prompt off the sidebar heading
Codex titles a new thread in a hidden ephemeral thread. When managed config pins hooks on,
that thread's SessionStart (source startup) took the pane and its prompt became the row heading.
Drop its prompt as a foreign ephemeral thread and keep the row's prompt across rollout-less starts.
Fixes#30049
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(agent-status): hold a rollout-less Codex start until its prompt shows what it is
The hidden title thread's SessionStart no longer swaps the row's state, session or tool snapshot.
A held start is dropped with the title prompt, or replayed as SessionStart on a new conversation's
first prompt so relayed desktops still let it take the pane. Covers a main Stop arriving first.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(agent-status): also hold Codex's hidden recap thread and clear the old completion stamp on takeover
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix(remote): a desktop ignores its own workspaces echoed back by a server it serves (#33424)
When two desktops pair to each other, each server republishes the tabs it
mirrors from its own servers. Desktop A then received B's mirror of A's own
workspace under the real workspace id and grafted it onto its local copy as
B-hosted tabs, so opening those files asked B (selector_not_found) and closing
them closed B's tabs.
A session-tabs frame from a paired server no longer writes a workspace this
app lists as its own (a stamped local or direct SSH row) unless that server
also lists a copy. Other servers' workspaces stay open to every runtime.
* fix(remote): retire echoes already taken and replay frames a twin's late row releases
An echo fenced out of this app's own workspace now applies as that server's
tombstone, so rows it left before the fence (restored sessions, or frames that
arrived before this app's own row listed) are retired without closing anything.
A held frame replays once the server's own copy of the workspace lists, so a
same-id twin that publishes before its row loads still mirrors. The e2e waits
on a later frame from the same stream instead of a fixed delay.
* fix(remote): serve a desktop's tabs on its servers' files only to its phone, not to other runtimes
Redesign of the #33424 fix at the source. A desktop publishes every editor tab in
its window, including ones on files of its paired servers; its phone uses those
through the desktop's relay. Served to another runtime, such a tab routes the file
to the desktop, and in a pairing cycle it is that runtime's own workspace coming
back as the desktop's tab: file reads fail with selector_not_found and closing it
closes the desktop's real tab.
Publishers now stamp those rows with hostRuntimeEnvironmentId (window, layout-owner
main projection and the windowless projection alike). projectSessionTabsForClient
strips the stamp for every caller and drops the stamped rows for runtime-scope
callers, keeping groups, layout and the active tab consistent. Phones and local
callers see what they saw before. The receiver-side fence is removed.
* refactor(remote): keep the file-host stamp helper outside the old-client boundary; test an all-server snapshot
History lists every managed OpenCode account's sessions since #33658, but
resume still launched with the active account's XDG_DATA_HOME, so OpenCode
looked in the wrong database and reported "Session not found".
Derive the owning account from the row's store path and request it via a
spawn env key; the host validates it against registered accounts and falls
back to the active one when the owner was removed.
Refs #28790, #33658
* fix(program-status): keep a Pi row working while a pi-subagents child outlives the parent's done
OSC 7501 projected the root record's done straight onto the row even when
live child records remained, so a Pi parent showed Done/IDLE while an async
pi-subagents worker kept running. Fold child liveness with the existing
foldMainAgentWithRowChildWork; mainAgent keeps the lead's own verdict so the
row settles to done once the last child ends.
Fixes#33657
* fix(program-status): announce a held turn's done on time and keep hook verdicts authoritative
- Stamp turnCompletedAt on the child-held row and its all-clear so the lead's
done is announced once, when it happens, as hook rows do.
- Hold only a finished (non-boundary) turn, only for working/waiting children;
in-turn rows and failed/idle children keep the root's state.
- A settled hook row is never reopened by a program's child-held working row,
so the 2-minute silence fallback cannot flip done -> working -> done.
* fix(program-status): lapse an unrefreshed child hold to the lead's done
Children report only changes, so a pi-subagents that dies or hangs inside a
live Pi would hold the row working until decay. The lease timer now settles a
child-held row no report refreshed within AGENT_STATUS_STALE_AFTER_MS minus one
renewal interval (20-30 min) to the lead's done, keeping turnCompletedAt so it
is not announced again.
Setup on Windows Server 2012 R2 and on Windows hosts with neither
System32\tar.exe nor Expand-Archive ended in the generic "Orca couldn't
start its server on this host. Try connecting again.", which no retry
fixes. The runtime probe that already runs before staging now also reads
the Windows build and checks for an extractor, read-only, and refuses
before anything is staged or uploaded. The refusal travels the existing
orcad_unavailable reason plumbing to the host card and the ssh:connect
error, and is rechecked on every connect like a noexec home.
Fixes#33574
Reported-by: HKLHaoBin