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