* Refuse a chat create whose close came first, behind the layout-owner switch
With ORCA_LAYOUT_OWNER=main, main remembers a close that reaches it before
the chat's create and refuses that create, or closes the chat as it lands.
The window then tells this machine's host about a cancel instead of
keeping a cancel tombstone; a host it cannot tell still gets the
tombstone. Paired hosts keep the tombstones, whose fence module moves to
lib/launch-cancel-tombstones-compat/.
* Close a chat whose create a cancel beat, on any create outcome
The local cancel now retires the chat on main (close and host-tab close),
so a create that landed first leaves no tab behind; a tab_not_found answer
means the create had not landed. Main closes the chat for any create
answer once a close was noted, including a refusal or a throw past commit,
and publishes no tab for it. The tombstone fallback notifies listeners.
* Keep the switched-off create path byte-identical
* Log, not throw, a failed close of a chat closed before create
* Run the close-before-create test in the Node SQLite project
* fix(agents): a resumed agent's chat is readable on the phone before its first new turn (#10923)
* fix(agents): skip a dropped Codex resume and keep the seeded identity across restart
* fix(agents): seed resume identity before the spawn's first await and keep it through terminal evidence
* fix(agents): seed after the spawn commits, and name the session on host-built resumes
* fix(terminal): tell panes their daemon died at once and say when typed input was lost
* fix(terminal): use the info toast the terminal suites already stub
A symlink to a directory was listed as a file on SSH and managed hosts, so
projects behind a linked path could not be reached. Follow links when
classifying entries; broken links and loops stay files.
Co-authored-by: Gideon <jjin6573@naver.com>
* Record a restored editor tab's move to its owning workspace through main (dark, build switch off by default)
* Refuse non-editor and duplicate moves; route the move by workspace
* Type the move routing test's command
* fix(projects): adding a project through a symlink alias reuses the existing project
On a managed host, adding repo-link (-> repo) created a second project for the
same checkout while re-adding repo was deduped. Compare the real path, resolved
on the host that owns the files, so an alias finds the tracked project; distinct
checkouts keep distinct real paths and stay separate. Local folder workspaces
get the same check.
* test: run fixture git through process-host
* fix: classify integration credential moves on their owner host
* test: install renderer API fixtures for terminal close paths
* test: restore IPC harness API and listener ownership contracts
* test: avoid unused RPC catalog setup in authorization suite
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Headless Linux CI (xvfb, no GPU) falls back to the DOM renderer, so the
WebGL scenario added in #29331 waited for a canvas that never attaches.
Other WebGL specs already skip the same way.
#29625 statically imported legacy-relay-bridge-panes, which subscribes to the
bridge (and calls listPanes) at load, so every useIpcEvents test whose
window.api mock lacks it threw at import.
Co-authored-by: Claude <noreply@anthropic.com>
* fix: classify integration credential moves on their owner host
* test: install renderer API fixtures for terminal close paths
* test: restore IPC harness API and listener ownership contracts
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* feat(layout): add the dark layout.apply channel and route pane splits through main behind ORCA_LAYOUT_OWNER
A build made with ORCA_LAYOUT_OWNER=main registers layout.apply: main applies the
window's layout command to the stored partition through the shared applyLayoutCommand,
minting new ids. A user split then waits for main's answer and mounts the pane id main
minted. Every other build substitutes 'window', so neither branch is compiled in.
* fix(layout): write only the records a layout.apply command touched
A load-then-save rewrite also normalizes records the command never touched. The command's
effect is now the diff between the save before and after it, written onto the stored
partition record by record. A split whose main reply says a paired server holds the tab
splits as today.
* fix(layout): write a load-supplied record whole and check that patched partitions reload to the command's result
* feat(layout): close, rename and equalize panes through main behind ORCA_LAYOUT_OWNER
In a layout-owner build the window's pane close, title change and equalize go through
layout.apply and the window shows main's answer. A window pane close keeps the legacy
close's rules: the tab's last pane is refused, a pane restarted since main read the close is
left alone, every partition holding the tab closes it, and the tab's remote session id follows
the remaining pane. The rules sit behind a command field only layout.apply sets.
* fix(layout): close the tab when a racing pane close leaves the window one pane
* fix(layout): leave an unmounted tab alone when main refuses its last pane
* feat(layout): move panes and save divider ratios through main behind ORCA_LAYOUT_OWNER
Claude-Session: 10569c9a-a0a1-4fc9-8469-0edfb7aed125
* test(layout): a taken pane drop whose pane closed does nothing
Claude-Session: 10569c9a-a0a1-4fc9-8469-0edfb7aed125
* test(layout): pin the window's pane move to main's saved tree, and the drops main cannot take
Claude-Session: 10569c9a-a0a1-4fc9-8469-0edfb7aed125
* fix(layout): close a user's pane even if its terminal restarted, as today
Claude-Session: 10569c9a-a0a1-4fc9-8469-0edfb7aed125
* test(layout): pin the restarted-pane close directly, not through a made-up profile state
Claude-Session: 10569c9a-a0a1-4fc9-8469-0edfb7aed125
* fix(layout): leave a split to today's path only when the workspace's row names a paired server
The split switch point asked which host to route to with a helper that falls back to the focused
server, so a focused server could claim a workspace whose row names no owner. Use the same owner
test the tab mirror uses.
* test(layout): type the split owner mock without a cast
* test(layout): type the layout-owner command mocks with the real signature
* test(layout): type the gesture command mock with the real signature
* fix(layout): close the dark path's gaps: unmounted split panes, refused or failed splits and moves, ratios during a move, release guard
- A pane main saved but the window could not mount (source pane or manager gone during the await)
is closed again in main, so it does not come back on restart.
- A split main refuses because its copy lacks the workspace, tab or pane, or a failed call, splits
as today; a refused or failed move moves as today.
- Divider ratios committed while a move is in flight wait for the window's move, then go to main.
- The orcad bundle gets the ORCA_LAYOUT_OWNER define when the switch is on.
- Packaging and the orcad template refuse ORCA_LAYOUT_OWNER=main and any output a dark build marked.
- workspace-layout-record-patch.ts: no non-null assertions; plain comment for the map depth.
* test(layout): count a split pane as mounted from the split mock's own results
* fix(layout): act as today whenever main lacks the target or the call throws, not just for splits
- sendPaneCommandToLayoutOwner runs asToday for workspace/tab/pane_not_found and a thrown call, so
close, rename and equalize fall back like split and move; a handler that throws no longer runs the
fallback too.
- A close main refuses as its last pane, while this window still has others, closes as today.
- A move whose local step throws still releases held divider ratios.
- Drop the orcad output marker: nothing read it; the template build refuses the switch itself.
* test(layout): plain names for the last-pane fallback test and the marker comment
* docs(layout): say plainly what an indeterminate write can leave behind
* fix(layout): finish the main merge with this branch's versions of the layout-owner files
* test(build): copy the layout-owner build guard into the compiler-cache checkout fixture
* feat(layout): send tab rename, color and pin through main behind the layout-owner build switch
In an ORCA_LAYOUT_OWNER=main build, the tab bar's rename, color and pin ask main's layout.apply
first; the window then runs today's action. When main lacks the tab or the call fails the
window acts as today; a paired server's workspace keeps today's path. Pinning a tab now also
ends its preview in main's copy, as the tab bar's pin does. Other builds compile the switch out.
* fix(layout): leave a pinned tab's preview mark to the window until main opens files
Changing the shared pin command would change what a paired client's pin does in every build.
* fix(layout): write a folded legacy row in its loaded form when a layout.apply command touches its claim
A legacy terminal row the load folds into a tab-bar tab stays stored as is. Closing the tab it
repeats, or giving it a tab-bar entry by moving it, let the next load resolve the claim the other
way: the row came back or took the other tab's terminal. Such a command now writes the row as
the load had it; every other record still stays as stored.
* feat(layout): accept tab moves, group splits and group sizes on layout.apply
Group sizes have no tab, so they go to the workspace's routed partition.
* refactor(layout): move the window's layout.apply sender out of the terminal pane folder
Tab groups send through it too; it now also acts as today when main lacks the group.
* feat(layout): reorder, move and split tab groups, and save group sizes, through main behind ORCA_LAYOUT_OWNER
A tab drop and Move Tab to Split wait for main's answer and then show main's placement, with main's
new group id; group divider sizes are drawn while dragging and saved by main when the drag ends.
Closing an empty group needs no command: main never holds an empty group.
* docs(layout): wrap the layout-owner sender's doc comment
* fix(layout): review fixes for tab moves through main
- Read a folded row's terminals with the loader's own claim rule, so rows sharing only an SSH
session are covered too, and drop the row's remote session entry with it.
- The record-patch test allows a folded row only in its loaded form.
- A tab drag ends at once; main's answer shows the move after.
- A lone group main moved whole is placed so only when this window's group is lone too.
* refactor(layout): adopt only a folded row's pane layout beside its row
A row's remote session entry is carried through load and save unchanged, so adopting it from the
load changed nothing (the earlier commit's message overstated it).
* feat(layout): record a window's terminal tab close through layout.apply
closeTabs can now record each terminal tab it closes, or does not find, as closed for the given
reason (pruned as a partition's records are), so a start that commits later is refused, as today's
close records it. layout.apply takes one tab's close with the window's reason: main forces it in
every holder partition, marks a user close's launches, stops no terminal (the window already did,
sparing any another tab shares), and leaves a workspace it lacks to today's close. A sibling the
load supplied that the close only renumbered is no longer written.
* feat(layout): send the window's terminal tab close to main behind ORCA_LAYOUT_OWNER
The window still closes the tab and stops its terminals itself; main records the close through
layout.apply instead of the close intent, which stays for paired servers and when main lacks the
workspace.
* fix(layout): review fixes for the tab close through main
- The window's close sends through a store-free sender, so the store no longer imports itself.
- Main marks a user close's launches by the terminal's own id.
- A recorded close prunes the whole partition's close records, as today, not one workspace's.
* feat(layout): open and close browser tabs and pages through main behind the layout-owner build switch
Co-Authored-By: Claude <noreply@anthropic.com>
* feat(layout): open editor tabs under the window's id, per split, and promote previews through layout.apply
openEditorTab takes the window's tab id (the same open again is a no-op, a held id is refused),
dedupes per group so a file open in another split gets its own tab, pins an existing preview on a
non-preview open, and places the new tab before closing the replaced preview so a group whose only
tab is its preview keeps its id. layout.apply accepts openEditorTab, promotePreviewTab and a
closeTabs with no terminal close record.
* feat(layout): record the window's editor tab opens, closes and preview promotions in main behind ORCA_LAYOUT_OWNER
The window still draws first (openFile returns at once and its callers act on the tab by id).
Main then records the tab under the window's ids and decides the group fallback and preview
replacement by the same shared rule. Switch points: the one editor tab create, the unified tab
close, preview promotion on activation, edit and make-permanent, and the reopened tab's position.
* refactor(layout): name the browser sender's no-op so the window bundle keeps its names
* feat(layout): move a window's dragged-out pane into a new tab through layout.apply behind ORCA_LAYOUT_OWNER
* refactor(layout): keep the topology commit under its line limit
* Record sleep and wake through main (dark, build switch off by default)
In a layout-owner build, main records each sleep the window decided and each
wake of the one pane it showed: a workspace sleep or a pane hibernation sends a
'sleep' per tab once the terminals stopped, and a resumed pane sends a 'wake'
naming only itself. The window keeps deciding, stopping terminals and saving
its own records.
* fix(layout): keep a served browser tab's open, address and close in order while main writes it
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(layout): record sleep and wake in every partition holding the tab
Review fix: a tab a legacy relay reattach left in two partitions gets its
sleeping record in both, as a pane close does. The pane hibernation comment
no longer claims a stop on the branch that stops nothing.
* fix(layout): review fixes for the window's pane drag-out through main
* fix(layout): review fixes for editor tabs through main
openEditorTab refuses a group main lacks instead of placing the tab in another group, and gives
a workspace with no group (the window keeps its emptied root group; main closes it) the sender's
group id. Tests pin close-all, the emptied root group, the refused open into a fresh split, and
that the window sends an edit-mode file's record exactly as its session save keeps it.
* docs(layout): note what a refused served-tab close leaves
* refactor(layout): re-key a moved pane from the move alone
* test(layout): assert the window drew the tab main refused
* refactor(layout): share one record-only layout.apply sender and inline the id schema
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
#29608 added integrationCredentials.moveKeyToFile without an ownership entry, failing host-operation-ownership.test.ts on main.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(agents): new-agent shortcut and direct launches ask the workspace's own host for its agents (#29599)
* fix(agents): probe a paired server's new workspace, not the server's default context
* fix(agents): drop a pending shortcut launch when the workspace's host changed
* refactor(agents): compare the shortcut's detection target through the shared inventory
* fix(agents): keep the disabled-agent preflight and recheck the host before explain launches
* fix(agents): a delayed shortcut launch opens in the pane it was pressed in
* fix(agents): pick the delayed default agent with the settings current when the host answers
* fix(ssh): keep a host connected for its bridged relay terminals when another desktop already moved their project (STA-10604)
Desktop 2's move onto the server desktop 1 already runs refused to link a
project its own relay-era terminal still runs in, and the connect then failed
outright: this version has no relay to fall back to. The decision now keeps
that host connected and unmoved, starts the legacy relay bridge, and the old
tab reattaches its shell through it. The SSH host card names the terminals and
how to clear them; the next connect after they exit links the project.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(ssh): release a bridged ssh: row pane's claim on detach too
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(ssh): admit the bridged mark in the renderer, and remount bridged tabs even when the connect reply lands first
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(ssh): keep the unverifiable line for a bridged host whose terminal can't be confirmed
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(ssh): keep the linked-project terminal guidance neutral; only the live bridged line reassures
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix(orcad): tell the user when a locked macOS Keychain hides Linear and Jira, and recover without a restart
A managed macOS host reached only over SSH keeps its login Keychain locked
after a reboot, so the server's credential key failed closed and the app
asked the user to reconnect a token that was fine. Exit 36 from
security find-generic-password (errSecInteractionNotAllowed) is now a
'locked' lookup: the server logs it once, retries the Keychain on each
credential use and before each Linear/Jira status read, and status carries
an optional credentialErrorReason: 'keychain_locked' that the app shows in
place of the generic text. Once unlocked, Settings offers a one-click move
of the same key into the owner-only key file.
* fix(orcad): keep retrying a locked Keychain after a timeout, fence the key move to the host that asked, report the lock only for credentials that need the key
* fix(settings): offer the file-storage switch to a paired browser's implicit server too
* fix(settings): route the Keychain notice and key move by the host that reported the status; sweep stale key-record staging; say the moved key is not machine-bound
* fix(orcad): sweep only this machine's key-record staging, so a shared home's other hosts keep theirs
* feat(wsl-mover): the ledgered desktop rewrite stage that finishes a committed move (D1b-7b)
* fix(wsl-mover): a move finishes its rewrite only once every store exists (D1b-7b)
* fix(wsl-mover): chats never move per decision C, the journal enforces registry completeness, stores time out (D1b-7b)
* fix(runtime): keep a restored agent turn unverifiable instead of done after a server restart
* fix(runtime): retire restored turns by the synced leaf's title clock; no Stop for unverifiable mobile turns
* fix(runtime): capability-gate restored agent rows so older clients keep the legacy done projection
* fix(runtime): retire restored turns only on shell titles; restored rows never pierce live client status
* test(web): the browser client advertises the restored agent status capability
* feat(layout): add the dark layout.apply channel and route pane splits through main behind ORCA_LAYOUT_OWNER
A build made with ORCA_LAYOUT_OWNER=main registers layout.apply: main applies the
window's layout command to the stored partition through the shared applyLayoutCommand,
minting new ids. A user split then waits for main's answer and mounts the pane id main
minted. Every other build substitutes 'window', so neither branch is compiled in.
* fix(layout): write only the records a layout.apply command touched
A load-then-save rewrite also normalizes records the command never touched. The command's
effect is now the diff between the save before and after it, written onto the stored
partition record by record. A split whose main reply says a paired server holds the tab
splits as today.
* fix(layout): write a load-supplied record whole and check that patched partitions reload to the command's result
* feat(layout): close, rename and equalize panes through main behind ORCA_LAYOUT_OWNER
In a layout-owner build the window's pane close, title change and equalize go through
layout.apply and the window shows main's answer. A window pane close keeps the legacy
close's rules: the tab's last pane is refused, a pane restarted since main read the close is
left alone, every partition holding the tab closes it, and the tab's remote session id follows
the remaining pane. The rules sit behind a command field only layout.apply sets.
* fix(layout): close the tab when a racing pane close leaves the window one pane
* fix(layout): leave an unmounted tab alone when main refuses its last pane
* feat(layout): move panes and save divider ratios through main behind ORCA_LAYOUT_OWNER
Claude-Session: 10569c9a-a0a1-4fc9-8469-0edfb7aed125
* test(layout): a taken pane drop whose pane closed does nothing
Claude-Session: 10569c9a-a0a1-4fc9-8469-0edfb7aed125
* test(layout): pin the window's pane move to main's saved tree, and the drops main cannot take
Claude-Session: 10569c9a-a0a1-4fc9-8469-0edfb7aed125
* fix(layout): close a user's pane even if its terminal restarted, as today
Claude-Session: 10569c9a-a0a1-4fc9-8469-0edfb7aed125
* test(layout): pin the restarted-pane close directly, not through a made-up profile state
Claude-Session: 10569c9a-a0a1-4fc9-8469-0edfb7aed125
* fix(layout): leave a split to today's path only when the workspace's row names a paired server
The split switch point asked which host to route to with a helper that falls back to the focused
server, so a focused server could claim a workspace whose row names no owner. Use the same owner
test the tab mirror uses.
* test(layout): type the split owner mock without a cast
* test(layout): type the layout-owner command mocks with the real signature
* test(layout): type the gesture command mock with the real signature
* fix(layout): close the dark path's gaps: unmounted split panes, refused or failed splits and moves, ratios during a move, release guard
- A pane main saved but the window could not mount (source pane or manager gone during the await)
is closed again in main, so it does not come back on restart.
- A split main refuses because its copy lacks the workspace, tab or pane, or a failed call, splits
as today; a refused or failed move moves as today.
- Divider ratios committed while a move is in flight wait for the window's move, then go to main.
- The orcad bundle gets the ORCA_LAYOUT_OWNER define when the switch is on.
- Packaging and the orcad template refuse ORCA_LAYOUT_OWNER=main and any output a dark build marked.
- workspace-layout-record-patch.ts: no non-null assertions; plain comment for the map depth.
* test(layout): count a split pane as mounted from the split mock's own results
* fix(layout): act as today whenever main lacks the target or the call throws, not just for splits
- sendPaneCommandToLayoutOwner runs asToday for workspace/tab/pane_not_found and a thrown call, so
close, rename and equalize fall back like split and move; a handler that throws no longer runs the
fallback too.
- A close main refuses as its last pane, while this window still has others, closes as today.
- A move whose local step throws still releases held divider ratios.
- Drop the orcad output marker: nothing read it; the template build refuses the switch itself.
* test(layout): plain names for the last-pane fallback test and the marker comment
* docs(layout): say plainly what an indeterminate write can leave behind
* fix(layout): finish the main merge with this branch's versions of the layout-owner files
* test(build): copy the layout-owner build guard into the compiler-cache checkout fixture
* feat(layout): send tab rename, color and pin through main behind the layout-owner build switch
In an ORCA_LAYOUT_OWNER=main build, the tab bar's rename, color and pin ask main's layout.apply
first; the window then runs today's action. When main lacks the tab or the call fails the
window acts as today; a paired server's workspace keeps today's path. Pinning a tab now also
ends its preview in main's copy, as the tab bar's pin does. Other builds compile the switch out.
* fix(layout): leave a pinned tab's preview mark to the window until main opens files
Changing the shared pin command would change what a paired client's pin does in every build.
* fix(layout): write a folded legacy row in its loaded form when a layout.apply command touches its claim
A legacy terminal row the load folds into a tab-bar tab stays stored as is. Closing the tab it
repeats, or giving it a tab-bar entry by moving it, let the next load resolve the claim the other
way: the row came back or took the other tab's terminal. Such a command now writes the row as
the load had it; every other record still stays as stored.
* feat(layout): accept tab moves, group splits and group sizes on layout.apply
Group sizes have no tab, so they go to the workspace's routed partition.
* refactor(layout): move the window's layout.apply sender out of the terminal pane folder
Tab groups send through it too; it now also acts as today when main lacks the group.
* feat(layout): reorder, move and split tab groups, and save group sizes, through main behind ORCA_LAYOUT_OWNER
A tab drop and Move Tab to Split wait for main's answer and then show main's placement, with main's
new group id; group divider sizes are drawn while dragging and saved by main when the drag ends.
Closing an empty group needs no command: main never holds an empty group.
* docs(layout): wrap the layout-owner sender's doc comment
* fix(layout): review fixes for tab moves through main
- Read a folded row's terminals with the loader's own claim rule, so rows sharing only an SSH
session are covered too, and drop the row's remote session entry with it.
- The record-patch test allows a folded row only in its loaded form.
- A tab drag ends at once; main's answer shows the move after.
- A lone group main moved whole is placed so only when this window's group is lone too.
* refactor(layout): adopt only a folded row's pane layout beside its row
A row's remote session entry is carried through load and save unchanged, so adopting it from the
load changed nothing (the earlier commit's message overstated it).
* feat(layout): record a window's terminal tab close through layout.apply
closeTabs can now record each terminal tab it closes, or does not find, as closed for the given
reason (pruned as a partition's records are), so a start that commits later is refused, as today's
close records it. layout.apply takes one tab's close with the window's reason: main forces it in
every holder partition, marks a user close's launches, stops no terminal (the window already did,
sparing any another tab shares), and leaves a workspace it lacks to today's close. A sibling the
load supplied that the close only renumbered is no longer written.
* feat(layout): send the window's terminal tab close to main behind ORCA_LAYOUT_OWNER
The window still closes the tab and stops its terminals itself; main records the close through
layout.apply instead of the close intent, which stays for paired servers and when main lacks the
workspace.
* fix(layout): review fixes for the tab close through main
- The window's close sends through a store-free sender, so the store no longer imports itself.
- Main marks a user close's launches by the terminal's own id.
- A recorded close prunes the whole partition's close records, as today, not one workspace's.
* feat(layout): open and close browser tabs and pages through main behind the layout-owner build switch
Co-Authored-By: Claude <noreply@anthropic.com>
* feat(layout): open editor tabs under the window's id, per split, and promote previews through layout.apply
openEditorTab takes the window's tab id (the same open again is a no-op, a held id is refused),
dedupes per group so a file open in another split gets its own tab, pins an existing preview on a
non-preview open, and places the new tab before closing the replaced preview so a group whose only
tab is its preview keeps its id. layout.apply accepts openEditorTab, promotePreviewTab and a
closeTabs with no terminal close record.
* feat(layout): record the window's editor tab opens, closes and preview promotions in main behind ORCA_LAYOUT_OWNER
The window still draws first (openFile returns at once and its callers act on the tab by id).
Main then records the tab under the window's ids and decides the group fallback and preview
replacement by the same shared rule. Switch points: the one editor tab create, the unified tab
close, preview promotion on activation, edit and make-permanent, and the reopened tab's position.
* refactor(layout): name the browser sender's no-op so the window bundle keeps its names
* feat(layout): move a window's dragged-out pane into a new tab through layout.apply behind ORCA_LAYOUT_OWNER
* refactor(layout): keep the topology commit under its line limit
* fix(layout): keep a served browser tab's open, address and close in order while main writes it
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(layout): review fixes for the window's pane drag-out through main
* fix(layout): review fixes for editor tabs through main
openEditorTab refuses a group main lacks instead of placing the tab in another group, and gives
a workspace with no group (the window keeps its emptied root group; main closes it) the sender's
group id. Tests pin close-all, the emptied root group, the refused open into a fresh split, and
that the window sends an edit-mode file's record exactly as its session save keeps it.
* docs(layout): note what a refused served-tab close leaves
* refactor(layout): re-key a moved pane from the move alone
* test(layout): assert the window drew the tab main refused
* refactor(layout): share one record-only layout.apply sender and inline the id schema
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix(agent-hooks): bound retained agent exits
Proven exits were only forgotten by a successor claim, so closed tabs and
retired panes kept theirs forever and every save rewrote them. Drop an exit
when its tab closes, its pane retires or its PTY is really torn down (not a
reconnectable disconnect), and prune exits older than the hydrate window
at save.
* fix(agent-hooks): closing a moved pane's former tab keeps its exit
* fix(agent-hooks): keep an aged exit while its retained resume row remains
* fix(agent-hooks): launch-authority retirement keeps a proven exit
* ci: remove Windows build and persistence test overhead
* ci: collect unit phase diagnostics during PR checks
* test(ci): seed hook reuse fixture from the canonical prebuild
The hook reuse regression first built into a missing private --output, so it
always reached Cargo (74s warm on Windows). Build through the default output
instead, which reuses a warm canonical prebuild, then copy its executable and
fingerprint into the private fixture root.
* fix(lint): clear inherited max-lines and broad object tab parameter after main merge
Combine the duplicate shared/execution-host imports in runtime-host-services
(301 -> 300 lines) and type the overlap test's tab mapper with the
RuntimeMobileSessionClientTab owner type instead of object.
* fix(ci): align inherited runtime and workflow contracts
* test: establish changed runtime probe signatures deterministically
* test: await asynchronous daemon profile cleanup
* test: shut down owned daemons before deleting profiles
* test: clean up oracle daemons during final app close
* test: recognize verified Linux zombie exit during daemon teardown
* fix: preserve folder SSH ownership when project groups conflict
Every orcad start ran installPendingOrcadMigrationShellHistory, which paid a
process inventory with an empty worktree list. That inventory could leave a
surviving daemon session unfiled and drop its restored orchestration receipt
before any client connected. Return early unless a committed migration still
holds a received shell history, and refresh against the host's real worktrees
when one does.
Follow-up to #28902.
* fix(orcad): reclaim a pre-#28125 lock whose PID another user now owns, and explain a possibly reused PID
Records written before #28125 carry no start ticks or boot id, so on Linux a
wall-clock mismatch is unproven and a reused PID kept the lock held forever.
A PID owned by another non-root uid cannot be the holder (orcad never changes
uid), so that now proves reuse in both lock reclaim and managed stop. When the
verdict stays unproven, the error names the live process and the orcad.lock
path that is safe to remove once no orcad is running.
* fix(orcad): treat the overflow uid as unproven and word the reused-PID error for any boot-less record
* test(orcad): keep the Linux-platform lock cases off Windows, where the platform hook also drives data-root privacy
* feat(layout): add the dark layout.apply channel and route pane splits through main behind ORCA_LAYOUT_OWNER
A build made with ORCA_LAYOUT_OWNER=main registers layout.apply: main applies the
window's layout command to the stored partition through the shared applyLayoutCommand,
minting new ids. A user split then waits for main's answer and mounts the pane id main
minted. Every other build substitutes 'window', so neither branch is compiled in.
* fix(layout): write only the records a layout.apply command touched
A load-then-save rewrite also normalizes records the command never touched. The command's
effect is now the diff between the save before and after it, written onto the stored
partition record by record. A split whose main reply says a paired server holds the tab
splits as today.
* fix(layout): write a load-supplied record whole and check that patched partitions reload to the command's result
* feat(layout): close, rename and equalize panes through main behind ORCA_LAYOUT_OWNER
In a layout-owner build the window's pane close, title change and equalize go through
layout.apply and the window shows main's answer. A window pane close keeps the legacy
close's rules: the tab's last pane is refused, a pane restarted since main read the close is
left alone, every partition holding the tab closes it, and the tab's remote session id follows
the remaining pane. The rules sit behind a command field only layout.apply sets.
* fix(layout): close the tab when a racing pane close leaves the window one pane
* fix(layout): leave an unmounted tab alone when main refuses its last pane
* feat(layout): move panes and save divider ratios through main behind ORCA_LAYOUT_OWNER
Claude-Session: 10569c9a-a0a1-4fc9-8469-0edfb7aed125
* test(layout): a taken pane drop whose pane closed does nothing
Claude-Session: 10569c9a-a0a1-4fc9-8469-0edfb7aed125
* test(layout): pin the window's pane move to main's saved tree, and the drops main cannot take
Claude-Session: 10569c9a-a0a1-4fc9-8469-0edfb7aed125
* fix(layout): close a user's pane even if its terminal restarted, as today
Claude-Session: 10569c9a-a0a1-4fc9-8469-0edfb7aed125
* test(layout): pin the restarted-pane close directly, not through a made-up profile state
Claude-Session: 10569c9a-a0a1-4fc9-8469-0edfb7aed125
* fix(layout): leave a split to today's path only when the workspace's row names a paired server
The split switch point asked which host to route to with a helper that falls back to the focused
server, so a focused server could claim a workspace whose row names no owner. Use the same owner
test the tab mirror uses.
* test(layout): type the split owner mock without a cast
* test(layout): type the layout-owner command mocks with the real signature
* test(layout): type the gesture command mock with the real signature
* fix(layout): close the dark path's gaps: unmounted split panes, refused or failed splits and moves, ratios during a move, release guard
- A pane main saved but the window could not mount (source pane or manager gone during the await)
is closed again in main, so it does not come back on restart.
- A split main refuses because its copy lacks the workspace, tab or pane, or a failed call, splits
as today; a refused or failed move moves as today.
- Divider ratios committed while a move is in flight wait for the window's move, then go to main.
- The orcad bundle gets the ORCA_LAYOUT_OWNER define when the switch is on.
- Packaging and the orcad template refuse ORCA_LAYOUT_OWNER=main and any output a dark build marked.
- workspace-layout-record-patch.ts: no non-null assertions; plain comment for the map depth.
* test(layout): count a split pane as mounted from the split mock's own results
* fix(layout): act as today whenever main lacks the target or the call throws, not just for splits
- sendPaneCommandToLayoutOwner runs asToday for workspace/tab/pane_not_found and a thrown call, so
close, rename and equalize fall back like split and move; a handler that throws no longer runs the
fallback too.
- A close main refuses as its last pane, while this window still has others, closes as today.
- A move whose local step throws still releases held divider ratios.
- Drop the orcad output marker: nothing read it; the template build refuses the switch itself.
* test(layout): plain names for the last-pane fallback test and the marker comment
* docs(layout): say plainly what an indeterminate write can leave behind
* fix(layout): finish the main merge with this branch's versions of the layout-owner files
* test(build): copy the layout-owner build guard into the compiler-cache checkout fixture
* feat(layout): send tab rename, color and pin through main behind the layout-owner build switch
In an ORCA_LAYOUT_OWNER=main build, the tab bar's rename, color and pin ask main's layout.apply
first; the window then runs today's action. When main lacks the tab or the call fails the
window acts as today; a paired server's workspace keeps today's path. Pinning a tab now also
ends its preview in main's copy, as the tab bar's pin does. Other builds compile the switch out.
* fix(layout): leave a pinned tab's preview mark to the window until main opens files
Changing the shared pin command would change what a paired client's pin does in every build.
* fix(layout): write a folded legacy row in its loaded form when a layout.apply command touches its claim
A legacy terminal row the load folds into a tab-bar tab stays stored as is. Closing the tab it
repeats, or giving it a tab-bar entry by moving it, let the next load resolve the claim the other
way: the row came back or took the other tab's terminal. Such a command now writes the row as
the load had it; every other record still stays as stored.
* feat(layout): accept tab moves, group splits and group sizes on layout.apply
Group sizes have no tab, so they go to the workspace's routed partition.
* refactor(layout): move the window's layout.apply sender out of the terminal pane folder
Tab groups send through it too; it now also acts as today when main lacks the group.
* feat(layout): reorder, move and split tab groups, and save group sizes, through main behind ORCA_LAYOUT_OWNER
A tab drop and Move Tab to Split wait for main's answer and then show main's placement, with main's
new group id; group divider sizes are drawn while dragging and saved by main when the drag ends.
Closing an empty group needs no command: main never holds an empty group.
* docs(layout): wrap the layout-owner sender's doc comment
* fix(layout): review fixes for tab moves through main
- Read a folded row's terminals with the loader's own claim rule, so rows sharing only an SSH
session are covered too, and drop the row's remote session entry with it.
- The record-patch test allows a folded row only in its loaded form.
- A tab drag ends at once; main's answer shows the move after.
- A lone group main moved whole is placed so only when this window's group is lone too.
* refactor(layout): adopt only a folded row's pane layout beside its row
A row's remote session entry is carried through load and save unchanged, so adopting it from the
load changed nothing (the earlier commit's message overstated it).
* feat(layout): record a window's terminal tab close through layout.apply
closeTabs can now record each terminal tab it closes, or does not find, as closed for the given
reason (pruned as a partition's records are), so a start that commits later is refused, as today's
close records it. layout.apply takes one tab's close with the window's reason: main forces it in
every holder partition, marks a user close's launches, stops no terminal (the window already did,
sparing any another tab shares), and leaves a workspace it lacks to today's close. A sibling the
load supplied that the close only renumbered is no longer written.
* feat(layout): send the window's terminal tab close to main behind ORCA_LAYOUT_OWNER
The window still closes the tab and stops its terminals itself; main records the close through
layout.apply instead of the close intent, which stays for paired servers and when main lacks the
workspace.
* fix(layout): review fixes for the tab close through main
- The window's close sends through a store-free sender, so the store no longer imports itself.
- Main marks a user close's launches by the terminal's own id.
- A recorded close prunes the whole partition's close records, as today, not one workspace's.
* feat(layout): open and close browser tabs and pages through main behind the layout-owner build switch
Co-Authored-By: Claude <noreply@anthropic.com>
* refactor(layout): name the browser sender's no-op so the window bundle keeps its names
* feat(layout): move a window's dragged-out pane into a new tab through layout.apply behind ORCA_LAYOUT_OWNER
* refactor(layout): keep the topology commit under its line limit
* fix(layout): keep a served browser tab's open, address and close in order while main writes it
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(layout): review fixes for the window's pane drag-out through main
* docs(layout): note what a refused served-tab close leaves
* refactor(layout): re-key a moved pane from the move alone
* refactor(layout): share one record-only layout.apply sender and inline the id schema
Co-Authored-By: Claude <noreply@anthropic.com>
* Restore the browser layout-apply hook dropped by the main merge
---------
Co-authored-by: Claude <noreply@anthropic.com>