Commit Graph
1573 Commits
Author SHA1 Message Date
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 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 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
Neil 5b861bf4b6 perf(ci): reuse verified Windows builds and trim release history transfer (#31227) 2026-10-20 14:47:02 -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 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
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 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
509f19867c [mem] Stream saved transcripts and stage large search writes (#30702)
* fix(memory): stream transcript documents and spill anonymous index writes

* fix(memory): declare read-local search staging in WSL storage inventory

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-10-19 23:58:33 -07:00
Neil 16fa2a844f fix(release): keep Mac qualification runs out of the release build's concurrency group (#31031) 2026-10-19 23:30:17 -07:00
Neil ba6599eabb fix(ssh): pair again over SSH when a managed server drops this desktop, instead of reconnecting forever (#30775)
* fix(ssh): say the host dropped this desktop's pairing, not that another Orca holds the port

* fix(ssh): pair again over SSH when our own managed server refuses this desktop's token

* fix(ssh): key re-pairs by the refused revision, cap attempts, retry on explicit connect

* fix(ssh): keep a newer pairing stored mid-repair, count only completed reads, and offer Connect, not an access link, for managed servers

* fix(ssh): judge a re-pair only against the pairing still stored; Check connection retries a managed server's pairing

* fix(ssh): a pane whose managed server refuses this desktop says why and offers Reconnect, instead of spinning

* test(e2e): live failure path for a refused managed pairing

* fix(ssh): a Connect that re-paired answers from the new pairing, not the retired one

* fix(ssh): only an explicit Connect lifts a managed re-pair's hold and cap; say SSH access can pair again

Startup restore reaches ssh:connect with automatic: true and went through the same
ensureTunnel that cleared the hold, so repeated automatic reconnects could bypass the
three-attempt cap. The reset now lives in connectTarget's explicit branch.

The Shared Server Access footnote now says revoking there doesn't stop someone with
SSH access to this computer from pairing again.

* ci(e2e): route the managed pairing-repair spec with the managed-host remote sources

* fix(ssh): the repair gate decides whether a managed tunnel builds, not the build

A refused shared-control open rebuilt the tunnel (SSH execs, forward, handshake, wake)
before the held failure or the attempt cap was consulted, so reopens on a blocked
server rebuilt without limit. buildWithPairingRepair now asks the gate first: a failure
stands for the refused pairing revision for 15 s, or through the window once the cap is
spent, and every caller gets it without reaching the host.

- A connect during an in-flight repair keeps its reset (per-environment reset epoch).
- An unpaired answer fails at once; it is not a moved port, so no re-read or wake.
- A refusal that only rebuilt the tunnel no longer reports "pairing changed".
- WSL and cloud VMs no longer say Orca tried to pair over SSH, and Check connection
  only re-pairs servers managed over SSH (runtimePairingRepairRoute).
- Translate the new strings; route the pane-banner files to the managed-SSH e2e lane.

* fix(ssh): fence a status re-pair follow-up to the same server, and hold a refused repair under its own revision

- getRuntimeEnvironmentStatus followed any pairing change mid-probe, past a caller's
  expectedEnvironmentPairingRevision fence. It now follows only for an unpinned caller and only
  a new pairing of the same SSH-managed server under its pinned host key.
- recordRefusedAfterRepair stood under whatever revision was stored now, so a newer pairing
  stored mid-refusal was blocked untried; it stands under the repaired revision.
- The WSL restart test expects the refused-pairing classification main's WSL path now reaches.

* fix(ssh): a re-pair hands its callers the new credentials, and a failed one judges only the pairing it tried

- ensureOrcadManagedTunnel returns the environment as stored once the tunnel stands. The
  terminal census and a deploy's already-current result use it, so after a re-pair they
  no longer authenticate with the refused token, which read as an unverifiable census and
  refused restarts, updates and stops.
- The shared-control refusal handler blocks the status owner only while the failure stands
  for the pairing stored now (managedOrcadPairingFailureStands). A newer pairing stored
  meanwhile keeps its owner and retries.

* fix(ssh): migrations commit with the pairing the tunnel stands on, and a Connect mid-handshake stays reset

- Conversion binds the environment ensureOrcadManagedTunnel returns, so the destination, the
  server project reads and the result use the re-paired credentials (the migration mark is kept).
- A delta move gets its destination only from connectDestination, which opens the tunnel and
  builds the destination from what it returns; a pre-ensure destination can no longer exist. A
  tunnel that cannot open keeps the delta journaled and the host marked for the next move.
- buildWithPairingRepair takes the reset epoch when it starts, so a refusal after the re-pair
  cannot reinstate a failure that a Connect pressed during the handshake cleared.

* refactor(ssh): one module owns a shared control socket's environment policy, keeping request-connections under its line limit
2026-10-19 22:45:25 -07:00
Neil e278bc9a17 feat(orcad-migration): unsaved drafts stay on this desktop and are renamed to the server on commit (#30894)
* feat(orcad-migration): unsaved drafts stay on this desktop and are renamed to the server on commit

* fix(orcad-migration): refuse clashing drafts from the rename's own collection; stricter live spec

* fix(orcad-migration): stamp renamed drafts with the server, rename at startup after a cut-off commit, keep draft refusals

* fix(orcad-migration): retain only after a startup rename is durable; startup survives an unreadable journal

* fix(orcad-migration): a cut-off commit is renamed and retained at startup, so the rename runs once

* fix(orcad-migration): one retain per migration at a time; no rollback that could drop newer edits

* test(e2e): check the visible draft in whichever editor a large file opens in

* fix(orcad-migration): startup retains only migrations of hosts this profile owns

* ci(e2e): route the editor-drafts migration spec to the sources it covers
2026-10-19 22:44:43 -07:00
Neil 937d2d3806 Share installed desktop and browser assets (#31010)
* Share installed browser assets with the desktop renderer

* Update packaged runtime fixtures for shared renderer assets

* Verify retained cloud clients in the shared renderer layout
2026-10-19 22:31:59 -07:00
Neil 84a4526508 Overlap macOS notarization with serial architecture staging (#31006) 2026-10-19 22:31:50 -07:00
Neil 1466deae45 Share identical packaged ripgrep binaries across server targets (#31005) 2026-10-19 22:16:37 -07:00
Neil acc6354363 Shrink feature tour animations without changing pixels or timing (#31012)
* Shrink feature tour animations without changing pixels or timing

* Update README media links for lossless animation assets
2026-10-19 22:11:56 -07:00
Neil 09fd0bec5a Exclude cloud service sources from desktop packages (#31004) 2026-10-19 22:03:36 -07:00
Neil 67eb94a918 perf(release): overlap server checks and share the mobile bundle (#30990)
* perf(release): overlap server qualification and reuse mobile bundle

* fix(ci): preserve draft suppression for independent compatibility checks

* test(release): update template download cache policy contract
2026-10-19 21:29:11 -07:00