* 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
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>
* 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(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.
* 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(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(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(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(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
- one repoRowOwnerStamp for the SSH and runtime session placeholder builders
- lineage refresh resolves the transport host once; pin that a server reply never appends a copy
- markdown-document-paths tests live next to the shared module
* fix(cli): refuse a paired server name --environment cannot tell apart, and say how to pick one of twin projects
* fix(cli): point an ambiguous --worktree at its host-qualified identity
* fix(cli): keep the ambiguity listing on the paired server the command chose
* fix(cli): compare saved server names the way --environment does, and route cross-host twin projects to --project --host
The revocation gate, the input cancel list and the hold's lifecycle each lived in two places.
Sinks now call one gate, held input is recorded at the CDP send (and the one native key path),
and runWithRemoteCliPageAdmission owns retire-on-revoke, release and error conversion.
* fix(persistence): a commit that runs out of disk no longer retires the profile-state writer
A WAL COMMIT on a full disk fails with SQLITE_FULL after SQLite rolls the transaction back. It was
reported as indeterminate, which retires the writer, so every later write failed until a restart:
on a managed SSH host, a disk that filled once left orcad unable to open a terminal even after the
space was freed. SQLITE_FULL fails a frame write before the commit point, so it is now a known
failure; every other failed commit stays indeterminate.
Co-Authored-By: Claude <noreply@anthropic.com>
* docs(persistence): say why SQLITE_FULL on COMMIT is safe to treat as not committed
Opus review: the claim holds because powersafe overwrite (on by default) means no padding frames
follow the WAL commit frame.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix(crash-reporting): record what a native access violation touched and the bytes that ran
Windows renderer AV reports carried only the faulting module offset. Two
recent ones (Orca.exe+0x23d3f88, +0x21674c2) map in official Electron
symbols to stores into memory the preceding instructions had just written,
which an intact binary on a healthy CPU cannot fault on. Without the access
kind, fault address and executed bytes, an engine bug, a corrupted code page
and unstable hardware look identical.
Read them from the Crashpad dump: AV access kind and target, the target's
distance from the faulting thread's stack pointer (x64), and 16 bytes at
the instruction pointer from the captured memory list.
* fix(crash-reporting): capture code bytes only inside a loaded module
They exist to diff against the shipped binary; bytes from JIT or other
anonymous memory add nothing and could carry page contents.
* fix(crash-reporting): capture code bytes only from Windows dumps
On macOS and Linux Crashpad records the touched data address as the exception
address, so bytes read there would be module data, not the faulting code.
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
* fix(diagnostics): reserve a share of the crash bundle for the daemon log
Trace spans (git.exec, renderer_memory) fill the 4 MiB bundle cap on busy
hosts, and the daemon log was read only after them, so 11 of 13 recent
Windows renderer-OOM bundles carried no daemon lines at all: no session
counts, no daemon deaths, no process-growth level.
Collect the daemon log first with a 1 MiB budget; whatever it leaves
unused still goes to trace spans.
* fix(diagnostics): return unused bundle space to the daemon log
The 1 MiB share is now a floor, not a cap: after spans are collected the daemon
log takes whatever they left, so a chatty daemon beside quiet traces keeps up to
the whole bundle as before.
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
* crash-reporting: say whether the main window was hung or blank when its renderer died
Windows killed/exit-1 renderer reports read the same whether Task Manager, an
EDR, or Orca ended the process. Field bundles show several of them follow
minutes of renderer silence or a document that never loaded after an update,
which points at users ending a frozen Orca. Record Electron's
unresponsive/responsive transitions as durable breadcrumbs and attach
rendererUnresponsiveForMs, rendererDocumentLoaded, rendererDocumentAgeMs and the
window's visible/minimized/hidden + focus state to the renderer crash details.
* fix(crash-reporting): a failed load reads as a blank window, and a crash ends the hang
Electron fires did-finish-load after did-fail-load, and a failed load never
commits, so track from did-start-navigation and flag main-frame load failures.
Clear the hang at renderer death so the replacement renderer's responsive is not
logged as the dead one recovering.
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
* fix(managed-server): import newestStateMtimeCommand from its new module
#30635 and #30618 merged in parallel: #30618 moved newestStateMtimeCommand
to orcad-state-newest-mtime.ts while #30635's byte-budget test imported it from
orcad-state-snapshot.ts, breaking typecheck on main.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(managed-server): measure the snapshot-space probe and record #30618's capture growth
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(layout): a pane main closed stays closed when its terminal starts late (dark, layout-owner build only)
* fix(layout): main takes back a CLI split pane whose terminal does not start within 10 s (dark, layout-owner build only)
* fix(layout): the timeout take-back keeps a pane whose terminal started or is starting (dark, layout-owner build only)
* fix(layout): one take-back call keeps the layout-writer count
* fix(layout): a split take-back is decided in the write queue, and a spawn for a taken-back pane starts nothing (dark, layout-owner build only)
* test(layout): a spawn for a taken-back pane is refused before the provider spawns
* test: typed store fake in the closed-pane spawn test, no casts
* fix(sidebar): the window raises the unread flag exactly when its host does not
With ORCA_SIDEBAR_INPUTS=main the host raises the workspace unread flag. A host
now advertises workspace-unread.host-raises.v1 in its status; a switch-on window
raises the flag itself only for workspaces whose paired server lacks it, waiting
for that server's status first. The desktop advertises the same capability to the
hosts it pairs with, and a host leaves raising to an older desktop (one that hosts
client pages without the capability) while it is connected.
* fix(sidebar): every desktop socket advertises the unread capability; window answers from cached status
The browser host lease and network tunnel build their capabilities on the shared
remote base, not the Electron list, so a current desktop's lease looked like an
older desktop and silenced the host. The capability now lives in the shared base.
The window reads the cached status first and probes only before the first one.
* test(sidebar): pin the cached-status path and both older desktop socket shapes
* test(e2e): narrow the server row's host before Mark as read
A repeat open or tab revisit of Session History's Workspace and Project
tabs dropped to 250 sessions, losing older matching sessions (341 -> 250
in QA). Every cache reuse re-cut the scan's result with a shared newest-N
counter for in-scope rows, which the scan's own per-agent budgets don't
match. The cut now bounds only out-of-scope rows, so a scan's result
passes through unchanged, and scoped requests reuse only a scan of their
own depth so lowering the depth rescans.
Tracked in STA-11167.
* fix(ssh): stop a closed bridged relay tab's shell from every close route, across restarts
The renderer's tab-close kill tombstoned a bridged lease before the bridge's
own close ran, so the old relay's shell kept running. Route it through the
bridge like the runtime kill. Persist a pending bridged stop on the lease's
pendingKill so a restart before reconnecting still sends it, and list such
leases for the stop without serving them as panes.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(ssh): end a closed bridged tab's lease when its stop can't be proven
Clearing the stop left the lease bridgeable, so a process that took the
recycled relay id was served under the closed tab. Retire it as any closed
tab's lease is.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(ssh): keep a closed bridged tab closed across restarts even when its stop can't be fenced
An unfenced legacy stop now gets one try and ends the lease, a repeated close
keeps the first record's age, and the Docker bridge spec covers a close while
disconnected followed by a restart.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(ssh): re-list after abandoning a bridged stop so the closed pane drops
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(ssh): route CLI and worktree stops of a bridged tab through the bridge, and keep a repeated close fenced to the first identity
* fix(ssh): refuse sleeping a bridged pane, settle bridged stop-and-wait on the relay's exit proof, and never attach a pane closed mid-attach
* fix(ssh): settle a bridged stop-and-wait on the exit proof alone, for every overlapping waiter
* fix(ssh): keep an unfenced bridged stop's pane tracked for its trailing exit report, never serving it
* fix(ssh): keep awaiting a bridged stop's exit after a failed reply, never name a retired pane to the relay CLI, and require a real empty listing in e2e
* fix(ssh): tell the runtime a bridged terminal exited once its stop-and-wait is proven
* fix(ssh): keep an unfenced bridged stop's exit observable after its reply fails
---------
Co-authored-by: Claude <noreply@anthropic.com>
* test(managed-server): byte budget for every POSIX host-op command
Builds each exported POSIX host-op builder at realistic maximal arguments and
asserts the raw command stays under 6 KB, under the 8,000-byte compression
threshold. Ops already over budget are listed with ceilings that may only
shrink until they move onto the host script (round 4 R4-10).
Co-Authored-By: Claude <noreply@anthropic.com>
* test(managed-server): budget the wrapped, fence-guarded command on Linux and macOS
Measures what a gateway sees on the plain fallback (fence guard + uncompressed
POSIX wrapper), takes the larger of Linux and macOS, launches the SSH way (nohup),
and also discovers the top-level src/main/ssh builders. Exports three builders
that are sent as their own exec request so they can be measured.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(managed-server): count the stdout fence; scan ssh transport subfolders
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix(projects): a project added from a paired client or the CLI through a symlink or subfolder shows its workspaces
* fix(git): keep a literal backslash in a POSIX checkout root
* fix(ssh): a cancelled or revoked host's browser command stops on the desktop
orcad now tells a cancel-capable desktop when the CLI gave up on a forwarded browser command, and a running host command on the desktop checks revocation before every side-effecting step (each helper CDP message and each direct input dispatch).
* fix(browser): keep local commands out of a host's scope, gate each helper on its proxy
* fix(browser): hold a host-driven page at its input sinks instead of per call site
* test(browser): checked mock types in the host revocation suites
* fix(browser): refuse only a host's own traffic, never the desktop's page commands
* fix(browser): a host's proxy never keeps its scope, and a revoked click still lets go of the button
* fix(browser): a revoked command may release only what it pressed, on every input path
* fix(browser): refuse every revoked step, and let the desktop release what a revoked command left held
* fix(browser): retire a revoked command's helper daemon before letting go of the page; release held input with a lease, synced pointer state and the focus guard
* fix(browser): deferred proxy input keeps its hold, releases count once they land, and a revoked helper is retired at once with its routes kept
* test(browser): keep the mock page cast on its SAFETY line