Commit Graph
11940 Commits
Author SHA1 Message Date
Neil e590699e7e refactor: round-3 pass-1 follow-ups (#30699)
- one repoRowOwnerStamp for the SSH and runtime session placeholder builders
- lineage refresh resolves the transport host once; pin that a server reply never appends a copy
- markdown-document-paths tests live next to the shared module
2026-10-19 03:27:09 -07:00
Neil aa739f3395 refactor(renderer): one FileTypeIcon component for file icons (#30695)
Pure refactor. Twelve rows, trees and menus called getFileTypeIcon(path)
and then rendered or createElement-ed the result themselves; they now
render <FileTypeIcon filePath />, so a change to where file icons come from
lands in one component. The tab and chat pill keep getFileTypeIcon because
they hand an icon component to another component.
2026-10-19 03:21:46 -07:00
Neil 224d126133 refactor(composer): split run-target rows and worktree-creation steps into named pieces (#30703)
* refactor(composer): build run-target rows from per-section builders

* refactor(composer): move run-target row actions into their own module

* refactor(composer): render run-target rows in RunTargetRowList

* refactor(worktree-create): dispatch remote-target preparation and share the provisioning log

* refactor(worktree-create): extract the pending create's tab strip

* refactor(sidebar): split the ephemeral VM resume out of worktree activation

* refactor(composer): give the run-target submenu rows an explicit row type
2026-10-19 03:13:56 -07:00
Neil 4f425a2861 fix(cli): refuse server names --environment cannot tell apart, and say how to pick one of twin projects (#30676)
* fix(cli): refuse a paired server name --environment cannot tell apart, and say how to pick one of twin projects

* fix(cli): point an ambiguous --worktree at its host-qualified identity

* fix(cli): keep the ambiguity listing on the paired server the command chose

* fix(cli): compare saved server names the way --environment does, and route cross-host twin projects to --project --host
2026-10-19 03:12:30 -07:00
Neil 90236c3bde refactor(browser): one page gate and one owner for a host command's hold (#30652)
The revocation gate, the input cancel list and the hold's lifecycle each lived in two places.
Sinks now call one gate, held input is recorded at the CDP send (and the one native key path),
and runWithRemoteCliPageAdmission owns retire-on-revoke, release and error conversion.
2026-10-19 03:11:54 -07:00
NeilandClaude 9f37fcdf70 fix(persistence): a commit that runs out of disk no longer stops all later saves (#30694)
* fix(persistence): a commit that runs out of disk no longer retires the profile-state writer

A WAL COMMIT on a full disk fails with SQLITE_FULL after SQLite rolls the transaction back. It was
reported as indeterminate, which retires the writer, so every later write failed until a restart:
on a managed SSH host, a disk that filled once left orcad unable to open a terminal even after the
space was freed. SQLITE_FULL fails a frame write before the commit point, so it is now a known
failure; every other failed commit stays indeterminate.

Co-Authored-By: Claude <noreply@anthropic.com>

* docs(persistence): say why SQLITE_FULL on COMMIT is safe to treat as not committed

Opus review: the claim holds because powersafe overwrite (on by default) means no padding frames
follow the WAL commit frame.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-19 02:56:37 -07:00
OrcaWinandm4air 83c768d1b4 fix(crash-reporting): Windows native crash reports say what the fault touched and which code ran (#30691)
* fix(crash-reporting): record what a native access violation touched and the bytes that ran

Windows renderer AV reports carried only the faulting module offset. Two
recent ones (Orca.exe+0x23d3f88, +0x21674c2) map in official Electron
symbols to stores into memory the preceding instructions had just written,
which an intact binary on a healthy CPU cannot fault on. Without the access
kind, fault address and executed bytes, an engine bug, a corrupted code page
and unstable hardware look identical.

Read them from the Crashpad dump: AV access kind and target, the target's
distance from the faulting thread's stack pointer (x64), and 16 bytes at
the instruction pointer from the captured memory list.

* fix(crash-reporting): capture code bytes only inside a loaded module

They exist to diff against the shipped binary; bytes from JIT or other
anonymous memory add nothing and could carry page contents.

* fix(crash-reporting): capture code bytes only from Windows dumps

On macOS and Linux Crashpad records the touched data address as the exception
address, so bytes read there would be module data, not the faulting code.

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-19 02:49:44 -07:00
OrcaWinandm4air 00d1545a65 fix(diagnostics): crash bundles always keep the terminal daemon's newest log lines (#30690)
* fix(diagnostics): reserve a share of the crash bundle for the daemon log

Trace spans (git.exec, renderer_memory) fill the 4 MiB bundle cap on busy
hosts, and the daemon log was read only after them, so 11 of 13 recent
Windows renderer-OOM bundles carried no daemon lines at all: no session
counts, no daemon deaths, no process-growth level.

Collect the daemon log first with a 1 MiB budget; whatever it leaves
unused still goes to trace spans.

* fix(diagnostics): return unused bundle space to the daemon log

The 1 MiB share is now a floor, not a cap: after spans are collected the daemon
log takes whatever they left, so a chatty daemon beside quiet traces keeps up to
the whole bundle as before.

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-19 02:47:39 -07:00
OrcaWinandm4air da6d823ea0 fix(crash-reporting): renderer crash reports say whether the window was hung or blank (#30692)
* crash-reporting: say whether the main window was hung or blank when its renderer died

Windows killed/exit-1 renderer reports read the same whether Task Manager, an
EDR, or Orca ended the process. Field bundles show several of them follow
minutes of renderer silence or a document that never loaded after an update,
which points at users ending a frozen Orca. Record Electron's
unresponsive/responsive transitions as durable breadcrumbs and attach
rendererUnresponsiveForMs, rendererDocumentLoaded, rendererDocumentAgeMs and the
window's visible/minimized/hidden + focus state to the renderer crash details.

* fix(crash-reporting): a failed load reads as a blank window, and a crash ends the hang

Electron fires did-finish-load after did-fail-load, and a failed load never
commits, so track from did-start-navigation and flag main-frame load failures.
Clear the hang at renderer death so the replacement renderer's responsive is not
logged as the dead one recovering.

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-19 02:45:42 -07:00
NeilandClaude Opus 5.5 912cb06398 fix(managed-server): repair main typecheck after the byte-budget and snapshot-room PRs crossed (#30680)
* fix(managed-server): import newestStateMtimeCommand from its new module

#30635 and #30618 merged in parallel: #30618 moved newestStateMtimeCommand
to orcad-state-newest-mtime.ts while #30635's byte-budget test imported it from
orcad-state-snapshot.ts, breaking typecheck on main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(managed-server): measure the snapshot-space probe and record #30618's capture growth

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 02:29:45 -07:00
Jinwoo Hong 0e1d0ad4a8 A CLI split that times out leaves no pane (dark, layout-owner build only) (#30563)
* fix(layout): a pane main closed stays closed when its terminal starts late (dark, layout-owner build only)

* fix(layout): main takes back a CLI split pane whose terminal does not start within 10 s (dark, layout-owner build only)

* fix(layout): the timeout take-back keeps a pane whose terminal started or is starting (dark, layout-owner build only)

* fix(layout): one take-back call keeps the layout-writer count

* fix(layout): a split take-back is decided in the write queue, and a spawn for a taken-back pane starts nothing (dark, layout-owner build only)

* test(layout): a spawn for a taken-back pane is refused before the provider spawns

* test: typed store fake in the closed-pane spawn test, no casts
2026-10-19 05:21:06 -04:00
Jinwoo Hong 9294b0ae9c fix(sidebar): the window raises the unread flag exactly when its host does not (dark switch) (#30638)
* fix(sidebar): the window raises the unread flag exactly when its host does not

With ORCA_SIDEBAR_INPUTS=main the host raises the workspace unread flag. A host
now advertises workspace-unread.host-raises.v1 in its status; a switch-on window
raises the flag itself only for workspaces whose paired server lacks it, waiting
for that server's status first. The desktop advertises the same capability to the
hosts it pairs with, and a host leaves raising to an older desktop (one that hosts
client pages without the capability) while it is connected.

* fix(sidebar): every desktop socket advertises the unread capability; window answers from cached status

The browser host lease and network tunnel build their capabilities on the shared
remote base, not the Electron list, so a current desktop's lease looked like an
older desktop and silenced the host. The capability now lives in the shared base.
The window reads the cached status first and probes only before the first one.

* test(sidebar): pin the cached-status path and both older desktop socket shapes

* test(e2e): narrow the server row's host before Mark as read
2026-10-19 05:14:00 -04:00
Brennan Benson 77195a1266 fix(session-history): cached results keep every session the scan found for a workspace (#30586)
A repeat open or tab revisit of Session History's Workspace and Project
tabs dropped to 250 sessions, losing older matching sessions (341 -> 250
in QA). Every cache reuse re-cut the scan's result with a shared newest-N
counter for in-scope rows, which the scan's own per-agent budgets don't
match. The cut now bounds only out-of-scope rows, so a scan's result
passes through unchanged, and scoped requests reuse only a scan of their
own depth so lowering the depth rescans.

Tracked in STA-11167.
2026-10-19 02:03:13 -07:00
NeilandClaude d600658b84 fix(ssh): closing a bridged relay tab stops its shell, even across a restart (#30587)
* fix(ssh): stop a closed bridged relay tab's shell from every close route, across restarts

The renderer's tab-close kill tombstoned a bridged lease before the bridge's
own close ran, so the old relay's shell kept running. Route it through the
bridge like the runtime kill. Persist a pending bridged stop on the lease's
pendingKill so a restart before reconnecting still sends it, and list such
leases for the stop without serving them as panes.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(ssh): end a closed bridged tab's lease when its stop can't be proven

Clearing the stop left the lease bridgeable, so a process that took the
recycled relay id was served under the closed tab. Retire it as any closed
tab's lease is.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(ssh): keep a closed bridged tab closed across restarts even when its stop can't be fenced

An unfenced legacy stop now gets one try and ends the lease, a repeated close
keeps the first record's age, and the Docker bridge spec covers a close while
disconnected followed by a restart.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(ssh): re-list after abandoning a bridged stop so the closed pane drops

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(ssh): route CLI and worktree stops of a bridged tab through the bridge, and keep a repeated close fenced to the first identity

* fix(ssh): refuse sleeping a bridged pane, settle bridged stop-and-wait on the relay's exit proof, and never attach a pane closed mid-attach

* fix(ssh): settle a bridged stop-and-wait on the exit proof alone, for every overlapping waiter

* fix(ssh): keep an unfenced bridged stop's pane tracked for its trailing exit report, never serving it

* fix(ssh): keep awaiting a bridged stop's exit after a failed reply, never name a retired pane to the relay CLI, and require a real empty listing in e2e

* fix(ssh): tell the runtime a bridged terminal exited once its stop-and-wait is proven

* fix(ssh): keep an unfenced bridged stop's exit observable after its reply fails

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-19 01:59:23 -07:00
NeilandClaude 79b7d702be test(managed-server): 6 KB wire-size budget for every POSIX host-op command (#30635)
* test(managed-server): byte budget for every POSIX host-op command

Builds each exported POSIX host-op builder at realistic maximal arguments and
asserts the raw command stays under 6 KB, under the 8,000-byte compression
threshold. Ops already over budget are listed with ceilings that may only
shrink until they move onto the host script (round 4 R4-10).

Co-Authored-By: Claude <noreply@anthropic.com>

* test(managed-server): budget the wrapped, fence-guarded command on Linux and macOS

Measures what a gateway sees on the plain fallback (fence guard + uncompressed
POSIX wrapper), takes the larger of Linux and macOS, launches the SSH way (nohup),
and also discovers the top-level src/main/ssh builders. Exports three builders
that are sent as their own exec request so they can be measured.

Co-Authored-By: Claude <noreply@anthropic.com>

* test(managed-server): count the stdout fence; scan ssh transport subfolders

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-19 01:55:29 -07:00
Neil f6e9e649f4 fix(projects): a project added from the CLI, web or a paired desktop through a symlink or subfolder shows its workspaces (#30657)
* fix(projects): a project added from a paired client or the CLI through a symlink or subfolder shows its workspaces

* fix(git): keep a literal backslash in a POSIX checkout root
2026-10-19 01:54:59 -07:00
Neil 94c858cda6 fix(ssh): a cancelled or revoked host's browser command stops on the desktop (#30630)
* fix(ssh): a cancelled or revoked host's browser command stops on the desktop

orcad now tells a cancel-capable desktop when the CLI gave up on a forwarded browser command, and a running host command on the desktop checks revocation before every side-effecting step (each helper CDP message and each direct input dispatch).

* fix(browser): keep local commands out of a host's scope, gate each helper on its proxy

* fix(browser): hold a host-driven page at its input sinks instead of per call site

* test(browser): checked mock types in the host revocation suites

* fix(browser): refuse only a host's own traffic, never the desktop's page commands

* fix(browser): a host's proxy never keeps its scope, and a revoked click still lets go of the button

* fix(browser): a revoked command may release only what it pressed, on every input path

* fix(browser): refuse every revoked step, and let the desktop release what a revoked command left held

* fix(browser): retire a revoked command's helper daemon before letting go of the page; release held input with a lease, synced pointer state and the focus guard

* fix(browser): deferred proxy input keeps its hold, releases count once they land, and a revoked helper is retired at once with its routes kept

* test(browser): keep the mock page cast on its SAFETY line
2026-10-19 01:54:54 -07:00
Neil a85c17650f refactor(process-host): let runProcess callers observe stderr as it arrives (#30628)
An optional onStderr hook receives every raw chunk, so long commands can
stream progress while still getting the bounded result. Capture,
truncation and termination are unchanged; existing callers pass no hook.
2026-10-19 01:54:26 -07:00
Neil fa0bd69099 refactor(ssh): derive a host-key policy from the target owner (#30627)
* refactor(ssh): derive a host-key policy from the target owner

The verifier and decision took an isEphemeralRuntimeTarget boolean.
They now take a HostKeyPolicy derived from the owner by one function,
and the owner shape gets a named SshTargetOwner type. No behavior
change: on-demand runtime VMs map to the ephemeral policy, everything
else to standard, and every existing decision test passes unchanged in
substance.

* docs(ssh): separate owned-target hiding from the recipe-VM id prefix

Hidden-ness is decided by owner in main; the runtime-ssh- prefix also
carries per-provision semantics (fresh filesystem, VM cleanup), so only
on-demand-runtime owners may use it. Comments only.

* refactor(ssh): one id predicate for every Orca-owned SSH target

Surfaces that hide owned targets or must never dial them from the
renderer (startup re-dial, reconnect hints, automation host catalog,
status bar, host registry, state broadcast) now ask isOwnedSshTargetId,
which also covers the machine- prefix. isRuntimeOwnedSshTargetId keeps
its narrower recipe-VM meaning (address and filesystem replaced every
provision), so name retirement and VM cleanup are unchanged. No target
uses machine- on main yet, so behavior is identical; tests pin both.
2026-10-19 01:54:22 -07:00
Neil 7ce58ec47e refactor(terminal): one PTY attribution for teardown, close and identity; delete the deprecated owner resolver (#30626)
* refactor(terminal): one PTY attribution for teardown, close and identity; delete the deprecated resolver pair

- strict ssh: route: a proven direct listing beats a same-id server proxy; other contradictions refuse
- terminal spawn's git branch takes the strict route when a host is named (rivals refuse)
- resolveTerminalPtyOwner + isUnattributedTwinWorktree replace resolveRuntimeOwnerForWorktree in
  shutdown, pane and agent hibernation, and PTY identity; close logs unroutable runtime ids
- delete getRuntimeEnvironmentIdForWorktree/resolveRuntimeOwnerForWorktree (legacy branch moved
  into the adapter), the alias golden test, the importer ratchet, its baseline and pr.yml step
- hasHostEvidence reads the owner indexes instead of scanning repos and groups

* fix(terminal): close and identity read spawn's attribution for unscoped remote ids (Opus P2)

- an unattributed twin's unscoped remote: id is skipped and logged on close, never closed locally
- setup terminals: identity and close follow spawn's runtime; raw-id teardown stays local
- drop the last stale getRuntimeEnvironmentIdForWorktree mock (tests/e2e unit suite)

* refactor(terminal): one ownership read in resolveTerminalPtyOwner

* test(terminal): pin the close-path twin skip and the two-proxy direct listing (Opus P3)

* test(terminal): keep the close-provider owner fixture's full union type (Codex P1)
2026-10-19 01:50:57 -07:00
NeilandClaude a451f472c3 fix(ssh): make room for the pre-activation snapshot on remote hosts, and say why a capture failed (#30618)
* fix(ssh): make room for the pre-activation snapshot, and say why a capture failed

Every update left its own full state copy behind, and a failed capture its partial archive, so a
small or quota-limited home filled until no update could snapshot. The capture now prunes every
pre-* snapshot except the one the activation record names, removes its own partial archive on
failure, and prints tar's error. Before stopping the running server, the activation checks that
the snapshot fits, so a host without room keeps its server.

Refs #30491

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(ssh): the room refusal says what is free apart from what a prune reclaims

It also stops claiming the recorded server is running, and a test pins that the record's
snapshot survives both the room check and the prune.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(ssh): measure snapshot room before the last terminal recount, within a deadline

A slow scan after the recount could let a terminal opened meanwhile be stopped. The scan now runs
first, gives up after 12 s per du (UNMEASURED reads as unknown), and a lost probe channel leaves
the room unknown rather than failing the update. The newest-mtime probe moves to its own module
to keep the snapshot module under its line limit.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(ssh): pick timeout's syntax for the room probe, and pin its failure paths

BusyBox before 1.30 takes `timeout -t SECS`; plain `timeout 12` disabled the probe there.
Tests cover a timeout on the old-snapshot scan and a lost fence from the probe.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-19 01:35:18 -07:00
Neil 699b9a0637 feat(files): guarded file writes that refuse to overwrite a changed file (#30605)
* feat(files): guarded file writes that refuse to overwrite a changed file

Adds an optional disk precondition to file writes: the writer compares the file
on disk with an expected signature inside the write, serialized per file (path
then inode), and refuses with a typed changed/missing result instead of writing.
Remote hosts serve it as files.writeIfUnchanged behind a capability; older
hosts get a client-side compare that fails closed, pinned to the probed runtime
and connection. Undo of a delete and the MCP starter config use it to write only
into a missing or identical file. Groundwork for guarding editor saves.

* fix(files): pin guarded writes on the sending socket; keep refused undo; narrow not-found and pinning

* fix(files): pin a guarded save to one pairing; keep only refused undo steps

* fix(files): ENOENT by code only; resolve symlink chains fully; pin lost-reply recovery to the save's pairing
2026-10-19 01:27:53 -07:00
Jinwoo Hong cf13e0cf9d fix(sidebar): phone feed list leftovers before the flip (dark) (#30631)
* fix(sidebar): phone feed list leftovers before the flip (dark)

Bump the feed's semantics version for P11's desktop facts; mark switch-on phone page bundles so packaging and orcad staging refuse them; show the page's last-listed rows before the first feed read; draw the host's pending creations; pass the listed host's own label into host sections instead of the running device's platform.

* fix(sidebar): review fixes for the phone feed leftovers (dark)

A dark orcad may stage the dark page (only a marker of an off switch is refused); the bundle tree writer leaves the marker; the pre-feed list keeps the view's filters; a creation names its project only outside that project's header and survives search when its project matches; the feed view and row labels read one resolved desktop platform.

* fix(sidebar): second review fixes for the phone feed leftovers (dark)

Test the off-switch marker check across switches; resolve the desktop platform once for rows and headers; a section search narrows keeps only matching rows.
2026-10-19 04:27:09 -04:00
da58ee0875 Session History: the Workspace tab stops re-reading every transcript to find its sessions (#30469)
* perf(session-history): remember each transcripts folder so Workspace scans stop re-reading files

* refactor(session-history): keep the scope cwd table in its own module

* test(session-history): type the hoisted read log without an assertion

* test(session-history): type the collected session folders

* chore(wsl-move): list the parse cache scope_cwds table

* Keep scoped session selection current with cache authority

* Consolidate scope cache imports in regression test

* Run scope cache SQLite regression tests under Node

---------

Co-authored-by: OrcaWin <alpha-eng@stably.ai>
Co-authored-by: Orca Worker <orca-worker@localhost>
2026-10-19 01:25:58 -07:00
Brennan Benson 2db7d5aac4 Refuse chat resume until terminal writers have exited (#29242)
* fix(native-chat): identify the owner when resume create is refused

* test(native-chat): consolidate transcript refusal cases

* test(native-chat): keep ownership coverage at the launch boundary

* test(native-chat): update adoption fixture for early ownership admission

* test(native-chat): align resume admission assertions with host ownership

* test(native-chat): keep RPC coverage focused on ownership proof

* test(native-chat): assert blank request presence before optional source

* fix(native-chat): admit the actual CLI source during chat creation

* fix(native-chat): satisfy source admission lint checks

* test(native-chat): provide the waiting hook prompt

* test(native-chat): isolate source admission fixtures from host homes

* test(native-chat): canonicalize adoption fixture directories

* test(native-chat): preserve early ownership admission in source stack

* Resume CLI conversations directly in native chat

* fix(native-chat): preserve resume refusal behavior and honest transcript feedback

* fix(native-chat): retain import read evidence in typed result

* Use the header test fixture record type

* Consolidate launch owner imports and correct feedback assertion

* Keep existing resume translation in runtime catalog

* fix(native-chat): preserve admitted source and prepared execution home

* fix(chat): retain source evidence for dispatched adoption

* fix(chat): keep source capture within module limits

* Keep deferred chat publication in the shared launch owner

* test(chat): allow host evidence in create routing

* Satisfy launch publication checks and scope slot cleanup

* Preserve canonical tab membership on projected reorders

* Use recorded launch ownership for surface recovery

* Reserve deferred chat order through existing group actions

* Pass the active layout owner into launch cancellation

* Stop terminal writer before adopting conversation in chat

* Guard chat adoption completion and satisfy static checks

* Switch completed terminal turns without confirmation and test handoff

* Recheck terminal writers on reserved adoption retry

* Split session entry delegates and fix linted test types

* Keep chat opening send below create module line limit

* Require explicit terminal stop consent before chat adoption

* Use direct process-table test mock

* Fix chat adoption types and runtime localization

* Localize terminal takeover refusal guidance

* Keep CLI stop admission scoped to active source panes

* Preserve ordinary history resume through attach

* fix(native-chat): admit an unsaved transcript under a linked account folder

A missing transcript stayed unresolved while its account root resolved through
symlinks, so containment failed and a not-yet-saved conversation showed a load
error instead of the unsaved notice.

* Update adoption replay test for at-rest creation

* refactor(native-chat): move the newest-item import window into its own module

Keeps journal-legacy-import.ts under the line limit after the source
identity observer landed on top of the streaming import.

* fix(native-chat): restore the refusal error import lost in the main merge

* Remove duplicate account resolver and condense terminal admission

* Refuse conversation adoption until terminal writers exit

* Keep conversation inspection within lint limits

* Preserve conversation owner in launch feedback

* Expect unavailable writer scan to refuse fresh adoption

* Keep terminal conversation evidence tied to its execution

* Correct writer continuity test refusal and event shapes

* Prove original terminal writers exited before chat adoption

* Scope saved conversation holds to their execution host

* Keep child-inspection fixture verdict literal

* Recheck awake saved conversation holds against their owned pane

* fix: inspect owned Windows jobs independently of foreground naming

* test: use braces in Windows inspection fixture

* test: complete ordinary Windows terminal inspection fixture

* test: explain partial terminal inspection fixture

* fix: inspect the terminal a conversation is switched out of

* test: separate OpenCode writer observation coverage

* fix: tie terminal conversation links to the PTY execution

The conversations a terminal reported now live on its PTY record for that
incarnation, so moving the pane or running many other terminals can no longer
drop them, and a link whose terminal can't be inspected refuses.

- Each link keeps the agent process its hooks last proved; a live or
  suspended process refuses even when the terminal's tree is shell-only
  (detached tmux, nohup).
- tmux-published events are never pane-owned, and an unmarked binding copy no
  longer revokes an earlier pane-owned proof.
- A restored row or saved checkpoint is censused against the pane's current
  local PTY instead of requiring this runtime's observed identity.
- The refusal now says Orca can't confirm the agent has exited and asks the
  user to stop what's running there or close the terminal.

* test: drop the stub-host admission calls from OpenCode observation coverage

* test: census suspended children through the real terminal host

* test: drop writer checks the real-terminal flow already covers

* fix: let prompt workers outside the terminal's session pass the exit check

- The POSIX shell-alone proof skips a session leader below the shell and
  everything under it: a prompt's zpty worker has left the terminal, while
  suspended, background, nohup and disowned jobs still refuse.
- Windows steady-state polls read the cached process table; every read that
  decides adoption or close stays fresh.
- The pane header and tab menu point back to Session History, since their
  action is gone once the agent exits.

* fix(terminal): count only Windows job members still linked to the shell

A pane's job keeps every process it ever started, including daemons whose
parent exited (an rc-started ssh-agent), so such panes never read as an idle
shell and the chat switch refused forever. Walk descendants of the shell along
live parent links within the job, for every shell; the Git Bash launcher keeps
its bash-chain allowance and a child older than its parent still refuses.

* Revert "fix(terminal): count only Windows job members still linked to the shell"

MSYS exec and fork leave intermediate Win32 parents exited, so a live Claude
started through a script (and 'sleep 60 &') became unreachable and the chat
switch admitted while it ran. Back to the job census until a console-based
design is measured.

* fix(terminal): set aside detached daemons on a Windows shell-alone decision

A pane's job keeps every process ever started in it, so an rc-started
ssh-agent made a Git Bash pane read as busy forever and the chat switch
refused. MSYS fork/exec leaves live work under exited Win32 parents, so parent
links alone can't tell the daemon apart; it is the one that also left the
console. On a scanChildProcesses decision read (switch admission, run-terminal
close, close guards), a member neither linked to the shell nor attached to its
console no longer counts. Polls and shell confirmation keep the job census.
The console-list helper now forks through the process-host chokepoint.

* refactor(daemon): name the process inspection options once

Keeps the daemon router and terminal host within their line budget now that
the options carry scanChildProcesses.

* fix(terminal): set aside detached daemons only for adoption, never for close

Closing a Windows pane kills its whole job, so a close check that ignored a
detached process (gitk &, cmd /c start ...) would kill it without asking.
Adoption asks whether anything is still in the terminal; close asks whether
closing destroys something. Only the adoption read now sends ignoreDetached;
close guards and the run-terminal sweep keep the job census. The win32 test
also stops the ssh-agent it starts.

* fix(native-chat): check the terminal being switched out of before opening its chat

When the conversation already had a native chat, the switch hit the
ownership conflict first and the header opened that chat, even while the
terminal's agent (a later claude --resume) was still running. The switched-out
terminal is now checked before the conflict, so a live one refuses as usual
and one at rest still opens the owning chat.

* test(terminal): assert close reads never call an ssh-agent pane idle

A plain read can answer unverifiable while the eval's short-lived processes
exit; either way it must not read as no-children.

* fix(native-chat): check terminal writers before naming an owning chat for any switch

Session History's Resume in New Native Chat carries no source pane, so with an
earlier chat still holding the conversation it skipped the terminal check and
the owning chat's first send would start a second writer beside a live
terminal Claude. Every adoption request now runs the writer check before the
owner conflict, as the no-owner path already does.

* test(reliability): run the chat-switch writer oracles in the provider-ownership gate
2026-10-19 01:06:36 -07:00
Jinwoo Hong 5f2bb930d1 fix(sidebar): Hide sleeping keeps a paired server's active workspaces (dark) (#30624)
* fix(sidebar): Hide sleeping reads a paired server's own terminal report (dark)

With ORCA_SIDEBAR_INPUTS=main, main's activity domain now lists a paired
server's rows: their terminals and agents as the server's worktree.ps
reports them, plus the browser and chat tabs this desktop's saved session
holds there. A server out of contact keeps live terminals unverifiable.
Switch-off builds are unchanged.

* fix(sidebar): keep server activity out of the published paired rows

Review: a strict field on paired rows could drop a whole row for an older
reader; the activity domain already carries it.
2026-10-19 03:49:36 -04:00
Jinwoo Hong 4aba68561d fix(browser): let a local HTML page follow its own file:// links (#30611)
The guest will-navigate guard cancelled every file: navigation, so clicking a
relative link on a file:// page did nothing. Allow file: -> file: when the
current page is itself a local file; web pages still cannot navigate or
redirect into the filesystem.
2026-10-19 03:46:57 -04:00
Neil f184c13bd7 Align root Node types with the supported Node 24 runtime (#30617)
* Align root Node types with the supported Node 24 runtime

* Preserve mobile rejection listeners with Node 24 types
2026-10-19 00:46:25 -07:00
Neil 5be871e537 refactor(plugins): name shared catalog and manifest schema parts (#30612)
* plugins: name the shared catalog and manifest schema parts

Pure refactor. Marketplace entry/header fields and the duplicate-category
and duplicate-id checks become named pieces, the bounded catalog JSON read
is split from schema parsing, and the v1 manifest object is named apart
from its refinement, so a second catalog file and manifest version can
reuse them.

* plugins: share the plugin tree-identity check

Pure refactor. Install integrity and instructional-content checks each
inlined the full-hash-or-legacy-prefix comparison; both now call
pluginTreeHashMatches. The tree limits are exported for reuse.
2026-10-19 00:34:42 -07:00
NeilandClaude 36ed236394 fix(persistence): repo writes take the row's identity, never a bare id (#30616)
* fix(persistence): repo writes take the row's identity, never a bare id

updateRepo(target, updates) now takes Pick<Repo,'id'|'connectionId'|'executionHostId'>
(RepoHostTarget) and resolves the host from it, so a caller that holds the row can no
longer drop its host and write a same-id sibling on another host. The id-only callers
(project host setup controller and IPC handler, folder-to-Git upgrade watch, both
clone paths, registration adoption, fork backfill) pass the row they resolved.

Co-Authored-By: Claude <noreply@anthropic.com>

* test: migrate the remaining Store.updateRepo callers to row targets

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-19 00:33:50 -07:00
Brennan Benson 7dcb7d4439 Sleep stops a workspace's native chat agents (#28445)
* fix(sidebar): Sleep stops a workspace's native chat agents

Sleeping a workspace stopped terminals and browsers but never its structured (native)
chats, so their claude/codex process kept running, and Sleep was disabled for a
workspace whose only content was a chat.

Sleep now stops each chat's agent through a new agentSession.sleep (the host's close,
keeping the tab), routed to the host recorded on the chat's tab; an older host without
the capability fails Sleep with a clear message. The Sleep menu counts a chat.

* Fix native chat Sleep ownership and delayed teardown

* Stop native chat sessions when sleeping their workspace

* Fix sleep teardown wake and host-qualified receipt races

* Verify mobile Sleep sends the selected desktop host

* Keep worktree Sleep RPC within method file limit

* Use sleep intent as attempt identity

* Gate workspace sleep on structured client support

* fix(native-chat): preserve sleep stop receipts and send timing

* fix(native-chat): keep superseded workspace sleeps quiet

* fix(native-chat): ignore superseded failed sleep stops

* Fix sleep lifetime test resolver types

* Accept workspace Sleep for already ended chats

* Sleep native chats through the shared no-toast sleep core

Main split runSleepWorktrees into sleepWorktreesReportingFailures (no toast) and
a toasting wrapper. Move the native chat stop, host routing and typed failures
into the shared core so the WSL move's Sleep request also stops chats, and
report every workspace not verifiably slept (an unconfirmed chat stop included)
in failedWorktreeIds. Fold one ACP adapter field onto a line so main's file plus
this PR's ownership check stays within the file length limit.

* Import isStructuredTab from its shared module

* fix(native-chat): leave chat rows to their host through Sleep

Sleep's worktree status drop deleted the chat's row, which the host feed then
rewrote, so the outcome depended on which write landed last. Open chat tabs
own their rows (same rule as terminal-tab close), and a chat only makes a
workspace sleepable while its host holds the agent's process.

* fix(native-chat): judge the Sleep receipt by facts, not lane occupancy

A running chat's stop queues the provider's own requested-close end report
onto the chat's lane, so the lane-occupancy check read every proven stop as
superseded and dropped its receipt. Later work now counts only once it
exists: a moved fence, a child, delivery, child work, a pending submission,
a pending create or attach, or an open dispatch.

* test(native-chat): report a fake close's end only for an acquired child

* Preserve canonical workspace identity when sleeping

* Propagate optional sleep host through file command contract

* Move the deliberate-sleep PTY tests to main's spec file

Main grouped the PTY connection suites (#29193) and moved this file to a .spec.ts registered from pty-connection-spawn-and-startup.test.ts. Taking main's copy here keeps the following main merge free of a modify/delete conflict; this PR's test changes are reapplied after the merge.

* Reapply Sleep's teardown waits to the moved PTY spec; keep acquisition under its line limit

The deliberate-sleep spec moved in #29193; this carries the PR's changes into it: a pane mounted after wake waits for issued teardown, an unqualified pane waits for every same-id host's teardown, and a queued startup waits too.

The merge left claude-structured-session-acquisition.ts over 300 lines. resolveClaudeAcquisitionError, which already builds the exit-unproven error, now decides whether to release the acquisition.

* test: keep the Sleep receipt checks on the closed handle and kept record

* fix(sidebar): a single-workspace Sleep failure says workspace, not workspaces

* fix(sidebar): Sleep stops a workspace's chats on every host that has a copy

* test: drop redundant casts from the Sleep flow test state

* fix(sidebar): stop a workspace's chats after its terminals, leaving main's teardown owner check as is

* fix: admit workspace chat stops before teardown waits

* fix: retain browser teardown caller sequencing

* fix: retire failed acquisitions after observed root exit
2026-10-19 00:33:01 -07:00
Brennan Benson 978b207cc1 fix(agent-status): never drop a running helper for lack of room (#30416)
* fix(agent-status): never drop a running helper for lack of room

Helper state (subagents, background tasks and commands) was bounded by counts and byte budgets that refused or evicted live records once hit, so the agent kept working while Orca listed it wrong. Every one of these records already dies with its session, pane or chat, so the count and size caps go; identity-history, transcript and transport bounds stay.

* fix(agent-status): keep the status field limit, measured without the subagent roster

The previous commit dropped the 256 KiB status check outright; it still guards every other field. Only the roster, whose rows are bounded individually, is left out of the measurement.

* fix(agent-status): fit helper previews and walk lineage once

* fix(agent-status): skip an oversized Pi row instead of stopping, and parse every envelope the store can write

Review follow-up: one long row no longer hides later rows that still fit the request, and the transport envelope's reader no longer refuses a size its writer now produces.
2026-10-19 00:31:42 -07:00
Brennan Benson add1eccae3 fix(codex): a Codex chat keeps its Orca identity when the user's Codex config strips command environment variables (#30406)
* fix(codex): a Codex chat's commands keep its Orca identity under a restricted shell environment policy

Codex rebuilds each command's environment from the user's shell_environment_policy, so
inherit = "core" dropped the chat id, marker, launcher and instance pin Orca gives the
app-server. orca orchestration then could not tell which chat was calling.

Every thread start and resume now sends those values as shell_environment_policy.set
overrides, which Codex applies after its filter and merges per key.

* fix(codex): accept the launch's read-only delete list

* test(codex): a chat without visuals gets no visuals config, identity overrides aside

* refactor(codex): a chat's command env is what the seal wrote, not a second key list

The seal now returns the session values it set (identity, a registered worker's own handle, the CLI launcher, the instance pin and the blanked remote selectors) as commandEnv, and the composed launch carries it. The Codex acquire sends that plus the chat's scratch folder from its own visuals. This deletes the hand-kept key list, the handle prefix test and the delete-list re-check, so a key later added to the seal reaches each command too, and an inherited worker handle the seal never named no longer does.

* test(codex): wait for the shell command's turn to complete before the cold resume
2026-10-19 00:31:11 -07:00
Jinwoo HongandClaude 9ea5434371 fix(mobile): faster relay recovery after an app switch and on a late desktop data link (#30607)
* fix(mobile): recover a relay that died during an app switch in one short probe, and retry an attach timeout through the director once

Refs STA-11130.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(mobile): redial the same cell once on attach-timeout instead of re-resolving

Aligns with the relay step 6 design: reason string attach-timeout, no director
re-resolve (the director names the cell the host is seated on).

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(mobile): probe decisively only on a real background return, 4s, and jitter the attach-timeout redial

Co-Authored-By: Claude <noreply@anthropic.com>

* test(mobile): replace type assertions with a typed fake in the relay probe tests

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-19 03:30:06 -04:00
Brennan Benson 503d919e64 feat(native-chat): + menu shows chat, disabled chat and CLI rows (#29074)
* feat(native-chat): choose which agents open as a chat

Settings → Chat gets a Providers list. Each agent the host can open as a chat
has a switch; off opens that agent in a terminal instead. Only choices the user
makes are saved. Unchosen: Claude and Codex on, others on when their CLI is
installed on this computer.

The renderer's route and the host's launch-mode decision both read one rule, so
the tab menu, shortcuts, CLI, orchestration and relayed phone launches agree.

* fix: keep chat provider settings with the other chat settings types

* test: route input carries this computer's detected agents

* feat(native-chat): the + menu shows chat, disabled chat and CLI rows

With Chat UI on, each agent whose chat provider is on gets a chat row, disabled
with the reason where the workspace's machine can't run that chat (not
installed, SSH, WSL, not on this host). Every other installed agent gets a row
marked (CLI) that opens its terminal. With Chat UI off, or on an older server
that opens no chats, the menu is unchanged.

* fix: new tab menu rows use a launch variant instead of restyling the menu item

* fix: name every route blocker the menu rows map

* fix: menu row subscriptions render on the server too

* feat(native-chat): open a chat agent's CLI from beside its + menu row

A chat row whose CLI is installed on the workspace's machine gets a › beside
it. Its side menu offers '<Agent> (CLI)', which opens that agent's terminal for
this launch only; nothing is saved and the row itself still opens a chat.

* fix: keep new tab agent surfaces authoritative

* fix: handle unknown host support reasons

* fix(chat): resolve provider choices from their owning computer

* fix: include chat launch feedback translations

* test(chat): use checked native inventory fixtures

* chore: retrigger PR checks

* test: pin pending chat row behavior

* chore: retrigger PR checks after cancellation

* Fix launch preparation lifetime and execution host startup

* fix: keep agent menu choices bound to their launch

* Simplify agent menu support reads and preserve explicit surfaces

* Keep focused menu behavior coverage within its budget

* Keep menu host fixtures typed as execution hosts

* Assert notes payload on the current launch action

* Combine agent session launch type imports

* fix(chat): keep unchosen providers on without discovery

* fix(chat): use workspace installation for menu defaults

* test(chat): type provider routing fixtures against store contracts

* fix(tab-bar): keep + menu chat rows clickable while the host answers

Each menu open discarded the host's answers, so every open showed the chat
rows disabled with 'Checking chat support' until the host replied. Answers
now outlive the menu and refresh in the background, rows use this client's
facts until the host answers, and a slow host no longer reads as a refusal.

* test(chat): align shared launch fixture with private settings reader

* fix(tab-bar): keep known + menu refusals and never treat a silent host as one

Closing the menu dropped the workspace's agent list, which changed which agents
the menu asks about and threw away the host's earlier answers, so a reopen showed
a refused chat as clickable. A support read that could not answer was stored as
"No chat on this host", disabling chats the client knows are supported.

The menu now keeps the stored agent list while closed (only fetching waits for
it to open) and only a real host answer relabels a row. The workspace's
detection target is resolved where main already resolves it (QuickLaunchButton
and the tab-bar runtime model) and passed in, so no new focus read is added.
Tab-bar test mocks now stub the QuickLaunchButton exports the menu imports.

* fix: sync runtime English catalog for agent menu

* test(chat): give the structured-session RPC fixture the private settings reader

The host now reads saved chat choices through requireStore().getSettings(); the
shared RPC fixture only had getClientSettings, so the launch-mode reader saw no
settings and create support refused Claude. The fixture now follows each test's
client settings.

* test(chat): pin create support's options argument and kept decline reason

* fix(native-chat): + menu chat rows wait disabled for the host and share its answer across tab groups

A chat the host refuses showed clickable for up to the 10s host read, then flipped to disabled,
once per tab group. Rows now stay quietly disabled until their host answers (or the read times
out), and every tab group on the same workspace host reads the same answers.

* fix(native-chat): + menu rows also wait before the host's chat list loads, and on every reopen of a host that could not answer

Asking the host no longer waits for its chat list, so Claude and Codex no longer show clickable,
then disabled, while it loads. A read that times out releases rows for that open only, so the
next open waits again instead of flipping on a late refusal. Agents not installed skip the read.

* fix(native-chat): typed + menu search puts a launchable row ahead of a disabled chat

In a WSL workspace, typing an agent's name and pressing Enter highlighted its disabled chat row,
so nothing launched where main opened the CLI. Search now ranks rows that can launch first.

* fix: preserve provider choice and cached native inventory

* feat(native-chat): hosts publish each workspace's chat availability as one stream

A new negotiated stream, agentSession.subscribeAvailability, advertised as
agent-session.structured.availability.v1. The host keeps one in-memory resource per
workspace, shared by every reader, that asks the existing createSupport check for every
registered agent in that workspace and publishes only complete generations. It answers
again when a launch setting changes, after an explicit agent refresh, and (for answers
that could not be checked) when a workspace appears; the last reader leaving retires it.
A check that could not answer is unknown, never a refusal. agentSession.agents stays
static and unchanged.

* feat(native-chat): + menus read a publishing host's workspace availability

The renderer holds the workspace's availability stream while its tab bar is mounted
(not while a menu is open), shared by every tab group, with runtime and contact fences.
It exposes a readiness model, { ready: false } | { ready: true; byAgent }, from
useHostStructuredAgentAvailability and the menu hook. On a host advertising the
capability the menu reads its answers from that model and makes no per-agent
createSupport reads; an older host keeps the per-agent reads, answer cache and deadline
unchanged. Row building and presentation are unchanged.

* fix(native-chat): workspace availability checks only what the shown workspace needs

Only the shown worktree (or an open menu) holds its host's availability stream, and only with Chat
UI on. Display checks no longer force a fresh login shell for an agent that isn't installed, so
Claude and Codex answer without waiting on it. A change to the Windows runtime default or a project's
WSL choice re-checks, and a workspace still on its first check answers again when one appears.

* fix: keep main's bytes for the create-support file the merge reformatted

The pre-commit formatter rewrapped main's unformatted lines during the merge and pushed the file past its line limit.

* test: type the + menu rows hook mocks without assertions

* test: match the availability stream's types in three tests

* test(cross-version): declare the workspace availability stream in the agent-session surface

* feat(native-chat): + menu holds a placeholder until its agents are known, and learns them while the workspace is shown

* feat(native-chat): + menu reads one agent health snapshot per machine

The + menu no longer checks chat availability per workspace. Each Orca
runtime keeps one agent health snapshot: every agent's location rule on
this machine plus a typed observation of its installed release, probed
in the server's own home. It is taken on first use and replaced only on
a launch setting change, an explicit agent Refresh (after a fresh shell
capture), or a restart, and kept without readers.

- The version probe reports supported, unsupported or inconclusive; a
  launch still refuses an inconclusive run, while the snapshot reads it
  as unknown rather than a refusal.
- agentSession.subscribeAvailability takes no params and publishes that
  snapshot; a replay that closes the transport releases its reader.
- The renderer keeps the snapshot per host, runtime and pairing, through
  lost contact and across workspace switches on the same host.
- The menu hook drops the per-open createSupport display reads, their
  deadline and answer cache; older hosts show client facts and the
  click's admission, unchanged, states any refusal.

* fix: a failed shell recapture still publishes the agent health snapshot

* fix(native-chat): capture fresh shell facts for every health snapshot

* fix(tab-menu): share settled agent actions across menu surfaces

* fix(tab-menu): preserve loading row line height

* feat(native-chat): the whole chat row opens its CLI submenu

Hovering a chat row in the + menu, or pressing ArrowRight on it, now opens
the "<Agent> (CLI)" submenu to the right instead of needing a separate
chevron button. Clicking the row, or Enter/Space, still starts the chat
and closes the menu; hosts pass closeMenu since a submenu trigger never
closes the menu on its own.

* fix(tab-menu): line the CLI submenu up with its chat row

* feat(native-chat): a chat row's terminal button opens its CLI

Clicking a chat row in the + menu starts a chat. Hovering the row (or
pressing ArrowRight on it) reveals a terminal button at its right edge that
opens the agent in a terminal instead. This replaces the submenu, whose
chevron made the row look like it only opened a submenu. The button's click
bubbles into the row, so the menu closes as for any item and the hosts no
longer need a closeMenu callback.

* fix(native-chat): the chat row's terminal button handles drags, arrows and screen readers

A press on + dragged onto a chat row's terminal button and released there
now opens the CLI: the row reads whether the pointer or focus is on its
button instead of the click target, which a drag-release never sets.
Arrow, Home and End keys pressed on the focused button move between rows
as they do on the row. The row is announced as "Start a <Agent> chat"
instead of including the button's label.

* fix(native-chat): chat rows read "Start a chat with <Agent>"

"Start a OpenCode chat" was ungrammatical for agents whose name starts with a vowel.
2026-10-19 00:27:17 -07:00
Jinwoo Hong f6cc8f67f5 fix(layout): a pane main closed stays closed when its terminal starts late (dark, layout-owner build only) (#30560) 2026-10-19 03:21:32 -04:00
Neil f7ce68fb1b fix(ssh): relinking a stopped managed server keeps the project groups it holds, and later moves don't re-send them (#30608)
* fix(ssh): relinking a stopped server keeps the groups it holds, and later moves reference them

Relinking found server-held groups only through the rows in them, so an empty group renamed on the
server was re-imported and refused. Delta moves re-exported a linked group whenever a new row sat in
it, so a group renamed on the server refused the next Move. Both now leave the server's group as is.

* fix(ssh): delta moves keep the server's record of groups the first move imported

* test(ssh): cover a deleted moved group and tidy the relink group fixtures

* fix(ssh): resume an older build's delta with the groups its manifest exported

* fix(ssh): take no group-ownership evidence from server rows on another host

* fix(ssh): a relink records only the groups the server's own list holds, with their host stamps
2026-10-19 00:21:08 -07:00
Jinwoo Hong c68c84f157 fix(automations): headless reuse-session sends the prompt into the existing agent (#24278) (#30594)
* fix(automations): headless reuse-session sends the prompt into the existing agent (#24278)

A headless host now continues the agent a previous run left idle, using the
desktop's selection rule (moved to src/shared/automation-session-reuse.ts) and
the launch's reused-terminal prompt writer; it launches fresh when no agent
takes the prompt. Runs of one automation now claim, precheck, pick, prompt and
record one at a time, and headless retention closes that automation's
terminals in the same lane, so no two ever claim one pane. A pane belongs to
its newest run by dispatch order. A run completes on a done reached after it
was dispatched; an idle pane alone completes it only when the pane never
reported agent status, measured from dispatch.

* fix(automations): keep the shared reuse rule Hermes-safe
2026-10-19 03:11:00 -04:00
Neil d03d731ea6 refactor(renderer): one twin check, one worktree-copy scan, drop stale resolver mocks (#30579)
- isTwinWorkspace replaces four inline resolveOwner(...).kind === 'ambiguous' checks
- someWorktreeCopy shared by isUnprovenServerSshRoute and hasHostEvidence
- file-explorer owner: drop the unreachable second unresolved guard and the stale comment
- native chat owner: drop a null ternary the helper already handles
- tests: delete stale getRuntimeEnvironmentIdForWorktree mocks (29 suites), one shared
  local+ssh twin fixture, file:// open-target tests next to their module, reset mocks.twin
2026-10-19 00:10:41 -07:00
21af18af35 fix(ssh): managed servers install through SSH gateways that cut long commands (#30491) (#30549)
* fix(ssh): preserve activation commands through SSH gateways

* fix(ssh): fall back when a host cannot decode a long command, and read claims and snapshot verdicts through gateway noise

On top of the gateway fix (#30503):

- A host without base64 or gzip gets a long command plain: the compressed wrapper
  checks for its decoders before reading anything, exits 125 with a marker, and
  execCommand resends once with compressCommand: false and remembers that host.
  `base64 -D` covers macOS before 13, whose base64 refuses `-d`.
- Snapshot capture, restore, presence and compare answers read the last line that is
  a verdict, so shell noise a stderr-merging gateway appends after it is ignored.
- The machine claim reads the claim line by line, and every failed step now prints
  CLAIM_FAILED <step> <error> with the df line, so "orcad could not claim this host's
  .orca-remote" says what failed and how to fix it instead of exiting 65 with the raw
  command or failing with no reason. A failed write no longer leaves a partial file.

Refs #30491

* test(ssh): size the simulated gateway cut from the command, not a fixed 10 KiB

The snapshot command repeats its temp path ~40 times, so on CI's short /tmp it fell under 10 KiB
and the truncation precondition failed.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: zzzpyyy <167217540+zzzpyyy@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-10-19 00:03:01 -07:00
68ed9db434 fix(terminal): relay-era SSH panes the bridge serves check printed file paths on the workspace's server (#30240)
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-18 23:59:58 -07:00
OrcaWinandm4air 2b3b64645a fix(editor): opening a quoted lettered list with an unquoted continuation no longer crashes (#30247)
* fix(editor): opening a quoted lettered list with an unquoted continuation no longer crashes

Marked's blockquote re-reads a lazily continued list with its built-in list(),
which rejects Tiptap's extended markers (a., iii)) and returns undefined; the
caller then reads .raw and the rich editor hits its error boundary. A per-lexer
tokenizer falls back to the registered ordered-list tokenizer, cut where
Marked's own list would end at an outer quote line.

* test(editor): guard extended-marker blockquote fallback against dependency drift

Source re-review against Marked 17.0.6/18 and Tiptap 3.31.3: pin the override
to Marked's lazy-list call site, build the quote-break regex once per item,
and cover more item starts Tiptap accepts so a bump can't reopen the crash.

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-18 23:49:23 -07:00
Neil 5f126af9f8 fix(watcher): flush file changes on a steady cadence instead of waiting for quiet (#30270)
* perf(watcher): flush file changes on a steady cadence instead of waiting for quiet

Watcher batches used a 150 ms trailing debounce capped at 500 ms, so a worktree
with agents writing continuously always waited the full cap. The git status
nudge had no cap and could starve until the 60 s safety poll. Batches now flush
25 ms after the first change following a quiet period, then at most every 100 ms;
new events join an armed flush instead of pushing it back.

* fix(watcher): keep atomic saves in one batch and survive clock jumps

Raise the first-flush delay to 75 ms so the native watcher's split burst lands
in one batch, cap the flush delay at one interval so a backwards clock jump
can't stall delivery, and widen the editor delete debounce to cover a split
across one flush interval.

* test: wait past the widened editor delete debounce

* fix(watcher): keep markdown list refreshing under sustained writes and widen delete grace

Markdown document refresh used a resetting 125 ms debounce that never fired
once watch batches arrive every 100 ms. Delete tombstones now wait two batch
intervals so slow stat calls (WSL, AV scans) can't split a save into a flash.

* fix(watcher): bound watcher-driven reads and scans under sustained writes

One watcher-triggered editor read and one markdown scan per target in flight,
with a single follow-up; markdown scans honour the latest change time; the
headless Windows watcher no longer resets its refresh timer on every event.

* fix(watcher): make the faster remote cadence opt-in and harden watcher-driven reloads

files.watch takes an optional batchCadence; hosts keep legacy trailing batching
unless the client opts in. Coalesced editor follow-ups re-check the tab and
replace a hung read only once; Markdown mounts skip pre-change scans; the
Windows fallback refreshes the whole tree at most every 500 ms.

* fix(watcher): stop restarting the remaining watch-driven timers

Combined-diff section reloads and per-file editor reload dispatch no longer
push an armed timer back; a queued editor follow-up for a tab that left the
screen marks it stale instead of dropping the change.

* fix(watcher): let combined-diff and worktree notifications land under sustained writes

A combined-diff section discards an invalidated in-flight load at most once in a
row; worktree base-directory notifications no longer restart their timer.

* fix(watcher): never overwrite an edited diff section and unstick hung reloads

A combined-diff load never commits over a section with unsaved edits; a reload
queued behind a hung read starts after the stale limit without needing another
change event.

* fix(watcher): recover from hung markdown scans and keep evidence for edited tabs

A Markdown scan that never settles is abandoned after the join window so later
changes scan again; a queued editor follow-up for a tab edited meanwhile marks
it changed-on-disk and stale instead of dropping the change.

* chore(watcher): use a numeric markdown scan id

* fix(watcher): don't let later events join a stale echo-verification read

Payloads from the same burst still share the in-flight read; a payload from a
later task gets exactly one fresh read issued after the current one settles.
2026-10-18 23:44:18 -07:00
Neil 5c71a6cc6b fix(watcher): stop unwatched windows from being re-subscribed to local watchers (#30412)
* fix(watcher): stop unwatched windows from being re-subscribed to local watchers

Two pre-existing races left a window subscribed after it unwatched:
- a re-watch waiting out a cancelled install was not tracked, so its unwatch
  could not withdraw it and the retry subscribed it anyway
- restoring after a failed worktree removal dropped the suspended owners up
  front, so an owner that unwatched mid-restore was still re-subscribed

Adds a randomized lifecycle model test plus regressions for both sequences.

* fix(watcher): keep a retried removal's owners when it overlaps the previous restore

The restore now leaves its snapshot in place while it works, so a retried
removal can add owners to the same entry. Stop restoring once the snapshot is
forgotten or replaced, delete it only when empty, and merge failed owners into
the current entry instead of overwriting it. Apply the same per-owner restore
to SSH watchers, which dropped the saved list before re-subscribing.

The lifecycle model now drives removals through the real coordinator and lets
them overlap restores; adds local and SSH restore regressions.

* fix(watcher): drop SSH re-watches unwatched while waiting out a cancelled install

Same leak as the local fix: a watch that could not join an aborted SSH install
waited untracked, so its unwatch had nothing to remove and the retry installed
it anyway. Record those waiters, clear them on unwatch, and retry only if the
sender is still recorded.

* fix(watcher): keep reloaded windows and cancelled-install waiters across removals

- An older restore checked a window's lifetime only after taking its entry, so
  a window reloaded under the same id lost its place in a newer removal's
  snapshot. Check the lifetime first (local and SSH).
- Removal now suspends senders waiting out a cancelled install and drops their
  retry; previously the retry installed mid-removal and the close tore it down,
  losing the window. Waiters keep their WebContents for this.
- Waiter maps live in watcher state: cleared on shutdown and per sender on
  cleanup.
- Model: windows can reload (same id, new lifetime) and subscribes can be
  wedged (ignore abort).
2026-10-18 23:44:01 -07:00
Jinwoo Hong e7b60c155b refactor(orchestration): delete the unreachable pre-mailbox terminal delivery path (#30576)
OrcaRuntimeWithDeliverPendingMessages.deliverPendingMessages was only
reachable from itself and settlePendingMessageDelivery, which was only
reachable from it. Live terminal mail delivery goes through
OrchestrationMailboxPointerDelivery (stage/submit), so this removes the
dead mixin link, its settle method, its state fields (flights, parked
redeliveries, probe dedupe, pointer watermarks), and the now-unused
isCursorAgentOrchestrationTarget and RuntimeMessageWaiters.typeHasLiveWaiter.
No behaviour change.
2026-10-19 02:39:45 -04:00
NeilandClaude Opus 5.5 243a08b851 fix(ssh): follow a relaunched managed server's port; match quoted Windows forward argv (#30583)
* fix(ssh): follow the managed server's port after activation and match quoted Windows forward argv

An update, rollback or recovery that launches a managed server now rebuilds the
tunnel so it follows the port the new server bound. Windows orphaned-forward
recovery now parses the process table's quoted command line instead of
comparing it to a space-joined argv, so a key path with spaces still matches.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(ssh): keep a successful activation when the tunnel rebuild fails; strip one LogLevel pair

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(ssh): soften the tunnel rebuild failure comment

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(ssh): tokenize posix expected argv like the process table before normalizing

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 23:36:41 -07:00
Neil 75b6fa2201 fix(runtime): project removal and edits stay on the selected project's host (#30584)
* fix(runtime): project removal and settings writes keep to the resolved execution host

- Removing a project now closes its native Claude/Codex chats on this host and retires their tabs.
- A terminal spawn after removal is admitted only by a surviving row on the spawn's own host.
- repo.update and repo.setBaseRef write the resolved row's host, not a same-id sibling.

* fix(runtime): close project chats alongside the terminal stop, with their own floor

- A failed or budget-spent terminal stop no longer skips the chat closes.
- Spawn admission still refuses on any host once no row with the removed id survives.
2026-10-18 23:13:50 -07:00
NeilandClaude 6d8e4cfef2 refactor(shell): one shell-argument-quoting module for the duplicated quote copies (#30564)
* refactor(shell): one shell-argument-quoting module for the duplicated quote copies

Each former copy now calls the canonical function with its exact spelling; a table test pins
byte-identical output for every copy over hostile inputs. Distinct spellings stay distinct
functions: '\'' vs '"'"', the two plain-word sets, and the cmd.exe doubled quote.

Co-Authored-By: Claude <noreply@anthropic.com>

* style: keep the quoting import at the top of two import-less files

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-18 23:12:27 -07:00
Neil 59b03cab00 fix(ssh): rehome a cold-start converted workspace the scan missed, and notify replayed background-turn completions (#30578)
* fix(ssh): rehome a converted workspace after a cold-start scan misses it, and notify replayed background-turn completions

A cold-start conversion now lists the server's worktrees itself when startup's scan finishes
without the destination row (the scan predated the server's projects or failed for them).

A reconnect replay of a working row carrying a new turnCompletedAt is now observed instead of
seeded; the completion observer already dedupes the stamps it handled.

* test(ssh): drive the cold-start rehome retry through the server RPC
2026-10-18 23:08:05 -07:00
Neil e5184c4c78 refactor(renderer): fold browser creation, file actions and file preview off the deprecated owner resolver (#30562)
* refactor(renderer): fold browser creation, file actions and doc-preview callers off the deprecated owner resolver

The creation-action policy and browser creation (#3, #48) fold together, so the provider the
policy offers is the one creation uses. Browser panes, page lifecycle and conversion, tab
duplication, the explorer tree pane, doc-preview actions and editor path moves (#21, #43, #45-#47,
#49, #50) read runtimeEnvironmentIdWithLegacyFallbackUnlessTwin. File-explorer writes on a folder
(#51) take the owner from the rows alone, never from the focused server. Deprecated importers
16 -> 6.

* refactor(renderer): fold file preview off the deprecated owner resolver; Open in Orca Browser refuses a twin

A project two hosts publish reads as no server for the doc-preview plan, but the file:// path now
refuses it with its own message instead of opening this machine's copy (#7). Deprecated importers
6 -> 5.

* fix(renderer): a twin's file preview and open-externally refuse instead of opening this machine's copy

Codex P2 on the F7 folds: a folder twin read as no server, so the preview plan built a local
file:// tab and open-externally handed the path to this machine's OS. Both now check the owner
for ambiguity first. Also fix two test fixtures that broke the renderer typecheck.

* refactor(renderer): move the file:// open target out of file-preview.ts

file-preview.ts crossed its line limit after main grew it; the file:// target, its type and its two
refusal messages move to workspace-file-browser-open-target.ts, which they never depended on
file-preview for.

* test: mock the replacement resolver in the quick-action availability suite (Codex P2)
2026-10-18 23:03:33 -07:00