Commit Graph
3522 Commits
Author SHA1 Message Date
Kelvin Amoaba 2266260bbd fix(links): end autolinked urls at CJK punctuation in chat, terminal and mobile (#32013) 2026-10-22 22:38:36 +00:00
Kelvin Amoaba e0bd073930 feat(browser): Chrome-style page context menu and DevTools window shortcuts (#31821) 2026-10-22 22:36:18 +00:00
Kelvin Amoaba f4974b8958 fix(mobile): send multi-line chat messages as one bracketed paste (#31776) 2026-10-22 22:14:18 +00:00
Jinwoo Hong 4b65d95964 feat(connectors): connector follow-ups, held during approvals, behind the off-by-default experimentalConnectors flag (B2b-2) (#31806)
* feat(connectors): connector seam lifecycle, conversation links and start behind experimentalConnectors (B2b-1)

* feat(connectors): connector follow-ups, held while the agent asks, behind experimentalConnectors (B2b-2)

* feat(agent-write): opt-in launch cancel, approval wait and binding fence on the shared writer

executeAgentLaunch takes an optional signal; sendTerminalAgentPrompt (and
sendAgentTurn) take optional onPermission: 'wait' and expectedBinding. No
existing caller sets them, so every current path is unchanged.

* fix(agent-write): keep the default writer path main's; fence cancel and real submit boundary

Only opted-in writes take the submit layer, so default timing and the leaf
re-read are unchanged. Cancellation is rechecked before a structured-refusal
terminal fallback, and an opted-in launch learns Enter from an onSubmitWritten
callback at the write itself, OMP's combined frame included.

* fix(connectors): pass the seam's signal to the launch, read links from each host's rows, and keep delivery ids on orchestration's durable receipts (B2b-1)

* fix(agent-launch): a cancel after a failed startup spawn recovers the created workspace

* fix(connectors): keep an unknown receipt once the launch published, and restore local-only metadata ownership (B2b-1)

* fix(connectors): settle a lane when its process is lost; removal stays host-qualified; folder delete announces removal

* fix(plugins): keep folder removals out of public worktree events

* fix(connectors): a launch admits its process for the lane, so queued sends never adopt a successor

* fix(connectors): classify connector RPCs as host-owned; CLI start output claims a start only when definite
2026-10-22 17:58:29 -04:00
Kelvin Amoaba 44ab202965 fix(native-chat): show spelling suggestions when right-clicking chat inputs (#32035) 2026-10-22 21:55:57 +00:00
Jinwoo Hong 57b5082404 feat(connectors): connector start and conversation links behind the off-by-default experimentalConnectors flag (B2b-1) (#31805)
* feat(connectors): connector seam lifecycle, conversation links and start behind experimentalConnectors (B2b-1)

* feat(agent-write): opt-in launch cancel, approval wait and binding fence on the shared writer

executeAgentLaunch takes an optional signal; sendTerminalAgentPrompt (and
sendAgentTurn) take optional onPermission: 'wait' and expectedBinding. No
existing caller sets them, so every current path is unchanged.

* fix(agent-write): keep the default writer path main's; fence cancel and real submit boundary

Only opted-in writes take the submit layer, so default timing and the leaf
re-read are unchanged. Cancellation is rechecked before a structured-refusal
terminal fallback, and an opted-in launch learns Enter from an onSubmitWritten
callback at the write itself, OMP's combined frame included.

* fix(connectors): pass the seam's signal to the launch, read links from each host's rows, and keep delivery ids on orchestration's durable receipts (B2b-1)

* fix(agent-launch): a cancel after a failed startup spawn recovers the created workspace

* fix(connectors): keep an unknown receipt once the launch published, and restore local-only metadata ownership (B2b-1)

* fix(connectors): classify connector RPCs as host-owned; CLI start output claims a start only when definite
2026-10-22 17:40:40 -04:00
Neil 9c4ad143bb refactor: colocate Jira favourite-filter contracts (#32102) 2026-10-22 14:30:36 -07:00
Jinwoo Hong 7cabd3a8dc Layout groundwork (switched off): every question about which pane holds a terminal asks main's layout (#32002)
* refactor(runtime): every reader of a terminal's pane goes through paneOfPty (L80)

With the layout-owner switch on, main's layout answers which tab and pane hold a terminal, and
nothing reads or writes the PTY record's tabId/paneKey copies or their graph stamp. Switch-off
reads the copies exactly as before.

* refactor(runtime): read a terminal's pane through the runtime's paneOfPty method

* fix(runtime): main's pane index answers paneOfPty before any window graph

A headless host never installs a window graph, so the query asked a publisher that did not exist
and every terminal read as paneless. The index is now made on first query; it publishes only once a
graph is installed, as before.

* test(runtime): pin that a query with no window publishes nothing

* test(runtime): a terminal summary reads its pane once (L80)

* feat(runtime): pin bind-first at registration, and one own-handle rule with or without a window (switch-on only)

* test(runtime): census and tests that every create path binds its pane before it registers (L80)

* test(runtime): a bind-first fixture that stands in for the spawn commit (L80)

* test(runtime): fixtures bind a terminal's pane before registering it, as the spawn commit does (L80)

Also, with the switch on: skip the bind-first check for a paired server's workspace, release a
closed tab's terminal ownership once main has dropped the pane, and publish a pane's agent status
from the terminal main's layout binds it to.

* feat(runtime): an exit after main's close still finds the pane it retires (switch-on only, L80 Q2)

Main's layout write records every pane it drops whose terminal no pane holds any more, in a new
optional session field closedTerminalPanesByPaneKey (same TTL and cap as closed-tab records).
Exit cleanup reads main's layout first, then that record by terminal id and incarnation, and
removes the entry it used. Older builds ignore the field.

* refactor(layout): one dropped-leaf step every live layout edit runs (switch-on only, L80 Q2)

The step receives each dropped pane (host, worktree, tab, leaf, terminal, incarnation), computed
across the whole layout so a moved pane is not dropped, and runs only for a commit that declares a
live edit. Today it records the closed-pane entries; a drop is recorded once per terminal.

* fix(runtime): a phone or headless tab close still stops and releases its live terminals after main's layout drops the panes (switch-on only, L80)

The headless close reads each row's live terminal before main's close drops the panes it is found
by, and a renderer-retired tab releases the owned terminal main left in no pane, as the explicit
close already did. Close tests now commit through main's layout and bind panes first.

* fix(runtime): a local workspace's terminal tab left in a paired server's partition is main's pane (switch-on only, L257)

Older builds could save a local worktree's tab rows into a runtime partition (#18202); its terminals
are local, so main's index takes those tabs for workspaces the router homes locally. A tab or
terminal the local partition also holds is indexed once, from there; SSH and server-owned
workspaces are skipped as before.

* test(layout): the legacy-close comparison leaves out the closed-pane record only layout-owner builds write (L80)

* test(pty): a pane whose failed start withdrew its tab resolves to terminal_not_found in layout-owner builds (L80 #7)

Main's layout no longer holds the pane, so there is no pane to answer for; window-owned builds keep
the disconnected stub from the exited terminal's record.

* test(runtime): orca-runtime suites bind panes first, as the real store and spawn commit do (L80)

Fake PTY spawns commit the binding into the pane the runtime wrote, stores list their partitions,
and SSH sessions sit in their ssh:<conn> partition; terminals registered directly bind first.

* fix(runtime): main's layout names a pane's terminal only when the workspace and stamped incarnation match (switch-on only, L80)

A record made before its adoption registers (a host restart) or a local id reused after one
matched a saved binding by id alone, so a phone or paired desktop could be handed a second handle
for the pane before the terminal's own handle arrived and stay on it.

* test(runtime): a restored pane is only unnamed in the adoption gap, and no local record rescues a paired server's tab (L80)

* test(runtime): recovery, close and routing suites set up terminals as production does (L80)

A recoverable pane is one the host inventory dropped with no exit, live terminals register
rather than appear only in the window graph, and SSH stores route as the real Store does.

* test(runtime): suspects 3, 4 and 5 bind, register and apply as production does; PR-1's main-layout closes in the part-10 and part-11 close tests (L80)

* test(runtime): a fixture store applies main's layout commands as a profile does; PR-1's own closes in the part-05, part-08 and UUID-leaf close tests (L80)

* test(runtime): the profile-store stand-in moves to its own fixture, keeping bind-first under the line limit (L80)

* test(runtime): orphan adoption reads the pane from main's layout with the switch on, not the record copy (L80)

* test(runtime): a phone's pane close and a pinned tab close are main's layout closes with the switch on (#31571), in the no-widening and surface-close tables (L80)

* test(runtime): a paired phone close commits in main's layout with no relay ack when the switch is on (#31571) (L80)

* fix(terminal): a parked pane resumes its agent once main has closed the other pane that held the session (layout-owner build, L80 P1)

* test(runtime): the parked-pane suite's restart holder is a saved pane with no registered terminal, under the line limit (L80 P1)

* fix(terminal): a spawn commit that binds a reused local terminal id unbinds it from the saved pane of the older process (layout-owner build, L80 P2)

* test(runtime): the legacy OSC row test uses the UUID leaf main's layout holds and registers its live terminal with the switch on (L80 E5)

* test(runtime): the pty-exit echo close of a dead headless tab is expected to fail with the switch on until W4's close PR (L255)

* fix(terminal): a terminal record that arrives, changes incarnation or moves workspace after main composed its panes re-composes them, so a restored pane lists once (layout-owner build, L80)

* fix(terminal): a pane bound to a terminal a paired server runs keeps that binding and host, and is never indexed as a local spilled tab (layout-owner build, L80/L257)

* fix(terminal): a pane main owns no longer proves its terminal runs; the host's inventory and the terminal's record decide, so a dropped shell reads disconnected (layout-owner build, L80)

* fix(terminal): an SSH pane whose host says its shell ended keeps its leaf on a negative or failed code, as the window does, so recovery starts a shell in it; a stale handle learns the connected replacement (layout-owner build, L80)

* fix(agent-launch): a terminal main's layout still binds counts as running through a host listing gap, so a launch never starts a second shell in its pane (layout-owner build, L80)

* fix(terminal): a disconnected terminal main still binds stays a stop target, never reads stopped unproven, and keeps an exit wait waiting until a certified exit (layout-owner build, L80)

* test(terminal): the wait unit fixture answers the exit-settled dependency

* test(terminal): realistic fixtures for three phone-row tests: unique leaf ids per pane, and a window that draws main's own panes (layout-owner build, L80)

* fix(terminal): a stale handle learns a pane's replacement only when it was resolved for that pane, its own terminal ended, and the replacement is connected there now (layout-owner build, L80)

* test(session-tabs): switch-on variant for a window's chat placement (main's layout order rules); headless phone tab order is L283

* test(terminal-close): switch-on close reds name their ledger rows (L255, L256, L259a, L259b); a same-id respawn's commit stamps its incarnation; the crossing terminal binds first

* test(terminal): the crossing registration is refused by main's workspace fence (L256 switch-on variant); the placement test's tabs are main's saved layout

* refactor(terminal): the stale-handle guard reads an issue-time pane stamp on the handle record, not a second map; moved-pane, restart and single-reader tests (layout-owner build, L80)

* test(session-tabs): switch-on variants: the desktop's tab name is main's rename, and a mobile-created tab's live shell is the one its commit registers (layout-owner build, L80)

* fix(terminal): a re-issued handle keeps its issue-time pane while no pane holds its terminal (review)

* fix(terminal): stop targets and exit waits use main's holds check (workspace and incarnation), and any exit event settles a later wait (review)

* test(session-tabs): name the switch-on placement and split-group reds L288 and L289

* fix(terminal): an exit drops a closed pane's record in the same write that retires the pane, and an unbind folds into the binding write (layout-owner build, L80)

The exit used to remove main's closed-pane entry in a session write of its own, before the
retirement write. It now reads the entry, and the retirement write for that pane drops it,
re-checked by terminal id and incarnation. An exit that keeps the surface (relay contact lost,
intentional stop) keeps the entry until a confirmed exit or its TTL.

A reused terminal id's unbind is now a pure computation that applyPtyBinding applies through
its existing writes. Two split-pane tests still expected a -1 exit to close the pane; they now
follow the ruling that any negative code keeps it. Static-gate fixes: test fixtures and tests
use typed member access instead of Reflect, and no longer use type assertions.

* test(runtime): the shared fixture store announces its session writes and the L283 store lists its partitions, as a profile Store does; L289's test passes on that store (layout-owner build, L80)

The fixture store bindPaneFirst fills now tells write listeners of every session write, direct or
by a layout command, so main's post-commit merge runs as it does on a real Store. The notice is
not enumerable, so a test's own `{ ...store }` copy, which keeps its writes elsewhere, does not
inherit a subscription it would never fire. With it, the targeted split-group create (L289) passes
at this head, so its it.fails mark goes. L283's test runs on a store that names its partitions and
stays it.fails("L283").

* fix(terminal): recovery checks the caller's handle after the cheap guards again, so the switch-off build re-issues no handle on a refused recovery (layout-owner build, L80)

The stale-handle switch point had moved the handle lookup ahead of the pane, terminal and
workspace checks. That lookup can re-issue a handle, so a switch-off recovery refused on those
checks gained a side effect base never had. The lookup runs after them again, and the switch-on
replacement check lives in one method.

* test(runtime): the layout-owner start fixture's spawn commits the pane binding before main registers the terminal, as the host does (L80 switch-on variant)

The window-tab start test came in with the main merge after bind-first; its fake spawn returned
a terminal no pane was bound to, so registration refused it. The fixture's spawn now binds the
pane first, as every other bind-first fixture does.

* fix(terminal): a new process under a reused terminal id starts with no exit code, so an exit wait on it cannot settle on the old process's code (layout-owner build, L80)

The record of a reused id kept the earlier process's lastExitCode, and with the switch on an exit
wait settles on any recorded code once the terminal reads disconnected. A new incarnation now
clears the code and cause. Also drops the L283 test's partition-list option: the fixture store
already lists its partitions.

* test(runtime): switch-on variants for four tests the full local run never loaded (L80)

- A restored pane's host lists the terminal it started; a pane main owns vouches for none.
- The other pane holding a slept session is in main's layout, so an unverifiable holder blocks
  the resume as it does through a window.
- Main sleeps a workspace whose panes it lists itself, even with a renderer attached (#31571).
- A graph sync no longer reconnects a disconnected terminal of main's pane; the inventory decides.

* test(runtime): the phone Markdown route tests use #31667's realistic store, and the untitled cases wait on L268 with the switch on

The window's tab uses main's id (the file path), and the store lists its partitions and saves each
open editor's unified tab and group, as the window does (the fixture from #31667). With the
switch on, the three untitled cases fail on L268, main's row merge dropping the untitled flag,
which #31667 fixes; they are it.fails until A1b's history includes it.

* refactor(runtime): two merged files back under the line limit, by moving A1b's additions to their helpers (L80)

After the main merge, the holder-pane check and a closed tab's live-terminal lookup pushed two
files past the line limit. The holder check is now paneHoldsSession (main-terminal-surfaces.ts)
behind one method, and the closed tab's lookup is one parent-class method. Behaviour unchanged.

* test(runtime): the headless chat tests open the chat on the publish path, as every product path does; L283 stays it.fails for the main-owned reorder

openChat recorded the chat only in the runtime's snapshot, so main's layout never held it and the
phone's reorder was refused as an unknown tab. On the publish path main's layout holds it; the
reorder is accepted but does not survive the row rebuild after a close (L283, reopened).

* test(runtime): bind-first census follows the one spawn-commit step (#31596); the unbound-registration test keeps its spawn from the suites' stand-in commit

After the main merge both start lanes register in spawn-commit-assembly.ts, after
savePtySpawnPaneBinding (or, adopted, on the live owner's pane); the census lists that file. A
phone create now writes its pane before the spawn, so the suites' stand-in spawn commit bound it
and the test of a path that registers unbound passed for the wrong reason; that test's controller
now opts out of the stand-in commit.

* test(runtime): convert four switch-on tests the disguised full runs hid

Real switch-on runs showed four files whose fakes registered terminals unbound
or confirmed a stop without its exit. Bind first, stamp the source as main
does, and deliver the exit a confirmed stop delivers. Switch-off unchanged.

* test: type the two converted fakes instead of casting them

The PTY controller fake now takes the controller's optional pane fields, and the
switch-on store is a typed RuntimeStore, so neither needs an assertion.
2026-10-22 17:28:42 -04:00
Kelvin Amoaba 589499e3bd fix(agent-status): a nested claude -p no longer takes over its parent pane's status (#31780) 2026-10-22 21:21:57 +00:00
Jinjing e61d062ef1 Revert HTML preview by default for the release (#31575) (#31893)
* Revert "Preview HTML files by default" (#31575)

Restore source/diff opening for general HTML file-opening routes for the release.

This reverts commit 524fa0039b, retaining the
published files.readDocPreview capability for mixed-version clients and removing
an unnecessary restored test assertion to satisfy the current lint gate.

Existing explicit previews and terminal HTML links retain their previous behavior;
the underlying local-file preview exposure remains tracked in STA-11670.

* Preserve file ownership and symlink guards during HTML preview rollback

Keep the existing owner checks after file-drop uploads and tab-entry stats so
an ownership change cannot reopen a file through a stale host. Preserve local
preview source checks so symlinks outside the workspace keep user-file access.

The race and symlink tests reproduce failures on the mechanical revert before
these guards are retained. Cover HTML source opening for folder workspaces,
Explorer, and Source Control without restoring preview-by-default.

* fix: split tab entry browser requests to satisfy lint limit
2026-10-22 13:45:36 -07:00
Neil 59497680d8 refactor: colocate Jira issue summary generation (#32099) 2026-10-22 13:42:03 -07:00
OrcaWinandOrcaWin b77d0eb76d perf(terminal): keep forward cursors for PR URL schemes (#31949)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-22 13:24:10 -07:00
OrcaWinandOrcaWin c8059ee3c0 perf(chat): avoid repeated skill catalog scans while building the picker (#31936)
* perf(chat): remove repeated scans and array copies from the skill picker

* perf(chat): keep picker grouping within the module line limit

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-22 13:20:06 -07:00
OrcaWinandOrcaWin 5dbd23364b perf(layout): index records once while patching session lists (#31935)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-22 13:20:02 -07:00
Jinwoo Hong 24f5c57030 A chat started in a server workspace drawn from the server's layout stays where you opened it (switch-on builds only) (#32003)
* fix(L273-core): tabs this desktop created in a paired server's frame-drawn workspace survive the server's frames (switch-on)

A tab the server's layout never lists (a client-hosted browser tab today; any kind recorded the
same way) used to vanish on the server's next frame rebuild. This desktop now records such tabs by
server and workspace, in its localStorage only (never a session field nor sent), and the server's
frame builds keep them as drawn, inside the server's own groups:
- A held tab drawn in a group the server lacks moves to the nearest server group; it shows there
  only if the user was on it, and the user's group follows it. The group tree stays the server's.
- The record forgets an id once the server's frame lists it, on close, and on worktree removal,
  and prunes ids the restored session no longer has before the first frame after a reload.
- A held tab's drag moves it here only (no session.tabs.move); it offers no new split, and a
  server tab sharing its group counts only the server's tabs for split no-ops.
- Client-hosted staging records its tab (both ids) in layout-owner builds; restage takes the
  worktree from its caller instead of scanning the store.

* refactor(L273-core): the desktop-created record is inert with the switch off; clearer names

- With the switch off the record is never read, kept or written (a single gate in the record).
- The frame-drawn check is isDesktopCreatedTabInFrameDrawnServerWorkspace in
  frame-drawn-desktop-created-tabs.ts: "held" stays the frame hold's word.
- The held-tab placement drops its unread activeGroupId; the group follow derives it.

* wip(L270): a host advertising agentSession.create.tab-placement.v1 opens a created chat's tab where the creating client asked, in its own group and tab ids

* test(L270): a switch-off windowless host saves a chat opened with no client place at the end of the remembered active group, as before (test only)

* wip(L270): a desktop's chat in a frame-drawn server workspace stays put: the frames hold its provisional tab until the server lists it, and the create names its group and the server tab before it (layout-owner build only)

* test(L270): a switch-on serve saves a client's chat after the tab it named; the server-layout read moves to one e2e helper (test only)

* refactor(L270): the create's tab place keeps its own name, and the layout rule's comment names the client's place first

* refactor(L270): the create's tab fields live in their own schema file and the windowless save passes its chat through, so no file passes its line limit; the placement test types its mocks without assertions

* refactor(L270): the chat's place names server tabs through H5c's route, which also skips tabs the server's frames do not hold

* fix(L270): a chat opened while a reconnected server stream awaits its snapshot still names its group; the layout-owner e2e list runs the placement spec

* fix(L270): only a create in a frame-drawn server workspace awaits its tab place, so every other create keeps its timing (the eager History joiner after an early terminal close opens again)

* fix(native-chat): read a frame-drawn chat tab's place without a tick, so an eager History joiner's create is still sent

* test(native-chat): type the create's params so an async placement cannot compile; name the probe that fills the cache

* test: type the placement mock's original through a type import
2026-10-22 16:16:43 -04:00
Neil 2e6963d707 refactor: colocate Jira query construction (#32089) 2026-10-22 12:28:18 -07:00
Jinwoo Hong db6ab720bc Install shared skills for 11 more agents and stop Qoder listing skills twice (#31657)
* Install shared skills for 11 more agents

Add skill install provider entries for OpenClaude (own .openclaude folders,
OPENCLAUDE_CONFIG_DIR), and OpenCode 2, MiMo Code, OMP, Prime Agent, Qoder CN,
Codebuff, Freebuff, Ante, Jcode and DeepSeek Harness, which read the shared
.agents/skills folder at both scopes. A coverage test now requires every Orca
agent to have an entry or a recorded reason it has none.

* Use the shared skills folder for Qoder

Qoder's CLI loads .agents/skills at both scopes by default, so its own
.qoder/skills placement made every skill appear twice.

* Keep scanning Qoder's own skill folders

Qoder still reads .qoder/skills, so discovery keeps both folders as
discovery-only roots now that installs go to the canonical root.
2026-10-22 15:23:40 -04:00
Jinjing 8fc1698ebb Add Resource Manager PTY inventory and checked terminal termination (#31593)
* feat(resources): end local terminals with checked identity checks

- Add a PTYs view to the Resource Manager that lists local terminals and can end one. Before stopping, the host confirms the terminal's incarnation, agent ownership, and attachments. This prevents ending a same-ID replacement that started after the list was read.
- Bump the daemon protocol to v50 and gate the new fenced kill on it. Older daemons ignore the extra fields, so the client refuses End instead of sending an unchecked kill.
- Admit diagnostic reads (PTY listing and memory snapshot) only while the Resources view is visible, so a hidden window does no background scanning.
- Report incarnation and host on spawn and exit events so the renderer can match them to the terminal it showed.
- Replace the popover's terminal-session list with the new inventory and move memory polling into the Resources view.

* Show unrouted daemon PTYs in the Resource Manager

* Simplify PTY controls and clarify safe termination

* Format local build compatibility protocol lists

* Add PTY resource management localization strings

* Harden PTY resource manager test fixtures

* Use typed IPC event in PTY kill test fixture

* Refine local PTY resource view model typing

* Refine local PTY resource view model typing

* Include local host identity in PTY lifecycle event tests
2026-10-22 12:20:01 -07:00
Jinjing f1189be850 Offer Resolve with AI for paused merge, rebase, and cherry-pick (#28569)
* Offer Resolve with AI for paused merge, rebase, and cherry-pick

A paused operation can need recovery after every conflict is staged, but the Resolve with AI action only appeared when unmerged paths remained. The operation banner now offers it whenever AI actions are visible and an operation is in progress.

Starting an AI conflict-resolution agent is also blocked while a Git operation is running. The composer, the banner and card buttons, and the agent launch itself check the running-operation state, so a launch that began during agent detection cannot start while Git is still running.

* Block paused-operation recovery only on Orca's own Git work

The previous gate scanned the host's process table and refused "Resolve with AI" whenever it could not prove no Git process was running. That refused recovery on older paired hosts, on Windows whenever any Git process ran anywhere on the machine, and while a paged `git reflog` or `git grep` was open in a terminal.

- The host tracks Orca's own mutating Git commands per worktree (including ones still queued) and reports them through `git.mutationInFlight`. Reads and fetch/push/ls-remote are not tracked.
- Recovery is refused only when the host reports that work. Older hosts without the method keep today's behavior.
- The gate applies only to launches from the paused-operation banner and conflict card, not every resolveConflicts launch, so the Checks panel's PR-conflict flow is unchanged.
- The agent prompt now tells the agent to stop if index.lock exists or another git command is running, which covers Git started outside Orca on every host. With no unmerged files, the prompt asks the agent to find why Git paused instead of listing zero conflicts.

* Preserve Git mutation state until terminated commands exit

- Cancel stale recovery checks and agent launches when their dialog or scope closes
- Guide rebase recovery agents to amend fixes after failed exec steps

* Cover failed rebase exec recovery with real Git

* Recognize global Git options in worktree mutation tracking
2026-10-22 12:08:38 -07:00
Neil a1448f9f58 refactor: colocate Jira image and stream contracts (#32079) 2026-10-22 11:10:10 -07:00
Neil a45bd06eac refactor: colocate the stored-agent contract (#32067) 2026-10-22 10:22:51 -07:00
Brennan Benson 726c572da7 Attach a long paste as a text file, and say a message is too large before it is sent (#30398)
* fix(native-chat): attach a long paste as a text file, and say a message is too large before it is sent

A paste of 32 KB or more (or one that would push the message past the 256 KB
send limit) into a structured chat is saved as a file the agent reads, through
the same pending-chip and @path route a dropped file takes: Orca's paste folder
here, or the chat's attachment store on its paired server. Claude gets the paste
folder as an allowed directory. A message still over the limit is handed back
before it is sent, on desktop and the phone, with words naming the limit instead
of the generic 'not sent, send it again'.

* test: Claude launch parity snapshot grants the paste folder

* fix(native-chat): a long paste replaces the selection at once and settles in place

A long paste now puts an inline Pasted text pill where it went, replacing any
selected text immediately, and the saved file (or inline text, or the original
selection on failure) replaces that same pill when the save finishes, in its own
chat's draft. Nothing reads the caret later, so order and position hold. The
paste never joins the image row, so image numbers are unchanged. The size check
measures the real message body with the selection replaced and images included.
A server that keeps no files takes the paste inline when the message still fits.
A pending paste a restart lost shows Paste not saved, and Send says so.

* fix(native-chat): type the pasted-text parser and the composer's missing draft scope

* fix(native-chat): admit paste references before goals and commands

* fix(native-chat): resolve staged paste paths without editing the draft

* fix(native-chat): consolidate paste imports for code quality checks

* fix(native-chat): retain checked string types for saved paste paths

* fix(native-chat): retain completed paste facts through history and delimit paths

* fix(native-chat): keep structured question rendering within file limit

* fix(native-chat): pasted or inserted text that looks like a paste token stays literal

* fix(web): keep the browser's UI bridge under the file length limit

* style: format the files #29982 merged unformatted

* fix(native-chat): retain oversized drafts before send admission

* fix(native-chat): keep the composer handle's merge resolution to main's destructure
2026-10-22 10:16:32 -07:00
Jinwoo Hong f8aa6854c7 A phone's new terminal lands after the tab it names, and its split selects the moved tab as the window does (dark, layout-owner build only) (#31924)
* fix(session-tabs): a tab main's groups lack keeps the place its writer listed it, so a headless phone lists a chat first (layout-owner build, L283)

On a host with no window, a rebuild writes its rows under a group id main's layout lacks. The row
writer's reorder treated every row of that group main does not hold (a chat, a browser page) as a
stray and appended it to the active group, so a chat the user moved first came back last, in both
the groups and the tab list. Strays now go after the id they follow in the writer's order (else
first), the same rule a group main does hold already used; the window publication path shares it.

* fix(session-tabs): a phone's split move and its create after a split tab go through main's layout on a windowless host (layout-owner build, L288, L289)

On a host with no window, a phone's split move only rewrote the runtime's own rows, and its new
terminal anchored after a split tab reached main's layout with no anchor. Every write of the rows
takes main's groups and order, so the split collapsed back into one group and the new tab landed
after the trailing tab. The split is now main's splitGroup command (the moved tab selected in its
new group, as a desktop drop), and the create passes the anchor's tab to main's createTerminalTab,
as a window's create does. An older phone sends no anchor and still gets the end.

* test(session-tabs): type the split move's async reply in the dispatch stub, and read an optional group map (L289)

* fix(session-tabs): a phone's split goes to main's layout only for a tab main holds, as the window sends it (layout-owner build, L289 review)

With the Store's write notices, today's runtime-only split already reached main's groups through
the session write; what it lost on a windowless host was the selection: the moved tab and its new
group did not become the active ones, as they do after a window's drop. The split stays main's
splitGroup for a tab main's groups hold, with the window's checks: a drop main's groups make a
no-op sends nothing (the window's pane-column rule, now in shared), and a refusal is dropped
silently. A row main does not hold (a chat or browser tab) splits as today, with no error. The
create anchor and its layout-owner hand-off move to their own module. The surface test fixture's
session writes now notify listeners, as the Store's do.

* chore(session-tabs): log when main refuses a phone's split, as the window logs its own refusals

* fix(session-tabs): a phone's terminal created after a chat or browser tab main holds lands after it, in its group (layout-owner build, L288 follow-up)

Since browser and chat tabs went main-first, main's layout holds them, but a windowless create
handed main the row id (agent-session:<id>), which is not main's tab id, so main put the new tab
at the end of its first group. The anchor is now resolved to main's own tab, as a window's create
names it; main's read of a workspace's layout is one method the tab-move path shares.

* fix(session-tabs): one move path: main's tab move selects a split tab in its new group and skips the window's no-op split (layout-owner build, L289)

A phone's split now goes only through main's tab move (#31812); this branch's own split routing is
gone. For a split only, main's tab move now does what the window's drop does: it sends nothing for
a drop that changes nothing (the window's pane-column check, reused), and after main applies the
split it selects the moved tab and makes its new group active. Refusals keep that path's codes.

* fix(session-tabs): resolve a create's anchor to main's tab inside the tab-move module, so only the old-client boundary's listed importers read old-client rows (L288 follow-up)

* perf(session-tabs): read main's layout for a create's anchor only when main places it, so switch-off and appending phones skip that load

* fix(session-tabs): restore the row-to-tab lookup the split selection and create anchor use, after #31925 dropped its import (layout-owner build, L288, L289)

#31925 removed the L280 bridge in main's tab move, and with it the move's last use of
resolveSessionTabRow, so it dropped the import; this branch's split selection and create-anchor
lookup still resolve a row to main's tab through it. A row main's layout does not hold is now
refused by main's move (tab_not_found) as #31925 rules; the phone test asserts that.

* test(session-tabs): the L283 phone-order test opens its chat as the host does and uses a store that announces its writes (layout-owner build)

Since #31925, main moves every tab of a workspace it owns and refuses a row its layout does not
hold, and since D3b a chat the host opens is saved in main's layout. The test now opens the chat
through the host's publish path and gives the runtime a profile-like store (partitions listed,
writes announced), so the reorder reaches main's layout and its commit reaches the rows.

* test(session-tabs): type the L283 test's profile-like store as the Store contract declares it

* revert(session-tabs): drop the stray-placement change; main's layout holds chats and moves every row now (L283)

Since D3b a chat the host opens is saved in main's layout, and since #31925 a phone cannot move a
row main does not hold, so the rebuilt-group stray this placed no longer reaches a phone; the
remaining strays are brief, and appending them gives the same order. withStrayIn and its callers
return to main's behaviour, with its unit test. The phone-order spec stays as a guard that a
windowless chat moved first stays first after a rebuild.
2026-10-22 13:12:56 -04:00
Brennan Benson 199fc7b2c1 A Pi chat shows the turns made outside it (#31618)
* feat(native-chat): a Pi chat shows the turns made outside it

When a Pi chat's agent opens its conversation, Orca asks Pi for the entries
saved after the point the chat last accounted for (get_entries since that
entry id) and adds the terminal's turns once, in Pi's order, under the
"Continued outside this chat." divider. That point is saved on the
conversation handle after each chat turn and at each open. When the chat's
own unsaved work and the terminal's share a suffix that can't be split
(Orca died mid-turn), that suffix is skipped rather than duplicated, and
later terminal turns show normally. A Pi reply over the 8 MiB line limit now
fails only the optional read that asked for it instead of ending the chat.

* refactor(native-chat): share the catch-up open runner across agents

The Codex catch-up's agent-neutral parts move to one module: the open-time
envelope (its own deadline and cancellation, never gating the open), the
bounded staged batch, the single settlement commit that re-checks the
journal before writing, and the "Continued outside this chat." divider.
Codex keeps only how it reads its saved turns and what the chat already
holds. No behavior change; the other agents' catch-up builds on this
instead of copying it.

* refactor(pi): catch up through the shared catch-up runner instead of a copy

* chore: remove unrelated pnpm executable metadata

* refactor(native-chat): the catch-up runner reads the chat's view only when the agent asks

* refactor(pi): read the chat's view through the runner's view()

* chore: remove regenerated pnpm executable metadata

* fix(native-chat): the catch-up deadline also bounds waiting for its commit to be written

A journal write barrier that never settled after the import was admitted held
the provider's open past the catch-up deadline. The wait now ends with the
deadline or the open's cancellation, like every other step.

* test(pi): check Pi's session entry ids and get_entries since against the pinned real Pi

* test: type the history catch-up sink fixture without assertions

* fix(native-chat): bound and revoke history preparation

* test: the runner's journal fake answers the cursor Pi's view reads

* feat(native-chat): validate expected saved items during history catch-up

* fix(pi): preserve outside history and optional read recovery

* test(pi): fail RPC smoke promptly when the child stops
2026-10-22 10:00:11 -07:00
Neil de2700d0fb refactor: colocate native-chat command envelopes (#32055) 2026-10-22 08:55:53 -07:00
Neil 6af21633e9 refactor: colocate native-chat command language rules (#32041) 2026-10-22 07:57:50 -07:00
Neil ec5eaf96c1 refactor: colocate provider retry transcript rules (#32024) 2026-10-22 07:02:39 -07:00
NeilandFrank-zhu2000 92c8f3cbdc fix(ssh): stop floating terminal metadata from blocking SSH access links (#31963)
* fix(ssh): stop local-only workspace metadata from blocking SSH access links

Unstamped floating and setup terminal metadata rows were projected onto every
execution host, so the dependent-state census refused SSH access links and
empty-host claims for every SSH target with orcad_migration_dependent_state.

- Local-only workspace ids project only onto the local host, are stamped local
  on load, and their metadata writes always land on the local host.
- The census counts an unstamped legacy row only when its repo is not
  registered solely on other hosts.
- The SSH access error now names the blocking state.

Builds on the approach in #31862 by Frank-zhu2000.

Fixes #31498

Co-authored-by: Frank-zhu2000 <Frank-zhu2000@users.noreply.github.com>

* fix(ssh): rehome remote-stamped local-only metadata, skip orphan rows, and word the SSH access error for a link

* fix(persistence): keep a local or unstamped floating row over a remote copy when rehoming

* fix(ssh): make the SSH access blocker copy switch exhaustive

---------

Co-authored-by: Frank-zhu2000 <Frank-zhu2000@users.noreply.github.com>
2026-10-22 03:39:54 -07:00
Jinwoo HongandClaude 2c937546dd A paired desktop keeps the tabs it created in a server workspace drawn from the server's layout (switch-on builds only) (#31853)
* fix(L273-core): tabs this desktop created in a paired server's frame-drawn workspace survive the server's frames (switch-on)

A tab the server's layout never lists (a client-hosted browser tab today; any kind recorded the
same way) used to vanish on the server's next frame rebuild. This desktop now records such tabs by
server and workspace, in its localStorage only (never a session field nor sent), and the server's
frame builds keep them as drawn, inside the server's own groups:
- A held tab drawn in a group the server lacks moves to the nearest server group; it shows there
  only if the user was on it, and the user's group follows it. The group tree stays the server's.
- The record forgets an id once the server's frame lists it, on close, and on worktree removal,
  and prunes ids the restored session no longer has before the first frame after a reload.
- A held tab's drag moves it here only (no session.tabs.move); it offers no new split, and a
  server tab sharing its group counts only the server's tabs for split no-ops.
- Client-hosted staging records its tab (both ids) in layout-owner builds; restage takes the
  worktree from its caller instead of scanning the store.

* refactor(L273-core): the desktop-created record is inert with the switch off; clearer names

- With the switch off the record is never read, kept or written (a single gate in the record).
- The frame-drawn check is isDesktopCreatedTabInFrameDrawnServerWorkspace in
  frame-drawn-desktop-created-tabs.ts: "held" stays the frame hold's word.
- The held-tab placement drops its unread activeGroupId; the group follow derives it.

* fix(L273-core): a simulator tab opened in a paired server's frame-drawn workspace survives its frames (switch-on)

The server never has this desktop's emulator tab, so its frame rebuilds dropped it. The simulator
open now records the tab as created here when a server's frames draw the workspace, and opens it
in the server group it would have split beside, since that workspace has no group of this
desktop's own. L290 (the host runs and streams the simulator, #6701) removes the note.

* test(L273-core): the mirrored-workspace simulator test draws the tab and can fail; the side-open test starts from another group

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

* fix(L273-core): list the created-here tab record in the WSL-move storage census

It is keyed by a paired server's environment; a local or WSL project never has one, so a move has nothing to carry.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-22 06:37:13 -04:00
Neil a3fa44ad07 fix(push): honor "only when desktop is away" on headless orca serve (#31956)
* fix(push): decide orca serve's desktop-away from attached desktops (#31570)

* fix(push): report away on sleep and lock, guard late reports and prune stale presence

* chore: justify the presence-reporter state slot widening

* fix(push): hold away from suspend or lock until resume or unlock

* fix(push): key presence sockets by connection instance so a replaced connection reports at once
2026-10-22 03:36:10 -07:00
Neil 6a9a80a7fe feat(telemetry): daily aggregate WSL usage snapshot on Windows (#31406)
* feat(telemetry): daily aggregate WSL usage snapshot on Windows

Adds a main-owned wsl_usage_snapshot event through the existing PostHog
track() pipeline (same consent, opt-out, build gate and validator): WSL
installed/version, distro counts (installed, running, WSL1/2), WSL-backed
project counts (distro fs vs Windows drive, git vs folder), distinct project
distros, and the internal WSL-hosts gate. Counts are bucketed; no paths or
names leave the machine. At most once per 24h per install, Windows only.

* fix(telemetry): bound WSL snapshot retries and name the default distro

Stamp before gathering so a failing gather isn't repaid hourly, treat a
future stamp as due, resolve 'default distro' to its name for distinct
counts, narrow the store type, and cover the running-probe choice and the
closed WSL-hosts gate in tests.
2026-10-22 03:34:31 -07:00
Neil 8e9690468a fix(orcad): ship computer use with the server and say when it is unavailable (#31958)
* fix(orcad): ship computer use with the server and say when it is unavailable

* test(computer): drop the type assertion in the sidecar app-environment mock

* fix(cli): carry the server reprovision hint into --json computer failures
2026-10-22 03:27:39 -07:00
Neil 0b4f2d9a86 fix(ssh): editing project groups no longer marks a moved host as changed (#31954)
A moved host was marked "changed by an older version of Orca" whenever its
retained catalog fingerprint drifted, and that fingerprint includes project
group ids. Deleting or creating a group on this desktop therefore unhid the
old SSH rows, failed every connect with source_changed, and left folder
workspace dialogs checking the fenced ssh target.

A host is now changed only when its source holds a project or folder
workspace that no move took or linked and the user did not leave behind,
which is all a delta move can carry. The fingerprint itself is unchanged, so
older builds and existing journals compare exactly as before and no move
flips on upgrade. "Keep the server's version" now records the rows it left
behind; journals an older build kept get those rows listed once at start.
Hosts an earlier build marked for nothing a move could carry are unmarked at
start. The banner now says what is actually different.

Fixes #31523
Fixes #30969
2026-10-22 03:22:40 -07:00
Neil aeb96c580a fix(session-tabs): mark re-sent held closes as replays for hosts that accept it (#31951)
A held close re-sent after reconnect went out as reason 'user', so a host's
trace could not tell automatic replays from clicks (#31449). Hosts now accept
an optional 'replay' reason, advertised by session-tabs.close-replay-reason.v1,
and treat it as the user close it re-sends. Clients mark replays only for hosts
that advertise it; older hosts keep receiving 'user'.
2026-10-22 03:20:15 -07:00
Neil ca9abc66c5 refactor(jira): colocate JQL predicate composition (#31933) 2026-10-22 02:49:46 -07:00
Jinwoo Hong ea60ec5d7c Layout-owner builds: a phone's tab move is always the server's own; no ghost editor tabs after restart (#31925)
* Layout groundwork (switched off): main derives agent tab titles from its own hook status

With the layout-owner switch on, main gives agent tabs their generated titles from
the hook-server status it owns, on the desktop, headless serve and orcad: one
renameTabs per status batch, so one write and one frame. It keeps the window's
rules (setting, custom and quick-command titles, first title wins, dispatch labels).
The window stops sending generated titles for tabs main holds; a paired server's
tab, or one main has no tab-bar entry for, is still titled by the window as today.

The title rules move to src/shared (agent-row-primary-text, and the new
agent-generated-tab-title-rules) so both sides share them; switch-off behaviour
is unchanged.

* feat(layout): main applies a phone's or paired client's tab move itself, and a tab move names the tab it follows so a stale index can't misplace it (layout-owner build, L193)

* fix(layout): a move in a group showing a tab main lacks goes the window's way, a twin's agent tab keeps its window title, and orcad subscribes titles through the store attach (layout-owner build)

* chore(layout): main's tab move reaches the old-client translation through its compat entry

* chore(layout): mark the window-only-row move fallback as bridge code (ledger L280)

* test(layout): attaching the hook store follows its rows for main's tab titles once

* chore(layout): the old-client move translation is a compat entry of its own, with no cyclic re-export

* fix(layout): every tab a group shows is main's, so a client's tab move never falls back to the window (layout-owner build)

- Main moves a client's tab itself in a workspace it owns; a row it holds no tab for (a combined
  diff) keeps no place, and an order must still name each row shown once (L280).
- The window publishes an editor row under its tab id only; a file no group shows gets none.
- Main's layout load gives a saved browser record with no tab its tab, as the window's hydration
  repair did, and the window no longer mints one for a workspace main owns.
- Main's layout load drops the saved tabs the window's restore drops (view-less diff, conflict and
  check tabs, old html previews), through one shared rule; the next save writes it back (L286).
- Main lists conflict-review and check-details tabs as the plain file rows the window lists, so
  older clients keep them once the window's rows are gone (L287).

* fix(lint): drop an import the shared restore rule left unused

* test: import the move translation through the folder's public path

* fix(layout): review follow-ups for the move fallback removal (layout-owner build)

- A transient tab stays at load while a view is held for its file, as the window's frame build
  keeps it, not only for its own tab id.
- The repaired browser tab goes to the saved active group only while it holds a tab, as
  selectHydratedActiveGroupId picks it; tests that the id is stable across loads and restarts,
  that a window-owned build restores it once, and that a paired server's record keeps the window's
  repair.
- Two switch-on tests keyed editor drafts by path; drafts are keyed by editorFileKey (an owned id
  in a layout-owner build), so they now use it.

* fix(layout): the repaired browser tab skips a saved active group this load empties

* test(layout): directly dialed SSH browser record is main's; a server's SSH workspace keeps the window repair

* fix(layout): drop view-less editor tabs only in loads that carry main's views, so main's own edits keep a live diff

* test(layout): a direct SSH partition gets the same browser repair and drop with no host contact
2026-10-22 05:26:12 -04:00
Neil 8967b10d41 chore: remove verified dead code and calibrate Knip entries (#31903)
* chore: remove verified dead code and calibrate Knip entries

* fix: include mjs consumers in dead-code analysis
2026-10-22 02:19:04 -07:00
Neil e5f2123e8d refactor(shared): delete exports nothing reads (#31917) 2026-10-22 01:46:25 -07:00
Jinwoo Hong 4165c9545c Layout groundwork (switched off): main tells the window whether its start resumed a pane's agent, so the restored banner and reopen count come back (#31908)
* feat(terminal): the window's store shows main's pane start status (layout-owner build)

A status-only frame changes one store entry and no pane tree, so the pane's view stays mounted.

* refactor(terminal): keep the frame sync within its line budget

* feat(terminal): every pane main starts shows 'starting' from before it is written (layout-owner build)

* feat(terminal): a pane main is starting waits and attaches; a failed start shows why (layout-owner build)

Fixes owner-visible row 15: a failed agent or setup start no longer opens a plain shell.

* test(terminal): iterate store listeners directly

* fix(terminal): an exit that reports no code keeps its pane, as lost contact (layout-owner build)

* test(terminal): a pane main started attaches to main's terminal at its own grid

* test(terminal): detach census tests assert main's move and frame with the switch on

* fix(terminal): a restored pane paints history only from its terminal's host, never the window's copy (layout-owner build only)

* fix(terminal): main releases a painted cold restore only for the pane's current terminal (layout-owner build only)

* test(terminal): typed ack args in the painted cold-restore test

* test(terminal): exited-pane tests assert main's close with the switch on

These 8 have failed switch-on since #31130 moved the exit close to main.

* feat(layout): main starts the panes a client's startPane or wake names, through its one restored-pane starter (layout-owner build only)

The model's start and stop effects reach the runtime once the command is written, never the
reply. startPane starts a pane whose binding has a start status, and a slept pane from its record.
A wake keeps a pane's resume record until its start binds; a failed start keeps it and shows why.
With the switch on, main owns a running agent's wake even with a window open.

* fix(layout): a window's wake of a pane it resumed itself is record-only, so main does not start it again (layout-owner build only)

* test(layout): a client's startPane starts the pane before any other trigger

* Revert "fix(terminal): a restored pane paints history only from its terminal's host, never the window's copy (layout-owner build only)"

This reverts commit a646d89b26.

* test(terminal): the cold-restore repaint ack names its pane in the layout-owner build

* fix(layout): a wake keeps the pane's resume record in the pane-owned form, so a host crash resumes the agent again (layout-owner build only)

* test(layout): a failing start in the pane-start runner test throws

* feat(terminal): main writes and starts every split of a pane it holds; the window attaches (layout-owner build)

The window's split, a startup split with its command, and a CLI split all go through main's
terminal.split; main's frame mounts the pane, with or without a window intent. Deletes the
window's split wait and withdraw, the split appear intents, and the queued-split skip and cancel.
The e2e helper uses the real split. A startup split leaves focus on its source pane.

* fix(terminal): the attach-only wait leaves sleeping, resuming and held panes to their restore path

* fix(terminal): a split pane attaches to the terminal main bound it to; a taken pane id is refused

Also restores the proof that a split closed while it starts gets one answer and leaves no
pane or start status behind.

* Reapply "fix(terminal): a restored pane paints history only from its terminal's host, never the window's copy (layout-owner build only)"

This reverts commit 420e605ad0.

* fix(layout): a trigger between a restored pane's start and its bind write joins that start, not a second one (layout-owner build only)

* fix(terminal): a window split main cannot start still shows its pane, failed, with why

* fix(terminal): keep the switch import the D1b-1 merge dropped from the layout restore

* feat(layout): main's starter shows a restored pane starting before any await, and a failed start with its reason; startPane starts a pane whose bound terminal ended (layout-owner build only)

* feat(terminal): a window's attach-only view of a pane attaches to its terminal or starts nothing, keeping the binding for main's start (layout-owner build only)

* feat(terminal): a restored pane with no live terminal asks main to start it and attaches once main binds it; the window's wake, hibernated wake, restart-on-show and gone-terminal fallbacks route through main (layout-owner build only)

* fix(terminal): check a taken pane id first, keep the start's own error, clear a gone pane's status

Three test stores gain the pane-start field the window's store always has.

* test(terminal): the admission stub answers whether its pane is still written; a closed pane's failed start clears

* test(terminal): with the switch on, the window's restores, wakes and restart replacements ask main to start the pane and attach to the terminal main binds, starting and resuming nothing themselves

* refactor(terminal): the rebound workspaces' bindings show through one call in the bindings module

* fix(terminal): a held pane connects as today so main's refusal holds it; main's start follows output; a capacity death after main's resume keeps the resume (layout-owner build only)

* fix(layout): main passes its start options only for commands that start panes; a windowed open resumes the agent under main (layout-owner build only)

* refactor(terminal): break the pane start module cycle; typed test fixtures

* test(terminal): a pane main wrote and the user closed mid-start drops its failed status

* fix(terminal): every waiter on main's start runs, a failed or refused start settles and is asked again on the next view, a hidden gone pane waits for its view, a twin main hands back stays on today's path, and lost contact never starts a pane (layout-owner build only)

* test(terminal): iterate the store listeners directly

* test(terminal): a cold-restore paint applies no window resume with the switch on

* fix(terminal): a split main starts is listed to phones and session.tabs without a window's report

* test(terminal): name the main pane start test helper as a fixture

* fix(terminal): a reconnect shows its pane starting and why it failed; a lost owner race shows no failure; a split no one asked to see keeps the phones' tab

* fix(terminal): a kept create another owner won shows no failure; the window attaches to the winner

* fix(terminal): a pane main starts at relaunch keeps its full history and starts before the window's graph lists its tab (layout-owner build only)

* feat(terminal): main replaces the empty shell a hibernated agent left and resumes the agent when a view asks; a start main could not attach shows main's own terminal_not_started (layout-owner build only)

* test(terminal): main seeds its copy of a restored pane even when a window declared one, with the switch on

* fix(terminal): a pane main started hidden paints once when its view attaches (switch-on builds only)

The view's attach snapshot, taken after main unmarks the hidden gate, already holds the bytes main
dropped, so the reveal no longer asks a second restore from main's buffer.

* refactor(terminal): main's pane start reads the pane from its own layout, not the client projection (switch-on builds only)

* fix(terminal): a pane restart stops its terminal before writing the pane unbound, so a stop that fails keeps the pane (switch-on builds only)

* test(terminal): a start main could not bind shows terminal_not_started through the window's wait, from one copy site (switch-on builds only)

* fix(terminal): main's pane start resumes an agent recorded under the pane's number, as the window did; stale switch-on tests show main's start and the known banner loss (switch-on builds only)

* fix(terminal): review round: a visible view alone retires main's hidden-start drops, by episode; main reads a split pane's numbered record by its number; the window keeps a shell main kept (switch-on builds only)

* fix(terminal): main's start of a pane a window view is attaching to leaves it visible, so the view paints once (switch-on builds only)

* fix(terminal): a window view that attaches while main starts its pane takes main's start, with no second provider attach (switch-on builds only)

* fix(terminal): review round: a view takes main's start only when it carries a payload and still owns its terminal; attaching views are counted, keep main's pre-spawn mark off, and a failed attach hides only what it kept visible (switch-on builds only)

* test(terminal): checked test doubles in the view-join and numbered-record tests; reattach options fit the file budget

* test(e2e): with the switch on, the mount-authority spec keeps the window's bindings, which only main's frames write

Claude-Session: W5b

* test(e2e): the mount-authority spec fits its line budget; the layout-owner build check is one helper

Claude-Session: W5b

* test(layout): an unbound pane holding a resume record starts and keeps the record, since main's start resumes from it

Claude-Session: W5b

* fix(terminal): a pane main resumed holds a running agent's record, so it reads awake, not sleeping (switch-on builds only)

Claude-Session: W5b

* fix(terminal): in a split where no pane owns a numbered record, main resumes the tab's one conversation in pane 1 only (switch-on builds only)

Claude-Session: W5b

* fix(terminal): main marks only the record a resume started from live, never a sleep captured after it (switch-on builds only)

Claude-Session: W5b

* test(terminal): with the switch on, a windowless resume leaves the pane's record a running agent's

Claude-Session: W5b

* test(terminal): the resumed-record test double typechecks

Claude-Session: W5b

* refactor(terminal): with the switch off, main's pane start reads no resume record for the live mark

Claude-Session: W5b

* fix(terminal): main publishes whether its start of a pane resumed the agent, for the window's banner (switch-on builds only)

Claude-Session: W5b

* fix(terminal): a pane main started shows its "session restored" or "resume unavailable" banner again (switch-on builds only)

Claude-Session: W5b

* test(terminal): the resume-banner store double carries no type assertion

Claude-Session: W5b

* fix(terminal): main records a resume outcome only when its start wakes the pane's own sleeping record, and each new start clears the old one (switch-on builds only)

Claude-Session: W5b

* fix(terminal): a renderer reload shows no resume banner again for a pane main started, and the window stops waiting once that pane holds another terminal (switch-on builds only)

Claude-Session: W5b

* refactor(terminal): the merged files fit their line budget: the folder-fallback notice and a hidden pane's wait for its first view are helpers, and the answering-transport lookup moves to its one test caller

Claude-Session: W5b

* fix(terminal): main's resume outcome arrives in the same frame as the terminal it names, a same-id respawn included (switch-on builds only)

Claude-Session: W5b

* fix(terminal): the reopened-terminals toast counts agent sessions main resumed again (switch-on builds only)

Claude-Session: W5b

* refactor(terminal): the attach's resume check names the window's resume type

Claude-Session: W5b

* test(terminal): pin the resume outcome's wire contract: an outcome alone rebinds nothing, it shows once per start, and no saved session holds it

Claude-Session: W5b

* refactor(runtime): the window's layout apply fits the runtime's line budget after the main merge

Claude-Session: W5b

* test(terminal): any create through main that wakes a pane's record publishes its outcome, and the sidebar resume keeps its restored banner (switch-on builds)

Claude-Session: W5b

* refactor(runtime): the layout stream takes main's pane facts from one source, keeping the runtime in its line budget

Claude-Session: W5b

* fix(terminal): a relaunch resume into a pane kept under its saved terminal id shows its banner again, and a start whose bind fails leaves no outcome (switch-on builds only)

Claude-Session: W5b

* fix(terminal): the reopen toast counts only a resume main made for the attached terminal since the attach began, and the banner reads main's outcome once at attach (switch-on builds only)

Claude-Session: W5b

* test(terminal): two pane test stores carry main's resume outcomes, which every attach now reads

Claude-Session: W5b

* test(terminal): the hidden-output repro's test store carries main's resume outcomes

Claude-Session: W5b

* fix(terminal): an SSH same-id resume whose bind writes nothing new keeps its banner, and a start that lost its pane leaves the winner's outcome (switch-on builds only)

Claude-Session: W5b
2026-10-22 04:35:42 -04:00
Brennan Benson f6585a3916 Keep Session History checking totals fixed throughout a scan (#30949)
* fix(session-history): freeze scan workload before checking candidates

* test(session-history): handle native fixture path separators

* test(session-history): control bucket fixture enumeration order

* test(session-history): preserve scoped fixture timestamps

* fix(session-history): repair integrated test and locale catalog

* ci: refresh history checks against current main

* ci: refresh history checks after catalog repair
2026-10-22 01:03:09 -07:00
Neil e6ce82f676 test: isolate Windows tree-kill load fixture startup and cleanup (#31891) 2026-10-22 00:56:03 -07:00
Brennan Benson 7c34a48529 fix(native-chat): show a prompt hook's block as its own row for every agent (#30898)
* fix(native-chat): show a prompt hook's block as its own row for every agent

When a prompt hook refused a message, Codex showed nothing, Claude showed
an unmarked warning the turn fold could hide (with a copy of the prompt),
and Grok read as the user stopping the turn.

Each adapter now writes one warning row, "Blocked by hook: <reason>" or
"Hook stopped: <reason>", marked with the hook-outcome presentation that
the turn fold never hides:
- Codex: a blocked or stopped UserPromptSubmit hook/completed frame
- Claude: a warning informational frame that halts the prompt, worded from
  its structured prompt_submit_outcome when present
- Grok: a HookDenied end, from either completion signal under one row
  identity; the turn ends completed instead of interrupted

Nothing new is saved and nothing changes on the wire beyond the new
presentation value.

* fix(native-chat): end a hook-denied Grok turn on its first signal, and clean older Claude block wording

Follow-ups from the size review of the hook-row change:
- Grok: a hook-denied turn_completed now ends the turn and writes the row
  straight away, instead of waiting for the prompt's answer; a later answer
  changes nothing.
- Claude: when an older Claude sends only its sentence, the row keeps the
  hook's reason and drops the hook path and the copy of the prompt. Whether
  the row shows still depends only on prevent_continuation.
- The turn-ending shape is declared once, the Codex hook payload is read
  once, and the Codex hook row returns early instead of threading through
  the generic frame row.

* test(native-chat): cover hook-block rows with real Grok frames and a preserved send

- Replace the source-derived Grok fixture with frames captured from Grok
  1.0.62 (session, prompt ids renamed to the test's).
- One whole-flow file, three parameterized cases (Codex, Claude current and
  older wording, Grok in every completion order), on the shared timeline
  rig with a per-agent journal identity.
- Each case starts with a send on record and checks it is unchanged after
  the reopen.

* fix(native-chat): word every Claude prompt-hook refusal, and draw the Codex hook row like the others

- Claude: a plugin module's dropped_by_hook / not_passed_on refusal reads "Blocked by hook" too.
- Older Claude sentence: an empty reason no longer turns the prompt copy into the reason; each hook's
  path label goes per line, while a hook's own [TAG]: stays.
- Codex: the prompt-hook row is read once, before classification, and carries no raw-frame
  disclosure, so it matches the Claude and Grok rows. The hook/completed table entry is back to main's.
- Comments the change had made wrong are corrected.

* fix(native-chat): strip only the older Claude sentence's first hook label, as before

Command labels without a slash were kept and labels inside a hook's own text were removed; the
first-line strip is back, with the empty-reason fix kept.

* refactor(native-chat): keep a hook's block row visible as a generic turn notice

The fold never hides a row presented as 'turn-notice', a host notice about the turn, instead of
a hook-specific value; the hook rows write it for every agent.

* fix(native-chat): report a Claude prompt hook's exit-2 block once

With hook events on, Claude also sends the blocking UserPromptSubmit hook's exit 2 as an error
hook_response; that is the block itself, already shown by the halting warning, so it writes no
red hook-failed row. Other hook events and exit codes are unchanged.

* docs(native-chat): say a prompt hook's exit 2 may also be a missing plugin script

* test(native-chat): type the Claude hook-outcome frame as a record

* fix(native-chat): keep a blocked Codex prompt's turn on its own message

Codex never echoes a prompt its hook refused, so its turn was anchored on the send only while
the send read in flight. Once the turn ended the host publishes the send as in doubt, the anchor
fell back to the turn record, and the block row moved to a second turn: live, the chat drew two
"Worked for" bars; after a reopen the bar sat on the row instead of the message. A send in doubt
now keeps anchoring the turn it opened (the first send in flight, else the newest in doubt).

* fix(native-chat): keep refused Codex hook notices outside turns

* revert(native-chat): keep Claude's exit-2 prompt hook_response as main's red row

A missing plugin script also reports exit 2 without blocking, so suppressing it hid a real failure main shows. With a user-added --include-hook-events an exit-2 block shows both rows (known limit).
2026-10-22 00:56:00 -07:00
Neil 66e50e22cc fix(images): avoid backtracking in data URI parameter parsing (#31882) 2026-10-22 00:24:41 -07:00
Neil 9e1ffb2333 fix(plugins): validate example panel reply dispatch (#31877)
* fix(plugins): validate example panel reply dispatch

* style(plugins): brace panel reply admission guard
2026-10-22 00:19:10 -07:00
Jinwoo Hong 01db1b89af Main titles agent tabs and applies phone tab moves itself, and tab moves name the tab they follow (dark, layout-owner build only) (#31812)
* Layout groundwork (switched off): main derives agent tab titles from its own hook status

With the layout-owner switch on, main gives agent tabs their generated titles from
the hook-server status it owns, on the desktop, headless serve and orcad: one
renameTabs per status batch, so one write and one frame. It keeps the window's
rules (setting, custom and quick-command titles, first title wins, dispatch labels).
The window stops sending generated titles for tabs main holds; a paired server's
tab, or one main has no tab-bar entry for, is still titled by the window as today.

The title rules move to src/shared (agent-row-primary-text, and the new
agent-generated-tab-title-rules) so both sides share them; switch-off behaviour
is unchanged.

* feat(layout): main applies a phone's or paired client's tab move itself, and a tab move names the tab it follows so a stale index can't misplace it (layout-owner build, L193)

* fix(layout): a move in a group showing a tab main lacks goes the window's way, a twin's agent tab keeps its window title, and orcad subscribes titles through the store attach (layout-owner build)

* chore(layout): main's tab move reaches the old-client translation through its compat entry

* chore(layout): mark the window-only-row move fallback as bridge code (ledger L280)

* test(layout): attaching the hook store follows its rows for main's tab titles once

* chore(layout): the old-client move translation is a compat entry of its own, with no cyclic re-export

* test(config): the boundary message test names the old-client folder's configured entries
2026-10-22 02:24:37 -04:00
Jinjing 8aa7f9c047 Combine untracked files into Changes (#31831)
* Combine untracked files into the Changes section

* Localize discard warnings
2026-10-21 22:23:59 -07:00
Jinwoo Hong 4c949cd111 fix(session-tabs): a confirmed close of a pinned tab in a server workspace closes it instead of snapping back (#31791)
* fix(session-tabs): a confirmed close of a pinned server tab closes it instead of snapping back

A paired desktop sent its confirmed close of a pinned terminal tab in a
server workspace over session.tabs.close, which had no force, so the
server refused it (terminal_tab_pinned) and the tab came back.

- session.tabs.close takes an optional `force: true`, honoured only with
  an explicit `reason: 'user'` and passed to the server's existing forced
  close (the same path worktree stop uses), in both builds.
- The server advertises `session-tabs.close-force.v1` in both builds. The
  desktop sends force only after the pinned-tab confirmation and only to
  a server advertising it; an older server keeps today's refusal.
- A forced close the server never received keeps its force in the held
  replay record, so the replay is still the confirmed close.
- Moves the undelivered-close hold helpers into
  web-session-undelivered-close.ts unchanged, to keep the lifecycle
  module under the line limit.

* fix(session-tabs): a pinned server tab closes when the pin confirmation is turned off

A local pinned tab closes at once when confirmClosePinnedTab is off, so the
close itself is the user's consent. A server workspace's pinned tab now
sends that consent as force too (still only to a server advertising
session-tabs.close-force.v1). Lifecycle closes (pty-exit), rejectPinned
callers and bulk closes are unchanged.

* test(session-tabs): load the forced-close server test once; cover held and probe-failed forced closes

- session-tab-close-force.test.ts imports the runtime once and flips the
  layout-owner switch per case through the mock's getter, instead of
  resetting modules and re-importing the runtime for every case; drops
  the 120 s timeouts.
- A forced close held while the session is inactive, and one whose
  capability probe failed, keep their force for the replay.
- A pinned lifecycle (pty-exit) close never forces, even with the pin
  confirmation turned off.
2026-10-22 00:00:51 -04:00
Jinjing a53c2a2a72 Add onboarding language selection (#31784)
* Add onboarding language selection and system locale detection

- Detect and synchronize native/browser language preferences safely
- Load catalogs before rendering onboarding and support recovery
- Let users choose a supported language or request another language

* Add onboarding language experiment cohorts and detection recovery

* Stabilize onboarding language detection tests

* Test persisted onboarding language cohort behavior
2026-10-21 19:43:22 -07:00
NeilandClaude 56143a9a5e feat(ssh): open a plain SSH terminal to a host whose move to its Orca server is blocked (#31716)
* feat(ssh): open a plain SSH terminal to a host whose move to its Orca server is blocked

A refused or unserved host fails the whole connect, so the user had no way to
reach their work there. The blocked-move banner and the SSH host row now offer
an unmanaged, unpersisted ssh login in a non-modal window. Main builds the argv
from the saved target (no -T, BatchMode or Orca multiplexing; the config alias
stays bare so ssh_config applies) and spawns ssh with an argv array through the
local PTY spawn primitive.

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

* fix(ssh): claim a host before awaiting, wait for ssh on quit, and share the preview terminal's keys

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

* fix(ssh): give the plain SSH terminal Orca's Unicode widths, kitty flags and spawn-helper repair

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

* refactor(terminal): one standalone terminal for the agent preview and plain SSH

Extracts the preview's xterm setup (Unicode widths, links, IME bridge, kitty
flags, copy/paste and shortcut policy, user-input routing) into
openStandaloneTerminal, so the plain SSH terminal reuses it instead of a second
copy. Plain SSH output is now acked once rendered, and main pauses ssh while too
much is unrendered.

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

* fix(ssh): plain SSH answers terminal queries, keeps early exits, and ends a paused pty on Windows

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

* fix(ssh): plain SSH applies theme changes live and never refits while minimized

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

* fix(ssh): a native call on a just-exited plain SSH pty never crashes main

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

* test(ssh): give the plain SSH test's ResizeObserver stub unobserve, which Tooltip needs

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-21 19:35:32 -07:00
99d18f7a2f Browser, chat and simulator tabs main-first: the window asks main for each change and draws only main's update (dark, layout-owner build only) (#31516)
* The window shows each layout records frame record by record, without reading its session back (dark, layout-owner build only)

A records frame no longer re-serializes the window's whole session and runs the
Loader. Panes' terminal bindings and live titles apply alone, to the tabs the
frame names; any other record builds only its own workspace's slices, with the
view and facts read from the store. Only a workspace the store does not show
from frames yet (startup, a reopened stream, a first visit, one sent whole)
takes the full build.

Tests install an apply check that, after every flush, compares the store with
the full build for the same cache and pre-flush store; the shared layout-apply
transport now sends main's records frames and fails a flush that differs.

* A frame of only tab rows shows without reading the window's session back or running the Loader (test, layout-owner build only)

* The account-restart test's fake binding runtime carries per-pane binding generations, so it passes with the switch on (test only)

* The account-restart test types its store writes, so its five old e2e type errors are gone (test only)

* A workspace build reads the editor selection and groups through the save's own writers, as the full build does (dark, layout-owner build only)

* A tab released from the window's hold shows the binding main wrote meanwhile (test, layout-owner build only)

* The frame-apply check still checks a flush's other workspaces when it builds one in full, and the shared transport keeps a test's own check (test only)

* The frame-apply check is proven to catch a mismatch beside a full build, and the shared transport restores only the check it installed (test only)

* wip(d3a-2): a windowless host lists a file main opens on session.tabs at once

* wip(d3a-2): the record key's fields are declared where the key is made

* wip(d3a-2): main publishes an editor tab's view, and a window rebuilds the open file it lacks from it

Main's in-memory editor view now carries the file facts an open file needs; it rides the tab row
(PublishedTab.editorView) and a frame build rebuilds a diff, conflict, preview or check-details tab.
Inert with the switch off: views are set only by layout.apply (switch builds) and read only in frame-sync.

* wip(d3a-2): a switch-on window sends editor opens, closes and promotions to main and draws the tabs from its frames

Layout-owner build only; the switch-off path is unchanged. An open proposes the tab id and sends
openEditorTab with the edit file's record or the tab's view (a reopen also its index); the open
file is written at once and the tab is selected once its frame draws it. Callers that act on the
tab (focus request, link reveal, group focus) wait for that frame through layout-owner-pending-tabs.
A close sends closeTabs; the tab goes with the frame and the file's content after it, and a
refusal keeps both. A promotion sends promotePreviewTab and the frame clears the flag. Deleted:
the drawn editor senders, their before/after captures, the editor drawn-tab holds and the
window-record keep rule (a transient tab is kept by its frame view).

* wip(d3a-2): a switch-on close keeps its selection and a reopen, a link names its group, and main stamps an open's time

Layout-owner build only. After main's frame drops a closed editor tab, the window selects the
group's previous tab and its surface through activateTab and focusGroup, after closeFile's
content cleanup rather than before it; a reopen sent while the close was in flight keeps the
file. openFileForLink's proposed view names the group the open asked for. The window's
openEditorTab sends no createdAt: main stamps the tab's time (ledger L186). Preview promotion
writes no tab itself: the window ends a preview only where a paired server owns the layout.

* wip(d3a-2): editor tests assert switch-on tabs through main's command and frame (test only)

Each store and hook test that assumed an immediate editor-tab draw now runs with main's model
through the T1 transport in apply mode and its frames (tabs-drawn-from-main.test-fixture), and
asserts the tab, its selection and its content after main's frame. The fixture fills the frame
slices a partial editor-and-tabs store lacks.

* wip(d3a-2): main takes a restored move as a command, renames a view tab's path, and keeps one tab per file per group

Layout-owner build only (the switch gates every change; shipped builds unchanged). moveTabToWorkspace no
longer skips a tab "the window's save got there first" (L160): window saves no longer carry the move, so
a repeat is tab_not_found; tab_exists and invalid_params stay. A move or rename may carry a non-plain
tab's new view (its file under the new path or owner), which replaces editorViews[tab] (L180). Main
refuses a moveTab or moveTabToWorkspace that would put a second tab for a file into a group (tab_exists),
and its loader folds a saved session's duplicate into the first, as the window's restore does
(dedupeEditorTabsWithinGroups): the window's frame dedupe hid the second, which no close could reach.

* wip(d3a-2): a switch-on restored move and disk rename are main's commands, and the frame that shows them rekeys the content

Layout-owner build only; the switch-off path is unchanged. A restored editor tab re-owned by its
file's workspace (L157, L158) sends moveTabToWorkspace (a re-own in place sends renameEditorFiles)
under the ids and group the window proposes, with each view tab's new view; a disk rename (L180)
sends renameEditorFiles per workspace and draws nothing first. The window's content rekey (open
file records, drafts, buffers, the editorBuffers echo gate, pendingLiveDiskVerification and
pendingSelfMoveEcho) waits in layout-owner-content-rekeys and the frame flush that first shows main's
change applies it, in the same store update as the frame (a flush now writes one patch). A change no
tab shows is rekeyed on main's answer; a refusal drops it and changes nothing. The move coordinator
and the restored-owner loader wait for that outcome; a refused rename rolls the disk move back.
A rename picks a runtime-owned or floating file's owned id, as main's frames restore it.

Deleted: recordRestoredEditorMoveInLayoutOwner, recordRenamedEditorFilesInLayoutOwner, the
reparent's before/after capture and the drawn-tab holds for moves and renames. Tests that assumed
the window moves or renames first now run through main's T1 transport in apply mode and its frames.

* wip(d3a-2): the static gate's casts and layout-writer ratchet hold for the switch-on move, rename and gap fixes

Layout-owner build only. The content rekey's pure store change stays in rekey-open-files-action
(its writer count drops 4 -> 2, pruned); a twin tab main routes to a paired server ends its
preview through patchTab, the existing tab writer, instead of a new one; a frame-only editor tab
takes its label through the frame build's tab-label facts, read from its view or file record; the
moved file's activation names the moved tab, writing no surface field. Test casts are typed or carry
a SAFETY note.

* wip(d3a-2): the switch-on rename takes the window's pure rekey as an argument, so the two modules do not import each other

Layout-owner build only; no behaviour change. Clears the static gate's no-cycle finding.

* wip(d3b): a switch-on window sends browser, chat and simulator opens, closes and page changes to main and draws them from its frames

Main places the chats its host lists (twin h), a local cancel keeps no tombstone (L155), and the
listing no longer writes chat tab structure where frames draw it (L153).

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

* wip(d3b): a switch-on browser, chat or simulator close waits for main's frame, lands an emptied workspace as today, and its tests read main's frames

* wip(d3b): the stale-listing and cross-version chat tests check a switch-on local cancel through main, with a presence precondition and per-test resets

* wip(d3b): main places a tab whose group it lacks in the workspace's first group, not beside the window's anchor (L150)

* wip(d3b): tests for main's chat tab commands from a host's chat changes

* wip(d3b): main's windowless chat tab and chat fork tests apply a switch-on host's chat tab through main's layout

* wip(d3b): the switch-on close and open keep their layout writes in the files the writer ratchet counts them in, and a drawn browser close carries its position instead of a rebuilt layout

* Editor tabs main-first: the window sends each editor action to main and draws editor tabs only from main's frames (dark, layout-owner build only)

With ORCA_LAYOUT_OWNER_IS_MAIN on, editor open (preview replace, open to the side), reopen at
its position, close, close-all, promote, the resolved conflict placeholder, a restored-editor
move and a disk rename are layout commands the window sends before it draws anything. Window
content (open file records, drafts, buffers, the autosave gate) is written at once for an open,
and rekeyed in the same store update as the frame for a move or rename. Main publishes each
non-edit tab's view, keeps views in memory across applies and partition changes, stamps the
tab's time, folds same-file duplicates, and republishes a windowless host's session.tabs after
its own file open. The switch-off path is unchanged.

* wip(d3b): a switch-on close main answers workspace_not_found drops the tab's content as tab_not_found does, and placement reads a missing active-group map safely

* wip(d3b): switch-on browser and chat tests open and close through main's command and draw from its frame (test only)

* wip(d3b): a switch-on chat listing writes no tab structure of a workspace main's frames draw; a host chat title changes only the drawn tab's label

* wip(d3b): main's phone rows show a diff tab and a markdown preview as the window's do, with the preview's source named by the window's open-file id

* wip(d3b): the cleanup close's page-read guard counts its named linear passes, so a per-entry rescan still fails switch-on (test only)

* wip(d3b): the static gate's compat entry, focus-read ratchet and formatting hold for the switch-on test conversions and phone rows

* test(d3a-2): the buffer view index follows a rename main answers

* wip(d3b): placement reads the active-group map the real store always holds; the browser test store seeds it

* wip(d3b): a switch-on background browser open leaves the worktree's remembered browser tab unset, as with the switch off

* wip(d3b): a switch-on chat open selects the chat in its group without moving the focused group, as the window's own open does; its test counts the chats main draws

* wip(d3b): the converted simulator, launch-cancel, cleanup-close and chat-listing tests assert what the window's draws asserted, through main's command and frame (test only)

* wip(d3b): main fences a chat's create on its close even when the close fails for want of the chat host, so a cancelled launch never shows

* wip(d3b): a frame keeps the window's hidden chat launch slot after its neighbour, so a resumed chat keeps its saved place through main's frames

* wip(d3b): a declined chat's terminal is placed after the chat and pinned as it is in one main move, and shown before main closes the chat

* wip(d3b): a switch-on chat close the host refuses asks main to open the chat again, as the window's re-read shows it with the switch off

* wip(d3b): a paired server's workspace, its row naming the server, opens and closes browser and chat tabs without main's layout (test only)

* wip(d3b): a refused chat close comes back only while this machine's host still lists the chat, and a close main answers workspace_not_found removes the window-only tab

* wip(d3b): the twin chat close test names what each build stops: no host off, main's host on (test only)

* wip(d3b): the refused-close reopen takes only a chat agent it can launch

* wip(d3b): a frame's browser selection keeps an unchosen workspace's unset through the restore projection's own rule, not a second write

* wip(d3b): the simulator and paired-server tests type their mock stores without assertions (test only)

* wip(d3b): the switch-off declined-chat placement runs as before, with no wait; only the switch-on move waits for main

* chore(d3b): drop the frame-binding helper the main merge left without a caller

* chore(d3b): drop the import the deleted helper used

* wip(d3b): a chat this machine's window creates is placed only by the window's own open, so a reserved chat slot keeps its place (switch-on)

* wip(d3b): main reads a chat tab change against its layout when its queue reaches it, through the one rule the switch-off save uses, so a close right after an open closes it

* wip(d3b): a declined chat's terminal moves to its index among the tabs main draws, not past a hidden launch slot, so it still replaces the chat (switch-on)

* wip(d3b): main's phone-row comments say which rows the window's own row wins, and a test shows a window-listed diff keeps its dirty state (comments and test only)

* wip(d3b): a switch-on chat open returns only the tab's id and chat, the fields main's frame keeps; no caller reads more

* chore(d3b): the unit-routing census registers the browser suites' drawn-from-main store fixture as a support bridge (test only)

* chore(d3b): the chat tab rule's comment wraps and names its two callers (comment only)

* wip(d3b): main's chat tab queue asks a worktree's host only once it has a saved session, and one failed change no longer stops the ones after it (switch-on)

* wip(d3b): a chat tab batch whose changes cannot be read is logged, and the batches after it still land (switch-on)

* test(d3b): a CLI-chat resume's tab is opened once, by the window through main, after the tab before its hidden slot (test only)

* test(d3b): an SSH worktree has no host to run a chat on, so none of the chat open, close or fix paths reach it (test only)

* chore(d3b): the content tab placement reads folder workspaces through main's workspace-record selector, as the new ratchet requires

* fix(merge): put back main's f47f632 files that the merge took from a later main by mistake (no D3b change)

---------

Co-authored-by: E2E Test <e2e@test.local>
Co-authored-by: Claude <noreply@anthropic.com>
2026-10-21 22:32:07 -04:00