Commit Graph
15799 Commits
Author SHA1 Message Date
Kelvin Amoaba 03e7ce0833 feat(checks): open pr comment file:line locations in the editor (#32031) 2026-10-22 21:48:04 +00:00
Brennan BensonandOrca Worker 1ed13cf687 fix(chat): split into a fresh chat beside the current conversation (#28950)
* Transfer keyboard focus when a sibling chat pane opens

* fix(native-chat): hand focus to newly opened chat composer

* fix(native-chat): keep tab bar controller within line limit

* fix(chat): split into a fresh conversation beside the current chat

* test(chat): declare the split fixture store without an assertion

* fix(chat): route the split to the source conversation host

* test(chat): keep the split regression focused on the launch contract

* test(chat): give the command palette split fixture its conversation agent

* test(chat): seed the legacy command fixture without expanding its asserted shape

* test(chat): isolate pane identity commands from the split launch boundary

* Prevent chat splits from starting a terminal on host refusal

* Cancel structured launches when their destination pane closes

* Model live destination groups in agent launch test fixture

* Capture split chat focus intent at the user action

* fix(chat): hand input to the split pane during admission

* fix(chat): Split opens a fresh chat beside the current one

Split Right/Down on a native chat used to move the chat into a new pane.
It now opens an empty pane beside the chat and launches a new chat of the
same agent on the same host into it through the New Chat launch, so the
new composer takes focus at once. A launch that never opens a tab closes
the empty pane. Drops the earlier admission and focus-transfer changes
that main now covers.

* fix(chat): Split palette search words describe a new chat, not a move

* fix: remove misleading chat move shortcut hints

* fix(chat): a new chat stays in its pane until its host lists it

A host tab list built before a chat's create landed can arrive after the
create reply. Once the reply settled the launch, nothing protected the new
tab, so that older list dropped it: Split's new pane collapsed and focus
moved to another chat, and the next list re-added the chat elsewhere.

Keep a small in-memory record for a chat this client created that no host
list has shown yet. While it exists, a list that omits the chat keeps its
tab. The record ends when an accepted list from that host includes the
chat, when the tab closes, on workspace teardown, or 30 seconds after the
reply, when the worktree is re-read from its host (including an unchanged
version) so a chat the host really closed is dropped then. The launch
lifecycle, status, persistence and settlement are unchanged.

* test(chat): type the faked create replies without an assertion

* Keep new chat protection tied to accepted host lists

* Update paired chat close regression to current suppression API

* fix(chat): Portuguese Split palette words describe a new chat

---------

Co-authored-by: Orca Worker <orca-worker@localhost>
2026-10-22 14:45:37 -07:00
Brennan Benson 1cf24e5e6f fix(orchestration): let a worker start helpers on the Run it works for (#29205)
* fix(orchestration): let a worker start helpers on the Run it works for

A dispatched worker could only start helper workers by taking a coordinator
seat on a new Run, and taking a seat moved its inbox. worker-start now also
accepts a worker on an active Dispatch: its helpers run in the same Run, report
to it (worker_done, heartbeat, escalation, status, ask), and it answers their
questions. The worker's own mailbox never moves. When it settles, whatever its
helpers sent that it never handled goes to the Run's coordinator.

Authority comes from the worker's own Dispatch, re-checked inside the
reservation transaction. Helpers are fresh agents on this host; a worker may
retry only its own helper's latest attempt. Remote workers keep the old
own-Run instructions. No saved field or wire change.

* fix(orchestration): read helper rows without type assertions

* fix(orchestration): hand a settled helper creator's mail to the Run as live delivery

When a worker that started helpers settled, its helpers' unhandled mail was
re-addressed to the Run by settlement SQL alone. Nothing woke the Run's
coordinator, delivered rows kept their old delivery stamp so pointers and the
restart scan skipped them, payload-free status reports were left behind
because selection required payload.dispatchId, and a question asked while the
creator was being stopped stayed with the stopped creator.

- Select handed-back mail by its recipient mailbox and Run (unread reports
  plus pending questions, even ones already read), reset delivery and pointer
  state on the moved rows, and fence the old mailbox's Deliveries.
- After the settling transaction ends, the runtime re-reads exactly the moved
  rows and wakes the Run with their real types; a rollback wakes no one and a
  failure falls back to the existing repoint retry. Restart recovery is the
  existing unread-undelivered scan.
- Hand mail back again at the final stop settlement, which also closes the
  stopped Dispatch's own questions asked during the stop.
- A worker_done receipt replay re-offers the handed-back targets.
- Scope markAsDelivered to the mailbox it pointed, so a late pointer stamp
  from the old owner cannot hide mail now owned by the Run.

* test(orchestration): drop an unused inbox type

* fix(orchestration): read only the Run when handing a settled worker's mail back

Every settlement runs the hand-back, so a full dispatch row read there added a statement per settlement. Also expect the mailbox-scoped delivered stamp in two delivery tests from main.

* fix(orchestration): re-offer handed-back Run mail on every worker_done replay

A helper creator's worker_done hands its helpers' unread reports and pending
questions to the Run. The send stored the Run's extra wake targets in the
mutation receipt, but the executor rewrites that receipt on completion and on
each replay, so an identical retry only woke the original destination.

Derive the targets at replay time instead: for the settled Dispatch (when it
has same-Run helpers), notify run:<id> for each type of unread report or
pending question there. Notification only; nothing is re-settled or inserted.
The receipt augmentation is removed.

* style(orchestration): format with the formatter version main now pins
2026-10-22 14:43:06 -07: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
Kelvin Amoaba aee882325f fix(native-chat): a missing claude or codex cli no longer stalls the model picker on every open (#32014) 2026-10-22 21:39:35 +00:00
Neil 9c4ad143bb refactor: colocate Jira favourite-filter contracts (#32102) 2026-10-22 14:30:36 -07:00
Kelvin Amoaba 4d162c4442 fix(mobile): keep a keyboard closed with Back from reopening on terminal touches (#32038) 2026-10-22 21:29:11 +00: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
Kelvin Amoaba 752f531b0a fix(chat): send pi and acp messages after a model-only change (#32004) 2026-10-22 21:10:01 +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
Jinjing e5df1522ce Gate draft image ownership checks on relevant local attachments (#32098) 2026-10-22 13:29:27 -07:00
Jinwoo Hong 7b70a290e9 feat(agent-write): opt-in launch cancel, approval wait and process fence on the shared agent writer (#32092)
* 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(agent-launch): a cancel after a failed startup spawn recovers the created workspace
2026-10-22 16:25:03 -04: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 ac9dc0bc0d perf(ai-vault): bound scope cache write batch enumeration (#31950)
* perf(ai-vault): bound scope cache write batch enumeration

* test(ai-vault): type scope cache stamp fixture

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-22 13:20:23 -07:00
OrcaWinandOrcaWin c1709f8f81 perf(mobile): index diff review notes by file path (#31943)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-22 13:20:16 -07:00
OrcaWinandOrcaWin 2a7942096f perf(ai-vault): reuse parsed Qoder transcript records (#31938)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-22 13:20: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
OrcaWinandOrcaWin 1a932d62aa perf(markdown): scan wiki-link delimiters without revisiting the document tail (#31934)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-22 13:19:58 -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
Jinwoo Hong e710e89832 A paired desktop hands its editor tabs to the server before drawing a server workspace from the server's layout (switch-on builds only) (#32088)
* wip(h5c-1): server workspace layout intents go to the server's main; positions per stream

* wip(h5c-1): e2e without a cast

* fix(h5c-1): a routed move never moves here; routed callers read the server's stream; a restarted main starts its positions over

* fix(h5c-1): layout-apply-send back under its line limit

* test(h5c-1): a routed answer reaches the handler with the server's stream key

* 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.

* fix(terminal): a CLI or phone close through main keeps a pinned tab, as the window-era close did

* feat(terminal): main applies phone closes, renames, pane titles, sleeps and agent-launch tabs itself, with or without a window (layout-owner build)

* fix(terminal): a tab close through main also ends the tab's dormant SSH relay session; agent-launch tabs main writes keep the window's focus rule, prompt and outcome (layout-owner build)

* feat(terminal): main starts a new tab's first terminal under the window's ids, and the pane attaches to it (layout-owner build)

* test(terminal): tests that counted the window's create calls or publications see main's start of a new tab

* fix(terminal): an account-switch restart binds only into a pane main's layout holds, with no window tree (layout-owner build, L197)

* test(terminal): the new-tab start tests hold no type assertions; the start's ledger row is L252

* fix(terminal): a new tab's pane attaches to any terminal main binds it to, and a failed start keeps the tab's startup

* test(terminal): a Claude account-switch restart's tests draw the pane main holds

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

* fix(terminal): a launch pane main takes back still tells the window why, which drops its hold and prompt; a rename through main answers the name it stored (layout-owner build)

* fix(terminal): a new tab another owner's terminal won attaches to it, and a failed start's startup never runs twice

* fix(terminal): main's pane close reads its stop result as a type only, so no runtime module loads before its base class

* fix(terminal): a phone's close through main drops the closed rows from what phones list at once, not at the window's next publish (layout-owner build)

* chore(terminal): mark the phone-close row drop interim (ledger L260)

* test(worktree-create): a new local tab's startup counts as run when main starts it (layout-owner build)

* test(worktree-create): record main's starts without a type assertion

* fix(terminal): a tab closed before main declines its start gets no startup back (layout-owner build)

* feat(h5d): hand desktop-only editor tabs to a layout-intents server before its frames draw the workspace

A paired desktop dropped an editor tab it opened in a server workspace (with its open file and
unsaved draft) on the server's next records frame once that workspace became frame-drawn, and
editor opens made after gating hit the same loss.

- openEditorTab is now on SERVER_ROUTED_LAYOUT_COMMANDS: the proposed tab id and group are kept,
  the file id becomes the server's own, and the record loses the desktop's runtimeEnvironmentId.
- Before a workspace gates, server-layout-frames hands its desktop-only editor tabs to the
  server's main (sendToServerMain), recomputes them on each answer, and gates only when all
  answered ok or tab_exists. Any other refusal or a failed call keeps it on the mirror for that
  connection; the next one retries. No timer, no hold store.

* fix(h5d): only an applied open counts as handed over; tab_exists keeps the tab on the mirror

* fix(terminal): the window's new tab starts in main when its own window asks over IPC, which labels itself a client (layout-owner build)

* feat(h5c-2): a paired desktop's writes to a server workspace its frames draw land in that server's main (layout-owner build)

- Routes setTabProps, setChatPane and non-terminal renameTab to the server's main as layout.apply;
  closes, starts, sleeps and terminal names keep the RPCs that main now applies (session.tabs.close,
  terminal.close, terminal.split, session.tabs.createTerminal, terminal.rename). A window-style
  closeTabs is never routed: main takes it as already stopped and stops nothing.
- A close the server refuses (a pinned tab) redraws the workspace from the server's frames, so the
  window's optimistic drop never outlives the answer; same for a refused pane close.
- A frame-drawn workspace keeps none of the mirror's ledgers (L240): no tab-id mappings, close or
  focus intents; they stay only for servers without layout.intents.v1 until the Phase 2 cutoff.
  Its pane-geometry push is skipped (it goes as intents); pane renames still push.
- e2e: paired-desktop-server-layout-writes covers close (layout, session.tabs, terminal stop),
  rename, split and new tab on a windowless switch-on serve.

* fix(h5c-2): an undelivered close of a frame-drawn tab is held for replay; a launch-agent clear stays off the server route

* refactor(h5c-2): the undelivered-close test moves to its own module, keeping the lifecycle file under its line cap

* fix(h5d): a tab closed while its hand-over was in flight is closed on the server before the workspace gates

* fix(h5d): an editor tab closed in a workspace a server's frames draw closes on that server by session.tabs.close; a tab kept on the mirror closes here only

* fix(h5d): a handed-over tab closed here before the gate is closed on the server first; a server-opened tab's close in a frame-drawn workspace is covered

* chore(h5d): a closed stream also closes the hand-over its hand-back started

* refactor(h5d): move the frame stream's routing inputs out of server-layout-frames to keep it under the line cap

Main's #31869 grew server-layout-frames past 300 lines once merged with H5d; the two helpers move unchanged.
2026-10-22 15:47:43 -04:00
Jinjing 64b734774b Set minimum width for native chat table cells (#31848)
* Set minimum width for native chat table cells

* Allow markdown table cells to wrap long content

* Set minimum width for native chat table cells
2026-10-22 12:46:49 -07:00
Neil 2e6963d707 refactor: colocate Jira query construction (#32089) 2026-10-22 12:28:18 -07:00
Jinjing 94bea877ea Stabilize remote host status bar during retries (#32083)
* Stabilize remote host status bar layout during retries

* Add connecting status translation for remote hosts
2026-10-22 12:27:14 -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
Brennan Benson 78bfc4d956 fix(acp): OMP chats send images; an ACP agent's own startup answer decides (#30910)
* fix(acp): OMP chats send images; an ACP agent's own startup answer decides

Images reached an ACP agent only when it advertised them and Orca also listed it, and OMP was never listed. Trust the advertised capability alone, offer attachments to every ACP agent until its live report narrows them, and send an image's @path reference to an agent that takes none instead of refusing the message.

Tracked in STA-11152.

* fix(acp): preserve image file references when native content cannot fit
2026-10-22 12:14:00 -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
Brennan Benson 814bb59ef6 fix(runtime): release remote streams when the renderer reloads (#29104)
* fix(runtime): release remote streams when the renderer reloads

* ci: re-run code scanning (no source change)
2026-10-22 11:42:13 -07:00
Jinwoo Hong 43118aeb7c fix(worktree-create): a workspace created on a paired server shows one set of default tabs when the same path exists on another host (#32080)
* fix(worktree-create): a workspace created on a paired server seeds against that server, so a same-path twin on another host can't duplicate its default tabs (L277)

* fix(worktree-create): name the create's host once, keeping the flow under its line cap; type-only test import
2026-10-22 14:29:56 -04:00
Neil a1448f9f58 refactor: colocate Jira image and stream contracts (#32079) 2026-10-22 11:10:10 -07:00
Jinwoo Hong 4cfa3977e8 A server-hosted browser tab opened in a server workspace drawn from frames survives other changes to that workspace (switch-on builds only) (#31920)
* 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(L282): a client-hosted browser tab this desktop creates in a frame-drawn server workspace completes once the server's snapshot lists it, instead of undoing itself after 8 s (switch-on)

* fix(L284): a server-hosted browser tab this desktop creates in a frame-drawn server workspace completes when the server's frames take it over, instead of reading as the user's close (switch-on)

* chore(L282/L284): adopt only staged client-hosted handles, and name the frame build's handover note (review nits)

* fix(L284): a staged tab the server's frames took over completes the create though its pane, unmounted, has no handle yet (layout-owner build only)

* fix(browser): a switch-on host answers a paired client's browser create with the new page's id, not its workspace's, before main's frame draws it

* fix(browser): the reply names the page id the create asked for first, then the new record's own page (L291)

* fix(L284): the frame build scans dropped pages for a handover only when a staged create's page is drawn, so a large close stays one pass

* fix(L285): a staged server-hosted browser tab stays through the server's frames until the server lists its page, then hands over to the server's own tab (layout-owner build only)

* fix(L285): release a held browser tab by the server listing its page, staged or adopted, in one scan per frame (review)
2026-10-22 14:01:59 -04: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 9613563f5c refactor(pty): one commit step for both terminal start lanes (#31596)
* refactor(pty): one commit step for both terminal start lanes

The window's pty:spawn commit and the host runtime controller's commit were two
copies of the same job (ownership, binding save, registration, size, hidden
mark, publication, reservation settle), so every fix had to land twice.

Both lanes now call one commitPtySpawn and supply what differs as data: the
binding each lane builds, its registration facts, pane key and aliases, the
Codex launch env and memory session id. Three inputs still select behavior:
hiddenDelivery (the renderer-scoped mark is refreshed here; the runtime-owned
mark stays at the host runner's await boundary), earlyExit (the window drains an
exit during start, the host throws) and joinPayload (what reservation joiners
and the caller receive).

One publication order for both lanes: note commit, Codex pane account, renderer
hidden mark, handle, size set with reflow, old-id size cleanup, serializer
readiness, restore records, command detector, telemetry, pane key and aliases,
memory registry, resume identity, inventory, Codex lifecycle, settle. Every
moved step was audited for read/write independence. Stable-owner adoption and
the adopted-claim publication set are unchanged.

Also pins that an agent launch whose record cannot open takes its tab back and
spawns nothing, and re-labels three TEMPORARY comments to their current owners.

* fix(pty): preserve adopted claim return scheduling

* fix(pty): keep shared commit reply types precise
2026-10-22 10:03:07 -07: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
Jinwoo Hong c9410991ba A paired desktop's pane drag-out, handle-less split and default-tabs mark reach a server's own layout (dark, layout-owner build only; stacked on #31714) (#31869)
* wip(h5c-1): server workspace layout intents go to the server's main; positions per stream

* wip(h5c-1): e2e without a cast

* fix(h5c-1): a routed move never moves here; routed callers read the server's stream; a restarted main starts its positions over

* fix(h5c-1): layout-apply-send back under its line limit

* test(h5c-1): a routed answer reaches the handler with the server's stream key

* fix(terminal): a CLI or phone close through main keeps a pinned tab, as the window-era close did

* feat(terminal): main applies phone closes, renames, pane titles, sleeps and agent-launch tabs itself, with or without a window (layout-owner build)

* fix(terminal): a tab close through main also ends the tab's dormant SSH relay session; agent-launch tabs main writes keep the window's focus rule, prompt and outcome (layout-owner build)

* feat(terminal): main starts a new tab's first terminal under the window's ids, and the pane attaches to it (layout-owner build)

* test(terminal): tests that counted the window's create calls or publications see main's start of a new tab

* fix(terminal): an account-switch restart binds only into a pane main's layout holds, with no window tree (layout-owner build, L197)

* test(terminal): the new-tab start tests hold no type assertions; the start's ledger row is L252

* fix(terminal): a new tab's pane attaches to any terminal main binds it to, and a failed start keeps the tab's startup

* test(terminal): a Claude account-switch restart's tests draw the pane main holds

* fix(terminal): a launch pane main takes back still tells the window why, which drops its hold and prompt; a rename through main answers the name it stored (layout-owner build)

* fix(terminal): a new tab another owner's terminal won attaches to it, and a failed start's startup never runs twice

* fix(terminal): main's pane close reads its stop result as a type only, so no runtime module loads before its base class

* fix(terminal): a phone's close through main drops the closed rows from what phones list at once, not at the window's next publish (layout-owner build)

* chore(terminal): mark the phone-close row drop interim (ledger L260)

* test(worktree-create): a new local tab's startup counts as run when main starts it (layout-owner build)

* test(worktree-create): record main's starts without a type assertion

* fix(terminal): a tab closed before main declines its start gets no startup back (layout-owner build)

* fix(terminal): the window's new tab starts in main when its own window asks over IPC, which labels itself a client (layout-owner build)

* feat(h5c-2): a paired desktop's writes to a server workspace its frames draw land in that server's main (layout-owner build)

- Routes setTabProps, setChatPane and non-terminal renameTab to the server's main as layout.apply;
  closes, starts, sleeps and terminal names keep the RPCs that main now applies (session.tabs.close,
  terminal.close, terminal.split, session.tabs.createTerminal, terminal.rename). A window-style
  closeTabs is never routed: main takes it as already stopped and stops nothing.
- A close the server refuses (a pinned tab) redraws the workspace from the server's frames, so the
  window's optimistic drop never outlives the answer; same for a refused pane close.
- A frame-drawn workspace keeps none of the mirror's ledgers (L240): no tab-id mappings, close or
  focus intents; they stay only for servers without layout.intents.v1 until the Phase 2 cutoff.
  Its pane-geometry push is skipped (it goes as intents); pane renames still push.
- e2e: paired-desktop-server-layout-writes covers close (layout, session.tabs, terminal stop),
  rename, split and new tab on a windowless switch-on serve.

* fix(h5c-2): an undelivered close of a frame-drawn tab is held for replay; a launch-agent clear stays off the server route

* refactor(h5c-2): the undelivered-close test moves to its own module, keeping the lifecycle file under its line cap

* fix(layout): a paired desktop's pane detach, handle-less split and default-tabs mark on a server workspace go to the server's main (layout-owner build, L272/L276/L275)

* fix(layout): a pane a paired server's main detached takes the terminal the server's frame drew, not the desktop's possibly stale one (layout-owner build, L272)

* test(layout): the detach test resets the drawn layout after each case

* test(layout): drop the in-test reset the afterEach now covers
2026-10-22 12:58:23 -04:00
Neil 2366072dc8 refactor: colocate browser cookie validation (#32062) 2026-10-22 09:43:42 -07:00
Neil de2700d0fb refactor: colocate native-chat command envelopes (#32055) 2026-10-22 08:55:53 -07:00
Brennan Benson 97d4518d5f Show per-agent Session History reading progress (#30954)
* fix(session-history): freeze scan workload before checking candidates

* Show per-agent Session History reading progress

* Keep reduced-motion behavior in progress primitive

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

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

* test: cover finished empty history progress rows

* 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 08:36:59 -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
Neil d053ff97ab refactor: colocate mobile web route and bundle tooling (#32008) 2026-10-22 06:04:04 -07:00
Jinwoo Hong 4dd97f02d5 A paired desktop's tab moves, splits, resizes, props and renames in a server workspace go to the server's layout (switch-on builds only) (#31714)
* wip(h5c-1): server workspace layout intents go to the server's main; positions per stream

* wip(h5c-1): e2e without a cast

* fix(h5c-1): a routed move never moves here; routed callers read the server's stream; a restarted main starts its positions over

* fix(h5c-1): layout-apply-send back under its line limit

* test(h5c-1): a routed answer reaches the handler with the server's stream key

* fix(terminal): a CLI or phone close through main keeps a pinned tab, as the window-era close did

* feat(terminal): main applies phone closes, renames, pane titles, sleeps and agent-launch tabs itself, with or without a window (layout-owner build)

* fix(terminal): a tab close through main also ends the tab's dormant SSH relay session; agent-launch tabs main writes keep the window's focus rule, prompt and outcome (layout-owner build)

* feat(terminal): main starts a new tab's first terminal under the window's ids, and the pane attaches to it (layout-owner build)

* test(terminal): tests that counted the window's create calls or publications see main's start of a new tab

* fix(terminal): an account-switch restart binds only into a pane main's layout holds, with no window tree (layout-owner build, L197)

* test(terminal): the new-tab start tests hold no type assertions; the start's ledger row is L252

* fix(terminal): a new tab's pane attaches to any terminal main binds it to, and a failed start keeps the tab's startup

* test(terminal): a Claude account-switch restart's tests draw the pane main holds

* fix(terminal): a launch pane main takes back still tells the window why, which drops its hold and prompt; a rename through main answers the name it stored (layout-owner build)

* fix(terminal): a new tab another owner's terminal won attaches to it, and a failed start's startup never runs twice

* fix(terminal): main's pane close reads its stop result as a type only, so no runtime module loads before its base class

* fix(terminal): a phone's close through main drops the closed rows from what phones list at once, not at the window's next publish (layout-owner build)

* chore(terminal): mark the phone-close row drop interim (ledger L260)

* test(worktree-create): a new local tab's startup counts as run when main starts it (layout-owner build)

* test(worktree-create): record main's starts without a type assertion

* fix(terminal): a tab closed before main declines its start gets no startup back (layout-owner build)

* fix(terminal): the window's new tab starts in main when its own window asks over IPC, which labels itself a client (layout-owner build)

* feat(h5c-2): a paired desktop's writes to a server workspace its frames draw land in that server's main (layout-owner build)

- Routes setTabProps, setChatPane and non-terminal renameTab to the server's main as layout.apply;
  closes, starts, sleeps and terminal names keep the RPCs that main now applies (session.tabs.close,
  terminal.close, terminal.split, session.tabs.createTerminal, terminal.rename). A window-style
  closeTabs is never routed: main takes it as already stopped and stops nothing.
- A close the server refuses (a pinned tab) redraws the workspace from the server's frames, so the
  window's optimistic drop never outlives the answer; same for a refused pane close.
- A frame-drawn workspace keeps none of the mirror's ledgers (L240): no tab-id mappings, close or
  focus intents; they stay only for servers without layout.intents.v1 until the Phase 2 cutoff.
  Its pane-geometry push is skipped (it goes as intents); pane renames still push.
- e2e: paired-desktop-server-layout-writes covers close (layout, session.tabs, terminal stop),
  rename, split and new tab on a windowless switch-on serve.

* fix(h5c-2): an undelivered close of a frame-drawn tab is held for replay; a launch-agent clear stays off the server route

* refactor(h5c-2): the undelivered-close test moves to its own module, keeping the lifecycle file under its line cap

* fix(h5c): a routed tab move anchors on the server's tabs only, and never sends an anchor the server does not hold (layout-owner build)

* fix(h5c): every routed tab move is placed among the server's tabs at the routing point, skipping desktop-only tabs (layout-owner build)
2026-10-22 08:00:09 -04:00
Neil c30fa6252a refactor(tooling): group mobile web preview and evidence tools (#31976) 2026-10-22 04:57:01 -07:00
Neil 32370a5e05 fix(terminal): settle a restored remote pane whose host session already exited (#31968)
* fix(terminal): settle a restored remote pane whose host session already exited

A stale-handle or no_connected_pty refusal kept retrying forever because nothing
read the host's own exit record. On those refusals (and the restore attach's
stale branch) ask terminal.show once; settle the pane as exited only when the
host's verdict is exited, or an older host records an exit cause for a
disconnected terminal. Anything else keeps today's retry.

Fixes #31539

* fix(terminal): ignore an unverified stop and keep the host's exit code

* fix(terminal): settle on a host exit only when the host holds no replacement pane

* fix(terminal): settle only while the host pane still holds the exited process

* fix(terminal): leave a stopped host surface to materialize instead of settling it
2026-10-22 04:47:58 -07:00
NeilandClaude 8556d119bb Conflict review shows the file's live unsaved text from any workspace (read-only) (#31990)
* Autosave relies on the guarded write instead of pausing restored or moved tabs

Delete the pendingDiskBaselineVerification and pendingLiveDiskVerification
pause flags. Every editor save is already a guarded write; a refusal raises
the changed-on-disk banner, which is what stops autosave until an explicit
save.

A guarded write now succeeds when disk changed but already holds exactly the
text being saved (local/host compare is byte-exact; client-side fallbacks
compare decoded text and refuse when it holds U+FFFD), so a restored draft
that matches disk goes clean instead of showing a banner.

The restore scan only raises the banner early for tabs that arrive dirty; a
watch resync no longer re-checks dirty tabs.

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

* fix(editor): skip guarded saves when disk is already identical

Return a compatible optional no-op result from hosts and client fallbacks, clean the tab, and clear its provisional self-write stamp without rewriting identical bytes.

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

* fix(editor): refuse lossy automatic saves, keep identical-skip on older hosts, flag dirty tabs on resync

- Autosave and pre-restart saves refuse when disk text can't be decoded losslessly, so a
  baseline that decoded to U+FFFD can't authorize overwriting different bytes.
- New files.write.identical-noop.v1 capability; for hosts with only the v1 guarded write the
  client compares first, skips identical saves, then delegates to the host's guarded write.
- Watch resync compares dirty tabs against disk and shows the changed-on-disk banner.
- Local guarded writes check for identical content before the write-only regular-file probe.

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

* fix(editor): tell non-UTF-8 files apart from disk changes, and quiet false resync banners

- Local guarded writes judge losslessness by bytes, so a valid UTF-8 file containing U+FFFD
  autosaves again. A file that really isn't UTF-8 is refused with its own reason, 'not-utf8'
  (older clients read it as 'changed'), which pauses autosave for that file with one notice
  until an explicit save converts it.
- A watch resync whose disk read fails leaves dirty tabs alone; the guarded save still catches
  a real conflict.
- Resync and restored-tab checks treat disk holding the tab's own just-written save as an echo.
- On hosts with only the v1 guarded write, only automatic saves read the file first; explicit
  saves go straight to the host's guard.

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

* fix(editor): lift the non-UTF-8 autosave pause when its edits end, and report disk changes first

- The pause is released once no dirty tab of the file remains (close, reload from disk,
  discard), so a reopened or reloaded file's next autosave asks the host again and notifies.
- Client-side precondition checks compare the signature before looking for U+FFFD, so an
  external rewrite shows the changed-on-disk banner rather than the non-UTF-8 notice.

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

* Schedule editor autosave per buffer record, and add the join rules the sharing switch uses

First of the stacked sharing-switch PRs (G1). Inert while every record has
one view, which is all main produces today; tests drive the shared shape
through hand-built view indexes.

- Autosave timers are decided per record by its first view that may
  autosave, so a read-only or preview view of a record no longer cancels
  the record's timer, and a timer survives the view that scheduled it
  closing (it resolves a live view of the record when it fires).
- A view of a detached record gets its own model owner key, so divergent
  dirty drafts never share text with the file's other record.
- Pure join and rejoin rules: rekey / attach / adopt (dirty wins) / merge
  (identical dirty) / detach (divergent dirty); a detached pair rejoins
  only once one side is clean.
- Mirrored host tabs: a record's dirty flag is the OR of its host-owned
  views, since an older host publishes it per tab; the dev invariant
  tolerates exactly that disagreement.

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

* Keep pauses and pending text out of buffer merges, and pick a writable view for autosave

- A merge needs equal drafts, baselines, conflict and owner-migration state;
  anything else detaches, so a paused record never resumes autosave by joining.
  A side whose text is still pending (no draft) never counts as identical.
- A clean-flagged view that still holds a draft counts as dirty in joins.
- The invariant tolerates a clean host-owned view on a dirty record only when
  another host-owned view of that record reads dirty.
- Autosave and restart skip CSV-preview and read-only views when picking the
  view a record writes through.

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

* feat(editor): views of one file share one buffer record

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

* fix(editor): review fixes for shared buffer records

A dirty view with no draft no longer pushes disk text into the shared model; merge and
deleted-banner joins mirror the record; web-session sync honours detached views.

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

* feat(editor): shared records take every writer, rejoin, outlive no-prompt closes, restore merges

- Rich Markdown, notebook and CSV commits also edit the record's models no editor shows, as one
  undoable edit; a save moves every loaded view's panel text to the written disk text, so a stale
  dirty check can't mark the record clean over unsaved edits.
- A detached pair rejoins once one side is clean (save, Discard, reload) or the partner closes:
  the clean side attaches, a dirty side wins with its own baseline, so its next save is refused
  and the banner shows on every view. Rejoin runs on the next microtask, after the caller has
  finished settling the record; doing it mid-settle let the settle clear the adopted draft.
- A dirty record whose last view a no-prompt removal closed is kept with its draft and model;
  reopening the file joins it, it never autosaves, and dismissing the removal notice drops it.
- Restored identical drafts merge; divergent ones detach with their own models and timers.

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

* test(editor): cover shared buffer records end to end, and restart-save a record through its writable view

Adds save-queue and store tests for rename and owner-resolution re-keys carrying a conflict, a
read-only sibling joining a dirty record, retired-runtime quarantine, runtime-in-SSH identity, the
reconnect writer, restart once per record, and a 200-tab index.

Fix: restart picked a record's first dirty view, so a read-only view listed first refused the
whole restart; it now picks the record's first view that may autosave.

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

* fix(editor): G3 review fixes for kept records, rejoin, undo steps and save events

- A no-prompt removal's keep request rides in the write that closes the tabs
  (editorBufferKeepRequest, consumed by the record middleware), so a removal
  closing tabs over several writes keeps every dirty record it closes.
- Kept records carry their removal notice's token; dismissing a notice, or
  failing to show it, releases only that notice's records.
- A divergent dirty view reopening a kept record stays detached on its own
  model; a rename onto a kept record's path drops the kept record.
- Each rich/notebook/CSV commit into a retained model is its own undo step.
- The saved event names the record's views, so the panel updates them even
  when the saving tab closed mid-save.
- A queued save compares the discard epoch of the record it was queued under.
- Typing no longer runs the rejoin check; only clearing a side's draft does.

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

* feat(editor): conflict-review overview shows the file's shared record, read-only and live

The overview of a conflict review now reads each file through any open view of its
shared buffer record (this workspace's first), so it shows the record's current unsaved
text and model and follows typing in another workspace. It stays read-only and is never
a view itself: it loads as that view would and never pushes text. With no view open it
still shows disk text.

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

* fix(editor): conflict-review overview follows its borrowed view and never acts as it

- The overview's load reruns when the view it reads through opens, closes, re-keys,
  detaches or rejoins in any workspace, so it no longer sticks on "Loading...".
- Retry reloads through the borrowed view's own workspace, and the editor resolves
  through that workspace, so this workspace never re-owns the other's tab.
- Overview editors are passive mirrors: they don't move the borrowed tab's cursor
  line or take a reveal meant for it.
- This workspace's own tab (even a detached one with its own edits) wins over
  another workspace's view.
- The panel's draft selector re-resolves when tabs or record views change, not only
  when drafts do; overview lookups are memoized per tabs/views/routes, and the
  stand-in cache evicts least recently used entries instead of clearing.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-22 04:42:00 -07:00