Commit Graph
12399 Commits
Author SHA1 Message Date
NeilandClaude 54acda1d12 Editor buffers ready for sharing: autosave per record, separate models for detached tabs (no behaviour change) (#31970)
* 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>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-22 03:41:20 -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 54b5dbe2cc refactor(renderer): colocate Linear issue empty-state policy (#31960) 2026-10-22 03:34:40 -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 d8aeb1f984 fix(claude-accounts): restart WSL Claude panes after an account switch (#31945)
* fix(claude-accounts): restart WSL Claude panes on account switch

A Windows host cannot see processes inside WSL, so the foreground gate dropped
every WSL pane from the restart set. WSL panes now use the guest's hook status
and shell marks, the evidence the execution host owns.

Reported-by: Kooooojun

* fix(claude-accounts): resume WSL Claude panes whose project runs in WSL

The account-restart resume builder declined every WSL project runtime, so a
WSL worktree or a WSL Windows runtime default left no Claude candidate to
restart. Keep that guard for Codex only; a WSL Claude launch reads the
guest's selected-account pointer.
2026-10-22 03:13:52 -07:00
NeilandClaude c8d0f99c29 fix(orcad): name a missing shared library when the pinned Node runtime can't start (#29829)
A host without libstdc++ (Alpine's default) failed with the loader's raw output. The self-test
error now names the library and how to install it, and the missing-library parser also reads
musl's loader wording, which node-pty's diagnosis uses too.

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-22 02:59:05 -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 d33b170a5a refactor(ssh): move the reconnect ladder and session channel opening out of SshConnection (#31923)
Move-only: the backoff timer, ladder and wake/network-return redial move into
SshConnectionReconnector, and exec/shell/sftp/system-command channel opening into
ssh-connection-session-channels, both reading the connection through host getters.
2026-10-22 02:22:59 -07: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 4c80dac0d8 refactor(tasks): colocate GitLab task-page loading (#31907) 2026-10-22 01:56:09 -07:00
Neil dc315a4e5a refactor(ssh): remove the dead SSH filesystem provider and desktop SSH file watcher (#31922)
* refactor(ssh): remove the dead SSH filesystem provider and desktop SSH file watcher

Nothing in production has registered an SSH filesystem provider since the
relay removal (#27214/#27215), so every getSshFilesystemProvider read
returned undefined. Delete the provider table and the remote watcher
subsystem, and collapse the call sites to that always-undefined behaviour:
fs:watchWorktree/fs:unwatchWorktree ignore SSH roots, the worktree base
watcher only resolves local repos, and worktree removal skips the desktop
watcher for SSH paths.

* test: drop the nested SSH watcher catalog case that can no longer occur

The base-directory watcher never carries a connection id now, so the
host-blind-event guard it exercised is gone. Also prune the deleted remote
watcher suites from the shard timings and the nested-SSH wording from the
worktree catalog reliability gate.
2026-10-22 01:54:29 -07:00
Neil dcf2795b1d refactor(ssh): keep the connection step host types private and justify the host key order cast (#31900) 2026-10-22 01:47:18 -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
Neil 8e7b08c617 refactor(ssh): delete the unused SFTP session late-error latch (#31909) 2026-10-22 01:30:33 -07:00
Neil f3f12a8b03 refactor(ssh): stop exporting declarations used only inside their own module (#31897) 2026-10-22 01:20:00 -07:00
Brennan Benson 6cd2550e61 refactor(native-chat): share the catch-up open runner across agents (#31622)
* 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.

* chore: remove unrelated pnpm executable metadata

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

* 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: type the history catch-up sink fixture without assertions

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

* feat(native-chat): validate expected saved items during history catch-up
2026-10-22 01:08:46 -07: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
Brennan Benson 33ec2ba9e7 fix(claude): close a cancelled approval card when the journal drains (#31735)
A Claude approval card cancelled while the chat journal was briefly full
waited for a later provider event to retry the cancellation, so an idle
session could leave the card open. The sink's drain wake now retries owed
prompt cancellations alongside owed background-task rows, and reading
still waits for the next drain if the retry fills the sink again.

Tracked in STA-7630.
2026-10-22 01:02:39 -07:00
Neil d5d9170475 refactor(managed-server): stop exporting declarations used only inside their own module (#31888) 2026-10-22 01:01:01 -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 43c32582e0 refactor(orcad): stop exporting declarations used only inside their own module (#31894) 2026-10-22 00:51:22 -07:00
Neil 66e50e22cc fix(images): avoid backtracking in data URI parameter parsing (#31882) 2026-10-22 00:24:41 -07:00
Neil 0964562786 refactor(orcad): drop the toolchain probe's unread toolchainMissing verdict (#31875) 2026-10-22 00:23:32 -07:00
Neil 7e967c8968 refactor(ssh): split host key verification, the ssh2 handshake, the system ssh probe and the credential fallback out of SshConnection (#31878) 2026-10-22 00:23:12 -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
Neil 73841a0720 fix(worktrees): pick a repo's catalog watcher transport from its resolved SSH target (#31872) 2026-10-22 00:02:14 -07:00
Neil 57f1fce9bb refactor(runtime): type worktree query stores with checked guards instead of casts (#31864) 2026-10-21 23:53:30 -07:00
Neil 70e997f4e5 refactor(runtime): resolve a watched repo's host through the canonical routing API (#31866)
* refactor(runtime): resolve a watched repo's host through the canonical routing API

* test(runtime): drop an unused mock reset
2026-10-21 23:44:49 -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
Neil 1769476014 fix(wsl-host): pair again from the distro when a WSL host refuses the enrolled token (#31745)
* fix(wsl-host): pair again from the distro when a WSL host refuses the enrolled token

A WSL host that lost its device registry, or revoked this desktop, refused the
enrolled token forever, and an update over it read as deferred. The connect now
re-pairs through the managed repair gate SSH uses (one repair at a time, a
capped number per window, only an offer under the pinned host key), reading the
host's published offer from the distro. The incumbent's terminal count uses the
same enrolled-or-live pairing the connect does.

* fix(wsl-host): retire a bridge whose pairing was refused, and let a user's Connect retry a repair

A refused or repaired-then-refused pairing on a reused bridge left its stale
token cached, and the failure status then saved it over the repair. That bridge
now closes first. A user-initiated Connect clears a standing repair failure or a
spent cap, as an explicit SSH connect does.

* fix(wsl-host): retire a refused bridge without cancelling a queued Update or Force

* fix(wsl-host): repair only a stored pairing, dispose the repair's transport directly, and log refusals neutrally

A host still being added has no stored record for the gate to re-pair, so its
live bridge is reused as before. The WSL repair deps are typed with the WSL
transport, and the shared refusal log no longer says SSH for WSL.
2026-10-21 23:15:59 -07:00
NeilandClaude 57ecdc2ca2 fix(ssh): say what plain SSH is for beside its button, and polish the plain SSH window (#31732)
* fix(ssh): point unclearable move conflicts at plain SSH, and polish the plain SSH window

Conflicts retrying can't clear now say how to keep working on the host. The
plain SSH terminal paints its theme background past the grid, and a failed
open shows only its error, not an empty terminal.

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

* fix(ssh): say what plain SSH is for beside its button, not in main's refusal text

Main's refusal text also reaches clients without a plain SSH terminal, so the
next step is shown next to the button wherever it renders. Also: a translucent
theme isn't painted twice, and a failed open shrinks to its error.

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

* fix(ssh): treat only opacity below 1 as a translucent plain SSH background

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-21 23:06:39 -07:00
Neil 9b060ebec0 fix(i18n): preserve runtime entries after removing untracked settings (#31852) 2026-10-21 23:01:10 -07:00
Neil 6698df3277 Polish explorer search layout and collapsible filters (#31825)
* Polish explorer search layout and filter disclosure

* Use compact search details disclosure and lowercase filter labels

* fix: preserve query focus when switching explorer modes

* fix: announce active explorer search filter counts
2026-10-21 22:26:05 -07:00
Jinjing 8b6f31c68b Stabilize pending admission refusal option retry test (#31840) 2026-10-21 22:24:05 -07: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
Jinjing aa5594d536 fix(native-chat): handle inline image preview edge cases (#31742)
* fix(native-chat): handle inline image preview edge cases

* Improve native chat image preview resilience
2026-10-21 22:15:33 -07:00
Neil 70ab09ca2f refactor(settings): group repository identity modules (#31830) 2026-10-21 21:54:24 -07:00
eba0124de9 [mem] Backpressure worker subprocess output without dropping bytes (#30698)
* fix(memory): backpressure worker subprocess output without dropping bytes

* style(wsl): clear formatting gates without increasing line limits

* test: cover Codex deadlines and shutdown under worker backpressure

* test: synchronize backpressured Codex deadline fixture

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: Neil <neil@stably.ai>
2026-10-21 21:52:41 -07:00
Neil 35596d16a5 chore: replace electron-toolkit utilities with Electron APIs (#31824)
* chore: replace electron-toolkit utilities with Electron APIs

* refactor: reuse development flag within startup preflight
2026-10-21 21:52:08 -07:00
Jinjing b0778082cb fix(i18n): recover language selection after rejected catalog loads (#31822)
* fix(i18n): allow retrying rejected language catalog loads

* fix(test): return null for unhandled locale asset imports
2026-10-21 21:46:29 -07:00
Jinjing dfa8788533 fix(i18n): restore localized editor safety copy (#31259)
* fix(i18n): restore localized editor safety copy

* Refine French and Chinese file change translations
2026-10-21 21:46:10 -07:00
Neil 0a7d6bb2d5 refactor(main): group ephemeral VM lifecycle modules (#31771) 2026-10-21 21:31:21 -07:00