* Route the remaining window pane-tree writers through main (dark, build switch off by default)
* E1b review fixes: valid pane ids, keep a mounted split, automatic closes skip the user mark, sole-pane exit guard
The fake Claude wrote .orca-sign-in-browser.link directly, so the link
poller could read it between create and write, see an empty link, and end
the sign-in. Write a temp file and rename it into place, as the real
stand-in script does. Flake introduced with the tests in #29566.
* test(orcad): wait for an idle-retiring daemon before deleting the serve-parity profile
A terminal daemon that loses its last client deletes its pid record before it
exits, so teardown that found daemons only through pid records skipped it and
raced its open files (EBUSY on Windows ARM). Record the pid before stopping
orcad and require that process's exit.
* test(orcad): record profile daemons before stopping orcad in launcher and stop-request tests
* test(orcad): wait for the daemon's child processes before deleting its profile on Windows
The terminal daemon runs with the profile as its cwd and its children inherit it,
so a child that outlives the daemon keeps the profile directory busy (EBUSY on
rmdir). Record the daemon's process tree before shutdown and require each
recorded child's exit, naming any survivor at the deadline.
* test(orcad): keep the real Windows process table out of the teardown unit tests
The web client positions a retained pane host from a measured tab-group body,
but the pane mounted (and fit xterm) a render before that measurement, at the
32px placeholder box, then refit when the measurement landed. Mount the pane
only after the first read.
FitAddon also divides by xterm's css cell, which is the rounded canvas width
over the current cols, so the same box proposed 51 cols from a 75-col replay
and 52 from a 52-col pane: a revealed pane resized twice. Fit with the
grid-independent device cell instead.
* Open into a new split in one layout command (dark, build switch off by default)
Main makes the window's new split group and its first tab (terminal, editor
or browser) in one apply, so main never refuses the open for a group it lacks
and never stores an empty group. The window derives the split from its own
group tree in the three existing switch-point recorders.
Co-Authored-By: Claude <noreply@anthropic.com>
* Open beside an emptied root split, and into a group main already holds
Co-Authored-By: Claude <noreply@anthropic.com>
* Describe both openInNewGroup fallbacks in its doc comment
Co-Authored-By: Claude <noreply@anthropic.com>
* Test: no split is read back beside a sibling that is itself split
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix(ssh): reopening a tab slept from a paired desktop resumes its agent on the managed host
A paired client's worktree sleep stops the host's PTYs, but the resume
record its renderer writes never reaches the host, and the host is what
answers the tab open. Capture the record on the host from its own hook
store before the stop, applying the shared holder rule (an agent the
hook store proved exited leaves no record).
* fix(ssh): keep the launch recipe and record acknowledged stops when inventory fails
* fix(ssh): only a proven agent exit retires a pane's earlier sleep record
* fix(ssh): stamp the captured record's SSH host and keep a saved launch recipe
* fix(ssh): read the session id after the presence probe that may outlast it
* fix(agents): a resume reuses only its launch's recorded args, never permission-widening defaults
Resuming a session started as a hand-typed `claude` filled the missing
launch recipe from settings defaults, which carry the permission bypass.
Host tab-open resume, window cold restore and worktree wake now share
one rule: the recorded recipe, or none.
* fix(ssh): a WSL relay tag on a captured record is local, not another host
* fix(ssh): a capture retires when the hook store names another agent in the pane
* ci: build the WSL server template once
* ci: reuse validated Windows native addons in E2E preparation
* ci: avoid the unused standalone server build in SSH E2E shards
* 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)