Commit Graph
1608 Commits
Author SHA1 Message Date
Brennan Benson 78ad9f4255 fix: keep chat option actions independent of inventory (#31927)
* fix: separate chat option input from inventory observations

* fix: preserve scoped chat choices and publication ownership

* fix: complete scoped catalog and applied control ownership

* test: make catalog fixture dependency boundaries explicit

* test: request catalog discovery explicitly in demand contracts

* fix(chat): preserve option observations and operation boundaries

* fix(chat): finish pending option and catalog lifetimes

* fix(chat): project saved permissions without launch preparation

* fix(chat): correct option rejection and catalog retirement

* Preserve Claude effort validation advice through chat rejection

* test: align refresh mocks and preserve fixture type evidence

* fix: share catalog claims only across equivalent owners

* test: use typed catalog corpus instrumentation

* fix: consolidate structured launch imports

* Validate SDK queue regression envelopes without assertions

* fix: complete catalog runtime and fixture type contracts

* Align native option fixtures with explicit discovery and applied facts

* Keep initial compatibility catalog reads on held memory

* Complete the installed catalog fixture envelope

* Observe learned defaults after asynchronous projection settles

* Consolidate host fixture journal type imports

* test(native-chat): align goal and seed fixtures with applied inputs

* fix(codex): retain applied proof when goal settings skip a write

* test: load rejection wording before timed queue regression

* Fix catalog fixtures and queued option snapshot typing

* Align permission publication fixture with passive option reads

* Separate startup config from held turn permission fixture

* Align input lifecycle fixtures with sparse applied choices

* Simplify scoped catalog refresh ownership and coalesce cache snapshots

* Load catalog persistence real-module fixture during collection

* Compose catalog availability fixtures from valid session records

* Align catalog and startup fixtures with current option contracts

* Fix permission read types and separate catalog fixture preparation

* Align option and restart fixtures with current lifecycle owners

* test(mobile): retain reported model alongside discovery

* test(mobile): recalibrate the measured route script envelope

* test: let the preflight shell-capture mock run its detection callback
2026-10-28 16:08:01 -07:00
Neil b47f12abd5 test(e2e): keys typed into a new remote terminal before its first prompt reach the shell (#35312)
* test(e2e): keys typed into a new remote terminal before its first prompt reach the shell

Add a headless paired-host spec that types into each new remote terminal after
it binds while the host shell's rc file is still running, then requires the
whole command to run. Drop the managed-SSH helper's wait for a prompt before
typing, so those specs assert the same guarantee.

* test(e2e): prove the rc gate held, and that the early-typed command ran exactly once

The tty echo of the typed command must precede the rc file's own prompt, the
command's output must appear exactly once, and the rc wait outlasts the bind
wait. Throw if the host has no isolated HOME.

* test(e2e): drop the early-typed echo-position check; keep the gate and exactly-once proofs

The check that the typed line's tty echo precedes the rc file's prompt failed on
Linux CI 2 of 2 (run 40763469331): the pane began with the prompt and the typed
line followed it, while the command still ran exactly once. macOS echoes it during
the rc. Where the echo lands depends on when the host's line discipline sees the
bytes, which this test cannot pin; it is not evidence of loss. What the test keeps
proving: the keys were typed while the rc file still waited (release file absent),
the rc ran (its own PROMPT appears), and the command ran whole, exactly once.

* test(e2e): name the early-typing test for what it proves

Rename it to say every key typed before the first prompt is delivered exactly
once, and drop the header's claim about how the tty keeps type-ahead.
2026-10-28 14:43:36 -07:00
Neil 9015f13735 feat(plugins): bind worker and panel approval to exact bytes and recheck before every start (#30637)
* plugins: bind worker and panel approval to exact package bytes

Consent for any plugin that runs code (a v2 sandboxed-ui or trusted-worker
profile, or a v1 manifest with a worker or panel) now names its installed
content hash, so an update cannot swap code under an old approval. Shipped
v1 installs re-prompt once. Data packs keep their fingerprints, and dev
trees stay unbound so a save doesn't re-prompt. The re-review notice is one
short line.

* plugins: integrity-check reviewed bytes before every worker spawn

The content verifier cached its first check, so bytes swapped between a
crash and its restart still ran. The controller now hands the manager one
uncached verifyBeforeSpawn hook that runs before every spawn, including
crash restarts, and replaces the cached first-start check, so nothing is
hashed twice and a running worker is reused without hashing. It is an
integrity check, not a sandbox: Node still reads files after it. Approval
is re-asserted at the same points, so a revoked plugin stops restarting
instead of counting as a crash, and cancellation does not wait on a hash.

* plugins: restart a running worker on its updated code

A refresh that changes a running worker's spec used to stop it and wait for
the next command or event. Reconcile now restarts it once on the new spec
after every stop, through the same approval and integrity path, and skips
the restart if a newer reconcile or dispose has taken over. Idle workers
stay lazy.

* test(plugins): re-review a dev copy that takes over an approved install

Approval of worker and panel plugins now names their exact bytes, so
switching the demo plugin from its installed copy to the dev folder asks
for review again; the journey approves the dev copy before using it.

* fix(plugins): admit worker restarts against current reviewed spec

* test(plugins): say why the worker refresh tests read the clock and wait out a backoff

* fix(plugins): refuse custom Git remote helpers in plugin downloads and test smudge filters for real

* fix(plugins): keep crash restarts through refresh pauses, stop workers before awaiting refresh on shutdown, and narrow inherited Git protocol allowlists

* test(plugins): shrink the flaky-test baseline for the worker controller test
2026-10-28 14:28:26 -07:00
Neil f7a114af29 fix(wsl-host): a terminal on a stopped distro offers Start, and New Terminal starts it (#35168)
* fix(wsl-host): a terminal on a stopped distro offers Start, and New Terminal starts it

* fix(wsl-host): a Start whose reconnect fails toasts and returns false

* fix(wsl-host): hide a pane's error toast only where the Start card replaces it

* test(wsl-host): give the terminal Start e2e's timeout and no-boot window their reasons

* test(wsl-host): await the pre-stop New Terminal so its user-act boot can't land in the no-boot window

CI saw 2 extra fake connects right after the first stop. Instrumented runs traced
every boot after a stop to the Start flow's two calls (wslHosts:connect, then the
runtimeEnvironments connect, logged as a pair) and the renderer stack of the
second pair to openNewTerminalTabInActiveWorkspace. In CI the spec clicked New
Terminal, then waited only for the first .xterm. That was the auto-created
terminal, so it wrote the stop while its own New Terminal was still in flight.
That terminal then started the distro as the user act it is. No background path
booted anything.

The spec now runs the tab bar's New Terminal action and awaits it before the
stop. The no-boot assertion is unchanged.

* test(wsl-host): narrow the tab group id before opening the pre-stop terminal
2026-10-28 14:09:27 -07:00
OrcaWinandm4air dd0c4cf35e Keep inactive tabs clickable in narrow docked strips (#34881)
* Keep inactive tabs clickable when active dock crowds the strip

* Fix narrow tab dock state after drag cancellation

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-28 13:51:55 -07:00
Brennan Benson 6e2b245ac6 Recover brief Linear read failures and retain network causes (#35074)
* Recover brief Linear read transport failures

* Preserve safe diagnostics across runtime Linear reads

* Match existing attachment metadata in read contract

* Use a checked runtime in Linear CLI integration tests

* Keep Linear CLI integration coverage in the backend test graph

* Use integration test ownership for Linear CLI recovery coverage

* Observe empty Linear recovery traces before the first sink write
2026-10-28 13:51:44 -07:00
Neil 751e974f88 feat(plugins): download sources without checkout and pin catalog releases in a v2 manifest/catalog (#30636)
* plugins: download Git sources without checkout, within a protocol allowlist and size cap

Plugin and marketplace sources were a real clone/checkout, so user hooks,
smudge filters and core.hooksPath ran, insteadOf rules could reach any
transport, and nothing bounded the download. Now a --no-checkout depth-1
clone (or exact-commit fetch) runs with an empty hooks path, only the https
and ssh transports, and Orca writes the commit's exact blobs from ls-tree +
cat-file --batch. Symlinks, submodules, .git aliases, colliding portable
paths and SHA-256 repositories are refused. A size watcher stops the whole
Git process tree once the pack outgrows an approximate cap.

The exact argv lives in shared code and joins the real-Git compatibility
matrix, so the Git 2.25 lane runs it. Tests reach fixtures over an SSH
shim, since file transports are now refused.

* plugins: manifest v2 declares an execution profile

data < instructional < sandboxed-ui < trusted-worker. A v2 manifest is
strict and fails when it carries contributions beyond its profile; v1
manifests keep loading with the least profile inferred from what they
declare.

* plugins: pin exact catalog releases in orca-marketplace.v2.json

A v2 catalog entry carries the release it vouches for: commit, content
hash, version, profile, minimum Orca and contribution types. Preview and
install fetch that commit directly and refuse bytes that differ. The v1
file stays strict and frozen for shipped clients; v2 entries strip unknown
keys, and entries this client cannot install are dropped at parse time.

Catalog fetches read only the index blobs under their own download cap,
so a catalog repository is not held to one plugin's limits. Release-bearing
snapshots are cached under a new key, so an older build never reads them
and simply refetches.

readMarketplaceJson and parseMarketplaceFile from #30612 are replaced by
parsePluginMarketplaceIndex: catalogs are now read as Git blobs, so the
on-disk JSON read has no caller left.

* test(plugins): serve marketplace e2e fixtures over SSH

Plugin downloads now refuse the file transport, even through an insteadOf
rewrite, so the fresh-profile marketplace journey reaches its fixture
repositories through the GIT_SSH_COMMAND shim the unit tests use.

* test(plugins): give the Git compatibility plugin source its own identity

* test(plugins): record the plugin fixture's executable bit in the Git index on every platform
2026-10-28 13:36:49 -07:00
Brennan Benson f869c5580c fix(agents): preserve Kanban verdicts and AI Vault monitoring (#34649)
* fix(agents): preserve turn verdicts and monitoring in secondary views

* fix(agents): publish accepted native status changes to dashboard

* fix(agents): suppress equal rebuilt status notifications

* test(agents): explain proxy read forwarding in perf fixture

* test(agents): type production status fixtures completely

* test(agents): stabilize status fixture formatting

* Align web session test mocks with status transactions
2026-10-28 13:11:13 -07:00
Neil 045f8c8266 feat(files): show images and PDFs from the byte-range preview path, local and remote (#35165)
* feat(files): show images and PDFs from the byte-range preview path, local and remote

ImageViewer and PdfViewer read an orca-media:// URL instead of base64 when
the editor opens a file: images load by URL, and pdf.js reads PDFs by
range, so a large PDF no longer arrives whole through files.readPreview or
pages into memory first. The grant mints only for the editor's viewers;
diffs, git blobs, markdown images and the paired web client keep base64.

- Minting an image checks its header with the base64 path's rule, so an
  image proven too large to decode safely never reaches the decoder.
- orca-media gains fetch and CORS privileges, and range responses expose
  Accept-Ranges and Content-Range, so pdf.js can read by range.
- Grants report the file size the viewer used to estimate from base64.

* fix(files): read PDFs by real range, check served images, keep old hosts' previews

- pdf.js fetches ranges only over http(s), so an orca-media URL was read
  whole; a small range transport now hands it bytes=a-b reads from the
  grant, and a failed range fails the preview instead of hanging it.
- A file can change after mint, so the raster header is checked again on
  the bytes each image request serves (the same handle locally).
- A paired host without files.readChunk falls back to files.readPreview
  for images and PDFs, as before; only audio and video need the update.

* fix(files): mint no local document grant for a renderer gone mid-mint

The local image/PDF mint is async now (authorize, open, header check), so
a renderer that closed meanwhile could leave a grant no teardown drops. It
binds the renderer lifetime first and refuses to mint once it's gone, as
the remote mint does.

* fix(files): keep the 50 MB cap for URL images, and probe remote headers incrementally

An <img> decodes the whole file, so image grants keep the base64 path's
50 MB cap; PDFs, now read by range, don't need one. A remote header probe
reads only each wider step's new bytes instead of re-reading from the
start. A replaced image of a different size resets its zoom.

* fix(files): tear down a PDF load whose range failed, and read 256 KiB ranges

A failed range only showed the error and left the pdf.js task, its worker
and pages alive; it now destroys that load first. pdf.js asks for one
readChunk-sized range at a time instead of 64 KiB, and a Range preflight
gets its CORS answer instead of 405.

* fix(files): read PDF ranges over IPC so orca-media needs no fetch or CORS

orca-media had gained supportFetchAPI and corsEnabled, and its responses
sent Access-Control-Allow-Origin: *, so pdf.js could fetch ranges. That let
any default-session frame holding a token read the bytes. pdf.js now reads
a PDF grant's ranges through fs:readPreviewRange, which main answers only
for the renderer that minted the token. The scheme is back to element-load
privileges (standard, secure, stream), responses carry no CORS headers, and
preflights get 405.

Grant bookkeeping moves to preview-grant-registry.ts. An e2e spec proves an
orca-media URL can't be fetched from the app page or read from another
session.

* ci(e2e): route the byte-range preview sources to the viewer specs that exercise them

Changes to the orca-media grant path, the image/PDF viewers and the PDF
range transport now run the PDF reload, rebuild and find specs, the remote
large-file open, media playback and the partition-isolation spec.

* fix(files): stop PDF range reads on abort, abort remote reads with the window, fix the CI route

- A closed or reloaded viewer schedules no further pieces of a merged range,
  and a remote range read stops when its window goes away.
- A URL without a size is an error rather than a fetch the scheme refuses.
- The PR guard job's sparse checkout and its admission test include the new
  route module, and the route now also covers the mint handler, preload
  bridge, editor surfaces, scheme registration and read client.

* fix(files): classify linked previews by their target, keep range reads off stopped WSL distros

A local image or PDF reached through a link is classified by the file it
resolves to, at mint and on every read, and a re-mint after the link is
retargeted gets a fresh grant of the new type. A paired host's document
whose first bytes are another kind defers to files.readPreview, which
classifies the target there. Lazy local reads go through the non-waking
WSL guard, as fs:readFile and fs:readFileChunk do.
2026-10-28 09:31:22 -07:00
Brennan Benson ab512bfe13 Show a chat's saved permission mode while its agent isn't reporting (#34546)
Chats with an explicit mode showed a disabled 'Permissions' placeholder whenever no running agent had reported, which is every idle or resumed chat, so the mode could not be read or changed. A live report still wins; chats that follow the agent's own settings still wait for the report.
2026-10-28 05:44:03 -07:00
Neil 30e75d94d3 feat(wsl): say WSL hosts are paused in serve-only (kill switch 3) (#34868)
* feat(wsl): say WSL hosts are paused in serve-only

Settings → WSL hosts explains that saved hosts and their projects keep working
while adding hosts and moving projects is paused, and the add-host menu says
where Add WSL distro went. Shown only after main answers serve-only, so open
and closed gates never show it.

* style(wsl): use the text-2xs token for the paused add-host line

* test(wsl): give the move status-line test's hosts mock the paused hook
2026-10-28 03:14:19 -07:00
Neil 448ef93938 test(e2e): retry the PTY write spy's main-process evaluates on transient CDP failures (#34912)
The spy already retried its install through retryTransientMainEvaluate, but its
reads, clear and delay setter did not, so a 'Resulting promise was garbage
collected' (playwright#33737) failed a passing Windows WSL paste test.
2026-10-28 03:06:24 -07:00
Neil 0cca424b51 feat(wsl): admit no new WSL host or move in serve-only (kill switch 2/2) (#34694)
* feat(wsl): admit no new WSL host or move in serve-only

Main refuses wslHosts:add while the internal gate is closed and reports
acceptsNewWork in the WSL host capability; moves already need the open gate.
The renderer hides Add WSL distro and every Move entry point once main says
serve-only, while saved hosts keep their Connect and Remove.

* fix(wsl): offer no new WSL host or move until main answers the capability

The renderer's acceptsNewWork now starts false, so Add WSL distro and the Move
entries never flash in serve-only before the capability reply.

* fix(wsl): point the WSL-bridge chat refusal at Add WSL distro only when it is offered

In serve-only Add WSL distro is hidden, so the refusal keeps its terminal-only
words there.

* test(wsl): give the move status-line test's hosts mock the new-work hook

#34676's test mocks use-wsl-hosts-enabled; WslHostsSection now also reads
useWslHostsAcceptNewWork, which follows the same gate there.
2026-10-28 02:15:57 -07:00
Neil fac8a7454a fix(wsl-mover): a moved project's chat opens and answers here; a fenced terminal names its host (#34880)
* fix(wsl-mover): a moved project's chat opens and answers here; a fenced terminal names its host

New e2e: a moved project's existing chat still answers after the move, run by this computer's
agent, with no host involved. Writing it surfaced two product gaps:
- Opening the chat from the moved project's sidebar entry asked its composer for focus before
  the prompt editor mounted; tiptap threw and the terminal workbench crashed. The editor handle
  now focuses (and answers contains) only once its view is mounted.
- A new terminal in a project its WSL host now owns (one a chat's agent might ask for) was
  refused with "is moving ... try again when the move finishes". It now names the host and the
  way back: use the project on this computer again.

* test(wsl-mover): wait for the moved chat's composer before typing and sending

Like #34573: the composer must be editable before typing, and hold the typed text before Send.
2026-10-28 01:51:57 -07:00
Agustin VarelaandNeil d20791e522 fix(session-search): read --since as an instant, whatever offset it carries (#28329)
Hosts filter with `updated_at >= ?`, comparing text against UTC
toISOString() values, so a `+08:00` cutoff was read as if UTC and a
whole-second `...00Z` cutoff dropped sessions in that same second
(`Z` sorts after `.`). The cursor key also hashed the raw spelling.

Normalize `since` to toISOString() in AiVaultSearchFiltersSchema. Every
client and host path parses this schema, so new clients send a cutoff
older hosts already compare correctly, and new hosts normalize whatever
older clients send before filtering or building the cursor key.

`Date` keeps milliseconds only, so a cutoff like `...00.0005Z` rounds up
to the next millisecond rather than letting in a session from before it.

Fixes #28253

Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-10-28 01:16:02 -07:00
Neil bd9455b616 test(wsl): cover a WSL-host orcad and the desktop at different versions (#34792)
* test(wsl): cover a WSL-host orcad and the desktop at different versions

The desktop and a WSL distro's orcad update independently, and nothing pinned how they meet.

- wsl-orcad-update-admission.test.ts: an older desktop keeps the newer orcad another desktop
  deployed serving, with no Update hint, and deploys over it only on Force; a policy refusal over
  that host carries orcad_host_newer. A newer desktop updates an older orcad on connect, keeps it
  serving with a Force hint after a failed update, and treats an orcad from before app provenance
  as updatable.
- cross-version-wire/wsl-host-desktop-downgrade: against the baseline release, a newer desktop
  withholds the agent-session account env from a WSL orcad that never advertised it (the release's
  strict schema accepts the call), sends agent.launch's account env the release accepts, and reads
  the release's errors with no drive refusal; an older desktop reads the Windows-drive refusal as
  the readable runtime_error it already shows.

* test(wsl): type the unused Claude preparation, and read the older orcad's error with the newer desktop's unwrap
2026-10-28 00:24:50 -07:00
Neil ad8d35eb59 feat(wsl): serve saved WSL work behind a closed hosts gate (gate states, kill switch 1/2) (#34683)
* feat(wsl): serve saved WSL work behind a closed hosts gate (serve-only)

The WSL hosts gate gains three states. A closed gate with a saved WSL host or a
committed move becomes serve-only: saved hosts stay listed and reachable,
committed rewrites, retention, interrupted-move recovery and the way back keep
running, and the preload API is exposed through a stamped window argument.
Adding hosts and starting moves still need the open gate. With nothing saved
the gate stays closed exactly as before.

* fix(wsl): stamp serve-only into the dashboard popout and Space windows too

They load the same preload, so without the argument they hid saved WSL hosts'
status and Connect in serve-only. One helper now supplies the argument for every
window on the shared preload.

* fix(wsl): reviewer follow-ups for the serve-only gate

- Refuse wslHosts:add unless the internal gate is open here, so this PR is safe
  on its own.
- Promote on any move journal entry, so recovery can reset an in-flight move
  whose host record is gone.
- List unfinished moves in serve-only.
- An unreadable WSL deployment store fails closed in serve-only too, through a
  shared serving flag main sets at startup.
- Report the gate state in wsl_usage_snapshot as an optional field.
2026-10-28 00:20:39 -07:00
Neil f173998203 feat(wsl-host): a chat on a stopped WSL distro says so and offers Start (#34681)
* feat(wsl-host): a chat on a stopped WSL distro says so and offers Start

* fix(wsl-host): a chat's Start never cancels a pending WSL host Remove
2026-10-28 00:03:58 -07:00
NeilandClaude 1eaaf85e9f Land the WSL project mover dark (merges only after verify-15b, the flag-off audit and green CI) (#34059)
* test(orcad-migration): pin the SSH manifest through the real dormant collectors

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

* refactor(orcad-migration): read the dormant-state export through a source-kind scope

A local-WSL scope lives on host local and owns only its projects, so host-wide
matches stay an SSH whole-host rule. SSH bytes are pinned by the collector golden.

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

* feat(wsl-mover): export a local WSL move's signed manifest with its dormant state (D1b-3c)

Collects the scoped projects, their groups and every dormant-state kind through the
local-WSL export scope, rewrites UNC paths to the distro's, and returns blockers for
anything the collectors can't carry instead of dropping it. Automations move disabled,
as the wire requires, and come back as a list to re-enable on the host after commit.

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

* fix(wsl-mover): reset an interrupted move to zero, or finish it once the host proves a commit

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

* fix(wsl-mover): abort a move by its identity when its manifest is too large for a request

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

* fix(wsl-mover): cap a new move's manifest below the request limit, and move forward on a by-reference commit only once the host flushed it

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

* fix(wsl-mover): remove only the newest move of a distro, so its chain stays readable

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

* feat(runtime): clients read a migrated pane's scrollback from its host (D1b-6d-1)

* fix(runtime): hold a remote pane only when its host is known to serve scrollback, and never read bytes in render (D1b-6d-1)

* fix(runtime): route the scrollback read by the tab's own host, never the focused one (D1b-6d-1)

* fix(runtime): resolve the scrollback host through resolveOwner and use the shared RPC error check, after main moved both (D1b-6d-1)

* feat(wsl-mover): a moved project's existing chats stay listed and resumable on this computer (D1b-7a)

* fix(wsl-mover): moved-project chats go through the layout owner instead of writing layout directly (D1b-7a)

* test(wsl-mover): run the moved-project chat runtime test in the Node runtime (D1b-7a)

* test(wsl-mover): read the hidden local row through main's per-host hidden-repo API (D1b-7a)

* feat(wsl-mover): rewrite the desktop's routing and seed the host's session after a move (D1b-7c)

Registers the main-side desktop rewrite stores ui-routing, session-globals and host-session-seed. Adds a +2 main layout-writer baseline line for wsl-move-main-rewrite-stores.ts: these are one-shot, ledgered move rewrites, and no LayoutCommand can set focus or seed a runtime host's partition.

* fix(wsl-mover): finish the main rewrite before the window loads, keep one copy of each moved tab, and copy instead of move (D1b-7c)

* feat(wsl-move): rewrite renderer stores for moved projects and keep their UI through catalog prunes (D1b-7d)

Registers the renderer-side desktop rewrite stores quick-open-history, file-view-preferences and notebook-environments. The renderer copies each moved workspace's entry to its WSL host key, marks it applied in the same storage, and records the ledger entry; main requests a storage flush first and accepts only the main window. Committed moves are re-listed for re-application at every startup. Catalog prunes keep filters, hook trust and setup-script dismissals of moved repos.

* fix(wsl-move): import the migration manifest types from main's orcad-migration folder (D1b-7d)

* fix(wsl-move): import wslUncPathToLinux from shared wsl-paths in the manifest exporter, where 7d moved it (D1b-7d)

* feat(wsl-mover): rewrite a moved project's orchestration history on the desktop (D1b-7e)

Settled local worker dispatches and their worker-terminal rows in a moved
project take the WSL host's worktree id, in one ledgered, idempotent store.
Chat workers, live rows, federated attachments and JSON receipts stay as
written; automations keep their unchanged local copy (the scheduler only
advances it). Per-table dispositions for orchestration.db.

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

* fix(wsl-mover): hold the orchestration store while a worker in scope is live; skip federated rows (D1b-7e review)

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

* feat(wsl-mover): copy in-app browser logins into the WSL host on a move (D1b-7f)

A committed WSL move now copies each in-app browser profile's cookies once into
the WSL host's stable route partition (owner decision 3a), merging without
overwriting what the host jar holds and never touching the source. Where that
is infeasible (no deployment link so no stable partition, the destination is
not a WSL host or is gone, or the partition name is held by another identity)
the host starts fresh and the move journal records why (3b). Registered as the
ledgered browser-cookies rewrite store; it runs only once browser sessions and
an app window exist. WSL_MOVE_DESKTOP_REWRITE_REGISTRY_COMPLETE stays false.

* feat(wsl-mover): retain moved source rows, reconcile them on start, and plan delta moves (D1b-8)

* fix(wsl-mover): keep the retained mark out of the WSL move manifest (D1b-8)

* feat(wsl-mover): the move driver runs a WSL move end to end, with production gate facts and journaled restore folders

One entry point (and preview/start/cancel/listUnfinished IPC for D1b-9) plans the move, gates
terminals on the hook server's agent status, the close-tab process probe, running automation runs
and live dispatches, pauses and proves exit, stages, sends blobs, commits only from
blobs-transferred (#29825), and finishes on the desktop. Any failure resets to zero or goes forward
when the host committed. Restore folders and the automations to turn back on are kept in the
journal; the host turns those automations back on after commit, retried until it confirms.
A real-PTY test pauses daemon shells, and runs on a real distro in the WSL orcad lane.

* feat(wsl-mover): move dialog with preview, blockers, progress and result behind the internal WSL hosts gate (D1b-9)

* fix(wsl-mover): move dialog hides instead of blocking, follows runs after a reload, and lets a start settle an unfinished move (D1b-9 review)

* fix(wsl-mover): word an unfinished earlier move as still finishing (D1b-9)

* test(wsl-mover): type the e2e's fake move state instead of reading it through Reflect (D1b-9)

* WIP(wsl-mover): 7f-2 registry complete, coverage proof, startup re-apply (D1b-7f-2)

* test(wsl-mover): registry-complete lifecycle, stale-focus restart, unload re-apply (D1b-7f-2)

* fix(wsl-mover): share the session focus write so the re-apply adds no layout writer (D1b-7f-2)

* fix(wsl-mover): redo additive rewrites once at a stale window's unload; behavioural coverage proof (D1b-7f-2 review)

* test(wsl-mover): the IPC record test now finishes the rewrite with the registry complete (D1b-7f-2)

* fix(wsl-mover): fence worktree-less spawns by cwd and re-prove terminal exit before commit (R46, WIP)

* test(wsl-mover): pin the commit re-proof, browser jar identity and repeated move generations (R45, R46, R47)

* fix(wsl-mover): fence any spawn by its folder and name the re-proof's reason (R46 review)

* fix(wsl-mover): a terminal in a known folder outside the move never blocks it (R46 follow-up)

* test(wsl-mover): kill the real move driver at every point against a real host store; abort a reset move's leftover stage on reconnect

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

* fix(wsl-mover): carry unsaved drafts into the moved project; round-trip every fixture through the real export, driver and host import

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

* fix(wsl-mover): reset a move waiting on a distro that is proven unregistered; test updates mid-move (R41), lost distros (R38) and two desktops (R39)

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

* test(wsl-mover): list the D1b-10 real-SQLite mover tests

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

* test(wsl-mover): type the fault test's journal hook without a cast

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

* fix(wsl-mover): reset for a lost distro even when its kept host fails to connect, never for a renamed one; compare filled-in repo keys only where the source lacked them

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

* fix(wsl-mover): carry markdown frontmatter visibility and closed-terminal-tab tombstones into the moved project

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

* test(wsl-mover): read the persisted stage through a typed record, not Reflect

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

* feat(telemetry): report how WSL moves end, by phase and reason code (R43)

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

* fix(telemetry): report a pause refusal's reason, an outcome for a thrown run, and only real transport errors as unreachable

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

* fix(agent-hooks): a WSL host's orcad never removes the hook entries the WSL bridge shares (R42)

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

* fix(agent-hooks): a WSL host's Codex account-home launch with hooks off skips too; mark the R42 guard to drop with the bridge

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

* feat(wsl-mover): "Use on this computer again" brings a moved project back as it was before the move

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

* fix(wsl-mover): judge a bridge WSL pane's foreground inside its distro

On Windows every live bridge pane's own process is wsl.exe, so the busy-terminal gate read every
open WSL terminal as running a program and refused the move. Read the pane's terminal foreground
process group inside the distro (processes carry ORCA_PANE_KEY): only shells means idle, anything
else blocks, and an unreadable guest stays unverifiable.

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

* fix(wsl-mover): only the selected projects' session rows can refuse a WSL move

The session eligibility check treated host local's whole partition as the move's, as the SSH
conversion does. So an agent session or unsaved draft in any other project on this desktop, or in a
project an earlier move retained, refused every later WSL move from that distro.

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

* fix(wsl-mover): sign the manifest as the journal and the host read it

The digest was computed over in-memory rows. A value with toJSON (a Date on a row added this
session) canonicalized to {} when signing but to its string once written to the journal or sent to
the host, so the first move after adding a project reset with orcad_migration_manifest_digest_mismatch
until a restart reloaded the row. Normalize through JSON before signing.

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

* fix(wsl-mover): carry a slept pane's scrollback from its daemon checkpoint

The move sleeps every pane first, and a slept pane's history lives only in the daemon's checkpoint,
never in a renderer snapshot. The export carried renderer snapshots only, so moved terminals opened
empty. After Sleep the driver reads each paused pane's checkpoint (as cold restore would replay it)
and the export carries it under the pane's own snapshot ref; the snapshot channel serves those bytes,
checked against the signed hash. SSH conversions pass no reader and are unchanged.

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

* fix(wsl-mover): say why when a move can't be prepared, instead of a bare reset

An export or journal failure after the pause (a digest mismatch, on awin) was rethrown and shown as
"everything was put back" with no reason. Nothing is journaled yet at that point, so refuse with the
error named instead.

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

* fix(wsl-mover): retry a renderer store main refused to record

The renderer applied a store and wrote its marker, but ignored a refused ledger record (gated-off,
failed, not-main-window), so a committed move stayed unfinished and refused every later move of the
distro until the next app start. Log the reason and retry with backoff until it lands.

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

* fix(wsl-mover): a WSL pane is idle only when its shell has nothing else

Review of #32343: reading only the terminal's foreground group let background jobs, nohup and
disowned work, setsid daemons and a script waiting at read pass as idle, and the move's Sleep would
end them. Scan the pane shell's subtree, its session and every process carrying its key; any
non-shell process, or a shell running a script, blocks.

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

* fix(wsl-mover): never leave behind an agent whose key may be the selected project

Review of #32343: scoping the session check to the selected projects let an unqualified key for a
repo id another host also registers fall out of both the transfer and the refusal. Such a key now
refuses. Adds tests that fail on the old code: another project's live agent with its tab open and
its draft don't refuse, the same kinds of state in the selected project still do.

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

* fix(wsl-mover): refuse an unreadable slept checkpoint, prefer it, and spool it to disk

Review of #32343:
- A checkpoint behind an unreadable log (or an unreadable one) refuses with the pane named instead
  of carrying stale history.
- For a pane the move just slept the checkpoint is the newest copy, so it wins over an older
  renderer buffer or snapshot.
- Buffers spool to a per-move folder and only their hashes and sizes stay in memory; the snapshot
  channel reads them by offset. The folder is removed whatever the outcome.
- A journal write that landed before it threw is reset, so no orphan record blocks the next move.

awin's checkpoints were 310-615 KB, well inside the 5 MB per-pane cap, so nothing there is trimmed.

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

* fix(wsl-mover): retry a failed listing, never a record another window was refused

Review of #32343: not-main-window can't clear by waiting, while a listing failure is transient.

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

* fix(wsl-mover): sign the manifest as the host parses it

The likelier cause of awin's digest mismatch: the signature covered the exported objects, but the
manifest parser keeps only the fields it knows, so a session field it drops (one a sleep can
write) left the journal and the host hashing something else. Sign the parsed form, and warn when the
parser dropped anything, since such a field does not move.

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

* chore(wsl-mover): split the journal step out of the driver, and format

The review fixes took wsl-move-driver.ts past the line limit; the export-and-journal step moves to
its own module unchanged.

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

* fix(wsl-mover): a slept pane passes the busy check; say what each move dropped or lacked

Re-run on awin (c5d263c77c):
- Every slept pane read unverifiable: the distro was asked about panes whose wsl.exe had already
  exited, and found no shell. Only a pane the host still sees running is asked now.
- A live pane read unverifiable when the WSL relay above its shell carried the pane key too; the
  pane's shell is now the topmost tagged shell.
- The parser-dropped-fields warning names the paths, and the slept-history reader logs how many
  panes it carried, could not read, or found no checkpoint for, so the next run says why a moved
  terminal opened empty.

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

* fix(wsl-mover): read the history of panes slept before the move too

verify-3 on awin: panes the user slept earlier in the session have no live terminal, so the move
listed 0 of 0 panes and moved them empty. Their layout still names the session whose checkpoint
holds the history; read those too, live panes first.

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

* fix(wsl-mover): moved panes paint their history, slept panes are found without a reload, and an older undo names the newer move

- A moved tab's first mount reads its history from the host even before this window has the host's status; it used to mount empty and never ask again.
- The window reports the panes it slept (main retires their binding on Sleep), checked against the moved workspaces before their history is read.
- A tab's Windows shell choice is dropped on purpose, not by the parser.
- "Use on this computer again" on an older move names the projects of the newer one to bring back first.

* fix(wsl-mover): moved panes read their history, an idle agent passes, refusals name tabs and show

- The manifest carries no desktop PTY binding: the host kept them, so the client took each moved
  pane for a live terminal, skipped the scrollback read and opened a fresh prompt.
- Progress counts the terminal history it sends while staging, not only the blobs.
- "Use on this computer again" shows a refusal in a dialog; the toast sat behind the update notice.
- Unreadable slept history names the tab by its title and is refused in the preview too.
- A bridge pane whose one job is an agent CLI lets the hook status decide: idle passes, working blocks.

* fix(wsl-mover): a split agent tab's shell panes restart as shells, not fresh agents

A moved (or any headless-restored) agent tab with a plain shell split beside the agent
pane brought every split back as a new agent: the start path used the tab-wide
launchAgent for each pane that had no recorded session. Tab-wide launch evidence now
applies only to a tab's sole pane, as the renderer already treats it.

Found in the D1b-10 real-host run on awin (report bug 6).

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

* fix(wsl-mover): restore folders of panes slept before the move; keep browser tabs

D1b-10 real-host run on awin (report bug 7, with bug 8 pinned), and verify-3:

- A pane slept before the move had no live terminal, so its last folder never reached
  the restore hints and it reopened at the project root (verify-3 lost `deep/`). The
  pause step now adds the panes the saved session or the window still binds, through
  the slept-pane listing that already feeds their history, so hints and history read
  the same panes.
- The move pauses projects through Sleep, which closes a workspace's browser tabs, so
  a moved project lost its in-app browser tab. A move's Sleep request now asks the
  renderer to keep them; an older renderer ignores the field.
- A moving project's unsaved draft carries (the host-session seed lays it back); a
  test pins that it never refuses the move.

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

* fix(wsl-mover): sweep journal writes that died before their rename

A killed journal write left <id>.json.<pid>.<ts>.tmp in the move journal forever
(D1b-10 real-host run, report bug 10). Once per directory and process, the journal now
removes another process's temporaries older than a minute; its own writes and a
write another instance may still be finishing are kept.

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

* fix(wsl-mover): move dialog and add-host fixes from the real-host run

D1b-10 real-host run on awin, report bug 9:
- A host that will not connect now refuses as host-unavailable, in the preview and
  at start, instead of "Orca could not check these projects" / an unexpected error.
- A run that ends without committing reports its result on the progress channel, so
  a window that only follows the run (reloaded mid-move, or started elsewhere) leaves
  "Moving… Preparing" for the result instead of sticking there until a reload.
- The preview counts terminal panes, as the terminal blockers do, not tabs.
- Small transfers read in B or KB, not "0.0 MB of 0.0 MB".
- A distro that stopped after the add-host list was read refreshes the list, so the
  button becomes Start and add, with a message that says so.
- The add-host copy no longer says bridge projects keep working "as they are" with
  no mention that they can move to the host.

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

* fix(wsl-mover): a sleeping WSL pane keeps its shell's folder

Bash on the bridge reports no folder, so a slept pane's checkpoint kept
its spawn folder and the move reopened every pane at the project root.
Before the final checkpoint of a Sleep, read the shell's folder inside
the distro (the busy check's probe, now with /proc/<pid>/cwd) and store
it. Internal WSL hosts gate and Windows only.

* fix(wsl-mover): moved browser tabs and drafts survive the moving window

The window open across a move saved its mirror of the host's tabs over
the host partition, and the unload's redo skipped any workspace that
mirror named, so the seed's browser tab and draft never came back. The
redo now adds the carried browser tabs and this desktop's drafts beside
that mirror, never replacing what it holds. A seeded draft is bound to
the host, as the SSH move's draft rename binds it.

* fix(wsl-mover): a pane whose only history is a stored snapshot cold-restores it for every client

A moved (or imported) pane carries its history only as a stored scrollback snapshot,
which no paired client's mirrored layout names, so the host started a fresh shell and
every desktop showed an empty prompt. Before starting such a pane, the host now seeds
a terminal session from the snapshot, so the start cold-restores it like any
interrupted session. Reproduced end to end in wsl-move-host-pane-history.spec.ts.

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

* fix(wsl-mover): seed history only for WSL-moved local panes, once per pane

Review of the first version: it seeded any pending pane with a saved snapshot,
including SSH panes (which would be asked to reattach a session the remote never
had) and ordinary panes. Now only panes of a project a committed WSL move brought
here (its import receipt names it) and never an SSH workspace. The session id is
the pane's own, so a later start reuses the folder the pane has since written,
or its clean exit, and never replays the stale snapshot; meta.json is written
last, by rename. Tests pin the gate, the reuse, and the legacy history restore
the seed relies on; the e2e adds the not-moved control.

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

* fix(wsl-mover): seed only panes the move carried; show history once; redo a cut-short seed

Second review: narrow the seed from per-repo to per-pane (a tab created after the
move's import is the host's own), redo a seed a crash left without meta.json, and
prove on a windowless host (as the distro's orcad is) that the desktop paints the
moved history exactly once - its mirrored layout never names the snapshot, so it
never reads it too - and that the moved browser tab is listed.

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

* fix(wsl-mover): refuse "Use on this computer again" while files are unsaved

An unsaved file's beforeunload silently vetoed main's reload after a release, so the
window kept the moved layout with panes bound to dead terminals (awin verify-5).
The renderer now asks for those files to be saved first; main logs a vetoed reload.

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

* chore(wsl-mover): log where an open pane's history, folder and busy verdict come from

awin verify-5 saw an open pane carry 140 B after 3,000 lines, reopen at its root, and
stay "cannot check" after its job was killed elsewhere. None reproduces off Windows,
since all three run through wsl.exe; these lines name the gap on the next run.

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

* fix(wsl-mover): an open pane keeps its shell folder through a move

Bash on the bridge reports no folder, so an open pane's checkpoint kept only its
spawn folder and `cd deep/er` reopened at the project root (awin verify-5). The
pause now reads each live pane's shell folder inside its distro before Sleep ends
the shell, and the restore hint prefers it.

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

* fix(wsl-mover): carry a slept pane's history once, not twice

Cold restore fills scrollbackAnsi from snapshotAnsi for a normal screen, so both
hold the whole buffer; the move concatenated them, carrying every line twice, and
the per-pane cap then kept only the newest ~1,040 of 2,000 lines (awin verify-6).
The move now carries the normal buffer once, as cold restore replays it.

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

* fix(wsl-mover): ask the distro when Windows can't observe a pane after a relaunch

After an app relaunch, Windows reads a reattached bridge pane's wsl.exe children as
unverifiable, so every live bridge pane stayed "cannot check" and blocked every
move until it was slept (awin verify-6). The gate now asks the distro about panes
Windows cannot observe too; only a pane whose shell the distro also can't find
stays unverifiable. Also formats the earlier commits' tests.

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

* fix(wsl-mover): name a terminal's pane after a relaunch before judging it

A terminal the runtime rediscovers after a relaunch carries no pane key until a
layout names it again, and the gate read every paneless terminal as unverifiable,
so no move could start after a restart (awin verify-6). The gate and the pause now
take the pane main's PTY bookkeeping binds the terminal to.

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

* fix(wsl-mover): read manifest diff paths through a typed record, not Reflect.get

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

* test(wsl-mover): type the slept-pane modes fixture instead of asserting it

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

* fix(wsl-mover): the move gate reads panes a relaunched window has not reopened

After an app relaunch, every live WSL bridge pane read "cannot check" and blocked every
move (awin verify-6, item 5). Root cause: the daemon adapter's hasPty only knows sessions
this app process spawned or reattached. A pane the relaunched window had not reopened is
still running in the daemon and listed by the runtime, but the inspection pre-check read
hasPty=false as terminal_gone, so the probe was unverifiable. Reproduced on macOS with a
two-launch e2e: an idle pane in an unopened workspace inspected as
{verdict:'unverifiable', reason:'terminal_gone'} after the relaunch. The same run showed the
runtime still holds the pane's original paneKey, so the paneKey theory is refuted.

The gate now passes includeUnattached, which skips that pre-check and asks the daemon,
which still refuses a session it no longer runs. Other callers keep the old behaviour.

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

* test(wsl-mover): an idle stub agent passes the move gate once its Stop arrives

awin verify-6 item 5d was inconclusive: p4's stub agent answered, but the preview stayed
"agent-working". Replaying the D1b-10 stub's hook payloads through the real hook server and
the gate's fact collection shows Stop settles the pane to done and the gate reads ready, so
the Stop path is not at fault. The run's screenshot (ev/p4-agent2.png) shows the likely cause:
the harness launched the stub twice in p4, and one instance sat at its prompt having fired
only SessionStart. The stub fires SessionStart at launch, which Orca's Codex normalizer treats
as a turn start, so a stub never given a prompt reads as working. No product change.

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

* test(wsl-mover): an unreachable daemon reads as unverifiable for the move gate

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

* test(wsl-mover): browserless host e2e and stale-window unit test for moved browser tabs and drafts

The distro's server has no browser of its own; an e2e-only switch (never in a packaged app)
starts the headless server the same way. With the desktop's per-host copy as a move leaves it,
the moved browser tab and pre-move draft show as tabs. The unload redo after a move rewritten
under an open window keeps both, whether the host published first or the rewrite seeded first.

* fix(wsl-mover): moved browser tabs and drafts survive the prepared reload after a move

"Reload now" backs up unsaved files by dispatching a synthetic unload first. That first unload
staged the window's mirror of the host (no browser tab, no draft) and drained the redo mark, so
the window, still open, saved its mirror again and the real reload had nothing to redo. The redo
now runs at every unload of the stale window and a last time when its document is replaced
(did-navigate), and only then is the mark drained.

* test(wsl-mover): the undo-path test's store answers the unsaved-file check

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

* fix(wsl-mover): read workspace records through the selectors

Main's new workspace-record read ratchet flags the mover's direct reads; the move dialog, the moved
projects section and its store helpers now read through workspace-record-selectors, and the scope
rows' catalog names its own fields. Prunes the layout-writer baseline to its actual count.

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

* test(wsl-mover): the dialog e2e expects the slept panes the start request now names

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

* fix(wsl-mover): a moved terminal's pane key comes from paneOfPty

Main's L80 census allows only paneOfPty and the switch-guarded writers to read a PTY record's pane
copies; the WSL move's live-terminal list now reads through it.

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

* style: format the imports the main merge repointed

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

* feat(wsl-mover): carry each moved pane's daemon checkpoint as a terminal-history blob the host restores once

Replaces the host-side stored-scrollback seed (#32789) with an installed interrupted session the pane's first start claims.

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

* feat(wsl-mover): the desktop sends each paused pane's checkpoint as its terminal-history blob

Drops the snapshot-ref spooling and the restore-hints blob for moved panes: one source (the pane's daemon checkpoint) carries history and folder.

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

* refactor(wsl-mover): register WSL move wiring from its own module

Keeps register-core-handlers under the max-lines limit.

* fix(wsl-mover): one import from ./ui in the move wiring

* refactor(wsl-mover): build the manifest's session before the payload

* style(wsl-mover): one import from stable-pane-id after the main merge

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

* test(wsl-mover): drop the e2e for the stored-snapshot seed this redesign deletes

The moved pane's history now comes from its own checkpoint; wsl-move-restore-lifecycle.spec.ts
covers it once, across reloads.

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

* chore(wsl-mover): keep #32668's two layout writers in the baseline until 2/3 deletes them

Main's layout-writer ratchet now counts main-process writers; wsl-move-echoed-browser-tabs.ts, the
file the hold-and-reload in 2/3 deletes, writes two. Restore redesign 2/3 removes the entry.

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

* fix(wsl-mover): move the project you have open into a host that has its own focus

The carried session named this desktop's focus, and a host that already focused another project
refused it (orcad_migration_dormant_focus_conflict:activeRepoId), so moving the project you had
open reset with "Nothing was moved" (awin verify-7 N1). The desktop's focus names nothing on the
host and the desktop rewrites its own focus after the move, so the export now carries none and
the host keeps its own. The reset result also says why the move stopped.

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

* fix(wsl-mover): startup removes the slept-history spools of moves the journal dropped

A run killed mid-pause is reset at the next startup without its process, so its export documents
(25 KB per pane on awin verify-7) stayed under wsl-move-slept-scrollback for good.

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

* fix(wsl-mover): a woken WSL pane keeps its folder unless the probe proves it gone

The cold restore checked a WSL pane's folder with stat over \\wsl.localhost, which waits for a
stopped distro and fails for reasons other than absence. After a rollback the pane stayed blank
about 15 s, then opened at the project root without its folder (awin verify-7, 5e). It now uses
the bounded wsl.exe probe every other restore uses, which keeps the folder unless proven missing.

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

* fix(wsl-mover): a moved pane's new shell prompt starts on its own line

The carried screen ends on the old shell's prompt with the cursor after it, so the host's new
shell printed its prompt beside it (`$ $`, awin verify-7). The exported checkpoint now ends the
normal screen with a line break; a TUI's screen and an unfinished escape are left as they are.

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

* test(wsl-mover): wait on conditions, a fixed clock and stated reasons instead of sleeps

Main's flaky-test ratchet flags the mover's tests:
- The browser-draft and pane-history e2e slept fixed seconds. They now wait on what they slept
  for: the host's tabs mirrored, the host terminal's last line, the moved tabs shown, the pane
  painted.
- The journal sweep test read the wall clock; it runs on a fixed fake time.
- Each longer per-test timeout says why it needs the time.

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

* refactor(wsl-mover): start the WSL mover from its own startup module

The mover's startup (rewrite stores, interrupted-move recovery, retention, retry) lived inline in
main-process-ready-foundation.ts. With main, the host reconnect and D1c branches each adding a
few lines on top, the file passed the max-lines limit in the combined build. It moves as one
unit to wsl-move-startup.ts; the foundation keeps the one call and its ordering.

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

* feat(wsl-mover): hold the window's saves from commit until main reloads it (restore redesign 2/3)

The window open across a move hydrated before the desktop rewrite, so every save it made
(session, UI, the unload checkpoint behind Reload now) wrote stale state back, and three files
re-applied the rewrite after it: echoed browser tabs, the seeded view state's redo and the
stale-window reapply. Now main holds that window's saves from the commit request until its
reloaded page has loaded, and reloads it itself once the rewrite is on disk. Use on this computer
again takes the same path, replacing its per-project release hold.

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

* fix(wsl-mover): only a settled commit reloads the window it held

A rewrite pass that ends while a commit request is out (another move's retry) must not reload
the window early: the reloaded page could save its view of projects the host is publishing.

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

* chore(wsl-mover): prune the deleted re-apply writers from the layout baseline

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

* fix(wsl-mover): the move's pause sleeps a workspace again before refusing

A workspace whose pane was still restoring, or had just spawned, could miss its final checkpoint
on the first Sleep and stay awake; the move then refused with "Orca could not pause the terminals"
although a second Sleep stopped it (awin verify-7, 6e). The pause now sleeps the failed workspaces
up to twice more, a second apart, and refuses only when they keep failing.

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

* fix(wsl-mover): a move retries a workspace whose pause failed instead of refusing at once

When one workspace of a move failed to pause, the move was refused right away with
host_terminal_stop_failed, and the retry added for awin verify-7 never ran (awin verify-8, 6e).
The window rolls a failed workspace's panes back and reports that rollback as an error. The
pause's persist step waited on every workspace and failed on that error, so the whole pause
call threw instead of returning the failed workspace. The retry only ran on a returned failure,
and the move's first pause had no retry at all.

The persist step now fails only on errors from workspaces main reported as slept; main already
reports the failed ones. Both of the move's pauses sleep a failed workspace up to twice more
before refusing. Each attempt is a fresh pause with its own id, so a re-sleep is not counted
as a duplicate.

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

* test(wsl-mover): e2e for the restart-style restore and undo (restore redesign 3/3, WIP)

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

* fix(wsl-mover): a moved pane's first subscriber gets its whole replayed history

The host replays a moved pane's checkpoint once, but every remote subscribe snapshot carried at
most 1000 rows, so the desktop's first view of a 2,000-line pane lost its first half. Only for
panes whose session is a moved one, the subscribe snapshot now tries 5000 rows first, then 2000,
then the usual ladder, within the same byte budget.

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

* test(wsl-mover): the restore lifecycle e2e waits for the printer instead of sleeping

Main's flaky-test ratchet flags it: it now waits until the host terminal shows the printer's
last line before the pane sleeps, and each longer timeout says why.

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

* test(wsl-mover): undo after reloads and a restart wakes split panes live, each with its history once

The undo e2e now follows a real move: main's own Sleep pauses a split tab, the committed move runs
its desktop rewrite and retention at startup, the window reloads three times and the app restarts,
then "Use on this computer again". The project comes back slept (no live terminal until opened),
and opening it wakes both panes from their checkpoints, history once, taking input.

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

* test(wsl-mover): carried and retained agents resume once across move, reloads and undo

The move e2e now carries the pane's idle Codex session to the host (its session file where the
distro keeps it): the agent resumes in the moved pane's folder on the first open and no reload
resumes it again. The undo e2e gives pane A an idle agent: after the undo it resumes there, once,
and pane B starts none.

Verify-6 item 5d (an idle agent read as working): the awin stub posts Codex's SessionStart at
launch, which Orca reads as a turn start; Codex posts it with the first prompt. The gate test now
pins that a resumed agent passes until its next prompt starts a turn.

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

* test(wsl-mover): read the undo e2e's agent record without a cast

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

* test(wsl-mover): parse the undo e2e's agent records instead of reflecting into them

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

* test(wsl-mover): the lifecycle e2e shows the moved pane when its agent does not start

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

* test(wsl-mover): start the carried agent by its recorded absolute command

A login shell may reset PATH (Ubuntu CI did), so the stub is named by the launch config the move
carries, as a recorded agent command is.

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

* test(wsl-mover): say where a resumed moved pane started when it is not its folder

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

* test(wsl-mover): print login profiles that move a shell when the resumed pane is outside its folder

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

* test(wsl-mover): keep the CI runner's login-profile cd out of the moved pane's folder check

Blacksmith's /etc/profile.d/blacksmith.sh ends with cd "$GITHUB_WORKSPACE", so every login
shell on CI, restored or new, plain or agent, starts there. Reproduced in Ubuntu 24.04 with the
same profile line: a plain restored pane lands in GITHUB_WORKSPACE too, and with it unset the
moved pane and its resumed agent start in the pane's folder. Orca spawns the shell in the right
folder; the runner's profile moves it.

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

* test(wsl-mover): judge the resumed pane's folder by where its shell started

Blacksmith's /etc/profile.d/blacksmith.sh sources the job env and cds into GITHUB_WORKSPACE, so on
CI every login shell, plain or agent, restored or new, moves there after Orca starts it in the
pane's folder (reproduced in Ubuntu 24.04 with that profile line; a plain restored pane moves too).
The check now accepts the pane's folder as PWD or as the OLDPWD a profile cd left behind.

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

* fix(wsl-mover): undo stops the host's copy of the project before its agents resume here

"Use on this computer again" resumed the project's agents here while the host's copies kept
running, so one agent session ran on both sides and two writers shared its transcript (awin
verify-7, 6c). Undo now sleeps the project's workspaces on the host and waits until none of their
terminals runs. A host that does not answer, or whose terminals do not exit, keeps the move and
says so; a host the user removed runs nothing, so the project comes back.

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

* test(wsl-mover): e2e: undo stops the host's running copy before the project comes back

A paired host holds the moved project under the move's ids with its terminal still running; after
"Use on this computer again" none of the host's terminals for it runs. Fails with the host stop
removed (the host's terminal stays at 1).

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

* fix(wsl-mover): "Use on this computer again" refuses only for the move's own unsaved files

An unsaved file in any project refused bringing a moved project back, so p6's draft README
blocked undoing p9 (awin verify-8). The window reload no longer stops on an unsaved file, and
only the move's own workspaces come back from their pre-move state. The window now tells main
which workspaces have unsaved files; main refuses only if the move holds one of them, before it
stops anything on the host, and the window names just those files.

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

* fix(wsl-mover): a refused "Use on this computer again" leaves the host's terminals running

Undo stopped the host's copies of the move's terminals before it checked whether the move could
be released, so an undo refused because a later move builds on it still killed the host's shells
and agents; the agents then resumed again (awin verify-9, X1). Undo now runs every refusal first:
unsaved files, a running move, a newer move in the chain, and whether the host answers for every
workspace. Only then does it stop anything, and a host that cannot answer for one workspace has
none of them stopped.

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

* fix(wsl-mover): the move gate finds a pane the window has not reopened since a relaunch

A pane the window has not shown since an app relaunch has no spawn-time pane key in main, so
the move gate's guest busy check could not find its shell. The gate now falls back to main's
own pane record. The Sleep half of the relaunch gap (awin verify-9, X2) moved to main with
#33398 and #33425.

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

* chore(wsl-mover): log who spawns each WSL pane and whether its cold restore came with it

After an undo reload a restored, inactive terminal tab showed only a prompt: something spawned
its session 2 s after the reload, before the window showed it, and its checkpoint never replayed
(awin verify-10). No log named the spawner. Each WSL pane spawn or reattach now logs its
session, spawn intent, whether it was new, whether a cold restore came with it, and the four
caller frames above the daemon client, for verify-11 to capture.

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

* test(wsl-mover): the undo host-copy e2e says why it needs its longer timeout

Main's flaky-test ratchet flags an unexplained per-test timeout.

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

* chore(wsl-mover): record the WSL spawn caller where it enters the daemon router

verify-11's `[wsl-spawn-origin]` lines named only the daemon client's own frames
(createOrAttachSpawn ← doSpawn ← withDaemonRetry): the async spawn path drops the caller.
The caller's frames are now captured synchronously at the router's `spawn` entry and logged
with the spawn, so verify-12 can name what starts a restored pane before its window shows it.

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

* chore(wsl-mover): capture the WSL spawn caller where every daemon spawn enters

verify-12 still logged "caller":[]. The router only wraps the daemon client when
legacy daemons exist; otherwise the bare client is the installed provider, so the
router capture never ran. Capture at the client's spawn entry (keyed by the
minted id too) and raise the stack limit so the async caller frames survive.

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

* chore(wsl-mover): log the seed and the daemon's screen for WSL cold restores

verify-13's second-cycle `$ $` checkpoint shows the daemon's own screen
without the fresh-shell blanking (old prompt still in the viewport,
scrollbackLines 0), which the macOS daemon harness never reproduces.
Log the seed's shape and tail with the spawn, and the daemon's screen
3 s after a WSL cold restore, so the next run shows where it is lost.

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

* fix(wsl-mover): keep an open host pane from restarting its agent during undo

"Use on this computer again" slept the host workspace and waited for its
terminals to go. A host pane open in the window saw its terminal exit and
resumed the agent on the host 1.6 s later, after the stop had already
counted zero, so the reloaded window resumed a second copy here (awin
verify-14, 6c). Refuse this window's terminal and agent starts on that host
from before the stop until the reloaded page has loaded (capped at 2 min),
and lift the fence when undo is refused.

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

* chore(wsl-mover): pre-landing cleanup for the dark landing

- Strip the verify-only spawn-origin and cold-restore-screen logging and the
  per-pane process warning.
- Read a pane's stored scrollback from its host only for WSL hosts; SSH and
  paired-server tabs no longer hold for a placeholder or ask their host.
- Limit the ambiguous-owner refusal to WSL moves; SSH conversions match as
  before.
- Drop the duplicate split-tab launch check (main fixed it in the projection,
  #32418).
- Reword comments that cited internal verify runs.

* fix(terminal): save a woken terminal's first history as a full snapshot

A wake that reused the Sleep-time cold-restore cache reopened the history
writer without asking for a full checkpoint, so the new process's output was
appended to the pre-wake checkpoint, which lacks the fresh-shell padding the
seed gave the daemon. The next save that replayed checkpoint + log (quit's
final save, or a relaunch's adoption) printed the new shell's prompt on the
restored one's row (`$ $`). Mark a full checkpoint for a new process in that
path, as the uncached cold-restore path already does.

* fix(terminal): keep ConPTY's viewport top through a width change, checked against a captured transcript

A captured ConPTY 1.23 resize (wsl.exe) shows it keeps the line at the top
of its viewport through every resize and repaints only that viewport. xterm
keeps the cursor's rows instead: a wider grid that rejoins wrapped rows pulls
rows out of scrollback (ConPTY's repaint then overwrites them), and a
narrower one that rewraps pushes rows into it (the repaint then repeats
them). The windowsPty row rule covered neither. ConPTY-backed emulators now
put the top line back on its row after a resize.

* feat(wsl-mover): D3a move orchestrator skeleton (dark) (#33802)

* feat(wsl-mover): D3a move orchestrator skeleton, dark behind the internal WSL hosts gate

* fix(wsl-mover): exhaustive orchestrator telemetry transition

* feat(wsl-mover): D3a T2 trigger, confirmed-running host readiness and T3 guard

* fix(wsl-mover): D3a review fixes (stuck moving, user release parks, A1 timer, deferred backoff, chain restore, GUID-only R38, uncounted window failures, backoff docs, restore settle, coalesced wakes)

* fix(wsl-mover): restore chain order without mutating

* fix(wsl-mover): D3a never touches a WSL host whose Remove is pending (E11 pendingStop)

* fix(wsl-mover): D3a survives an unreadable journal, honours a release mid-step, replays wakes after Restore, re-checks pendingStop before readying a host

* refactor(wsl-mover): orchestrator ports in their own module

* test(wsl-mover): warm the orchestrator wiring import before its first test

* fix(wsl-mover): a user Remove of a WSL host declines its move until the user adds it or retries

* test(wsl-mover): one checked cast in the host user-action test

* fix(wsl-mover): D3a review 3 — removal-pending clears, start re-checks after the window answers, non-blocking replies, replay in finally; split orchestrator modules under max-lines

* test(wsl-mover): index check style

* test(wsl-mover): core handler test mocks the move orchestrator wiring

* fix(wsl-mover): every user Remove of a WSL host declines its move; readying a host re-checks the decline before connect or add

* fix(wsl-mover): a failed sidecar save or step error never rejects a fire-and-forget trigger

* test(wsl-mover): split orchestrator tests under max-lines with shared fake ports

* fix(wsl-mover): report a host Remove before its cleanup, so a failed cleanup keeps the decline

* fix(wsl-mover): keep the moved-pane history wire surface off outside a WSL host

Register session.readTerminalScrollback, advertise its capability and the
terminal-history migration blob, and install that blob only on a WSL move's
destination (the WSL host's orcad, or an e2e host standing in for one).
Restoring a pane only probes for a moved pane's history there too. The
journal's orphaned-write sweep is limited to the WSL move journal, so SSH
migration journals behave as on main. Pin the advertised blob kinds again.

* fix(wsl-mover): user moves connect the host, automatic ones never boot; gate D3a's selector; tolerate a missing environment list

- The dialog's preview and start reach the host through service.connect (may boot, lifts a park);
  the orchestrator and its startup recovery keep the no-boot path (A1, E9). A host with a Remove
  pending is never connected either way.
- WslMoveDialogHost's active-distro selector does no worktree lookup with the flag off.
- hostScrollbackEnvironmentId returns null before the environment list loads; the mount test
  fixture supplies a WSL host.

* fix(wsl-mover): D3a waits out a move still being retained; orchestrator-over-driver tests (#34087)

* fix(wsl-mover): D3a waits out a move still being retained instead of failing attempts; orchestrator-over-driver integration tests

* fix(wsl-mover): D3a counts an unverifiable in-flight refusal, caps uncounted waits at a day (parks as stuck) and re-checks a done distro waiting on retention

* fix(wsl-mover): cap D3a's uncounted waits by checks, not wall clock; any in-flight detail counts

* fix(wsl-mover): a user's move leaves an already-ready host connected as is

service.connect bumps the liveness generation and re-ensures the server; the orchestrator readies
the host before the driver runs, so the driver connects only a host that is not ready.

* Revert "fix(terminal): keep ConPTY's viewport top through a width change, checked against a captured transcript"

This reverts commit ecd8f07396.

* test(wsl-mover): the orchestrator's automatic path never reconnects a serving host, and adds a bridge host once (E9)

* fix(wsl-mover): keep session.readTerminalScrollback in the RPC manifest; gate its registration

Gating the method out of ALL_RPC_METHODS dropped it from the generated params catalog. It is
static again, so the catalog types it, and the dispatcher leaves it unregistered on any host that
does not serve moved panes' history (method-not-found there, as before).

* fix(wsl-mover): undo returns focus from the host copy, so the reload does not reopen it

verify-15b 6c: undo stopped the host's copy of p9, but the window's focus still named the host
copy (the move pointed it there), so the reloaded window opened that workspace, the host
re-created its pane as a visible user-resume, and, since #34054 resumes a restored host pane's
agent from the hook store, Codex resumed on the host beside the one restored here. With writes
held and before the reload, undo now points focus at the project's copy here, reusing the move's
focus writer with the inverse patch.

* fix(wsl-mover): undo turns the host's copies of the move's automations off before releasing

After "Use on this computer again" the desktop's retained copy of a moved automation runs again,
while the host's copy stayed on: both ran against the same files. Before each move is released,
undo now turns off, on the host, that move's automations whose copy here is on (one off here keeps
the user's choice there). A host that cannot be reached refuses as host-unreachable and one that
refuses as failed, releasing nothing, like the terminal stop.

* fix(wsl-mover): undo turns host automations off before stopping terminals, and back on if it refuses

d3-gate-audit: with the automation step after the terminal stop, a refusal there left the host's
terminals and agents killed while undo refused, breaking X1. Per move, undo now turns the
automations off first (a step that undoes cleanly), then stops the terminals, then releases. A
terminal stop that fails turns back on what was turned off, and a disable that fails partway
turns back on what it had turned off before it throws.

* fix(wsl-mover): roll back undo's host automation step on any later refusal; review lows

- Any refusal after the automations went off (terminal stop, release failure or supersede) turns
  back on exactly what this attempt turned off. One it cannot is recorded on the move
  (hostAutomationsLeftOff), shown in the unfinished-move notice and retried by the rewrite retry.
- The spawn fence is released in a finally, and the port answers failed instead of throwing on an
  unreadable environment store.
- A host with a pending Remove (E11) is left alone with its own refusal copy; the
  host-automations refusal tells the user to connect the host.

* fix(wsl-mover): undo carries the host's automation states back and leaves each running in one place

Owner decision on d3a's item 5: undo reads every one of the move's automations on the host, turns
off each host copy that is on, and, with the release, gives the copy here the host's state (the
user's latest choice; one deleted there reads as off). An automation turned off on the host after
the move no longer comes back on here, and one turned on there no longer keeps running against a
project the host no longer owns. A refused undo restores the host's flags and changes nothing
here.

* fix(wsl-mover): the left-off automation retry re-reads the move after the host round trip

d3-gate-audit: the retry wrote back the snapshot it read before awaiting the host, so an undo that
released the move meanwhile could be undone, or a phase advance rolled back. It now re-reads the
record after the await, writes nothing for a move that is gone, and removes only the ids it
turned on, keeping everything else current.

* fix(wsl-mover): undo records the user's automation choice before turning any off, and takes turns with the retry

d3a re-review and d3-gate-audit:
- Undo reads every moved automation on the host, records those states on the move before turning
  anything off, and applies them here as part of the release step (undone if the release then
  refuses). An earlier attempt's record wins, so one left off by a failed rollback, or by a crash,
  keeps its choice (on) here instead of reading as off.
- A disable the host drops mid-loop carries what it could not turn back on on its error; those
  are recorded as left off, shown in the unfinished-move notice and retried.
- Undo's per-move steps and the left-off retry take turns, and the retry re-reads the move inside
  its turn, so a release during a retry neither resurrects the move nor leaves the host copy on.
- At startup, an undo a crash interrupted sends what it read as on back on through the retry.

* fix(wsl-mover): a left-off retry that finds the move released turns its host copies back off

If a move is released while the retry is turning a left-off automation back on, the host copy
would run for a project that is local again. After the round trip the retry re-reads the move;
when it is gone it turns what it just turned on back off (best effort, logged).

* fix(wsl-mover): a restart keeps a left-off automation's choice; undo's rollback never skips the host

d3a re-review:
- Startup settling keeps the recorded choice (on) for what it sends to the retry, and undo also
  reads any id on the left-off list as on, so a later undo after a restart and failed retries keeps
  it on here.
- A failed revert of the copies here no longer skips turning the host's copies back on; each
  revert write is tried on its own.
- Turning automations back on stops at a host that stops answering, leaving the rest as left off,
  instead of waiting on each in turn while undo's turn is held.

* fix(wsl-mover): undo always asks the host for the move's terminals, refusing a host that does not answer

verify-15c: with the host's orcad stopped, "Use on this computer again" for p7 proceeded, and after
a normal undo p6's host shell kept running. Undo's host-copy stop took the host workspaces to stop
from this computer's session, which after a move keeps only a moved project's chats: a project
with none named no workspace, so undo asked the host nothing (no reachability check, nothing
stopped) and released it. The stop now always lists the host's terminals first, adds every
workspace of the move's scope that runs one, and refuses as host-unreachable when the host does
not answer, answers with an error, or cuts the list short.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-28 00:02:37 -07:00
A. S.andNeil f150deb226 feat(native-chat): show the full date and time when hovering a message timestamp (#32051)
Message timestamps render time-only; the full localized date and time was
only exposed via aria-label. Wrap the <time> element in the shared Tooltip so
hover and keyboard focus reveal the same full label, without adding a
duplicate aria-describedby announcement. Transcript render tests now go
through a TooltipProvider harness, matching App's production provider.

Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-10-27 23:52:16 -07:00
Brennan Benson 8d908764dc Revert "Keep same-machine folders with matching Git remotes in separate projects (#32272)" (#34662)
* Revert "Keep same-machine folders with matching Git remotes in separate projects (#32272)"

This reverts commit 29a5f89b8a.

On a machine with several clones of one repo, #32272 moved the oldest clone
into the shared cross-host project. Shared Windows QA machines have several
orca clones, so everyone's sidebar "orca" project suddenly gained that
machine's main checkout and its worktrees. Revert until the intended
grouping is decided.

Conflicts resolved against later main: Settings files moved into
settings/repository (imports adjusted), and two test files later deleted on
main stay deleted.

* Use checked types in restored test fixtures

* Combine restored fixture type imports
2026-10-27 20:38:35 -07:00
Neil 2893d06533 test(native-chat): name the stalled step in the live-history watcher test (#34663)
* test(native-chat): name the stalled step in the live-history watcher test

A CI run hit the 30 s test timeout with no step named. Each wait now has its
own 8 s deadline that reports the stalled step with what the watcher had
delivered, and the file sets IS_REACT_ACT_ENVIRONMENT so act() flushes.

* test(native-chat): share one 27 s budget across the live-history waits

Per-step 8 s deadlines were stricter than the old 30 s test timeout; a shared
deadline keeps the total unchanged and only improves the failure message.

* test(native-chat): use an AbortSignal budget instead of the wall clock

Date.now trips the flaky-test wall-clock rule; AbortSignal.timeout gives the
same shared 27 s budget without reading the clock.
2026-10-27 19:13:52 -07:00
Neil 5e9e4c84a3 test(wsl-host): type into the WSL host terminal only after its shell prompt (#34607)
On CI the first keystrokes typed right after the WSL host terminal attached
were sometimes dropped (the shell received "L HOST TERMINAL ..."), leaving an
open quote, so the marker never printed. The spec now waits for the prompt,
which names the project folder, before typing. The dropped input itself is a
separate follow-up.
2026-10-27 16:42:54 -07:00
Neil 90c713af1f fix(wsl): refuse Windows drive folders on a WSL host, offer Add on this computer (#34530)
* fix(wsl): refuse Windows drive folders on a WSL host and offer to add them on this computer

A /mnt/<drive> project on a WSL host shows false line-ending changes, misses
edits made from Windows, and searches slowly. Add Project refuses typed,
picked and dropped Windows drive folders for a WSL host and shows the refusal
inline with Add on this computer, which switches the open dialog to this
computer. The WSL-host orcad re-checks by /proc/self/mountinfo (after
resolving symlinks) in repo.add, repo.create, repo.clone, explicit
folderWorkspace.create and the nested scan/import, answering with the
existing runtime_error code plus optional data, so older clients still show a
readable message. Everything is behind the WSL hosts gate; SSH is untouched.

* test(wsl): hold the e2e's this-computer add instead of racing its failure

On the Linux CI host the switched local add of a Windows path fails its nested
scan and hands off to the Open as Folder modal, closing Add Project before the
test looked. The refusal itself already came before any scan. The e2e now
records the local add at its scan step and asserts it went to this computer
with the Windows path; a unit test pins that a refused pick never reaches the
host path step.

* test(wsl): type the e2e's held-scan window hooks instead of Reflect
2026-10-27 16:29:54 -07:00
OrcaWinandOrcaWin 791502fae4 ci: move secondary, scheduled and contract workflows to GitHub-hosted runners (#34568)
* ci: move secondary, scheduled and contract workflows to GitHub-hosted runners

Public-repo standard hosted runners are free under Enterprise. Release, build,
benchmark, e2e and unit workflows stay on Blacksmith. ci-xterm-cache seeds both
cache stores since its PR consumer's pool is undecided.

* test(pi): resolve the smoke bundle's external packages from the repo

#34148 pulled zod into the commit-message planner graph; the bundle in the
OS temp dir could not resolve it on any runner.

* ci(unit): use four workers on the hosted four-core x64 runner

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-27 16:15:14 -07:00
Neil eb7f4d9c31 test(wsl-host): native chat on a WSL host through the fake transport, including a stopped distro (#34573)
* test(wsl-host): native chat on a WSL host through the fake transport, including a stopped distro

* test(wsl-host): type the chat spec's focus host id

* test(wsl-host): harden the WSL host chat spec and re-run fake-connector specs on change

* test(wsl-host): scope the chat reply check to the transcript and test the fake's route

Find the prompt only in the transcript so the composer can't stand in for it,
re-check the boot-free stop window after unfocusing, right before the focus,
give the fake's distros.json temp file its own name so it can't collide with the
spec's, and test that the fake connector routes to both WSL host specs.
2026-10-27 16:02:12 -07:00
Brennan Benson e71d4358c1 Native chat tabs can hand off work from their right-click menu (#34504)
* Add native chat hand-off menu on main

* Fix saved chat hand-off observations and RPC exports

* Reuse indexed chat tabs for hand-off subscriptions

* Match saved-chat clear fixture to current journal API
2026-10-27 14:06:57 -07:00
Brennan BensonandWooseong Kim 287a34c0e5 fix(github-project): show linked PRs and sub-issue progress (#34185)
* fix(github-project): render linked PR and sub-issue progress values

* fix(github-project): probe schema support for project column values

* fix(github-project): preserve independently supported project fields

* Preserve nullable project values and unsupported fetch concurrency

* Exercise project relation payloads through unchanged consumers

* Combine project coordination test cache imports

* Use checked project payload types in consumer tests

* Place project provider column contract in cross-layer tests

---------

Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
2026-10-27 13:48:54 -07:00
NeilandClaude Opus 5.5 9a51b9845c fix(ssh): start a stopped managed server whose recycled PID hid it on hosts without ps (#34519)
* fix(ssh): prove a recycled server PID exited on hosts without ps

On slim Linux images that ship no ps, the managed server's liveness probe
could not read the recorded PID's argv, so after a reboot a PID that now
named another process read as LIVE (same user) or UNKNOWN (root). The wake
then refused to start the stopped server, the update deferred on the
terminal census, and the host stayed unreachable. Read argv and the
zombie state from /proc when ps is missing.

Fixes #34149

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

* fix(ssh): name argv-less PIDs from /proc stat; annotate e2e timeout

Kernel threads and zombies have an empty cmdline, so a recycled PID owned
by one stayed UNKNOWN on hosts without ps. Quote the stat pattern so the
probe also parses inside command substitution on older bash.

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

* fix(ssh): fall back to the stat line only after cmdline was read

A failed tr must stay unproven rather than make a live server's PID look
recycled.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 13:27:49 -07:00
Brennan Benson a238091bc0 Warn before closing an active native chat (#34236)
* fix(chat): confirm before closing active native chats

* Repair native chat close settings and consent coverage

* Cover native close consent in workspace and floating tests

* Require consent before closing active native chats

* Fix close-warning typed values in deferred actions and tests

* Preserve admission ownership and reconcile relevant send evidence

* Separate chat read lifetime from send settlement and activation effects

* Fix required read activation typing
2026-10-27 12:32:53 -07:00
Jinjing 4b4bcc2e60 Revert "Size tabs to titles and preserve floating panel drag space (#34025)" (#34506)
This reverts commit 316b1887fd.
2026-10-27 12:12:33 -07:00
Simon ChoandNeil 13757d773d test(e2e): update four stale e2e specs to match intentional product changes (#30115)
* test(e2e): repair specs that missed intentional product changes

Align still-stale e2e expectations with shipped product behavior:
- the Cmd+J placeholder names workspaces and tasks
- the worktree meta dialog is titled "Workspace details"
- OS-opened CSV/TSV files are editable and render as an ARIA grid
- the delayed-save notice carries the app toaster's close button

Fixes #30114

Co-authored-by: Neil <neil@stably.ai>

* test(e2e): stub host Linear status in linear-url-workspace-entry spec

Since #33270, Create worktree reads Linear status from the repo's host
(linear:status IPC) instead of the store, so the store-only fixture left
Linear disconnected in the composer and the pasted URL was never looked up.
Stub the linear:status handler with the same connected status.

---------

Co-authored-by: Neil <neil@stably.ai>
2026-10-27 10:15:28 -07:00
Younghwan ChaeandNeil 46c0a1f453 test(e2e): skip contributor git hooks and signing when seeding fixture repos (#31591)
Global setup and the worker-scoped fallback now share one seedTestRepository()
that runs its commit and worktree add with per-command
`-c core.hooksPath=/dev/null -c commit.gpgsign=false`, so a contributor's
global hooks or always-on signing no longer abort e2e before any spec runs,
and the test repo's own hook config stays untouched for specs that install hooks.

Fixes #31587

Co-authored-by: Neil <neil@stably.ai>
2026-10-27 10:10:18 -07:00
Neil 2a08315494 refactor: group Claude structured session lifecycle owners (#34449)
* refactor: group Claude structured session lifecycle owners

* refactor: consolidate retargeted Claude state type imports
2026-10-27 09:17:40 -07:00
Kelvin Amoaba e310a63624 fix(mobile): use desktop's rules for queued messages, turn status, Stop and the chat composer (#34000)
* refactor(mobile): share the queued-card projection with desktop

Image-only queued drafts showed a blank card on the phone.

* fix(mobile): honor the host's Queue follow-ups setting

With it off, a mid-turn send from the phone still queued while desktop sent it into the running turn.

* fix(mobile): keep a queued-card edit when the card leaves the queue

The phone closed the editor and dropped the typing; it now goes to the chat box as on desktop.

* fix(mobile): hold /clear and /compact for background work and sends

The phone fired the command where desktop holds it, showed start failures twice, and left the agent unnamed in failures.

* fix(mobile): hand a queued edit back to the chat it came from

* fix(mobile): scope a command's send hold to its own chat

* fix(mobile): read a published null queue as empty

* refactor(mobile): share the returned queued-card caption rule

* test(mobile): drop queued-card tests the shared rules already own

* refactor(native-chat): share the needs-attention rule and drop leftover wrappers

* fix(mobile): type the structured sends mock, drop the moved queued-cards path and ship queued edit copy at boot

* fix(native-chat): drop the queued card's unused image-label import after the rebase

* fix(mobile): order the phone's queue reorder preview by position, as the shared cards sort

* test(mobile): keep the queued-messages test within its line budget

* fix(mobile): settle terminal chat working state and turn timing with desktop's rules

Move desktop's live-status merge, Stop suppression, hook-state resolver and
terminal turn timing to src/shared/native-chat. Mobile reads the transcript
lifecycle the host already sends, so a provider turn end settles Working the
same way desktop does. Terminal chats now get the turn bar, timer and folding,
and the streaming preview uses the shared deriveNativeChatStreamingText.

* fix(mobile): send desktop's terminal Stop keys, a second Escape only for OpenCode

Desktop sends one Escape on Stop. It adds a paced second Escape only for
OpenCode, which confirms an interrupt that way. Claude Code and Codex read
Esc-Esc at an idle prompt as rewind. The rule moves to
native-chat-agent-support, and the phone now paces the second Escape at
desktop's answer step instead of 80ms.

* fix(mobile): word Orca-stop and usage-limit notices like desktop

Move desktop's Orca-stop sentences to src/shared with an English table that
desktop translates. The phone names the Orca host it pairs with, and when
that host is unnamed it says "the remote server". Usage-limit rows rebuild
desktop's sentence from the failure fact. The usageLimitLineText fallback,
which only the phone used, is deleted.

* test(mobile): pass the overlay's host name in the prompt controller harness

* test(mobile): re-record Stop goldens at desktop's step, split status-notice tests, pin the turn clock and ship notice copy at boot

* fix(mobile): offer structured Stop while a send is out and read queue-sends-next as working

Move desktop's structured Stop rule, its Stop-joining helpers and the
queue-sends-next rule to src/shared. Both sides now use them. The phone
counts its in-flight sends, so a conversation-stop host offers Stop before
the turn opens. Between queued turns the phone reads as working, and
commands that can't wait in line hold like they do on desktop. Untargeted
Stops are no longer joined on legacy hosts.

* fix(mobile): offer terminal Stop again when its Escape went nowhere

Stop hides Working on the phone like it does on desktop. The phone's Escape
can fail outright, though, while desktop writes to a local PTY. When no Escape
could have landed, the phone now drops that suppression. The streaming preview
also treats a pending echo as the transcript's tail, as desktop does, so an
earlier reply can't hide a new preview.

* fix(mobile): hold structured Stop while a send it covers is in flight

On desktop, Stop's held and Stopping state depends on whether the chat can
stop, and that includes a send still in flight. The phone now does the same.
Once Stop is pressed before the turn opens, the button holds and repeated
presses send no more untargeted cancels.

* fix(mobile): end the terminal Stop hold when a new message is sent

Desktop clears Stop's working suppression on an optimistic send, so a new turn
that keeps the same hook epoch (Claude with working subagents) still offers
Stop. The phone now does the same when a new pending echo appears.

* refactor(mobile): trim chat turn wiring after the desktop rule move

Drop the controller's unread nativeChatAgentWorking field. Re-render only when
a chat starts or stops having a send out. Keep the structured turn indicator
stable across stream frames. Type the Stop test table instead of casting.

* refactor(mobile): track structured sends in flight once for Stop and command holds

* test(mobile): re-record Stop goldens now that Stop settles with its Escape delivery

* fix(mobile): escape terminal image paths like desktop drops

* fix(mobile): keep launch-draft prefill over older user turns

* fix(mobile): rank @ file matches with desktop's fuzzy Quick Open search

* fix(mobile): use desktop's composer triggers for mid-prompt / and $ picks

Picked @ files are now quoted when the path has spaces.

* fix(mobile): send picked skill paths on structured chats

* fix(mobile): list the host's disk-scanned skills in the / and $ menus

The phone names the workspace and the host resolves its folder, refusing SSH
workspaces as desktop does; hosts without skills.discover-workspace.v1 list none.

* fix(mobile): forget a picked skill once its token is deleted

* fix(mobile): drop a picked skill as soon as its token is deleted, and retry failed skill scans

* fix(mobile): treat a picked leading skill as chat and stop failed skill scans from refiring

* test(mobile): drop the menu ranking case the shared picker tests own

* refactor(mobile): keep skill picks on the draft record and reuse shared send rules

A rejected send now keeps its picks and leading-skill origin on both send paths.

* fix(mobile): replace a re-picked skill's file and restore picks with a rejected send

* fix(mobile): read the chat agent for the skill catalog from the active resolution

* style(native-chat): format the composer autocomplete files

* fix(native-chat): import the picker's apply and send-classify rules from the shared composer module

* refactor(mobile): import the composer's draft-store hooks once

* style(mobile): format the notice row's style import
2026-10-27 15:34:56 +00:00
Neil 16a29bc2fe refactor(pty): group IPC test harness and provider fixtures (#34440)
* refactor(pty): colocate IPC test fixtures with PTY owners

* test: exclude PTY test constants from credential source census
2026-10-27 08:26:26 -07:00
Kelvin Amoaba f776923c04 fix(mobile): use desktop's rules for chat tool rows, pending echoes and session option picks (#33977)
* fix(mobile): resolve terminal chat agents in desktop's order

Mobile now uses the shared detected-then-launch precedence and transcript-readability check; the phone's remembered agent only fills in after them.

* fix(mobile): hide Codex foreground commands from the background-task strip

* refactor(native-chat): share diff-card gutter, basename and task count

* fix(mobile): show clipped tool output sizes in KB/MB like desktop

* refactor(native-chat): share the tool-run header rules with mobile

* fix(native-chat): share tool-line detail rules; skip unsafe-only search hits

* fix(mobile): copy message text with desktop's rule

Copy now leaves out roster and background-task fallback twins and wordless provider frames.

* refactor(native-chat): memoize shared tool-line view and output sizes; share result preview

* fix(native-chat): read a lone result's preview from its narrowed body

* chore(i18n): ship chat tool and stop copy in the runtime English catalog

* refactor(native-chat): move the pending-send echo rule to shared

Mobile reuses it instead of its own matcher.

* fix(mobile): match chat echoes after their send boundary, sink unmatched ones

Uses desktop's rule: rows before the send no longer retire an echo, a send that
never lands no longer lets later glued sends retire, and an unmatched echo sits
below later turns.

* fix(mobile): keep glued photo captions and folded boundaries in echo matching

* fix(mobile): give image preview echoes the send time the occurrence rule reads

* fix(mobile): match echoes on image-normalized rows and cap open sends at eight

Same input and bound as desktop; each open send rescans the transcript.

* test(mobile): drop a pending-order case the tail test already covers

* refactor(mobile): read the send boundary once and skip echo normalization when idle

* test(native-chat): type the occurrence case's pending send

* test(mobile): drop the pending-echo wall-clock entry from the flaky-test baseline

* refactor(native-chat): share session option labels with mobile

Mobile's options pill now shortens effort levels (Med, XHigh, Min) and names
choices and options the way desktop does, from one English table that desktop
translates.

* refactor(native-chat): run mobile terminal chat options on the desktop surface

The terminal-chat option surface, its appliers, the option cache and the
dispatch types move to src/shared, and the phone builds its pickers from them.
Grok and every other agent with a catalog now get model and effort pickers on
the phone, picks are saved as the host's launch defaults, the OMP live switch
gate and discovery mapping come from one copy, and authoritative discovery
retires persisted models it no longer lists. The option stamp moves to shared.

* refactor(mobile): merge the session option type imports

* fix(mobile): keep terminal chat option picks on their own chat

The phone keeps one option surface per chat, so a pick that lands after leaving
and returning shows there, and a picker command only switches the chat it came
from. Discovered models merge with the seed catalog as on desktop, so Grok keeps
its effort picker. A Claude model switch the phone cannot confirm is never
saved as a launch default.

* fix(mobile): start terminal chat options over when a tab changes agent

A tab that runs another agent drops its kept option surfaces, report markers
and cached record, so a fresh session shows its own model. A pick that lands
after the phone moved to another chat is not saved as a launch default.

* fix(mobile): fence retired terminal chat option surfaces

A write that lands after its tab started another agent no longer publishes the
old agent's record, and a changed OMP switch capability rebuilds the chat's
surface from the cache instead of reviving an older one.

* refactor(native-chat): simplify the shared session option wiring

Desktop translation pieces use literal English defaults so the localization gate can read them, the selected-choice lookup is shared once, and the phone keeps one option surface per tab with the agent change detected from the cached record.

* refactor(mobile): return no option surface for an agent without a catalog

* test(native-chat): expect the shared Effort label in the startup picker test

* test(native-chat): keep only the label cases main still checks after the rebase

* refactor(native-chat): import the session option snapshot rules once in the PTY option surface

* refactor(native-chat): share structured option pick bookkeeping with mobile

Desktop and the phone now track picks per option through one shared book: a
repeat of an option on its way waits, other options go together, a lost reply
is not a pick done, and a stale reply only commits its own option. The phone
also re-reads options each turn and after the provider starts, and shows picks
at once while the host is still starting the provider.

* refactor(native-chat): share the option pick blocking rule

* fix(native-chat): read in-flight option picks from the shared book for launch send options

* fix(native-chat): keep the structured options hook within its line budget and pass the phone turn in the e2e harness
2026-10-27 15:00:27 +00:00
Takuya AkiyaandNeil 176852ad5a fix(terminal): highlight the active IME conversion range (#27903)
Japanese IMEs convert one clause at a time, but the terminal's preedit overlay
painted the whole composition uniformly, so the clause the candidate window
targets was invisible. Read the IME's selection from xterm's textarea on the
insertCompositionText input event (Chromium only updates it there) and paint
that range of the preedit in the theme's selection colors. Cell-grid advance
corrections are unchanged and nothing reaches the PTY; a collapsed caret or a
range outside the preedit highlights nothing.

Fixes #27873

Co-authored-by: Neil <neil@stably.ai>
2026-10-27 04:51:06 -07:00
Neil 295ed991dd refactor: group shared native-chat session options (#34356) 2026-10-27 04:14:07 -07:00
Seiji KoharaandNeil 2327180d12 feat(quit): add Quit and Stop All Sessions to the app menu and tray (#32153)
* feat(quit): add Quit and Stop All Sessions to the app menu and tray

Quit keeps daemon sessions alive for warm reattach. The new item, placed
directly above Quit in the macOS app menu and menu bar icon, the Windows
File menu and tray, and the Linux File menu, asks for confirmation, runs
Settings' Kill all sessions in the renderer, and quits only when every
session stopped. Closing the tabs first means nothing reopens or resumes
on the next launch. Serve hosts do not offer the item.

* fix(quit): keep Orca open when Kill all cannot prove every session stopped

- Kill all no longer reads a daemon generation that failed to list as
  "no sessions": a failed poll keeps the last confirmed remaining set,
  and a failed initial listing sets listingIncomplete.
- Quit and Stop All Sessions stays open on an incomplete listing or a
  terminal tab opened during the sweep, logs why it stayed open, and
  reveals the window before the final quit.
- Share the main-window message box helper, stub the request on the web
  client, and add Portuguese strings.
- Cover the windowless path, request expiry, the in-flight guard, and
  failed listings in tests; bound the E2E exit wait.

* fix(terminal): count Kill all per daemon generation within a time window

- A poll updates only the daemon generations that answered, so one
  failing generation no longer hides the kills of the others.
- Poll for 6.5 s of wall-clock time instead of 65 attempts, because a
  wedged generation fails each listing only at the request timeout.
- Flag a sweep with no daemon provider as listingIncomplete.
- Log the leftover-tab exit, share the one-shot window intent with
  Open Settings, and cover the sender check, the reveal before the final
  quit, and retries in tests.

* fix(quit): stay open while a newer build's daemon is held

- Set listingIncomplete while this build holds a newer protocol's
  daemon, whose sessions it can neither list nor stop.
- Reuse getDaemonAdapters in Kill all, type the shared one-shot helper
  by its flag factory, and move the app-menu quit handlers next to the
  tray options so both serve/desktop splits are tested.
- Cover a quit that outlasts the request expiry and a failed request
  pull, and fix the docs lead-in for the new quit-time case.

* fix(quit): explain, without stopping anything, while a newer version runs terminals

- Check the newer-version daemon hold before the confirmation and show
  an explanation instead, so Kill all no longer closes every tab and
  then refuses. listingIncomplete stays as the backstop.
- Translate the explanation, describe listingIncomplete generically,
  and assert the single timed-out poll in the Kill all test.

* fix(quit): explain before asking whenever Kill all cannot list every session

- Show the explanation instead of the confirmation while the daemon
  provider is missing (still starting or failed) as well as while a
  newer version's daemon is held.
- Name only the steps that work (wait for terminals to open, reopen a
  terminal opened in a newer version), avoid stating a hold as fact, and
  label the dialog's button through the catalog.

* fix(quit): add the relaunch step to the explanation and align the ja wording

* test(pty): keep a confirmed stop when its generation fails a later poll

* test(quit): give the E2E timeout bump its reason

* fix(quit): stay open when the daemon still lists a session after Kill all

Kill all counts only the sessions of its first listing, so a session
started since then, for example from the CLI or an automation before its
tab reaches the renderer, would outlive the quit. Main now lists every
daemon generation again before quitting, and quits only after a listing
in which every generation answers and none lists a session. It polls
within Kill all's 6.5 s window so the tab sweep's PTY shutdowns can
finish; otherwise the renderer shows the existing error and Orca stays
open.

ui:quitAfterStoppingAllSessions becomes an invoke that answers whether
main quits.

* feat(quit): rename to Quit Completely with a macOS hover hint, and explain plain Quit in the confirmation

* fix(quit): show progress while Quit Completely runs, and refuse at once when the daemon can't be fully listed

---------

Co-authored-by: Neil <neil@stably.ai>
2026-10-27 03:52:06 -07:00
Brennan Benson a0e7fa0619 Keep Claude approval choices across model changes without freezing delivery (#34031)
* fix(native-chat): keep Claude approval choices across model changes without freezing delivery

A Claude chat saved as "Approve for me" could reopen asking for every approval,
because Orca read the folder's saved model list as launch policy, and a model
change could keep running in a weaker approval mode Claude opened with. The
first fix for this was reverted after it froze the app: restored history left
a turn reading as running, a send was refused against it, and delivery retried
the same send with nothing changed.

- The saved approval choice goes to Claude as-is; the menu offers every mode.
- Orca records the mode Claude actually opened in. A send that picks a new
  model reopens Claude first when that mode differs from the one requested,
  only when the chat owes no work; Orca mail notices never reopen it.
- A message sent while a Claude turn is running joins that turn on its current
  choices; new choices apply at the next new turn. Nothing is refused.
- History catch-up closes every turn it opens, in the thread it opened in.
- At startup, a turn reading as running under the new agent that the agent is
  not running is ended as interrupted.
- Delivery goes round again only after a step shows what it changed (a commit,
  a different child, a start it joined ending, or a wake); otherwise it waits
  for the next change instead of repeating the same step.

Tracked in STA-10953.

* fix(native-chat): preserve execution evidence at dispatch

* Fix delivery iteration ownership and progress contracts

* Correct journal fixture types in delivery regressions

* Separate Claude input choices from error correlation
2026-10-27 00:01:09 -07:00
Brennan Benson 1c4d4a03bc feat: drag to reorder queued messages on desktop and phone (#34026)
* feat: drag to reorder queued messages on desktop and phone

* fix: use the queue grip button padding from its primitive

* fix: preserve the queue grip touch width with hit slop

* test: carry queue animation mocks into the chat view harness

* test: complete the shared chat view harness registrations

* test: carry the queue move cross-version wire contract

* Scope queue move feedback to the issuing chat and preserve phone touch width

* Keep queue reorder presentation stable during measurement and pending moves

* Keep measured row handoff tokens constant size

* Avoid rebuilding queue arrival IDs on unchanged renders

* Fix mobile queue regression test fixture boundaries

* Share the queued message leading slot with its reorder grip

* Fix queue regression fixture quality findings

* Check queue icons within the leading slot

* Allow every unsettled queued message to reorder

* Supply stable scope in queued row test mock
2026-10-26 23:57:36 -07:00
hwantageandNeil 483573f0b9 fix(browser): focus newly opened image tabs (#34127)
* fix(browser): focus newly opened image tabs

Focus the window before the webview so Command+W closes the new
image tab instead of the original tab after a plain image click.

Add tests for focus order, image loading, and close-shortcut forwarding.

Fixes #34114

* test(browser): verify image tab focus with native keyboard input

* refactor(ci): keep browser E2E routes within registry size limit

* fix(ci): enable background launch for native browser regression

* test(ci): include browser routes in isolated detector fixture

---------

Co-authored-by: Neil <neil@stably.ai>
2026-10-26 22:52:40 -07:00
Neil bff2843fca refactor(shared): organize native chat ask input owners (#34251)
* refactor(shared): group native chat ask input owners

* refactor(shared): keep split ask type imports erased
2026-10-26 22:18:52 -07:00
Jinjing 887b781d72 Move palette matching utilities into shared modules (#34252) 2026-10-26 21:29:57 -07:00
Kelvin Amoaba 3347d47a7d fix(mobile): use desktop's PR lanes and status-dot rollup in the workspace list (#33966)
* fix(mobile): ask before running orca.yaml default-tab commands from the phone

The host's repo.hooks reply now carries desktop's setup config, so the phone
gates the Run/Skip choice and the trust prompt on defaultTabs commands too.

* fix(setup): keep the local script in run-both setup config without orca.yaml commands

* test(setup): drop the shared duplicate-host trust test desktop already covers

* refactor(setup): derive host setup trust from the shared setup config

* fix(mobile): count every host's repo rows before honouring repo-wide setup trust

* fix(mobile): show desktop's PR lanes and failed/interrupted status dots on the phone

The host builds worktree.ps linkedPR with the desktop's PR-lane cache reader
and sends the desktop sidebar dot as an optional sidebarStatus field.

* fix(mobile): keep uncached PR badges and let finished turns show through working titles

* fix(mobile): suppress agent panes' permission and parent titles in the host status dot

* test(runtime): expect sidebarStatus on the compact ps summary

* test(runtime): keep only host-specific sidebar status cases

* refactor(mobile): keep host title suppression and the uncached PR lane in one place each

* test(wire): pass the terminal title evidence map to new host builds

* fix(runtime): read each workspace's PR through its own host's repo
2026-10-27 03:20:58 +00:00
NeilandClaude 284411d893 fix(wsl-host): uninstall and startup release WSL holders; a partial Remove keeps the host pending (#33843)
* fix(wsl-host): uninstall and startup release the WSL distro holders Orca left running

The detached wsl.exe holder outlives the app, so an uninstall left its distro,
orcad and shells running. The uninstaller now runs Orca with
--orca-uninstall-release-wsl-holders after killing the app and its daemon; it
ends only the holders this profile recorded, each verified by pid, start time
and command line, and keeps any it cannot verify.

At startup the WSL host service now releases holders whose distro is no longer
a host (a failed add or remove), and every holder while WSL hosts are off; a
closed gate releases them too.

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

* fix(wsl-host): a Remove that can't prove the server stopped keeps the host, pending

A partial Remove dropped the host anyway, leaving a server whose live
terminals block the next add's activation. Remove now drops the host only on a
proven stop; otherwise it records a pending stop on the WSL record, keeps the
host listed and says why (live or unverifiable). Remove again retries without
ever booting a stopped distro (it says to start it); Connect or Add on the same
distro keeps the host; a distro that is no longer registered finishes. Nothing
reconnects a pending host on its own.

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

* fix(wsl-host): E11 review — keep holders when records are unreadable; finish removals a stopped or renamed distro proves

- Startup keeps every holder when the WSL host records can't all be read
  (unverifiable), and no longer re-adopts a pending host's holder.
- Remove matches a renamed distro by GUID (isWslDistroRegistered), and a
  stopped distro finishes a pending Remove: the cold probe proves the stop.
- Only the user's Connect or Add withdraws a pending Remove; other connects
  are refused, and re-adding respects startIfStopped.
- Settings' Remove carries the kept result, so it warns instead of 'Removed'.
- A holder record without a start time is never killed on its command line
  alone, except the one this process just spawned.

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

* fix(wsl-host): E11 review 2 — a closed gate keeps live hosts' holders; stop a renamed distro by its current name

- With the gate closed, a launch releases only orphan holders (no host record,
  or a pending Remove), under the same keep rule as startup; a user with no
  holder records costs one folder check, nothing else.
- The keep rule re-reads host records per holder and keeps an Add in flight.
- Remove resolves the distro's current name from its GUID and stops it there.
- The uninstall command arms its exit timer first and exits 1 if setup throws;
  NSIS launches it only when a userData holder folder exists.

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

* fix(wsl-host): never boot a host whose Remove is pending at the next launch

Early wake built its host list from every record, so a host focused when its
Remove ended pending was booted at each launch, undoing the stop's proof. It
now skips pending records, and Remove forgets the host's early wake whatever
the outcome.

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

* fix(wsl-host): no wake for a pending host; the e2e fake stops an unbridged server like the real one

- mayWake skips a host whose Remove is pending, so a due automation can't
  boot it (focus already couldn't).
- The e2e fake connector read a host with no bridge as stopped. Since E9 a
  re-enable connects nothing, so Remove after off/on left the host's shells
  running. Like the real connector, it now reconnects the always-running fake
  distro to stop its server.
- Status bookkeeping moves to WslHostStatusBoard, and the pending-host connect
  rule to admitConnectToPendingHost, to keep the service under max-lines.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-26 20:17:53 -07:00
Brennan Benson e79235371f fix: restore main typecheck after concurrent merges (#34202) 2026-10-27 02:55:37 +00:00
Brennan Benson 29a5f89b8a Keep same-machine folders with matching Git remotes in separate projects (#32272)
* WIP: same-host same-remote checkouts keep their own project id

* WIP: claim the shared project id per checkout surface; drop the display-only clone split

* WIP: index derived ids by repo in the hydration normalizer

* Resolve workspace project affiliation from current checkout owners

* fix: preserve concrete project ownership across catalogs and operations

* fix: combine recent project selector imports

* fix: enforce canonical catalog reads and preserve search continuity

* fix: scope catalog lint exceptions to renderer files

* Resolve project search ownership before native candidate limits

* Combine duplicate session scope imports

* fix: derive returned history row ownership on the host

* Reconcile full session history replies by content

* Require authoritative project catalogs for terminal snapshots

* Preserve registered project claims and partial history results

* test: check orphaned setup cleanup state without assertions

* fix: align catalog callers and native search fixture contracts

* test: check full repo shapes in runtime routing fixtures

* Fix checked catalog inputs in compiler regression fixtures

* Keep checked PTY fixture repos mutable

* test: align catalog fixtures with canonical readers

* test: check catalog and runtime fixture inputs

* test: check catalog fixtures without outer assertions

* test: follow persistence harness relocation

* Adapt catalog test inputs to integrated main checks

* Test History hashing across cached depth revalidation

* test(settings): expect host-qualified setup removal

* test: capture host split listings and scope relay recovery

* ci: regenerate merge ref

* test: type host-split listing diagnostics
2026-10-26 19:07:06 -07:00
Brennan Benson 7f846fd4f9 Codex restarts keep the configured command before a session is recorded (#33629)
* fix(codex): honor configured command on unrecorded restarts

* test: update account restart startup consumers
2026-10-26 18:45:52 -07:00