Commit Graph
2145 Commits
Author SHA1 Message Date
Neil ba6599eabb fix(ssh): pair again over SSH when a managed server drops this desktop, instead of reconnecting forever (#30775)
* fix(ssh): say the host dropped this desktop's pairing, not that another Orca holds the port

* fix(ssh): pair again over SSH when our own managed server refuses this desktop's token

* fix(ssh): key re-pairs by the refused revision, cap attempts, retry on explicit connect

* fix(ssh): keep a newer pairing stored mid-repair, count only completed reads, and offer Connect, not an access link, for managed servers

* fix(ssh): judge a re-pair only against the pairing still stored; Check connection retries a managed server's pairing

* fix(ssh): a pane whose managed server refuses this desktop says why and offers Reconnect, instead of spinning

* test(e2e): live failure path for a refused managed pairing

* fix(ssh): a Connect that re-paired answers from the new pairing, not the retired one

* fix(ssh): only an explicit Connect lifts a managed re-pair's hold and cap; say SSH access can pair again

Startup restore reaches ssh:connect with automatic: true and went through the same
ensureTunnel that cleared the hold, so repeated automatic reconnects could bypass the
three-attempt cap. The reset now lives in connectTarget's explicit branch.

The Shared Server Access footnote now says revoking there doesn't stop someone with
SSH access to this computer from pairing again.

* ci(e2e): route the managed pairing-repair spec with the managed-host remote sources

* fix(ssh): the repair gate decides whether a managed tunnel builds, not the build

A refused shared-control open rebuilt the tunnel (SSH execs, forward, handshake, wake)
before the held failure or the attempt cap was consulted, so reopens on a blocked
server rebuilt without limit. buildWithPairingRepair now asks the gate first: a failure
stands for the refused pairing revision for 15 s, or through the window once the cap is
spent, and every caller gets it without reaching the host.

- A connect during an in-flight repair keeps its reset (per-environment reset epoch).
- An unpaired answer fails at once; it is not a moved port, so no re-read or wake.
- A refusal that only rebuilt the tunnel no longer reports "pairing changed".
- WSL and cloud VMs no longer say Orca tried to pair over SSH, and Check connection
  only re-pairs servers managed over SSH (runtimePairingRepairRoute).
- Translate the new strings; route the pane-banner files to the managed-SSH e2e lane.

* fix(ssh): fence a status re-pair follow-up to the same server, and hold a refused repair under its own revision

- getRuntimeEnvironmentStatus followed any pairing change mid-probe, past a caller's
  expectedEnvironmentPairingRevision fence. It now follows only for an unpinned caller and only
  a new pairing of the same SSH-managed server under its pinned host key.
- recordRefusedAfterRepair stood under whatever revision was stored now, so a newer pairing
  stored mid-refusal was blocked untried; it stands under the repaired revision.
- The WSL restart test expects the refused-pairing classification main's WSL path now reaches.

* fix(ssh): a re-pair hands its callers the new credentials, and a failed one judges only the pairing it tried

- ensureOrcadManagedTunnel returns the environment as stored once the tunnel stands. The
  terminal census and a deploy's already-current result use it, so after a re-pair they
  no longer authenticate with the refused token, which read as an unverifiable census and
  refused restarts, updates and stops.
- The shared-control refusal handler blocks the status owner only while the failure stands
  for the pairing stored now (managedOrcadPairingFailureStands). A newer pairing stored
  meanwhile keeps its owner and retries.

* fix(ssh): migrations commit with the pairing the tunnel stands on, and a Connect mid-handshake stays reset

- Conversion binds the environment ensureOrcadManagedTunnel returns, so the destination, the
  server project reads and the result use the re-paired credentials (the migration mark is kept).
- A delta move gets its destination only from connectDestination, which opens the tunnel and
  builds the destination from what it returns; a pre-ensure destination can no longer exist. A
  tunnel that cannot open keeps the delta journaled and the host marked for the next move.
- buildWithPairingRepair takes the reset epoch when it starts, so a refusal after the re-pair
  cannot reinstate a failure that a Connect pressed during the handshake cleared.

* refactor(ssh): one module owns a shared control socket's environment policy, keeping request-connections under its line limit
2026-10-19 22:45:25 -07:00
Neil e278bc9a17 feat(orcad-migration): unsaved drafts stay on this desktop and are renamed to the server on commit (#30894)
* feat(orcad-migration): unsaved drafts stay on this desktop and are renamed to the server on commit

* fix(orcad-migration): refuse clashing drafts from the rename's own collection; stricter live spec

* fix(orcad-migration): stamp renamed drafts with the server, rename at startup after a cut-off commit, keep draft refusals

* fix(orcad-migration): retain only after a startup rename is durable; startup survives an unreadable journal

* fix(orcad-migration): a cut-off commit is renamed and retained at startup, so the rename runs once

* fix(orcad-migration): one retain per migration at a time; no rollback that could drop newer edits

* test(e2e): check the visible draft in whichever editor a large file opens in

* fix(orcad-migration): startup retains only migrations of hosts this profile owns

* ci(e2e): route the editor-drafts migration spec to the sources it covers
2026-10-19 22:44:43 -07:00
Neil 937d2d3806 Share installed desktop and browser assets (#31010)
* Share installed browser assets with the desktop renderer

* Update packaged runtime fixtures for shared renderer assets

* Verify retained cloud clients in the shared renderer layout
2026-10-19 22:31:59 -07:00
Neil 84a4526508 Overlap macOS notarization with serial architecture staging (#31006) 2026-10-19 22:31:50 -07:00
Neil 1466deae45 Share identical packaged ripgrep binaries across server targets (#31005) 2026-10-19 22:16:37 -07:00
Neil acc6354363 Shrink feature tour animations without changing pixels or timing (#31012)
* Shrink feature tour animations without changing pixels or timing

* Update README media links for lossless animation assets
2026-10-19 22:11:56 -07:00
Neil 09fd0bec5a Exclude cloud service sources from desktop packages (#31004) 2026-10-19 22:03:36 -07:00
Neil 85e690ff60 fix(provider-process): kill a provider group its SIGKILLed supervisor leaves behind (#30997)
* fix(provider-process): kill a provider group its SIGKILLed supervisor leaves behind

* fix(provider-process): re-read a provider start in its read second; poll instead of sleeping in tests

* fix(provider-process): keep the report channel through the Codex session adapter; re-prove same-second starts

* fix(provider-process): never kill a leaderless provider group; list guard files in the CLI project
2026-10-19 22:03:22 -07:00
Jinwoo HongandE2E Test 23d68112df Restored terminals named only on their tab reattach after a restart (dark, layout-owner build only) (#30986)
* fix(layout): restored terminals named only on their tab reattach after a restart (dark, layout-owner build only)

In a layout-owner build, a terminal recorded only on its tab (an older session, or an SSH tab
whose relay session was never visited) started a new shell after a restart and orphaned the old
one, and an unvisited SSH tab lost its relay session id on the next save.

- Main's Loader binds a one-pane tab's own terminal to its unbound pane in a workspace that was
  live at shutdown, as the window's reconnect does, so main's frames and saves hold it.
- The window's reconnect writes the restored row terminal again: ptyId is a field the frame merge
  leaves to the window, so main's frames never set it on a restored row.

* fix(layout): main binds a restored tab's own terminal once at load, not on every Loader run (dark, layout-owner build only)

The Loader re-derived the binding on each load from fields the window rewrites live, so an exit
main recorded could be undone. A switch-gated Store load repair now binds a one-pane tab's own
terminal to its unbound pane, as the window's reconnect does, and main's saves keep it. It skips
a terminal another mounted pane or a tab-bar tab holds, and a paired server's partition.

* fix(layout): an SSH workspace's restored tab binds only its relay session id; repair runs after the load purge (dark, layout-owner build only)

* test(layout): the reconnect gate test expects the row ptyId write; cover the repair's purge order and SSH repo predicate

---------

Co-authored-by: E2E Test <e2e@test.local>
2026-10-20 00:41:41 -04:00
Neil 67eb94a918 perf(release): overlap server checks and share the mobile bundle (#30990)
* perf(release): overlap server qualification and reuse mobile bundle

* fix(ci): preserve draft suppression for independent compatibility checks

* test(release): update template download cache policy contract
2026-10-19 21:29:11 -07:00
Neil 972d6dcb0a Speed up macOS updates by avoiding background installer throttling (#30944)
* fix(updater): avoid macOS background installer throttling

* fix(updater): harden installer scheduling build and diagnostics
2026-10-19 20:41:54 -07:00
NeilandClaude 8de37199e2 fix(runtime): a failed orphan adoption write rolls back instead of keeping the adopted tabs (#30942)
* fix(runtime): a failed orphan adoption write rolls back instead of keeping the adopted tabs

The adoption rollback ran outside the store's durable transaction, so the store
rebased it like a stale renderer write below the topology fence and kept the
adopted tabs, layouts and incarnations; the next save then persisted them.
Run the adoption through runDurableMutation, whose rollback phase skips that
rebase, and refuse if a queued write changed the session it was checked against.

Co-authored-by: Claude <noreply@anthropic.com>

* fix(runtime): an adoption into an unwritten host partition is not refused as a conflict

Co-authored-by: Claude <noreply@anthropic.com>

* test: run the orphan adoption rollback test in the SQLite runtime project

Co-authored-by: Claude <noreply@anthropic.com>

* refactor(runtime): check and write an orphan adoption inside the store transaction

A snapshot comparison could not see a queued binding write that edits the
session in place. Plan the adoption against the session the transaction
reads instead, so queued writes are validated against rather than guessed at.

Co-authored-by: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-19 19:58:14 -07:00
Neil 28d9f959de Run selected SSH tests instead of the whole suite on PRs (#30928)
* fix(ci): run only selected managed SSH specs on pull requests

* fix(ci): retain full SSH coverage for routed shared helpers
2026-10-19 18:21:53 -07:00
Brennan Benson a817602e7c chore(ratchet): prune the tabs-group-actions layout-writer baseline (#30869)
#29150 removed one layout write from tabs-group-actions.ts without pruning
its baseline entry, so every PR's static analysis fails with 'baseline still
allows 13'.
2026-10-19 18:07:35 -07:00
Neil 8316531ab3 fix(diff-comments): a review note on a twin worktree saves to the open copy and names its host (#30855)
* fix(diff-comments): a review note on a twin worktree saves to the open copy and names its host

* fix(diff-comments): rendered notes and queued saves follow the open twin

* fix(diff-comments): one host-qualified copy per change; async delivery finds its copy by note id

* fix(diff-comments): a copy is its row's own host; unstamped rows keep the owner their repo names

* fix(diff-comments): the open host picks a twin by the owner index and never falls back to another host's copy

* fix(diff-comments): a worktree's saves stay in one queue when its row gains a host stamp

* fix(diff-comments): one queue and floor per worktree id; paired servers stay separate copies

* refactor(diff-comments): keep main's queue and floors; scope rows only when two hosts publish the id

* fix(diff-comments): per-twin rollback floors that follow the mutation chain; write module split out

* fix(diff-comments): a queued save finds its copy by the list its chain produced

* fix(diff-comments): name the row's own host on every save, and treat a refused save as failed

* fix(diff-comments): a queued save keeps its rows' identity even before a twin appears

* fix(diff-comments): a queued save follows its row when a listing stamps it with the write's host

* fix(diff-comments): refuse a write when the open host's copy isn't listed and another host's is

* fix(diff-comments): a lone unstamped row's queued save survives a stamping refresh
2026-10-19 17:58:45 -07:00
Neil d42e6e0813 fix(renderer): host writes route only through the strict owner, and twin projections stay on their host (review-5 #2 #9 #11) (#30890)
* fix(renderer): host writes route only through the strict owner, and twin projections stay on their host (review-5 #2 #9 #11)

A selected ssh: host that two paired servers proxy now resolves ambiguous at the one
selected-host resolver, so no caller can fall back to this app's own SSH connection.
File Explorer and SSH mutation expectations derive the full {endpoint, place} pair from
resolveOwner, so a server folder's nested SSH target stays the write's host. A local
lineage write projects onto its own host's rows only. The id-only store-write ratchet
now also covers persistence repo-row lookups by id alone (review-5 #10's shape).

* fix(ratchet): the id-only lookup scan also sees optional chaining and destructured ids

* fix(renderer): an unstamped folder keeps reading its host from its repos; editor focus fallback left for a follow-up

* docs(renderer): the File Explorer owner comment names its folder fallback
2026-10-19 17:46:01 -07:00
Neil cbb9f369b8 Stop running the experimental layout suite on every matching PR (#30907) 2026-10-19 16:16:50 -07:00
Jinjing f14475caa6 fix(browser): label popups from committed document origins (#30540)
* fix(browser): label popup chrome from committed documents

* fix(browser): keep opaque popup documents neutral

* test(browser): prepare real popup identity validation

* test(browser): apply background policy in popup probes

* test(browser): fix popup title and view-lifetime checks

* fix(browser): ignore subframe failures in popup chrome

* rm draft docs

* fix(browser): finish popup probe teardown after signal exit

* test(browser): use typed popup probe window access

* fix(browser): prevent automatic popup title updates

* commit

* fix test
2026-10-19 16:13:54 -07:00
Neil 92eb9790bc ci: fail the static job on unformatted files (#30863)
* ci: fail the static job on unformatted files

Runs oxfmt --check over the same tree pnpm format writes (~2s), and makes the
pre-commit hook format markdown and YAML so it covers what CI now checks.

* ci: route docs-only and workflow-only diffs through the formatting check

The format check reads the whole tree, so a diff that skipped the static job
could land drift that fails every later PR. Any changed file oxfmt formats now
selects static analysis (not typecheck or units), like the mobile-only route.
The path detector checks out .oxfmtrc.json for its ignores. The hook no longer
fails a commit whose only matches oxfmt ignores (lockfiles).
2026-10-19 16:08:52 -07:00
Neil daad41f892 perf: preserve matching Terminal settings sections during search (#23164)
* perf: preserve matching Terminal settings sections during search

* fix(settings): keep a retained Terminal sessions section fresh and share the section stack

Move the search-stable section wrapper into SettingsSectionStack so every settings
pane keys sections by their own element key instead of the array index, and subscribe
ManageSessionsSection to the daemon session inventory invalidation signal so a kill
routed through another surface still reaches a section that no longer remounts.

Restates the read-count oracles as node identity plus non-growing read counts so a
future freshness fix does not read as a regression.

* test(settings): mock the Windows administrator section in the Terminal section-lifetime suite

* style: format Terminal section-lifetime reliability gate
2026-10-19 15:39:20 -07:00
Brennan Benson 3d0973d0d4 A chat's visuals and sent images live as long as the chat (#30413)
* fix(native-chat): a chat's visuals folder lives as long as the chat

A chat's scratch folder was deleted once the chat had gone 30 days
unused, so an old chat's inline visuals turned into "Visualization
unavailable" while the chat and its history stayed. The common pattern
keeps a chat's files for as long as the chat and never deletes them on
an "unused for N days" timer.

The sweep now removes only folders no chat record maps to, plus
leftover .expired folders older builds parked. Everything that existed
only to make the age rule safe goes with it: the per-folder save lock
and its expiry candidates, the session-lane idle check, the journal
last-activity query, and the migration's fresh-window timestamp. The
visuals skill no longer tells agents the folder is temporary.

* fix(native-chat): an image sent in a local chat lives as long as the chat

A local chat's pasted images lived in one paste folder that deleted
every file older than 30 days, sent or not, so an old chat's images
turned into label chips. Paired-server chats already keep pastes in the
host's attachment store, where the send that names a file claims it for
its chat and only unclaimed files are ever swept: the common pattern.

A local composer paste now goes into this machine's own attachment
store (starting the local chat host first), so its send claims it the
same way. The old paste folder is no longer written or swept: messages
sent before this change may name its files. Restored drafts still read
it.

An unclaimed upload now waits 30 days instead of one before the sweep
takes it. A draft names its image by this path, so the window is how
long an unsent draft keeps its image; 30 days is what local drafts had,
and it outlasts the host's send-id window so a resent draft still finds
its image.

A chat folder an older build set aside as unused (<hash>.expired) goes
back to its chat instead of being deleted.

* fix(native-chat): a terminal-backed chat's pastes are never swept

A terminal-backed chat has no journal, so no send claims its pastes and
nothing can tell a sent one from an abandoned one. Its pastes therefore
stay in the paste folder, which is no longer swept, rather than in the
attachment store, whose sweep would take them after 30 days unclaimed.
Only a local structured chat's paste (forStructuredChat) goes to the
store, where its send claims it.

* fix(native-chat): keep the attachment owner's literal kind for structured local chats

* test(ipc): expect the local chat store option on the clipboard registrar

* fix(native-chat): preserve attachment ownership and restore held files safely

* fix(native-chat): settle restored image checks independently

* test(native-chat): assert restored image outcomes without batching

* Re-check draft images when the attachment sweep removes their upload

A draft checks its pasted images once, when its composer comes back. Every
chat in an open workspace mounts its composer at launch, so that check ran
before the store sweep (60 s after host start, then every 6 h) deleted an
expired unsent upload, and nothing checked again: the chip lost its picture
with no Not kept feedback and Send stayed enabled.

The sweep now reports each removal attempt; the main process forwards it to
its window, and the draft store starts a fresh finite check for every loaded
local draft image in that folder, mounted or not. A notice is a reason to
check, never proof of absence.

* Let the app effect remove the draft re-check listener it adds

startNativeChatDraftLoad dropped the unsubscribe for the upload-removal
listener, so each recovery through the root error boundary's Retry remounted
App and added another listener. Return it so the effect's cleanup removes it.
2026-10-19 15:11:24 -07:00
Neil a388005706 fix(i18n): stop extracting <style> bodies as translatable copy (#30765)
The localization audit treated the CSS inside three feature-wall <style>
elements as rendered copy, so the extractor wrapped 7-10k character style
sheets in translate() and every locale carried them. A translation could
then rewrite the CSS, and no pack could translate them under the
per-string cap. The audit now skips <style> and <script> children; the
three sheets are plain strings again, and their keys leave all catalogs
and the runtime-required English catalog.
2026-10-19 14:17:57 -07:00
e9fa0cf95c fix: a tab close by the handle a split returned finds the new pane (dark, layout-owner build only) (#30786)
* fix(renderer): list a pane main holds before the window mounts it, so a tab close by a split's handle finds it (dark, layout-owner build only)

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

* test(e2e): the split-close spec polls through the host's "can't answer yet" terminal list

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

* fix(runtime): a pane the window lists before main binds its terminal keeps main's terminal (dark, layout-owner build only)

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

* fix(runtime): a tab close by a main-side split's handle stands on main's own record of the pane (dark, layout-owner build only)

Replaces the window-side listing and the binding fill: the window's list lags main in three
ways (built before main wrote the pane, from it before the binding, or before the window mounts
it), and only main's own layout answers all three.

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

* fix(runtime): main's relisting for a close tells no clients (dark, layout-owner build only)

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

---------

Co-authored-by: E2E Test <e2e@test.local>
Co-authored-by: Claude <noreply@anthropic.com>
2026-10-19 15:41:18 -04:00
NeilandClaude 22b23203c9 style: format the 10 files on main that oxfmt --check reports (#30828)
Co-authored-by: Claude <noreply@anthropic.com>
2026-10-19 11:58:30 -07:00
Jinwoo HongandE2E Test 40e7bd1db9 A pane main first sees when its terminal starts goes where the window split it (dark, layout-owner build only) (#30772)
* fix(terminal): a pane main first sees at its binding goes where the window split it (dark, layout-owner build only)

* fix(terminal): keep main's divider positions when taking the window's split tree

* test(terminal): main's divider above the window's new split is kept

* ci: the layout-owner switch-on job runs the hidden-terminal load spec that caught the split graft

* test(terminal): type the side-by-side fixture as a split so ratio spreads typecheck

---------

Co-authored-by: E2E Test <e2e@test.local>
2026-10-19 14:52:07 -04:00
Neil eedca5ee1c fix(persistence): worktree meta and lineage writes take the row's host, not the id (R4-2b) (#30811)
* fix(persistence): worktree meta and lineage writes take the row's host, not the id (R4-2b)

* fix(persistence): review fixes for host-targeted worktree writes

* test: SAFETY notes on partial runtime store mocks

* test: drop imports duplicated by the rebase onto #30797
2026-10-19 11:39:54 -07:00
Neil 86ef00ad55 fix(projects): a project move names the row it moves; a paired server's twin by its SSH target (#30794)
* fix(projects): a project move names the row it moves; a paired server's twin by its SSH target

* fix(sidebar): pass the header's row without an assertion; e2e helpers and focus baseline follow the row-shaped move

* fix(sidebar): a header drop re-reads the dragged row and drops nothing if its group changed mid-drag

* fix(sidebar): read live repos through a stable store reader, not a ref written during render
2026-10-19 11:28:29 -07:00
a1ea2e3978 perf(orchestration): stop recompiling a mailbox lookup on every agent title (#30792)
* perf(orchestration): stop recompiling the remote-attachment lookup on every agent title

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

* chore(orchestration): state why the attachment row casts are safe

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

* fix(ci): run the mailbox owner test in the Node runtime project

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

---------

Co-authored-by: E2E Test <e2e@test.local>
Co-authored-by: Claude <noreply@anthropic.com>
2026-10-19 14:05:16 -04:00
Brennan Benson 53191250e8 Accept messages while any agent starts, and bound each startup step (#28046)
* Finish structured agent readiness before delivering held work

* Publish the Codex process before protocol startup

* Publish ACP agent processes before session startup

* Publish Pi at spawn and settle signed-out starts once

* Adapt permission fixtures to startup lifecycle

* Keep model discovery for chats before their first start

* Use one host readiness promise for provider operations

* Await provider opening in the execution host

* Await Claude initialization in host-owned open

* Observe Claude readiness through the host status

* Keep optional readback behind host readiness

* Recheck launch permission after host readiness

* Preserve the awaited child during permission revalidation

* Await Codex protocol open under host ownership

* Await ACP protocol open in the host

* Derive ACP opening from its protocol session facts

* Exercise unanswered ACP options with an explicit silent route

* Await Pi open and fail signed-out construction

* Read Pi catalog facts from awaited opening

* Keep the Pi fixture exit observer

* Record failed owned opening before the first message

* Abort a matching provider open when its child exits

* Cover provider exit during an unanswered host open

* Cover explicit failure facts on a host-stopped child

* Capture message choices per accepted send and preserve picker state

* Apply accepted Claude message choices at prompt handover

* Apply captured message choices at provider dispatch

* Bind native chat choices to accepted message dispatch

* Reset Codex turn choices for each captured message

* Resolve Codex message defaults from native facts

* Align message choice and Pi default fixtures with provider dispatch

* Align option fixtures with saved message choices

* Match Codex message choices to the active turn origin

* fix: let pending commands reach handover refusal

* Abort pre-prompt message choice writes with host-owned wait

* Verify message choices through provider input boundaries

* Keep legacy mobile test hosts explicit about message choices

* Keep startup waits cancellable and preserve operation order

* Derive Codex startup exits from host phase

* Open Codex protocol in background-task test fixtures

* Keep ACP startup ownership until handshake completes

* Run ACP opening catalog fixtures with Node SQLite

* Verify saved ACP picks without live option writes

* Keep ACP catalog bookkeeping from failing an options read

* Restore signed-out restart and fail Pi startup faults

* Run Pi opening catalog fixtures with Node SQLite

* Preserve message choices across cold clients and kept cards

* Remove the unused provider option restore ledger

* Clarify launch choices and legacy option hydration

* Remove remaining restore-ledger forwarding and assertions

* Remove unused ACP and Pi restore bookkeeping

* Keep live choices and delivery order while agents start

* Drop retired startup revision from Codex opening

* Keep live-option cancellation until message choices and clarify catalog reads

* Hold ACP option replies in live-write cancellation regressions

* Remove retired Pi startup revision and match saved-pick behavior

* Check running choices and retained rewind messages after delivery integration

* Apply startup picks and let parked messages release operations [skip ci]

* Remove unused Codex opening report field [skip ci]

* Keep ACP restoration facts local to the provider [skip ci]

* Apply Pi startup picks after readiness and remove unused open facts [skip ci]

* Cover goal changes during the held Codex message wait [skip ci]

* Save a waiting startup pick after Stop ends its opening [skip ci]

* Save choices when startup ends or a replacement is still opening

* Check saved choices separately from Claude startup reports

* Release scripted Codex startup before awaiting queued picks

* Check the saved picker selection in per-message choice fixtures

* Exercise current choices and eager client capabilities in runtime fixtures

* Reuse runtime fixture reads and type the fake connection directly

* Align startup fixtures and preserve observed Claude exits

* Honor queued send authorization in Claude runtime fixtures

* Open Codex protocol sessions before Stop fixture dispatch

* Wait for the opened Codex child before runtime notifications

* Keep repeated permission picks usable and align Codex fixtures

* Validate pending picks against the live option state

* Order startup picks before later messages and retain flow regressions

* Keep reviewed lifecycle contracts while pruning duplicate tests

* Save picks after a proven exit without requiring a child

* Type the startup reply fixture against the Claude control contract

* Keep Codex startup flow contracts and remove overlapping adapter tests

* Restore the committed dependency lockfile after test tooling

* Keep startup recovery flows and remove duplicate Pi handshake cases

* Cover both Pi startup choices in the delivery flow

* Match the Pi prompt entry recorded by the scripted wire

* Honor the before-input callback in the startup reply fixture

* Keep message choice flows and remove overlapping selector tests

* Keep startup option menus independent and show the saved choice

* Exercise startup picks across real menus and the runtime

* Match startup picker fixture to saved message choices

* Keep saved effort visible when picking a model during startup

* Include the running-turn scope in repeated Stop fixture

* Use the current attach contract in import fixtures

* Keep the Codex journal translator within its line limit

* Match ACP startup fixtures to saved picks and late Stop behavior

* Check startup effort after saved picker choices are acknowledged

* Complete Stop target types and keep the effort regression concise

* Use requested picker values in the effort regression acknowledgement

* Require owned provider spawn and one host-awaited open (from #28055)

* Restore the live checks the startup cleanup's deleted tests pinned

One assertion each, in the surviving suites: a stale-generation exit is
ignored, a failed create lets its sink go, a readback before open is saved,
an idle stop that fails is retried, a no-pick Pi start teaches its default,
an older client's pre-spawn failure and packed answer, an undrivable chat
stays readable, the resting slash menu stays on its own host, the main agent
reads apart from subagents, restore publishes status, naming runs off the
host's status feed, and a throwing logger never reaches the operation.

Also corrects the reliability gate note that claimed the send-idempotency
suite replaced the refusal-retry tests.

* Read a rewind in doubt outside the chat's serialize and apply it in the commit

The started settle and the attach's ready-child path read the provider's
history first, then apply what it proves in a short commit that re-checks
the rewind it read.

* Accept messages while the agent starts: run the start outside the chat's serialize

The chat's serialize was both where a send was admitted and where its agent
started, so every slow step of a start (closing-child join, lease
reconcile, recovery probes, resolvers, history read, spawn, sink drain,
provider dispatch) held every later send.

A start now registers in one short step before its first wait: its abort
controller (which Stop, close and quit already reach outside the lock),
its startup clock (the 10-minute ceiling now counts from here) and quit's
drain. Every wait after runs outside the lock, raced with that signal, and
every write is a commit that first re-checks the start is still the
session's. A start that loses the check is late: its spawn is closed
through the post-acquisition failure path and nothing is handed to it.

- The delivery loop's prepare returns a start handle; the loop awaits the
  start outside, then re-prepares. Handover writes the pending row and
  begins the dispatch abort handle in one commit, calls the provider
  outside after re-checking the child, and resolves in a second commit.
- Ready operations claim a start instead of running one in their step.
- A client attach runs the same steps under one serialize, as before.
- An exit reported between spawn and publish is held for the publish
  commit, which ends the child it indexes.
- An unproven close still fails only what was queued when its start was
  asked for.

* Test that a send is accepted at every wait an agent start makes

One whole-flow test holds the start at each wait in turn (closing-child
join, reconcile probe, recovery re-probe, resolver, spawn, sink drain,
open, started recovery, pending row, provider call): a second send is
accepted at once, a Stop is recorded without waiting and nothing reaches
the provider, and what was sent goes to the published child once, in
order. Also quit before spawn, concurrent sends, a lease left recovering,
and an idle sweep racing a send.

* Bound each startup step on its own and drop the startup silence rule

A start no longer ends after 60 s without output: output is not protocol
progress. Each protocol step now has its own bound where its handshake
lives, and one 10-minute ceiling from registration stays as the outer bound.

- Codex: initialize 15 s and thread start/resume on the connection's 30 s
  default again, instead of the startup ceiling.
- ACP: initialize 60 s, authenticate 120 s, session new/load/resume/fork
  300 s; turns stay unbounded and option writes keep their caller's bound.
- Claude: the initialize answer has a 60 s bound; past it the open fails
  with the ordinary startup-failure words and the host ends the child.
- The onOutput plumbing that only fed the silence rule is removed.

* Count Pi ready once it reports its state

Pi's start no longer waits for its model listing and slash commands.
The open returns after get_state and the unknown-provider check; the
listing then arrives as an options report (with the catalog listing the
start resolved) and the commands as a later publish. An empty model list
no longer fails the start as signed out: a signed-out Pi runs the unknown
provider, which still fails it, and a prompt it cannot run is refused
with the sign-in words by the existing prompt path.

get_state keeps the connection's 30 s request bound instead of the
startup ceiling.

* Test that model and effort picked while a chat is created ride its first message

A slow host's create reply arrives after the user picked a model and effort. The picker's view
layers the launch's held picks over its seed and the host's saved selection, and every send
carries what the picker shows, so the first message carries the picks whether the host's status
names its selection or not, and while the last pick is still on its way to the host.

* Start a phone's new empty chat on its first message

The phone's new chat is an agent.launch with no prompt, which always created the chat with its
agent started, while desktop chats start on the first message. The phone now advertises the
create-without-start capability, and a launch with no prompt from a caller advertising it creates
the chat at rest, keyed in the ledger by its create intent as a client's create is. Launches with
a prompt, the CLI (which advertises nothing), orchestration workers and ensure still start at once;
an older host ignores the capability and stays eager.

* Take main's lockfile; the branch changes no dependencies

* Restore main's exact text in files the merge only reformatted

The pre-commit formatter rewrote 37 of main's files staged by the merge, though this branch never changed them, pushing two past the line limit. Committed without the hook because the hook would reformat main's text again; CI lints the result.

* Fit the startup tests to the owned-spawn, host-awaited-open contract

Test doubles gain the mandatory open and an acquisition generation, the
removed started event gives way to the rig's proveStart, and two literal
types are tightened for CI's typecheck. A Stop during a held start writes
no host note on this branch: the taken-back send draws its own
stopped-before-start row, which the startup-attempt test now asserts.

* Fit the remaining CI checks to the startup limits and current releases

- Run agent-launch-at-rest.test.ts in the SQLite Node runtime project.
- Pin the send-options cross-version case to v1.4.228, the first published release with agent-session.send-options.v1; v1.4.226 was never pushed.
- Load the baseline's isStructuredTab from src/shared (moved in v1.4.228), falling back to the renderer path for older baselines.
- End the never-proven start with a short startup ceiling: the silence limit it relied on is gone, and the ceiling counts from registration.

* Read the option stamp from the chat's one host-execution selection

* fix: preserve startup authority and accepted agent choices

* Retain dispatch authority and reconcile accepted options

* fix(native-chat): preserve accepted choices and cancellation authority

* Restore established client selection flow

* Preserve provider output observation and typed startup fixtures

* fix: preserve initial Claude launch choices and effort readback

* test: complete Claude catalog session fixture

* Preserve incoming main files after merge formatting

* Preserve compact handover through startup merge

* Type instant startup fixtures before their open is supplied

* Keep browser sign-in under its existing expiry owner

* Finish the main merge: retired-model display, start rewind cut, new tests

A model the Claude CLI retired is reported again, so the picker shows what the child runs and the
next send moves the chat off it. The start reads a rewind in doubt against the handle it answered
with, the record its commit then applies to. Main's tests follow the start's open: the admission
fixture holds a real turn, and a sign-in close during setup fails the open.

* Type the start's rewind record as the store returns it

* Type the Sleep test's held open as the provider open it replaces

* Sleep counts a chat stopped once the start it aborted leaves it at rest

A start runs outside the chat's lane, so Sleep's step can run before the start it aborted has
settled; it then re-reads the chat once that start is done, instead of reporting it unverified.

* Keep Sleep's start-abort bookkeeping beside the rest of Sleep's stop
2026-10-19 10:53:46 -07:00
Neil 236eba1ecf fix(persistence): folder, group and session-state store writes name their host or why they needn't (R4-2c) (#30797)
* fix(persistence): session, automation-label writes take the workspace's host; exempt minted-unique folder and group ids (R4-2c)

* fix(persistence): keep the session removal's spill rule; the twin guard was unreachable (review)
2026-10-19 10:50:25 -07:00
Neil 30f5bef947 fix(ci): background steps are single run steps, so no action reads the event payload while the runner rewrites it (#30795)
* fix(ci): background steps are single run steps, so no JavaScript action reads the event payload while the runner rewrites it

* test(ci): guard the background slot cap and pin the payload-free allowlist
2026-10-19 10:28:19 -07:00
Neil baf123eda8 fix(persistence): repo-row store writes take the row, not the id; ratchet the rest (R4-2a) (#30756)
* fix(persistence): repo-row store writes take the row, not the id; ratchet the rest (R4-2a)

* fix(persistence): host-filter the runtime nested import; widen the id-only write ratchet (review)

* fix(runtime): nested import keeps matching runtime-stamped native rows on a paired host

* fix(persistence): class-scope the ratchet param exemptions; update a new main test to the target shape

* fix(ipc): fence a project-group move by the resolved twin host, so an SSH twin is not WSL-fenced

* test(ci): pin the id-only store write ratchet step to the static phase
2026-10-19 10:14:07 -07:00
Neil d4813638d2 fix(orcad): orca open-url opens the page on the paired desktop (#30782)
* wip: orcad client-hosted browser pages

* wip: tests and renderer placement

* wip: engine-free client tab queries; keep paired server intent

* wip: one logical tab list for orcad engine tabs and desktop-hosted pages

* wip: one selection rule for engine tabs beside desktop-hosted pages

* refactor: one owner per mode for orcad tab selection; hide reopen-on-server without a server browser

* fix: an engine tab create or switch takes workspace selection from desktop-hosted pages

* refactor: one logical tab list and one selection rule for orcad engine tabs and desktop-hosted pages

* fix: keep the engine's per-workspace current tab beside desktop-hosted pages

* fix: an unscoped current tab matches unscoped automation routing

* fix: retained desktop-hosted pages stay listable and closable after their desktop leaves
2026-10-19 10:04:09 -07:00
NeilandClaude 36bda5efe5 fix(pty): an exit proof names its incarnation; a spawn is tracked from its reply (#30754)
* fix(pty): an exit proof names its incarnation, and a failed discard keeps ownership

A same-id respawn could be retired by its predecessor's exit proof; finishPtyShutdown now refuses
a proof whose incarnation is not the record's, and retireRejectedPty is scoped to the rejected
spawn's incarnation. A spawn discarded after a failed save no longer drops ownership when its
shutdown failed: it reads unverifiable until a later stop reaches it.

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

* fix(pty): a failed discard leaves a same-id replacement's verdict alone

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

* fix(pty): a lost discard reply does not overwrite an observed exit

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

* fix(pty): a failed discard tracks the PTY it could not stop; test the incarnation refusal

The discard runs before the spawn is published, so it records ownership and incarnation before
stopping; a failed stop now leaves a handle. Every exit proof states its incarnation.

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

* refactor(pty): track a spawn from its reply, so only an exit or a proven stop removes it

Ownership and incarnation are recorded at commit start instead of at publish, before the binding
save. The discard no longer re-records anything, so an exit observed during the save stays exited.

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

* fix(pty): admit a same-id respawn despite a predecessor's verdict; retire an exit seen before commit on its fence

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

* fix(pty): admit a fresh spawn only past the runtime's incarnation-scoped start fence

Replaces the id-only verdict check, so a same-id respawn is tracked and an incarnation that exited
before its commit is not resurrected; registration no longer has to retire it.

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

* fix(pty): a proof that names no incarnation still retires; tidy kill-time capture and adopted re-record

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-19 09:59:24 -07:00
Neil 40ac8e5f9b fix(remote): keep a paired host's undelivered tab close across a reload (#30758)
* fix(remote): keep a paired host's undelivered tab close across a reload

* fix(remote): show a tab again when its held close expires

* fix(remote): one choke point shows every held close the store drops

* fix(remote): keep held closes while the pairing is unknown; tidy dispositions

* fix(remote): save a held close for a long path-named editor tab
2026-10-19 09:40:17 -07:00
Jinwoo HongandClaude 36913fd4f5 test(e2e): a phone lists the desktop's managed SSH workspace as the desktop sidebar does (switch on) (#30718)
* test(e2e): a phone lists the desktop's managed SSH workspace as the desktop sidebar does (switch on)

* 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>

* test(e2e): route the SSH phone feed list spec from its sources

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-19 09:19:39 -04:00
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
Jinwoo Hong 9def13dde2 ci: run the layout-owner switch-on specs as an advisory PR check (dark, layout-owner build only) (#30582)
* ci: run the layout-owner switch-on specs as an advisory PR job

Build the e2e app with ORCA_LAYOUT_OWNER=main through the existing e2e.yml
(new layout_owner input, read only by the app and orcad builds), run the
layout oracle, its positive control and the layout-owner acceptance specs
with the save-divergence log on, and upload a summary by kind and spec.
Not in the PR aggregate's needs.

* ci: summarize divergence logs without a test report, and drop file-path ids from kinds

* ci: switch-on job adds the stream-loss spec and the managed-SSH Docker lane, drops diff-word-wrap (fails switch off), and names each looped test in the summary

* test(e2e): editor-model-host-isolation opens both files through the store's open action, so a layout-owner build's main holds the tabs

* ci: the divergence summary skips a cut-short test report instead of failing

* ci: re-run PR checks on a merge ref that includes the main typecheck repair (#30680)
2026-10-19 06:41:26 -04:00
Jinwoo HongandClaude 78bad24d33 test(e2e): nine sidebar specs create workspaces and links through main so they pass with the switch on (#30667)
* test(e2e): create reveal-spec workspaces through main so they pass with the sidebar switch on

The agent-send picker and host-section reveal specs injected fake repos and
workspaces into the window store. With ORCA_SIDEBAR_INPUTS=main the sidebar
lists main's rows, so those workspaces never appeared. The SSH host is now an
unreachable target seeded into the stopped profile, and the paired-server host
is a real headless orca serve host; pins, lineage and notes go through main.

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

* test(e2e): type the seeded hosts as execution host ids

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

* test(e2e): poll the reveal specs' page setup instead of awaiting it across CDP

An evaluate that awaits a preload promise sometimes fails with "Resulting
promise was garbage collected" in switch-on runs. evaluatePolled runs the work
in the page, polls its outcome, and still fails on a rejected or failed write.

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

* test(e2e): parent reorder spec creates its workspaces and links through main

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

* test(e2e): lineage specs create their workspaces and links through main

seedLineageScenario names and orders the two seeded workspaces and links them
with assignWorktreeParent. Extra families and surrounding rows come from a
fresh repo with real worktrees; pins and stale instances are main writes.

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

* test(e2e): default-branch and downward-drag specs create their workspaces through main

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

* test(e2e): reveal and active-delete specs create workspaces through main

The 60-row reveal, the folder-workspace reveal and both active-delete tests
now use real workspaces and folder workspaces added through main, ordered with
metadata writes. The deletes go through the real removal path, so the row
removal sampler waits by time rather than a frame count.

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

* test(e2e): address review on the switch-on spec follow-up

writeWorkspaceMeta now fails unless main lists the written values; the sleep
test's parent is a real worktree instead of a store-faked one; the legacy
inline-lineage case stays window-only and is skipped with the switch on;
the parent-reorder collapse key is built from the row's host.

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

* test(e2e): read-back reports an unlisted workspace and compares values structurally

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

* test(e2e): wait for the added repo before showing its worktrees

updateRepo returns false without writing when the store does not list the repo
yet. On CI the add's own refresh superseded the helper's fetch, so the visibility
write was skipped and every discovered worktree stayed hidden. The helper now
waits until the store lists the repo and fails if the write is refused.

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

* test(e2e): name the repo wait in its failure message

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

* test(e2e): size the setup poll budget to the number of main writes

evaluatePolled waited a fixed 10 s, sized for one write. Linking 53 children takes
about 8 s locally and 36 folder workspaces about 4.5 s, so CI ran past it ("page
work never settled"). Bulk setup now passes a budget of 10 s plus 500 ms per write.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-19 06:29:17 -04: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
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
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 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
Jinjing d2bb60735b test(browser): document blocked popup compatibility gates (#30537)
* test(browser): expose native popup admission blockers with faithful app events

* test(browser): probe Electron popup identity and inherited sandbox API limits

* test(browser): handle requests without associated contents in popup probe

* docs(browser): record blocked Electron popup compatibility gates and test routing

* docs(browser): keep popup evidence in PR
2026-10-18 23:00:10 -07:00
Neil 07bf286c7b build: use the shared pnpm store on Windows by default (#30565)
Without the symlink privilege pnpm links the global virtual store with junctions,
which standard users can create. Verified on Windows 10.0.26200 at medium
integrity with no SeCreateSymbolicLinkPrivilege and Developer Mode off: the
install, both native runtime checks, a ConPTY spawn and windows-process-tree all
pass. ORCA_PNPM_GLOBAL_VIRTUAL_STORE=0 still opts out; CI stays off.
2026-10-18 22:32:45 -07:00
Brennan Benson 9a662c3980 Native chat: rewind works at any chat length (#30394)
* fix(native-chat): rewind works at any chat length

Rewind copied the kept part of the chat into its saved record and, for
Codex, re-read and re-translated the whole provider history before and
after the revert. Both were bounded (10,000 rows / 2 MB host, 1,024
entries / 2 MB Codex), so long chats were refused with "too long to go
back in".

The record now names the cut; the kept rows are derived from the
journal, which keeps the old epoch until the rewind completes. Codex
reads only the turns it undoes and trusts thread/revert; recovery
proves an applied revert from the newest turn alone and never
truncates otherwise.

* test(codex): type the rewind fixture's page params instead of asserting them

* fix(native-chat): rewind recovery reads the provider handle only from a record that exists

* test: run the rewind cut test in the SQLite runtime project

* fix(native-chat): omit rejected submissions from rewound history
2026-10-18 21:35:31 -07:00
Jinjing 43a6cfe538 Confirm before bulk-closing tabs (#30551)
* Confirm bulk tab closes before closing tabs

Close Others, Close Tabs to the Left/Right, and Close All Editor Tabs now
show a confirmation dialog first, so one misclick or shortcut no longer
closes a group of tabs at once. Pinned tabs are still skipped.

Tabs are selected again after the user confirms. If a tab was pinned, moved,
or removed while the dialog was open, it is left alone. Tabs created after
the request are not closed.

The shared confirmation lives in use-bulk-tab-close-confirmation.ts and is
used by both the tab bar and the tab group. The tab group hook now reads
the store directly instead of taking group and groupTabs as props.

* Confirm bulk tab closes before closing tabs

Close Others, Close Tabs to the Left/Right, and Close All Editor Tabs now
show a confirmation dialog first, so one misclick or shortcut no longer
closes a group of tabs at once. Pinned tabs are still skipped.

Tabs are selected again after the user confirms. If a tab was pinned, moved,
or removed while the dialog was open, it is left alone. Tabs created after
the request are not closed.

The shared confirmation lives in use-bulk-tab-close-confirmation.ts and is
used by both the tab bar and the tab group. The tab group hook now reads
the store directly instead of taking group and groupTabs as props.
2026-10-18 21:33:03 -07:00
NeilandClaude 24e218c101 refactor(shared): move orcad-migration-* into src/shared/orcad-migration/ (#30536)
Pure git mv plus import and path-literal rewrites. Two files #29339 edits stay put:
orcad-migration-dormant-state-validation.ts and orcad-migration-manifest-contract.test.ts.

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-18 21:28:03 -07:00