Commit Graph
15173 Commits
Author SHA1 Message Date
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
Neil 69157847e3 fix(remote): a removed server's saved tab closes go with it; a same-machine re-pair keeps them (#30803)
* fix(remote): a removed server's saved tab closes go with it; a same-machine re-pair keeps them

* fix(remote): a re-pair move leaves expired closes behind
2026-10-19 11:16:41 -07:00
Brennan Benson 6285797284 fix(session-history): the "Reading N / M files" count reaches its total (#30683)
The scan's first pass announced its whole candidate slice (limit x 2) as files
to read, then stopped once it had enough sessions, and nothing took the unread
files back out of the total. The parse loop now owns its share of the total:
an unfiltered pass announces its list and takes back what it never read; a
filtered walk keeps announcing per selected batch.
2026-10-19 11:14:40 -07:00
Neil b99392e10b fix(orcad-migration): a pane saved in two tabs refuses on this desktop and names the tabs, not a server conflict (#30801)
* fix(orcad-migration): a pane saved in two tabs refuses on this desktop and names the tabs, instead of a server conflict

Fixes #30610

* test+copy: unique pane ids in migration fixtures; refusal names the tabs before the folder
2026-10-19 11:09:42 -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
Jinwoo HongandE2E Test 846cba6e96 feat(relay-load): drive paired-phone resumes so staging can time the step-6 phone path (#30816)
The load generator only opened invite splices, which never reach the device-resume accept or the
desktop's resume confirm. --resume-phones N installs one device credential per phone over an
invite splice, then reconnects it every --resume-interval-seconds: accept, device-resume-confirm,
and a held splice with echoes. Each attempt is a relay_load_resume_attempt line with accept and
confirm ms; failures are recorded, never fatal, so a database-block drill can run under it.

Claude-Session: f2fe8cb5-4467-4a25-8aab-98598b70791f

Co-authored-by: E2E Test <e2e@test.local>
2026-10-19 14:00:05 -04:00
zzr 95f544aedf fix(git): derive clone folder from scp-style remote path (#30763) 2026-10-19 10:58:05 -07: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
Jinwoo HongandE2E Test 9ffb153dde chore(relay): pin staging c1 and c4 to image J for the step-6 staging test (#30812)
J is main 30f5bef9 (relay sha256:21f1fead…), which adds the phonePath cell flag.

Co-authored-by: E2E Test <e2e@test.local>
2026-10-19 13:47:55 -04:00
Kelvin Amoaba ca672ef926 feat(browser): ask before a page opens another app (#30490) 2026-10-19 17:44:32 +00:00
Neil b30a798dee fix(runtime): a terminal spawn names its host; the removed-project fence keys by host (R4-2) (#30798)
* fix(runtime): a terminal spawn names its host; a removed project's fence is per (project, host)

acquireWorktreeTerminalSpawn takes the spawn's HostWorktreeTarget instead of a
bare id plus an optional connectionId that defaulted to this machine. The
removal fence records ExecutionHostIds and only counts surviving rows that run
in this process, so a paired server's same-id residue row no longer readmits a
removed local project's spawn.

* fix(runtime): keep every surviving row in the spawn fence; normalize the spawn connection (review)
2026-10-19 10:44:12 -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
Jinwoo HongandE2E Test 20fc7af557 test(relay): drill a memory cell through a database outage, and pin phone renewal (#30666)
* feat(relay): add the per-cell phonePath switch, effective only in reserve mode

Step 6 of the relay redesign keys every phone-path change on this switch. The cell
reports the effective value in runtime status, and the flag tool refuses phonePath=memory
unless the cell is reserve, and a reserve flip that would turn phones over unnamed.

* feat(relay): resume paired phones from one device read on memory cells

With phonePath=memory in reserve mode, a phone resume is admitted from one device
read and the cell's own seat, and takes no activity leases. A refused device closes
4401, a failed read 4503, and only a missing device row falls through to the invite
path. A phone whose host has no seat gets one assignment read: 4409 when the row
names another cell or the read fails, otherwise 4404.

* feat(relay): keep a memory resume's basis in the cell and confirm it from one read

A phone resumed on the memory path keeps its connection basis in the cell, not in
relay_connection_bases. Its confirm rereads the device row once, renews it only when
less than 29 days remain, and always answers a current-version confirm renewed:true
with the expiry the database holds. Confirms route by where their basis lives, so a
flip between accept and confirm completes either way. Retries answer from a 10-min
in-memory result map.

* test(relay): roll a memory phone cell back with confirms in flight

Flipping back by admitMode=db, phonePath=db or a dead-man trip leaves in-flight
confirms to finish on their memory basis, writes no phone lease and leaks none, and
evacuation still refuses the cell while Postgres records it as reserve.

* feat(relay): batch a memory cell's resume-confirmed audit rows

Memory confirms buffer their audit rows and the cell writes them as one multi-row
insert every 5 s or 500 rows, idempotent by a per-event id. The buffer holds at most
5,000 rows and drops a row older than 10 minutes rather than retrying it, counting
and logging both kinds of drop.

* feat(relay): count and log phone-path failures, and every attach deadline

The runtime metrics line gains phone refusals by close code, attach deadlines, accept
database errors, resume confirm failures by code, completed accepts by phone path and
the resume audit drop total. Every attach-deadline 4404 now logs what the cell knew of
the desktop's control (pong age, ping outstanding, control age and round trip), so a
half-open control can be told from a slow data dial.

* feat(relay): close an attach-deadline 4404 with the attach-timeout reason

The phone close reason from #30607 now rides the close frame of the 4404 a cell
sends when the desktop's data leg misses the attach deadline, so OTA phones can
redial the same cell once instead of waiting out the host-offline delay. The failed
relay-hello frame is unchanged, and old phones ignore the reason.

* test(relay): drill a memory cell through a database outage, and pin phone renewal

A blocked database leaves splices forwarding, closes new resumes 4503 and fails
confirms cleanly, lets an in-flight confirm finish on its memory basis after a flip
back, and drops audit rows past 10 minutes instead of writing them late. On the phone,
a relay-only session lives 31 days under the once-a-day renewal rule, and would not
if a not-due confirm answered renewed:false.

* fix(relay-ops): store phonePath=db as no key, so old images keep taking writes

An explicit phonePath=db left in the switch file would make the flag tool refuse every
later write after an image rollback, including a watcher's admitMode=db flip back.

* fix(relay): log a failed no-seat read, recheck the switch after the device read

A failed assignment read for a phone with no seat now logs a rate-limited line before
its 4409. A switch that turned off during the device read sends the phone down today's
path. resolveResume reuses the one-read lookup, so the expiry and grace rules have one copy.

* fix(relay): log a failed memory renewal; test flips, replays and basis cleanup

A renewal write that fails still answers the stored expiry, but now logs a rate-limited
line so a persistently failing update is visible before phones age toward re-pairing.

* test(relay): assert a rolled-back memory splice issues no statement when it closes

* fix(relay): flush resume audit early only on crossing the batch size, drop the shutdown flush

A failing database no longer takes a 500-row insert per confirm; retries wait for the
5 s timer. The shutdown flush raced the pool closing, so it is gone and the at most 5 s
loss is documented. A stopped buffer no longer re-arms its timer.

* test(relay): assert the aged audit rows were dropped, not written

---------

Co-authored-by: E2E Test <e2e@test.local>
2026-10-19 13:24:18 -04:00
Brennan Benson 04548087a5 fix(mobile): edit a queued message in a plain text box with compact cancel and save buttons (#30382)
* fix(mobile): edit a queued message in a plain text field with compact cancel and save buttons

* fix(mobile): the queued edit field grows from one line to four in the phone's web page

* fix(mobile): the queued edit's cancel and save buttons take 44pt-tall taps, and the queue no longer jumps while typing

- Measure the edit field's height in a hidden copy of the textarea instead
  of collapsing the live one: WebKit clamped the queue list's scroll
  position on every keystroke. It also re-measures when the field's width
  changes (rotation).
- react-native-web drops hitSlop, so the X and check were 28pt targets.
  Each button is now a 44pt-tall target reaching its neighbours, with the
  28pt square drawn inside; the layout looks the same.
- Announce saving with aria-busy, which react-native-web maps.
- List the new web sibling in web-overrides.json.
2026-10-19 10:17:21 -07:00
Jinwoo Hong e49236f1ec feat(relay): count and log phone-path failures, and every attach deadline (#30656)
* feat(relay): add the per-cell phonePath switch, effective only in reserve mode

Step 6 of the relay redesign keys every phone-path change on this switch. The cell
reports the effective value in runtime status, and the flag tool refuses phonePath=memory
unless the cell is reserve, and a reserve flip that would turn phones over unnamed.

* feat(relay): resume paired phones from one device read on memory cells

With phonePath=memory in reserve mode, a phone resume is admitted from one device
read and the cell's own seat, and takes no activity leases. A refused device closes
4401, a failed read 4503, and only a missing device row falls through to the invite
path. A phone whose host has no seat gets one assignment read: 4409 when the row
names another cell or the read fails, otherwise 4404.

* feat(relay): keep a memory resume's basis in the cell and confirm it from one read

A phone resumed on the memory path keeps its connection basis in the cell, not in
relay_connection_bases. Its confirm rereads the device row once, renews it only when
less than 29 days remain, and always answers a current-version confirm renewed:true
with the expiry the database holds. Confirms route by where their basis lives, so a
flip between accept and confirm completes either way. Retries answer from a 10-min
in-memory result map.

* test(relay): roll a memory phone cell back with confirms in flight

Flipping back by admitMode=db, phonePath=db or a dead-man trip leaves in-flight
confirms to finish on their memory basis, writes no phone lease and leaks none, and
evacuation still refuses the cell while Postgres records it as reserve.

* feat(relay): batch a memory cell's resume-confirmed audit rows

Memory confirms buffer their audit rows and the cell writes them as one multi-row
insert every 5 s or 500 rows, idempotent by a per-event id. The buffer holds at most
5,000 rows and drops a row older than 10 minutes rather than retrying it, counting
and logging both kinds of drop.

* feat(relay): count and log phone-path failures, and every attach deadline

The runtime metrics line gains phone refusals by close code, attach deadlines, accept
database errors, resume confirm failures by code, completed accepts by phone path and
the resume audit drop total. Every attach-deadline 4404 now logs what the cell knew of
the desktop's control (pong age, ping outstanding, control age and round trip), so a
half-open control can be told from a slow data dial.

* fix(relay-ops): store phonePath=db as no key, so old images keep taking writes

An explicit phonePath=db left in the switch file would make the flag tool refuse every
later write after an image rollback, including a watcher's admitMode=db flip back.

* fix(relay): log a failed no-seat read, recheck the switch after the device read

A failed assignment read for a phone with no seat now logs a rate-limited line before
its 4409. A switch that turned off during the device read sends the phone down today's
path. resolveResume reuses the one-read lookup, so the expiry and grace rules have one copy.

* fix(relay): log a failed memory renewal; test flips, replays and basis cleanup

A renewal write that fails still answers the stored expiry, but now logs a rate-limited
line so a persistently failing update is visible before phones age toward re-pairing.

* test(relay): assert a rolled-back memory splice issues no statement when it closes

* fix(relay): flush resume audit early only on crossing the batch size, drop the shutdown flush

A failing database no longer takes a 500-row insert per confirm; retries wait for the
5 s timer. The shutdown flush raced the pool closing, so it is gone and the at most 5 s
loss is documented. A stopped buffer no longer re-arms its timer.
2026-10-19 13:16:00 -04: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
Jinwoo HongandE2E Test 1f555c095a test(e2e): two chat specs follow the pan-and-zoom preview and the renamed New tab chat row (#30755)
Co-authored-by: E2E Test <e2e@test.local>
2026-10-19 13:00:14 -04: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 0ddec6f368 fix(ssh): a network cut during a managed-server install no longer hangs, and says contact was lost (#30732)
* fix(ssh): a network cut during a managed-server install fails promptly and says contact was lost

* fix(ssh): settle an SFTP transfer when its session dies, since ssh2 never answers the requests left

* fix(ssh): only a closed SFTP session reads as lost contact; a dead session also cancels the abandoned transfer

* test(ssh): put the mock's SAFETY note on the cast's own line

* fix(ssh): a channel open the host never answered also reads as lost contact
2026-10-19 09:47:21 -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
Neil 5cc0f8575e fix(runtime): a project removal stops its workspaces' terminals together under one deadline; every teardown is classified by kind (#30752)
* fix(runtime): a project removal stops its workspaces' terminals together under one deadline; every teardown is classified by kind

* fix(runtime): a workspace the inventory finds mid-sweep is still stopped; the walk test sees const and top-level teardowns
2026-10-19 09:12:26 -07:00
Neil 59a9da22a0 refactor(pane-layout): retire the unread intent tag; renamedLeafIds is the one layout delta (#30734)
No host ever read intent: 'gesture'. The client stops sending it, the params
schema strips it from older clients, and docs/reference records the rule that a
snapshot never carries intent.
2026-10-19 08:01:07 -07:00
Neil 3d91e9a003 fix(projects): removing a project on this computer stops its chats, pages and every terminal on the host (#30728)
* fix(projects): removing a project on this computer stops its sessions on the host, like repo.rm

* fix(runtime): a spent removal budget still asks every project terminal to stop; paired residue rows are no twins
2026-10-19 08:00:37 -07:00
Neil 43abe329c5 refactor(orcad): every snapshot state op fits the gateway exec budget in shell; the state lock sends its script once (#30744)
* refactor(orcad): every snapshot state op fits the gateway exec budget in shell; the state lock sends its script once

* test(orcad): a restore that stops part way reports FAILED and reruns from the archive
2026-10-19 07:54:34 -07:00
Jinwoo Hong 8ff19bc126 perf(sidebar): announce a burst of sort-stamp writes once (dark switch) (#30746)
Each stamp write made every client re-list the repo's worktrees; a pane whose
agent title flips while streaming turned that into hundreds of re-lists a
second. The write stays immediate; the notice is coalesced per workspace
(50 ms quiet window, 250 ms cap) through the existing keyed coalescer.
2026-10-19 10:53:33 -04:00
Jinwoo Hong b358328b7f test(e2e): pane-close specs close panes the way the user does and check main's copy (#30745)
The pane-close consistency spec closed panes with a bare PaneManager.closePane, the
drawing-layer call that never asks main. In a layout-owner build that close reached main
only because killing the pane's terminal made main retire the pane; right after a reveal
the terminal has not attached yet, so main kept the pane. Every user close path already
sends the close to main first.

The helpers now close through the pane close request the menu and shortcut use, and every
scenario also checks that main's stored panes match the window's.
2026-10-19 10:32:01 -04:00
Neil 9a46695f9f test(e2e): mount-authority spec polls through the host's "can't answer yet" terminal list (#30743)
* test(e2e): mount-authority polls retry the host's "can't answer yet" terminal list

First-mount adoption briefly makes terminal.list refuse with
terminal_surface_ownership_unavailable. Thrown out of an expect.poll
callback, that ended the poll instead of retrying it, so the spec failed
whenever a slow runner's probe landed in that window.

* test(e2e): say the refused read returns undefined
2026-10-19 07:29:03 -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
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
Jinwoo Hong 7cc2859e90 fix(relay-ops): let same-cap rolls run while another cell is reserve; refuse a reserve roll target by name (#30707)
* fix(relay-ops): let same-cap rolls run while another cell is reserve; refuse a reserve roll target by name

The director refuses cell-status for a step-5 reserve cell with a 404
{error: cell_in_reserve_mode}. The same-cap headroom check read it for
every general cell, so any roll failed while c7 was reserve. A reserve
cell that is not being rolled now counts as no free slots (its seats are
not in the database) and is listed with reserve: true. A reserve roll
target, in the headroom check, the same-cap job's first cell-status read
and the candidate tool, now fails with the design's procedure: flip it to
admitMode=db first, roll, then flip it back. Any other 404 still fails.

* fix(relay-ops): pin the headroom gate's imports as evidence code; name both cells in a two-cell reserve refusal
2026-10-19 06:57:06 -04: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