Commit Graph
2197 Commits
Author SHA1 Message Date
Kelvin Amoaba 6970cad0d3 feat(browser): ask before sites use camera, mic or location (#31158) 2026-10-21 14:45:15 +00:00
NeilandClaude 5dd5294294 perf(orcad): keep the server bundle ASCII so V8 stores it at half the size (#31495)
* perf(orcad): keep the server bundle ASCII so V8 stores it one byte per character

129 non-ASCII characters in regex literals (and three in a String.raw plugin
template) made V8 hold the whole ~11 MB orcad-server.js source as a two-byte
string. Write them as \uXXXX escapes and fail the build if any return.

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

* fix(orcad): keep license comments ASCII and name the module the guard trips on

esbuild gathers legal comments at the end of the bundle; escape that comment so
a dependency's license text cannot fail the ASCII guard, while licenses still
ship with the code. The guard now reports the offset and, from an unminified
rebuild, the module the character came from. A dark-switch build is covered.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-21 04:06:49 -07:00
Jinwoo Hong 2eae220b2e refactor(renderer): read workspace records through typed selectors (#31462)
* refactor(renderer): read workspace records through typed selectors

* refactor(renderer): keep only the selectors with readers; type the host owner lookup
2026-10-21 06:29:18 -04:00
Neil c149395c20 fix(remote-terminal): repaint an idle full-screen program when a reconnect can't image its pane (#31482)
* fix(remote-terminal): repaint an idle program when a reconnect can't image its pane

After an SSH reconnect the pane is repainted from the host's screen
snapshot. When the host cannot give one (too large for the replay limit,
no buffer, unanswered, overflowed), the pane only printed a loss banner and
an idle full-screen program that writes diffs kept its pre-disconnect frame
until the user resized.

The two places a remote pane declares it has no host image now ask the
program to repaint through one choke point: a one-row viewport claim and
back, which every TUI honours (Node's tty ignores a same-size SIGWINCH).
It respects a phone or another desktop holding the size, skips hidden
panes, and puts the row back even if the pane unmounts mid-nudge.

Fixes #26887

* test(e2e): idle full-screen program frame after a managed SSH outage

* test(e2e): wait for the pane before typing in the idle-frame spec

* fix(remote-terminal): pick the redraw height on the host's clamped grid

* fix(remote-terminal): wait for the host's grid before putting the redraw row back

* test: give transport mocks a connection status for the redraw gate

* test(e2e): keep the orcad idle-frame proxy typed for the e2e ratchet

* fix(remote-terminal): let a new PTY's redraw supersede a swapped-out nudge

* test: keep new transport-mock lines free of type assertions

* ci(e2e): route the managed-SSH idle-frame spec to its sources
2026-10-21 02:48:21 -07:00
Leo(오영택)andJaeyoung22 582efa7989 fix(i18n): correct Korean sleep with descendants wording (#31456)
* fix(i18n): correct Korean sleep with descendants wording

* fix(i18n): keep Korean descendant sleep label compact

---------

Co-authored-by: Jaeyoung22 <89302528+Jaeyoung22@users.noreply.github.com>
2026-10-21 01:38:06 -07:00
NeilandArthur Chang 0cba9f2e08 fix(mobile): render offline Nerd Font symbols on first terminal display (#31446)
Fixes #17168

Supersedes community PR #17220. Original contributor credit is preserved below.

Co-authored-by: Arthur Chang <arthurc0102@gmail.com>
2026-10-21 01:17:08 -07:00
Neil e2bcd6e09c Reuse Windows server native prebuilds across app version bumps (#31450)
* Measure Windows ARM qualification worker concurrency

* Reuse Windows server prebuilds across app version bumps

* Match native cache measurement to adhoc compiler settings

* Keep measured cache fix and remove temporary comparison modes
2026-10-21 01:10:17 -07:00
Neil 73930bedee Prepare server template assembly alongside qualification (#31422)
* Prepare template assembly during qualification and cache Intel downloads

* Limit early template qualification polling to fifteen seconds

* Verify Windows retains its download cache policy

* Drop Intel store restoration after measuring its overhead
2026-10-21 00:06:40 -07:00
Neil a04b2ebc33 fix(release): avoid API rate limits waiting for the mobile bundle (#31431)
* fix(release): replace mobile bundle API polling with job dependencies

* test(release): update qualification token permission expectations
2026-10-20 23:49:57 -07:00
Neil 2bf77e605b fix(ssh): draw every host list through one bounded, searchable host picker, so a 13k-host ssh config can't crash any of them (#31426)
* fix(ssh): draw every host list through one bounded, searchable host picker

A generated ~/.ssh/config can import 13k+ SSH hosts, and several surfaces drew a
row per host: Settings > General workspace directory 'Apply to', Notifications >
Machines, the sidebar and Agents Hosts submenu, the composer run-target picker,
the Add Project host selector and remote step, the skill-install machine pickers,
Settings > Servers SSH host picker, repository 'Add to another host' and the
automation host field and filter.

They now share components/host-picker: boundHostList (pinned hosts plus a ranked,
capped match list), HostCombobox for single choice (a Command in a Popover that
mounts nothing until opened, replacing Selects that mount every item), and
useBoundedHostRows with HostListSearchField and HostListOverflowNote for
checklists. A host-list-render ratchet stops new JSX from mapping straight over a
host collection.

* fix(ssh): host lists keep room for search results, menu search is keyboard-reachable

- boundHostList gives pinned hosts and other matches separate limits, and narrows
  pinned hosts by the query, so a large selection never hides what the user types.
- In a dropdown, the host search is a menu item: arrow keys reach it, focus moves
  into the input, and arrows leave it again.
- The Add Project host picker hides its setup actions while searching, so Enter
  picks the matching host.

* fix(ssh): re-picking the current host in a host picker is not a change

The Selects these pickers replaced suppressed same-value changes; the skill
install form clears its workspace and target on a machine change.

* chore(ci): let the shared ratchet runner pick up the host-list render ratchet

main now discovers every check-*-ratchet.mjs through run-ratchets.mjs, so the
separate workflow step would run it twice.

* fix(ssh): host lists rank pinned matches and keep input order; label and scroll fixes

- boundHostList ranks pinned and other hosts by the same match order before
  cutting each to its limit, then keeps input order among equal ranks, so a
  muted or checked exact match is always reachable and clicking a row never
  moves it.
- The Servers SSH host picker is named by its form label again.
- The sidebar Hosts submenu scrolls its host rows instead of running off screen.
- The composer's overflow note sits outside the listbox.
2026-10-20 23:33:36 -07:00
Neil 309d71c003 Build adhoc Mac architectures in parallel (#31415)
* Parallelize adhoc Mac architecture packaging

* Include parallel Mac jobs in release timing reports

* Cover parallel Mac publication and failure gates

* Explicitly scope archive targets to each Mac architecture
2026-10-20 22:30:47 -07:00
Brennan Benson 2187f0b6b2 ci: one list for Windows boundary tests and one step for ratchets (#30643)
* ci: one list for Windows boundary tests and one step for ratchets

The Windows boundary step's 74 test paths were hand-listed in pr.yml and
again in WINDOWS_PACKAGE_TESTS, and the copies had drifted: six files ran
in the step but could not trigger the job. The step now prints
WINDOWS_BOUNDARY_TESTS, which WINDOWS_PACKAGE_TESTS spreads.

The ten per-ratchet preflight steps and the eight lint entries become one
run-ratchets.mjs that runs every check-*-ratchet(s).mjs it finds.

* fix(ci): preserve existing local lint ratchet scope
2026-10-20 22:13:36 -07:00
Brennan Benson 818a25d430 test(vitest): derive the Node SQLite test list from imports (#30647)
* test(vitest): derive the Node SQLite test list from imports instead of a hand-kept list

The two SQLite list files held ~511 test paths that every PR adding a SQLite test had to edit,
which caused frequent merge conflicts. The vitest config now finds tests that directly import a
SQLite module or harness when it loads, using the classifier the boundary test already had, with
a modification-time cache so unchanged files skip parsing. The 42 tests that reach SQLite only
through a shared helper or a dynamic import stay listed in the Node runtime list.

* test(vitest): skip unused SQLite derivation and reject malformed cache entries

* test(vitest): use typed test convention for cache contract
2026-10-20 22:01:13 -07:00
Neil 6964f819b9 Reuse qualified templates and batched signing for production releases (#31397) 2026-10-20 21:55:01 -07:00
Neil 411b5c024a Batch macOS signing calls for adhoc releases (#31340) 2026-10-20 20:16:08 -07:00
Neil 71cf31bf69 Overlap native qualification setup with the mobile bundle build (#31336)
* Prepare server native lanes alongside the shared mobile bundle

* Expect read-only Actions access in template callers
2026-10-20 19:57:29 -07:00
Neil fece03b5ac fix(orcad): voice dictation on headless orca serve hosts (#31217)
* fix(orcad): voice dictation on headless orca serve hosts

Mobile dictation against an orca serve host answered speech_unavailable
since serve moved to orcad: orcad never installed the speech service
factories, and the model manager and download imported Electron.

The model download now takes a host request opener (Chromium net on the
desktop, Node https on orcad, with redirects mapped to the same event),
the STT worker ships in the orcad slot, and orca serve hands orcad the
installed app's on-device recognizer. A host without a recognizer (an
SSH or WSL managed slot) lists only cloud models and sends a phone that
picked an on-device model to the setup sheet. Mobile names the fix when
an older server still has no speech at all.

The parity test now requires both roots to install speech.

Reported-by: delorean2020 (@delorean2020)

* fix(orcad): retry Node download drops, stage the recognizer on Windows, review fixes

- Node https errors (ECONNRESET, ETIMEDOUT, ...) are retryable, so an
  interrupted orcad model download resumes (Codex P2).
- On Windows orca serve hands orcad a per-version copy of the recognizer
  in the profile, so a loaded addon never locks the app's files against
  an update (Opus).
- Drop a recognizer candidate path that could never match; one SAFETY
  helper per test file instead of repeated casts; comments.

* fix(orcad): cloud speech is ready only with a key this host can decrypt; prune superseded recognizer copies

Codex round 2: a desktop-sealed OpenAI key exists on disk but orcad's
secret store cannot open it, so the catalog reported cloud models ready
and dictation failed at the end. orcad's model manager now proves the
key decrypts. The desktop keeps its existence check (no keychain prompt).

* fix(orcad): never prune staged recognizers before the profile lock; copy linked packages as real files

Codex round 3. ref-oss check: versioned copies are cleaned only by the
process that owns them, so the pre-lock prune is dropped rather than
patched.

* fix(orcad): remove a partial recognizer copy when staging fails (Codex round 4)

* fix(speech): never hand out another store's ciphertext as the OpenAI key (Codex round 5)

* refactor(orcad): no recognizer copy on Windows; Windows orcad keeps cloud dictation only

Design check after review round 5: every remaining finding came from the
Windows copy that dodges addon file locks. Dropping it shrinks the
surface to a path lookup; Linux and macOS serve keep on-device models.

* fix(speech): name the fix when an old server refuses a mic tap; always set the recognizer env; retry ENOTFOUND (final Opus review)

* fix(managed-server): probe install completeness relative to the slot dir

CI: shipping stt-worker.js added one more absolute slot path to the
install-complete probe, pushing it further past the exec-size budget.
Testing each artifact relative to the slot (inside a subshell cd) cuts
the probe from ~12.2 KB to ~3.6 KB, so it drops off the over-budget
list instead of raising a ceiling.

* fix(mobile): pin host.platform's read to a platform type, independent of which zod copy resolves

With mobile's own dependencies installed (which CI does for any PR that
touches mobile/), the e2e program resolves two zod copies, and the
salvaged platform arm from src/shared inferred as {}. The schema now
narrows through an explicit platform guard.

* fix(mobile): keep the host.platform read line intact for its RPC mutant; narrow in a second step

Mobile verify: the recorded-RPC mutant anchors on the read line, which
the previous guard rewrote, so the mutant could not apply.

* refactor(cli): app-executable resolution in its own module; drop the platform guard now zod is aligned

launch.ts went one line past max-lines with main; resolveAppRoot,
resolveForegroundOrcaExecutable, getExecutableAppArgs and
stripElectronRunAsNode move to orca-app-executable.ts (re-exported, so
callers are unchanged). With mobile and root on one zod (#31260), the
host.platform schema no longer needs the extra narrowing step.

* refactor(cli): import app-executable helpers from their module; no re-exports from launch.ts
2026-10-20 19:54:55 -07:00
Neil a1f2094d8c fix(github): work-item cache keys a twin by the row at the request path and its canonical host (#31290)
The fetch picked a twin's row by id first, so the SSH copy's results were cached
under the local copy's key; the task-page reader keyed by the raw executionHostId,
so an SSH row carrying only connectionId read the local bucket; and an unstamped
row was keyed by the focused server while its fetch ran locally. The repo lookup
now prefers the row matching both id and path, and writer and reader both key by
getRepoExecutionHostId.
2026-10-20 19:18:18 -07:00
Neil a308277f9a fix(deps): root and mobile resolve one zod, and CI fails if they drift (#31260)
* fix(deps): root and mobile resolve one zod, and CI fails if they drift

Mobile type-checks src/shared zod schemas. With mobile on zod 4.6.5 and the
root on 4.6.7, TypeScript treated them as unrelated packages, so wrapped
schemas inferred {} (tests/e2e/helpers/phone-feed-list.ts:154) whenever a
job installed mobile deps.

* fix(deps): guard every package mobile's tsconfig remaps, not only zod

* test(ci): pin the shared-dependency version check to the static phase
2026-10-20 19:12:29 -07:00
Jinwoo HongandE2E Test 6e589bb4ba Layout-owner build: phones see main's terminal and editor rows (A2, dark) (#31276)
* wip(layout): applyDefaultTabs command in the layout model (dark)

* wip(layout): applyDefaultTabs takes the window's proposed ids; main refuses a taken one

* wip(layout): main closes or keeps an exited split pane by the window's rule (dark)

* wip(layout): a create or split sent again after its close is refused (dark)

* wip: typed test doubles

* wip(layout): main writes a worktree's default tabs in one command and starts them, window or none (dark; twin e)

* wip(layout): main writes a worktree's default tabs in one command and starts them, window or none (dark; twin e)

* wip(layout): main provisions setup and default tabs even when the agent fails to start; no window takes over (dark)

* wip(layout): pane start status in frames; a failed start keeps its pane; a terminal close stands after a failed write (dark)

* wip: lint fixes (max-lines, test throws)

* test(layout): a terminal close stands after a real failed profile write; switch off still refuses

* fix(layout): checked types in the close-failure rule and its tests

* fix(layout): a failed start keeps its pane whatever failed; default tabs start once; a tab's last pane keeps its card (dark)

* test(layout): the agent's kept pane counts as the workspace's terminal; main starts it whatever failed

* fix(layout): the switch-off create path gains no await

* fix(layout): a failed start never writes back a pane the user closed (dark)

* fix(layout): a failed start's pane goes through main's create rules, so a closed tab stays closed (dark)

* test(layout): D1a's switch-on close tests assert the intent main receives (both builds)

* test(layout): the close fixture answers main's command without naming the method

* fix(layout): a re-created pane binds as designed; the id-only ratchet reads a shared import (dark)

* fix(layout): a pane's start status may sit on a pane still bound to its old terminal (dark)

* fix(layout): a pane's start status ends when its pane binds again by any path (dark)

* test(layout): same-id restore ends a status; a replayed split brings back a closed pane by design

* test(layout): terminal reply fixtures captured from the window-owned build, and main's switch-on proofs

* wip(layout): main's terminal surface index feeds the runtime graph (dark)

* wip(layout): main's panes stand in for the window's terminal list (dark)

* test(layout): the window sends main-listed panes as mount facts (dark)

* test(layout): surface proofs use public runtime calls

* chore: lint

* feat(layout): close by handle closes in main's layout; the close-time relist goes (dark)

* feat(layout): main's binding outranks a caller's pane claim on the terminal record (dark)

* test(layout): window-list tests stay on the rollback build; switch-on mount-fact cases (dark)

* wip(layout): main's terminal index reads E1's shared projection and follows its operation ends (dark)

* refactor(layout): the main-surfaces host lives beside the session-tabs readers, keeping the graph file under its line cap (dark)

* fix(layout): main's panes skip the window-era membership fence, follow workspace routing, and list a terminal when it registers; window close only hands the graph back (dark)

* refactor(runtime): explicit-close PTY stops move unchanged to their own module, for the file's line cap

* test(layout): the cold-restore table runs in a layout-owner build too: one push per registration

* test(layout): the parked and automation pane tests assert the same window report in both builds (mount facts with the switch on)

* chore: one layout-owner switch import after the restack on D1a

* feat(layout): main's terminal.list rows carry the host's verdict; a pane with a status is never live (dark)

* chore(layout): fit main's surfaces under the lint gates after the D1a restack

* feat(layout): every pane status D1a publishes keeps a row from reading live; remote host answers pass through (dark)

* feat(layout): a drag-out moves main's own binding and refuses the home copy's last pane; the window's terminal claim is not consulted (L127, dark)

* fix(layout): a tab close by a starting pane's handle stops the tab's terminals; a starting pane is never exited on the inventory alone; terminal.list lists every saved pane main owns; the pane claim lookup leaves the output path (dark)

* fix(terminal): keep shipped exit and default-tab behaviour with the layout switch on

An exited pane closes through the legacy retirement unless the keep rule holds; the
default tab's title and color still reach the window and the phone's live tabs.

* fix(terminal): one id guard for every pane write; keep panes on an unvouched -1

Main's taken and closed-id guard now runs where every pane write goes, so a runtime create
replayed after its tab closed is refused before it spawns. An exit code nothing vouches for
keeps the pane, as the window does; a kept pane sends the phone no retirement. Converts the
switch-on tests that asserted the deleted window hand-off and title bridge.

* fix(terminal): keep the closed-tab guard on every pane write, held-id checks on client commands

A paired host starts each pane of a client's tab as a create under the tab's id, so a held tab
id is refused only for a client's own layout command.

* fix(terminal): a provider-reported -1 keeps the pane, as the window does

Only a requested stop or a host-confirmed death closes a pane on a negative exit code. Pins that
the closed-tab guard never fires in the shipped build.

* chore(terminal): satisfy the exhaustive-switch and test typing checks

* test(terminal): give the replay test's live process a cwd

* fix(layout): main's index reads routing only for a workspace two partitions hold, the fence reads ownership once, a legacy pane keeps its claim; switch-on tests assert main's answers after a window closes (dark)

* refactor(runtime): a worktree stop's terminal collection moves unchanged to its own module, for the file's line cap

* fix(layout): main's index routes every workspace as the layout stream does, re-reading routing only on a write or a terminal registration; a worktree stop names every terminal main binds; a starting pane is unverifiable (dark)

* feat(layout): a workspace main owns publishes its terminal and editor session.tabs rows from main's layout; the window still supplies browser, chat and view-only rows until D3b (A2, dark)

* chore: lint

* test: type the row fixtures

* chore(layout): main's session.tabs projection is an old-client entry, imported only by main's terminal surface index

* fix(layout): main's session.tabs rows carry the tab's name, the window's agent status and markdown file id, list rows in tab order, and keep groups and selection consistent (A2, dark)

* refactor(layout): the projection's group order moves unchanged to its own old-client module, for the file's line cap; lint

* feat(layout): every pane status main publishes keeps a row from reading live; a resuming pane is unverifiable like a starting one (dark)

* test(layout): a windowless server's layout change reaches a subscribed phone's session.tabs rows (dark)

* fix(layout): a window-only row keeps its place in the rows, the window's selected row lands in a group that selects it, and a workspace with no main group keeps the window's groups (A2, dark)

* fix(layout): a pane main has no binding for yet keeps the window's terminal in its phone row, so an SSH restore lists it pending (A2, dark; ledger L243)

* fix(session-tabs): phones show the window's live selection, focused pane, dirty state and titles over main's saved view; exited terminals stay gone; stray rows join the active group (switch-on builds only)

* fix(session-tabs): main's groups stand with main's selection; certified exits only; a selected stray row joins main's active group

---------

Co-authored-by: E2E Test <e2e@test.local>
2026-10-20 22:10:25 -04:00
Neil be0961e911 Overlap adhoc release gates and cache qualified server templates (#31300)
* perf(ci): accelerate adhoc release builds and runner scheduling

* Overlap adhoc release preparation and reuse qualified build inputs

* Bind qualified template reuse to its embedded CLI source commit

* Use the available CPUs for native compilation in fast dev builds

* Verify qualified template cache provenance and daily invalidation

* Avoid incremental MSBuild tracking for fresh fast-release builds

* Right-size Intel macOS and Windows ARM release qualification

* Route Intel qualification through the standard larger macOS label

* Overlap Windows preparation and initialize the server native compiler once

* Test server compiler invocation through its process-runner interface

* Align adhoc signing contract with its larger packaging runner

* Recognize the native cache fingerprint as batch-file metadata
2026-10-20 18:45:13 -07:00
Jinwoo Hong 4e483ae2e6 perf(serve): clone the supervisor's code copy in one step on macOS (#31303)
* perf(serve): copy the supervisor's code aside with one cp clone on macOS

orca serve --supervise copies the app's unpacked code and node_modules out
of the app before re-running itself from the copy. Node's fs.cp clones those
~6,000 files one at a time, which took 8-15 s in the foreground and around
35 s under the service manager's throttled I/O. One /bin/cp -cR per tree
does the same copy in about 1.5 s.

The clone-then-plain-copy fallback the terminal host copy already used now
lives in src/shared/macos-tree-clone.ts, and both copies call it. Linux keeps
fs.cp with reflink cloning, and Windows does not support --supervise.

Claude-Session: serve-snapshot-clone

* fix(serve): let the macOS tree copy run without a time limit and throw on failure

The shared cp helper ran under runProcess's 30 s default time limit, which the old fs.cp copy never had. At background I/O priority a large tree can outlast it, so the supervisor would quietly give up and run from the app itself. The helper now runs cp with no limit; the terminal helper copy stays bounded by its deadline signal.

The helper moves to src/main beside the asar-safe rm and uses it directly, so it no longer takes an rm option. It throws instead of returning a boolean, so copyBundle is gone. The relocation reads process.platform instead of a platform dependency, and its copy test now runs on every platform but Windows.

Claude-Session: serve-snapshot-clone

* fix(build): keep the macOS tree copy module where the packaged CLI loads it

The serve supervisor's relocation code in the CLI now imports the macOS
tree copy module from the main-process sources. The CLI is compiled file by
file, but the later main-process build replaces out/main and keeps only the
files it is told to build as entries, so the packaged CLI failed at load
with "Cannot find module '../../main/macos-tree-clone'" on every platform,
and the CLI runtime verifier rejected the build.

Register the module as a CLI main entry, like the other main modules the
CLI imports, and unpack it from the app archive. Its asar-safe removal
dependency is bundled into a shared main chunk, which is already unpacked.
The build output contract test now covers both.

Claude-Session: serve-snapshot-clone

* fix(build): unpack the asar-safe rm the CLI's tree clone imports

The compiled CLI layout keeps out/main/macos-tree-clone.js requiring
./asar-transparent-fs directly, and that file was still packed, so the
packaged CLI failed to load (caught by packaged-cli-assets in CI). The
bundled build only passed because the main build inlines it into an
unpacked chunk.

Claude-Session: serve-snapshot-clone
2026-10-20 21:42:36 -04:00
Neil 496bbaafcc fix(ci): resolve one Zod version in the E2E typecheck (#31320) 2026-10-20 18:33:01 -07:00
Jinwoo HongandE2E Test 70b2197c97 The window shows tab, title, move and divider changes only from main's update, never drawing them itself (dark, layout-owner build only) (#31257)
* The window shows each layout records frame record by record, without reading its session back (dark, layout-owner build only)

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

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

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

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

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

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

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

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

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

* A tab move shows from main's frame alone; the window no longer re-applies it or adopts main's group id (dark, layout-owner build only)

On main's answer the window only selects the moved tab in its new group, a view choice the
frame does not carry. While main's stream is down it still moves the tab itself, as the frame
sync's closed-stream rule says, but under its own group id: the reopened stream's snapshot
redraws main's copy.

The three switch-on census files for tab moves now pass without a switch mock: the context
menu's move routes to the tab bar's move, the drag hook's pane-column split goes to main in a
layout-owner build, and the tab bar's own move is pinned on a paired server's workspace. A new
frame test pins each move through main and its frame.

* A tab's rename, color, pin, view and a pane's title show from main's frame alone; the window no longer applies them again (dark, layout-owner build only)

* A divider drag sends main the ratios it moved, not the window's tree, and an in-tab pane move shows from main's frame; the ratio hold is gone (dark, layout-owner build only)

* Divider ratio params and their command tests get their own modules (file size)

* Layout-writer baseline pruned: the tab move no longer writes the layout from the window

* The window never draws a tab prop, pane title or in-tab move itself; while main's stream is down the reconnect's snapshot shows them (dark, layout-owner build only)

* View-mode test seeds its paired-server workspace without a type assertion

* A moved tab is selected in the frame flush that shows the move, unless the user selected since; main's answer no longer selects it (L120, dark, layout-owner build only)

* Frame sync stays within its line budget

* Frame sync writes its patches through the moved-tab selection, within its line budget

* The moved tab's selection reuses activateTab's own store update, so no new layout writer is added (L120)

* Format the tab-move frame test

* A tab moved while main's stream is down is selected by the reconnect's snapshot that shows the move (L120)

* A generated tab title main gave is drawn by its frame alone; the window writes only titles left to it as today, and the rename test checks the phone graph republishes (dark, layout-owner build only)

* A divider ratio names the split it means, and main refuses it where that path holds another, so the window snaps back to main's ratios; a drag back to main's ratio before it answers is sent too (dark, layout-owner build only)

* test(layout-owner): tab moves drawn from frames make no window tab or group writes

* test(layout-owner): a reopened tab shows its title and color from main's frame

* test(layout-owner): the tab bar's preview pin through main ends the preview and counts a use

* test(layout-owner): read main's generated-title renames without a type assertion

* test(layout-owner): type the sent renames so the title helper reads them without an assertion

* The frame-apply check selects the moved tabs the flush selected, so the tab-move tests exit clean and a wrong selection still fails (dark, layout-owner build only)

* test(layout-owner): the title batch's live labels are checked in both builds; only the generated title differs

* Main keeps a tab's first agent-generated title unless the window's title replaces it, so a second prompt while main's stream is down no longer renames the tab (dark, layout-owner build only)

* test(layout-owner): a revealed terminal's tab name is drawn from main's frame, not its reply (dark, layout-owner build only)

---------

Co-authored-by: E2E Test <e2e@test.local>
2026-10-20 20:58:51 -04:00
Neil c35ff3559d perf(ci): accelerate adhoc release builds and runner scheduling (#31297) 2026-10-20 17:51:19 -07:00
NeilandClaude d6bc6cb3b3 fix(process-host): let a slow Windows taskkill finish instead of orphaning the grandchild (#29501)
* fix(process-host): let a slow Windows taskkill finish instead of orphaning the grandchild

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

* fix(process-host): abandon taskkill shortly after the root exits so it cannot walk a reissued pid

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

* test(process-host): log the taskkill outcome and caller settle time under load

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

* ci: register the late-taskkill Windows test in the package-windows path list

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

* fix(process-host): release the git admission slot on root close again; cap the background taskkill at 30 s

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-20 17:30:38 -07:00
Brennan Benson 29e55047b5 build(cli): typecheck the CLI by following its imports, not a hand list (#30632)
tsconfig.cli.json listed 284 src/main files because composite made any
unlisted import fail the parallel typecheck with TS6307. Nothing references
it as a project, so drop composite and keep only the CLI's roots plus the
backup worker entry, which is loaded by path. The Electron import ratchet now
also walks the CLI entry, replacing the fence the list implied.
2026-10-20 17:16:00 -07:00
Brennan Benson d1aeba939f Native chat: remove unused journalCheckpoint and setConversationCommand (#30645)
* refactor(native-chat): remove three chat mechanisms nothing drives

- The adapter's optional startAnswered hook lost its only implementer in
  #27925; it was still forwarded through four layers and always read as
  answered. A start that never answered is read from the child's phase, as
  the exit already does.
- The saved lease's journalCheckpoint is only ever written as null and read
  only by validation. Records an older build saved with it still load: it
  joins the retired lease fields dropped at load, so the next write sheds it.
- The record store's setConversationCommand had no production caller. The
  tests that stood in for an older build with it now seed the saved row and
  reopen the store; three that only re-pinned another test are removed.

* test(native-chat): an older build's null journal checkpoint also loads

* Keep startAnswered cleanup in its owning PR

* Keep historical chat fixtures aligned with their readers
2026-10-20 17:12:41 -07:00
Neil 49e6107ebe ci: stop running WSL host tests for UI-only changes (#31243) 2026-10-20 15:32:46 -07:00
Neil 1a11050eba docs: remove redundant max-lines agent instructions [skip ci] 2026-10-20 15:27:14 -07:00
Neil 32343cc703 test(skills): synchronize Windows rename contention (#31237) 2026-10-20 15:15:59 -07:00
Neil e02e8f5bcb fix(chat): preserve Claude rewind context after rollback (#31231)
* fix(chat): preserve Claude rewind context after rollback

Fixes STA-11420.

* fix(lint): merge duplicate journal-types import

* fix(chat): skip torn Claude transcript lines when materializing a rewind fork
2026-10-20 14:55:36 -07:00
Jinwoo Hong 695ac882a9 fix(editor): a file from a server no longer reloads once after each restart (#31196)
* fix(editor): name a server's file by its owned id from the first open, so it no longer reloads after a restart

* refactor(mirror): a server file's mirror id no longer needs the bare-path collision lookup

* fix(editor): conflict review reuses an open server file under its owned id instead of opening a second copy

* fix(editor): conflict review overview shows an open server file's unsaved edits under its owned id

* fix(editor): route conflict files by their workspace owner only; keep the overview lookup a plain read

* test(mirror): the browser-placement replay test names its server file by the owned id
2026-10-20 17:50:25 -04:00
Neil 5b861bf4b6 perf(ci): reuse verified Windows builds and trim release history transfer (#31227) 2026-10-20 14:47:02 -07:00
NeilandClaude 5f40d0c88b fix: apply review feedback for the watcher, guarded-save and chat-start stacks (#31216)
Fixes bugs found reviewing #30270, #30605, #30885, #30417, #31113, #30415,
#30992 and #30997, and removes code those PRs made unnecessary.

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-20 14:07:27 -07:00
Neil 12bf32836b feat(ci): ratchet raw repo connectionId host checks; migrate the cheap ones (#31096)
* feat(ci): ratchet raw repo connectionId host reads; one drop-in resolver

Codex review 6 class fix (#4/#6/#7): host decisions read off a repo row's
legacy connectionId miss rows that name SSH only as executionHostId.

- getRepoRoutingConnectionId (shared/execution-host.ts): drop-in for a raw
  repo.connectionId routing check that also finds executionHostId-only SSH.
- check-repo-host-read-ratchet: per-file count of raw repo connectionId
  reads in src/main, src/shared, src/cli; counts may only shrink.
- Migrated 66 cheap reads: runtime GitHub/GitLab commands, PR/MR head ref
  fetches, preserved-branch cleanup, ref queries, base-ref IPC, worktree
  preparation, trash sweep, scan cache TTL, usage metadata.

* fix(ipc): PR base IPC routes executionHostId-only SSH rows; ratchet is a stated lower bound

Review follow-up (Opus on #31096):
- worktrees:resolvePrBase decided git/gh routing on the raw connectionId while
  its head-ref fetches already used the resolver; one resolved connectionId
  now drives all of it (test fails on main).
- getRepoRoutingConnectionId's contract is stated exactly: never local when the
  raw field is set; on a contradictory ssh row executionHostId wins.
- The ratchet regex no longer matches report.connectionId; header and docs
  say it is a lower bound.
- Regression tests for a runtime GitHub and GitLab command site.

* test(ci): pin the repo host-read ratchet step to the static phase

* fix(ci): repo host-read ratchet skips verbatim field copies into projected rows

A copy of connectionId into a projected row decides nothing; migrating it to
the resolver would change what the projection publishes. The sidebar feed
catalog's copy (added after the baseline) and two older projections no longer
count; baseline pruned to 91.
2026-10-20 14:00:32 -07:00
Brennan Benson de849c1416 Rewind Claude chats by forking the kept conversation (#30400)
* fix(native-chat): rewind works at any chat length

Rewind copied the kept part of the chat into its saved record and, for
Codex, re-read and re-translated the whole provider history before and
after the revert. Both were bounded (10,000 rows / 2 MB host, 1,024
entries / 2 MB Codex), so long chats were refused with "too long to go
back in".

The record now names the cut; the kept rows are derived from the
journal, which keeps the old epoch until the rewind completes. Codex
reads only the turns it undoes and trusts thread/revert; recovery
proves an applied revert from the newest turn alone and never
truncates otherwise.

* feat(native-chat): Claude chats launch a rewind's fork of the kept conversation

A rewind record can name the provider session the chat continues in (providerFork). Claude's next
launch forks it with the SDK's own truncating resume at the rewound prompt's parent, and publishes a
forked link once the provider has created it. Removes the transcript proof branch that could never
pass on the real CLI.

* test(native-chat): pin Claude's rewind fork against the real CLI

* feat(native-chat): rewind Claude chats

Claude declares rewind. The adapter answers where a rewind forks (the rewound prompt's parent in
its own transcript); the host saves that fork with the prepared record, stops the agent, and
rebuilds the journal from the rows before the message. Recovery completes a prepared fork, since
nothing outside Orca can have half-happened, and the next launch makes the fork.

* test(native-chat): keep the Claude rewind tests to the flows they pin

* test(codex): type the rewind fixture's page params instead of asserting them

* ci: run the PR checks against main

* fix(native-chat): rewind recovery reads the provider handle only from a record that exists

* fix(native-chat): import the session record type once

* test: run the rewind cut test in the SQLite runtime project

* test(native-chat): run the fork rewind test on SQLite, and give Claude's test double its fork

* fix(native-chat): omit rejected submissions from rewound history

* fix(native-chat): rewind forked chats without restarting the agent

* fix(native-chat): Claude rewinds also leave out messages that never ran

* fix(native-chat): Claude rewind to a mid-turn message drops the rest of that turn

A message sent while Claude is answering joins the running turn. Rewinding to it
forks Claude's conversation at that message, so the chat now drops the rest of
that turn too instead of keeping replies Claude no longer has.

A rewound chat whose kept conversation lives only in another Claude account now
gets the same "history is in another account" refusal as a resume, instead of
Claude's raw start failure on every launch.

* fix(native-chat): rewind only to messages that start a turn

A message sent while the agent is still answering joins that turn. Claude's CLI
holds it until a break and records it after the reply; Orca's chat records it
when it was sent. Rewinding to it therefore cut the chat and Claude's
conversation at different places: the previous fix dropped reply text Claude
still remembered, and the version before kept the message itself. For Codex the
cut started at the turn's own first message, removing a message the user did
not pick.

Rewind now returns only to messages that opened their own turn, the common
pattern: the chat stops offering it on a message sent mid-reply, and the host
refuses one, so a mid-reply message and its turn are always kept or dropped
together. The Claude cut goes back to keeping a kept turn's late rows.

* fix(native-chat): rewind targets follow the turn record's opener; saved rewinds still complete

A message opened its turn when the turn record names it, not only when it sits
at thread level: restored history scopes its openers to their turns, so those
chats lost Rewind.

The opener check now runs only when a new rewind is admitted. Recovery cuts
wherever an already saved rewind points, so one saved by an earlier build
still completes instead of blocking the chat.

* fix(native-chat): recovery's read-ahead reads turns only from a Codex rewind cut
2026-10-20 12:58:21 -07:00
Jinwoo HongandE2E Test 997d34b043 Layout groundwork (switched off): main creates, starts and closes terminal tabs and panes itself (#31130)
* wip(layout): applyDefaultTabs command in the layout model (dark)

* wip(layout): applyDefaultTabs takes the window's proposed ids; main refuses a taken one

* wip(layout): main closes or keeps an exited split pane by the window's rule (dark)

* wip(layout): a create or split sent again after its close is refused (dark)

* wip: typed test doubles

* wip(layout): main writes a worktree's default tabs in one command and starts them, window or none (dark; twin e)

* wip(layout): main writes a worktree's default tabs in one command and starts them, window or none (dark; twin e)

* wip(layout): main provisions setup and default tabs even when the agent fails to start; no window takes over (dark)

* wip(layout): pane start status in frames; a failed start keeps its pane; a terminal close stands after a failed write (dark)

* wip: lint fixes (max-lines, test throws)

* test(layout): a terminal close stands after a real failed profile write; switch off still refuses

* fix(layout): checked types in the close-failure rule and its tests

* fix(layout): a failed start keeps its pane whatever failed; default tabs start once; a tab's last pane keeps its card (dark)

* test(layout): the agent's kept pane counts as the workspace's terminal; main starts it whatever failed

* fix(layout): the switch-off create path gains no await

* fix(layout): a failed start never writes back a pane the user closed (dark)

* fix(layout): a failed start's pane goes through main's create rules, so a closed tab stays closed (dark)

* test(layout): D1a's switch-on close tests assert the intent main receives (both builds)

* test(layout): the close fixture answers main's command without naming the method

* fix(layout): a re-created pane binds as designed; the id-only ratchet reads a shared import (dark)

* fix(layout): a pane's start status may sit on a pane still bound to its old terminal (dark)

* fix(layout): a pane's start status ends when its pane binds again by any path (dark)

* test(layout): same-id restore ends a status; a replayed split brings back a closed pane by design

* fix(terminal): keep shipped exit and default-tab behaviour with the layout switch on

An exited pane closes through the legacy retirement unless the keep rule holds; the
default tab's title and color still reach the window and the phone's live tabs.

* fix(terminal): one id guard for every pane write; keep panes on an unvouched -1

Main's taken and closed-id guard now runs where every pane write goes, so a runtime create
replayed after its tab closed is refused before it spawns. An exit code nothing vouches for
keeps the pane, as the window does; a kept pane sends the phone no retirement. Converts the
switch-on tests that asserted the deleted window hand-off and title bridge.

* fix(terminal): keep the closed-tab guard on every pane write, held-id checks on client commands

A paired host starts each pane of a client's tab as a create under the tab's id, so a held tab
id is refused only for a client's own layout command.

* fix(terminal): a provider-reported -1 keeps the pane, as the window does

Only a requested stop or a host-confirmed death closes a pane on a negative exit code. Pins that
the closed-tab guard never fires in the shipped build.

* chore(terminal): satisfy the exhaustive-switch and test typing checks

* test(terminal): give the replay test's live process a cwd

---------

Co-authored-by: E2E Test <e2e@test.local>
2026-10-20 15:01:25 -04:00
Jinwoo HongandE2E Test 893c4e24a4 fix(ssh): an old SSH host moves to its managed server while a relay shell named only on its tab still runs (#31093)
* fix(ssh): an old SSH host converts while a relay shell named only on its tab still runs

The conversion check counted legacy pane-key aliases as terminals waiting to
reconnect. An alias only routes hook events to a pane whose terminal the lease
check, the bridge or the terminal gate already accounts for, so it no longer
blocks the move.

* Tighten the alias census comment

* Register the tab-only relay spec: typed deployment lookup, relay fence baseline, PR route

---------

Co-authored-by: E2E Test <e2e@test.local>
2026-10-20 14:18:04 -04:00
Jinwoo HongandE2E Test 98e7dbecfe The window applies each layout update record by record instead of rebuilding from its whole session (dark, layout-owner build only) (#31152)
* The window shows each layout records frame record by record, without reading its session back (dark, layout-owner build only)

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

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

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

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

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

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

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

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

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

* Name the pane tree the window compares by what it holds: the tree without its panes' bindings or live titles (dark, layout-owner build only)

---------

Co-authored-by: E2E Test <e2e@test.local>
2026-10-20 11:28:45 -04:00
Jinwoo HongandE2E Test cf94325fb5 fix(layout): main shows live terminals no pane shows and derives what the next startup reconnects (dark, layout-owner build only) (#31118)
Main runs one pass at startup and each window load, with or without a window
(desktop, orca serve, orcad): every live local terminal no pane names is bound
to the pane its record names while main still holds it free, else shown in a new
tab bound to it. The window no longer asks main to adopt terminals; it only reads
that main holds them. The window's layout.apply loses its adopt field.

Main also derives activeWorktreeIdsOnShutdown, remoteSessionIdsByTabId and
activeConnectionIdsAtShutdown from its own layout, terminal records and SSH
connections, replacing the window's copies in every save and the quit checkpoint.

Co-authored-by: E2E Test <e2e@test.local>
2026-10-20 09:00:10 -04:00
Jinwoo HongandE2E Test 03e9a03e9e feat(layout-owner): main opens CLI and phone files itself, in the window's live group (dark) (#31123)
Behind ORCA_LAYOUT_OWNER_IS_MAIN, a CLI or phone file open is applied by main as an
openEditorTab layout command, so a host with no window can open files. The window
reports each workspace's active group as an optional view fact on syncWindowGraph,
and shows main's tab once the frame that draws it arrives. Editor language
detection moves to shared so main can name a file's language.

Co-authored-by: E2E Test <e2e@test.local>
2026-10-20 08:48:53 -04:00
Neil cf6582caaa fix(renderer): a twin project refuses instead of following the Active Server focus, and a no-row listing is this app's (#31119)
* fix(renderer): a twin repo id refuses instead of following the Active Server focus, and a listing for a repo with no row is this app's

findRepoOwner no longer lets focus pick between same-id repo rows: runtimeTargetForRepoOwner refuses a
twin, as resolveOwner does, and getRuntimeEnvironmentIdForRepo names no server for one. repoHostId
stamps and routes a repo with no row as this app (or a web client's only server), never the focused
server.

* fix(renderer): a fork names its source's host, and GitHub routing keeps its twin rule until it can refuse

* fix(renderer): a fork captures its source's server transport, and a GitHub edit on a twin refuses instead of using this computer

* fix(automations): a new-per-run workspace is created on the run's captured host

* fix(automations): pass only a captured run host, so a twin without one refuses

* test: stamp the server repo row through a store helper, prune the focus-read baseline
2026-10-20 05:38:41 -07:00
Jinwoo HongandE2E Test 444064105c build(orcad): a dark test template opt-in, marked so packaging refuses it (#31097)
build-orcad-template.mjs takes --dark-test-template to let ORCA_LAYOUT_OWNER=main
through for test servers; the template and out/orcad carry the switch marker, and
packaging refuses a marked template. The e2e template steps build a switch-on server
when the workflow runs with layout_owner=main.

Co-authored-by: E2E Test <e2e@test.local>
2026-10-20 07:22:34 -04:00
Jinwoo HongandE2E Test 4e998861e1 test: shared switch-on fixtures so 71 layout tests pass with main owning the layout (#31076)
* test: shared switch-on fixtures so 71 layout-owner test files run with the switch on

- layoutApplyTestTransport answers layout.apply (accept, apply to main's model with its frame, pending, refuse)
- main's loaded layout as snapshot frames, shared from the frame-sync test
- tab-layout maps for the editor store harnesses; missing store methods in the main harnesses
- workspaceRepos/registerWorkspaceRepos so main's load keeps seeded layouts
- ORCA_LAYOUT_OWNER=main runs vitest with the switch on
- the 4 rollback-build tests stay switch-off, labelled

* test: keep switch-off assertions exact, settle only after layout commands, reset the transport per test

* test: parse the transport's command without Reflect.get; name the staged-session type

* test: pin the test transport in the layout.apply census; route the autosave fixture as unit support

---------

Co-authored-by: E2E Test <e2e@test.local>
2026-10-20 06:50:11 -04:00
Neil 917c577433 fix(renderer): no owner route reads the Active Server focus, so a local row never routes to the focused server (#31058)
* fix(renderer): no route resolver reads the Active Server focus, so a local row never routes to the focused server

The worktree route's single-focused-runtime branch, the folder owner's focus fallback and the
worktree host default's focus fallback are gone. The client stamps every server row on arrival, so
an unstamped row is this machine's: terminal spawn and teardown, browser tabs, quick-open, chat and
path links now agree on that. resolveOwnerWithLegacyFallback folds into resolveOwner, and the
focus-free write route becomes the one operation route. Setup terminals keep their explicit
focused-runtime choice.

* fix(renderer): setup terminals keep their focused-runtime host, unstamped repos stay local for the composer, and fixtures stamp server rows

* chore: merge duplicate route imports, drop new fixture casts, prune focus-read baseline
2026-10-20 03:04:03 -07:00
Neil c8962bd529 perf(build): check CLI with Bun before TypeScript emission (#31043) 2026-10-20 01:51:32 -07:00
Neil 8802b55db7 fix(orcad): ship node-pty's winpty backend so Windows 1809 / Server 2019 hosts can deploy (#31056)
* fix(orcad): ship node-pty's winpty backend so Windows 1809 hosts can open terminals

node-pty selects winpty below Windows build 18309, but the Windows server
slot carried only the ConPTY addon, so the candidate's PTY self-test failed
on Windows 10 1809 / Server 2019 and the host was recorded as unsupported.

Reported by @lksr202 in #30906.

* test(orcad): smoke the winpty backend in the Windows prebuild slot
2026-10-20 01:43:46 -07:00
Neil e534527407 perf(checks): use Bun for end-to-end typechecking (#31046) 2026-10-20 01:00:32 -07:00
Neil cf6bbd30da perf(release): overlap Windows builds and gate signing on Mac success (#31032) 2026-10-20 00:21:25 -07:00