mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
* 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 commitecd8f07396. * 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>