Commit Graph
11958 Commits
Author SHA1 Message Date
Neil 90e99c48f4 refactor(pty): every PTY retirement names its exit proof; ratchet the unproven ones (#30722)
* refactor(pty): every PTY retirement names its exit proof; ratchet the unproven ones

* refactor(pty): count raw ownership drops, route an abandoned relay stop to unverifiable, honest proof labels
2026-10-19 05:48:19 -07:00
Neil 1bfebad8ec fix(runtime): a new terminal the host can't confirm says why, instead of nothing (#30725)
* fix(runtime): say why a new terminal on a host failed, with a retry

* fix(runtime): never offer a retry or say refused when the host's reply was lost

* fix(runtime): only a host error reply counts as refused; the wake flow keeps its own toast

* refactor(runtime): one 'couldn't confirm' notice carrying the reason, no failure classification

* fix(runtime): one create notice per workspace, so retried seeding can't stack toasts
2026-10-19 05:46:10 -07:00
Neil 5a9e26ef9f fix(ssh): say a noexec home can't run Orca's server, instead of "install an older version" (#30724)
* fix(ssh): name a noexec home instead of calling the host unsupported

* test(ssh): measure the stage exec probe in the host-op byte budget

* fix(ssh): the connect error names a noexec home too

* chore(i18n): add the noexec status line to the English catalog

* fix(ssh): only the shell's exec refusal reads as noexec; other probe failures reach the self-test

* fix(ssh): a stage the probe can't write skips the probe, so a full disk still reads as full

* fix(ssh): read the probe's exit 126 as noexec in any locale
2026-10-19 05:45:39 -07:00
Kelvin Amoaba 2b6077807a fix(native-chat): show the model picker in a new OMP or Antigravity chat (#30713) 2026-10-19 12:32:33 +00:00
Jinwoo HongandClaude 4bc3971b98 fix(sidebar): key folder workspace rows by host so the phone matches the desktop (dark switch) (#30721)
* fix(sidebar): key folder workspace rows by host in the shared builder so the phone matches the desktop

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

* test(sidebar): pin the SSH folder row key encoding

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-19 08:26:12 -04:00
Neil a9011d083c fix(projects): refuse to file a folder under a project its own GitHub remote contradicts (#30717)
* fix(projects): refuse to set a folder up under a project its own GitHub remote contradicts

Setting an existing folder up for a project stamped the project's identity
as the folder's upstream even when the folder's own remote named another
GitHub repository, so the folder was silently relabelled into the wrong
project (fabricated upstream, wrong projectId on its setup and workspaces).

Fixes #30663

* test: type the folder-remote spec store without casts

* fix(projects): judge a registered folder by its current remotes; ignore host aliases

* test: cover the desktop re-read of an already-registered folder
2026-10-19 05:10:00 -07:00
Neil f22aecf1ed fix(runtime): one teardown policy per kind; removal closes host browser pages everywhere, headless sleep stops terminals (#30715)
* fix(runtime): one owned-session teardown policy; removal closes host browser pages everywhere, headless sleep stops terminals

A declared table says what each entry point (worktree, folder and project removal,
sleep, disconnect) does to each owned kind (PTY, native chat, host browser page,
launch record), so a new kind is a compile error until decided. Removal over desktop
IPC and repo.rm now close the host's browser pages and launch records like the
runtime removal; worktree.sleep with no renderer sleeps the host's terminals instead
of doing nothing. Sleep->chat is declared pending #28445; disconnect stops nothing.

* fix(runtime): project removal finds headless pages and failed launches without a client page; stub the new runtime method

* fix(runtime): headless sleep rejects an unproven stop; read headless page owners through the bridge, not Electron
2026-10-19 05:02:21 -07:00
Kelvin Amoaba 162e07cf97 fix(browser): refuse agent clicks while the user is mid-IME composition (#30358) 2026-10-19 11:45:29 +00:00
yi111andYi-111-a ec0c1f115a fix(github): accept SSH certificate org remotes (org-<id>@github.com) (#30659)
GitHub orgs using an SSH certificate authority get an SCP remote whose user is
org-<id> rather than git. The three GitHub remote parsers only matched a
literal git@ user, so those repos were classified not-github and lost PR,
issue and check support.

Co-authored-by: Yi-111-a <yi-111-a@users.noreply.github.com>
2026-10-19 04:43:32 -07:00
Stefan StefanovandClaude Sonnet 5.5 346f6028a3 fix(git): stage and discard files in WSL repos opened by UNC path (#30693)
The Git runner sends a command into WSL when the working directory is a
\wsl.localhost UNC path, even if no WSL distro is set in the options.
literalPathspec only looked at the options, so Windows-style backslashes
in file paths reached Git inside WSL unconverted and stage, unstage and
discard failed to match the file.

literalPathspec now also checks the worktree path, and every caller
passes it.

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-19 04:43:08 -07:00
Neil 25e53f4b0c fix(security): put every trust decision on a host-owned token surface (#30710)
* fix(security): put every trust dialog on a host-owned token surface

Plugin themes write CSS tokens on the document root, and dialogs portal to
body, so any trust surface without plugin-security-chrome inherits them; a
theme could hide what is being approved. The orca.yaml hook trust dialog,
notebook run confirmation, plugin install, remove and rollback dialogs were
missing it. DialogContent gains surface="security", which applies the
host-owned tokens, and a scrollable prop for the plugin dialogs' existing
viewport-height scrolling; every trust dialog uses the surface and a test
lists them. The style guide says when to use each.

* fix(security): keep inline trust decisions on the host-owned surface

The agent command approval card (Allow/Deny) and the browser certificate
override sit outside a dialog, so plugin theme tokens still reached them.
A SecuritySurface primitive puts its child on the same host-owned token set
as DialogContent surface="security"; both use it, and the security surface
test now lists inline trust surfaces as well as dialogs.
2026-10-19 04:21:16 -07:00
NeilandClaude e72d8f926a fix(ssh): say the host's disk is full when a managed-server install runs out of space (#30679)
* fix(ssh): say the host's disk is full when a managed-server install write fails for lack of space

OpenSSH's sftp-server reports ENOSPC and EDQUOT as the generic SSH_FX_FAILURE, so a deploy onto a
full home failed with the bare detail "Failure" under "Try connecting again". After a failed
install write, probe the host's free space where the write was going; when it cannot hold the
write, fail with an error that names the filesystem, how much is free and how much is needed.
Lost or aborted transfers and Windows hosts keep their original error.

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

* fix(ssh): diagnose only SFTP's generic failure on an exhausted filesystem

Codex review: a partial upload may spend the room the rest needed, so compare against an empty
filesystem, not the bundle size, and keep explicit refusals such as permission denied.

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

* refactor(ssh): reuse formatKb and size an upload the way uploadDirectory walks it

Opus review: the shared formatKb already exists, and following symlinks inside the tree could
recurse; uploadDirectory follows the root and skips links below it.

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

* test(ssh): measure the free-space probe in the host-op byte budget

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-19 04:20:09 -07:00
Neil 92ed331184 refactor(plugins): extract the shared content-pack registry and list store (#30697)
* plugins: extract the verified artifact-pack registry from language packs

Pure refactor. Verify, bounded read, parse and per-plugin error handling
move into PluginArtifactPackRegistry; language packs become one instance,
so later data families reuse it instead of copying the file.

* plugins: reconcile content families from one list

Pure refactor. Reconcile and error reporting walk one family list in the
same order as before, so a new family is one entry.

* plugins: one line per family for declared artifacts and contribution defaults

Pure refactor. Declared artifact paths are a list of per-family rows, and
the contributes default comes from the schema (prefault) instead of a
second copy of the field list; the existing defaults test pins the result.

* plugins: extract the renderer plugin content list store

Pure refactor. The fetch generation, startup join window and content-change
subscription move into createPluginContentListStore; language packs become
one instance, and store fields are renamed to entries/fetchEntries.
2026-10-19 04:01:12 -07:00
Neil 148944c020 refactor: de-barrel remote-runtime-client, runtime-terminal-stream and runtime-terminal-inspection (D2, D3) (#30684)
* refactor: de-barrel remote-runtime-client and runtime-terminal-stream (D2)

importers name the defining modules; the two vi.mock factories that named a re-export move to
the defining module (pty-id spy) or drop the dead key (RemoteRuntimeClientError)

* refactor(renderer): de-barrel runtime-terminal-inspection (D3)

importers name runtime-terminal-input-recording, runtime-terminal-verified-input and
shared/remote-runtime-pty-id; 22 vi.mock factories move the re-exported keys to those modules
(pty-id keeps its other exports via importOriginal)

* test: drop the duplicated verified-input mock (Opus P3)
2026-10-19 03:58:26 -07:00
NeilandClaude 41fdd7e6d8 fix(ssh): keep SFTP uploads pipelined so a high-latency link doesn't crawl (#30704)
* fix(ssh): keep SFTP uploads pipelined so a high-latency link doesn't crawl

ssh2's WriteStream acks each chunk before writing the next, so uploadFile moved one 64 KiB chunk
per round trip: about 200 KB/s on a 300 ms link, which made the ~90 MB managed-server deploy (Node
runtime plus orcad bundle) take over seven minutes. A 2 MiB write buffer lets ssh2's _writev keep
that many bytes of WRITE requests in flight, as fastPut's default does.

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

* test(ssh): type the fake session's stream options with ssh2's WriteStreamOptions

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-19 03:48:35 -07:00
Neil cffcef7be0 fix(projects): setup edits and removals name the host, so the twin on another host is never touched (#30664)
* fix(projects): setup edits and removals name the host, so a twin on another host is never touched

Repo-backed project host setups take the repo id, so a project on two hosts has
two setups with one id. Update and delete took the first match by id, so editing
or removing the SSH copy could rename or remove the local one. The args gain an
optional hostId (older peers strip it and keep the first match).

* fix(projects): tolerate only the legacy runtime stamp when a named host misses an independent setup

* fix(projects): refuse a twin edit on a server that would act on the wrong twin; read legacy runtime stamps as local

* fix(cli): refuse setup --host on a server that would ignore it

* refactor(projects): exact host match only; name the host on the wire only for twins on one route

* fix(projects): a named host the catalog lacks refuses; settings remove stays by id

* refactor(projects): name hosts on the wire only for this computer's catalog; paired servers keep the by-id request
2026-10-19 03:47:23 -07:00
Neil ec89fa6099 feat(orcad-migration): host applies a signed editor-drafts blob inside the commit (S10-H) (#30665)
* feat(orcad-migration): host applies a signed editor-drafts blob inside the commit

* fix(orcad-migration): consume received blobs only after the commit is on disk
2026-10-19 03:46:41 -07:00
Jinwoo Hong 218b9a737b The layout drift log names the values that differ (dark, layout-owner build only) (#30678)
* The layout drift log names the values that differ

* Keep the drift log's value copies type-checked and lint-clean

* Give each side of each logged change its own size allowance

* Name the drift log's id length cap apart from other modules' caps

* The drift log names the terminal bound to each pane of a changed tab

* Pin the terminal id length rule in the drift log tests
2026-10-19 06:41:38 -04:00
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