Skip to content

Sync upstream/main to v1.4.229's cut point, and ship a Windows installer - #2

Merged
damianjarosz-erli merged 4226 commits into
erli/linux-releasefrom
sync/upstream-1.4.229
Oct 23, 2026
Merged

damianjarosz-erli merged 4226 commits into
erli/linux-releasefrom
sync/upstream-1.4.229

Conversation

@damianjarosz-erli

@damianjarosz-erli damianjarosz-erli commented Oct 23, 2026 •

Copy link
Copy Markdown
Files Added Deleted Net
Test 662 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​97662 $\color{#cf222e}{\Huge{\mathbf{−}}}$​11047 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​86615
Prod 2338 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​416928 $\color{#cf222e}{\Huge{\mathbf{−}}}$​29332 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​387596

ELI5

This moves ERLI's Orca from upstream v1.4.199 to v1.4.229, fixes the places where the merge silently undid ERLI's own changes, and adds a Windows installer next to the Linux .deb.

What Changed

Upstream sync. Merges upstream/main at de1f6e3d81, the commit v1.4.229 (2026-10-17) was cut from — about a week old, per SYNC.md. 4216 upstream commits. Upstream's release commit is cherry-picked so builds are stamped 2.0.<run>-erli-upstream.1.4.229.

Fork patches the merge undid, now restored (typecheck and the targeted tests were green throughout):

Area What the merge did Fix
Failed local terminal exit (pty-exit-hibernate.ts) Port onto upstream's new keepsUntouchedFreshPane read the raw input timestamp instead of the input generation; upstream's PTY suite split (stablyai#29193) brought back the 5 assertions the fork inverts and dropped the fork's tests; the fork's guard read session.connectionId, renamed upstream to worktreeConnectionId (untyped session, so always undefined) Generation check restored; the 5 upstream assertions removed from the spec and the fork's versions moved to pty-connection-failed-local-exit.test.ts; the connection guard dropped, since upstream removed it itself in stablyai#27489 (a pane is now local or remote-runtime)
Background push (new upstream feature) Started push.onorca.dev with mobile compiled out Gated on the mobile capability
Native chat (new upstream feature) New chats defaulted to Full access with yolo-default compiled out Defaults to Ask in that build
max-lines Two fork-patched upstream files sit at exactly 300 counted lines Fork patches rewritten to add zero lines
electron.vite.config.ts Upstream left 2 lines of budget Fork defines wrapped in config/build-plugins/erli-defines.ts
Updater updater.ts split further Fork feed patches now live in updater/updater-release-feed.ts and updater/updater-setup.ts (added to FORK_OWNED_PATHS)

Security fix past the cut point. The managed-server Node runtime pin moves to 24.22.1 (Node's October security release), taken from upstream eb9d2f9a06b (stablyai#31687, 2026-10-21) — only the pin, its tests, and the test image they check.

Release workflow. erli-linux-release.yml becomes erli-release.yml: one dispatch builds the Linux x64 .deb and an unsigned Windows x64 NSIS installer from one commit and publishes both in one release, only once both succeed (separate releases would leave one platform's updater pointing at a release without its latest*.yml). ERLI builds drop upstream's SignPath publisherName, as upstream's dev channels do, so an installed build does not reject the next unsigned one. Both builds now also:

Why

Cut-point evidence (SYNC.md step 2):

Linked Issue

N/A — fork maintenance.

Visual Proof

N/A — no UI change beyond what upstream shipped; the new defaults are covered by tests.

Testing

  • typecheck, oxlint (0/0), audit:code-quality:native, audit:code-quality:type-aware, check:max-lines-ratchet, verify:skill-bundle-manifest, check:node-runtime-pin
  • Fork-only line audit, plus a new check that every session.* field fork lines read is still assigned (added to SYNC.md)
  • pnpm test:node: 12 320 files pass. The 28 that fail fail identically on a pristine checkout of de1f6e3d81 (no Bun, git 2.43 without reftable, cloud/ deps not installed). pnpm test itself now requires Bun.
  • Merged tree differs from de1f6e3d81 only in fork-owned files, fork tests, the version bump and the Node-pin cherry-pick
  • Linux .deb built locally (2.0.9001-erli-upstream.1.4.229) and launched under xvfb with an isolated profile: app-update.yml targets ERLI-SA, the capability list is in the asar, the updater checks ERLI-SA/orca only, and no request reaches stablyai or onorca.dev
  • Windows installer: not built locally; the first ERLI release run (as a prerelease) is its test
  • orcad template packaging: CI-only, same run

Expected noise from the smoke run: [agent-state-rules] download failed: HTTP 404 — the rules feed resolves through ORCA_RELEASE_REPO to ERLI-SA/orca, which publishes no rules release, so the app keeps the rules bundled in the package.

AI Disclosure

Merge resolution, fixes and workflow written with Claude Opus 5.5 (Claude Code), reviewed by the author.

Notes

The audit baseline (config/erli-upstream-audit-baseline.txt) is deliberately not moved; that happens when an audit round closes, per AUDIT.md.

🤖 Generated with Claude Code

nwparker and others added 30 commits October 16, 2026 02:34
…9566)

* fix(accounts): open the browser for a WSL account sign-in

A WSL Add account / Sign in again ran the provider CLI inside the distro,
where stock distros have no opener, so no browser opened. Claude's link is
now caught by the same BROWSER stand-in Copy link uses, Codex's by the link
it already prints, and the desktop opens it after validation.

* fix(accounts): never open a cancelled WSL sign-in's link; wait the full login window

Addresses review: watch the login's cancel signal before opening, give auto-open
the whole login window to receive Claude's link, log the refused host, and cover
cancel-before-delivery and service wiring in tests.
…ent has proven exited (stablyai#29588)

A pane whose shell outlived its agent kept counting as the session's live
holder because its PTY was alive, so a paired client got a plain shell.
Ask the hook store: a holder it proves exited no longer blocks the resume;
unverifiable still does.
…marker out of terminals (stablyai#29569)

* test(e2e): prove the held-6768 case really held the port and orcad bound another

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

* fix(pty): keep orcad's managed-launch marker out of user terminals

A terminal on a managed host inherited ORCA_ORCAD_MANAGED_ACTIVATION_ROOT, so an
orcad a user started there looked client-managed: --port stopped being strict
and idle exit applied. Scrub it like the other per-launch identity variables.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
…tablyai#29577)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
…owner build switch (off by default) (stablyai#29353)

* feat(layout): add the dark layout.apply channel and route pane splits through main behind ORCA_LAYOUT_OWNER

A build made with ORCA_LAYOUT_OWNER=main registers layout.apply: main applies the
window's layout command to the stored partition through the shared applyLayoutCommand,
minting new ids. A user split then waits for main's answer and mounts the pane id main
minted. Every other build substitutes 'window', so neither branch is compiled in.

* fix(layout): write only the records a layout.apply command touched

A load-then-save rewrite also normalizes records the command never touched. The command's
effect is now the diff between the save before and after it, written onto the stored
partition record by record. A split whose main reply says a paired server holds the tab
splits as today.

* fix(layout): write a load-supplied record whole and check that patched partitions reload to the command's result

* feat(layout): close, rename and equalize panes through main behind ORCA_LAYOUT_OWNER

In a layout-owner build the window's pane close, title change and equalize go through
layout.apply and the window shows main's answer. A window pane close keeps the legacy
close's rules: the tab's last pane is refused, a pane restarted since main read the close is
left alone, every partition holding the tab closes it, and the tab's remote session id follows
the remaining pane. The rules sit behind a command field only layout.apply sets.

* fix(layout): close the tab when a racing pane close leaves the window one pane

* fix(layout): leave an unmounted tab alone when main refuses its last pane

* feat(layout): move panes and save divider ratios through main behind ORCA_LAYOUT_OWNER

Claude-Session: 10569c9a-a0a1-4fc9-8469-0edfb7aed125

* test(layout): a taken pane drop whose pane closed does nothing

Claude-Session: 10569c9a-a0a1-4fc9-8469-0edfb7aed125

* test(layout): pin the window's pane move to main's saved tree, and the drops main cannot take

Claude-Session: 10569c9a-a0a1-4fc9-8469-0edfb7aed125

* fix(layout): close a user's pane even if its terminal restarted, as today

Claude-Session: 10569c9a-a0a1-4fc9-8469-0edfb7aed125

* test(layout): pin the restarted-pane close directly, not through a made-up profile state

Claude-Session: 10569c9a-a0a1-4fc9-8469-0edfb7aed125

* fix(layout): leave a split to today's path only when the workspace's row names a paired server

The split switch point asked which host to route to with a helper that falls back to the focused
server, so a focused server could claim a workspace whose row names no owner. Use the same owner
test the tab mirror uses.

* test(layout): type the split owner mock without a cast

* test(layout): type the layout-owner command mocks with the real signature

* test(layout): type the gesture command mock with the real signature

* fix(layout): close the dark path's gaps: unmounted split panes, refused or failed splits and moves, ratios during a move, release guard

- A pane main saved but the window could not mount (source pane or manager gone during the await)
  is closed again in main, so it does not come back on restart.
- A split main refuses because its copy lacks the workspace, tab or pane, or a failed call, splits
  as today; a refused or failed move moves as today.
- Divider ratios committed while a move is in flight wait for the window's move, then go to main.
- The orcad bundle gets the ORCA_LAYOUT_OWNER define when the switch is on.
- Packaging and the orcad template refuse ORCA_LAYOUT_OWNER=main and any output a dark build marked.
- workspace-layout-record-patch.ts: no non-null assertions; plain comment for the map depth.

* test(layout): count a split pane as mounted from the split mock's own results

* fix(layout): act as today whenever main lacks the target or the call throws, not just for splits

- sendPaneCommandToLayoutOwner runs asToday for workspace/tab/pane_not_found and a thrown call, so
  close, rename and equalize fall back like split and move; a handler that throws no longer runs the
  fallback too.
- A close main refuses as its last pane, while this window still has others, closes as today.
- A move whose local step throws still releases held divider ratios.
- Drop the orcad output marker: nothing read it; the template build refuses the switch itself.

* test(layout): plain names for the last-pane fallback test and the marker comment

* docs(layout): say plainly what an indeterminate write can leave behind

* fix(layout): finish the main merge with this branch's versions of the layout-owner files

* test(build): copy the layout-owner build guard into the compiler-cache checkout fixture

* feat(layout): send tab rename, color and pin through main behind the layout-owner build switch

In an ORCA_LAYOUT_OWNER=main build, the tab bar's rename, color and pin ask main's layout.apply
first; the window then runs today's action. When main lacks the tab or the call fails the
window acts as today; a paired server's workspace keeps today's path. Pinning a tab now also
ends its preview in main's copy, as the tab bar's pin does. Other builds compile the switch out.

* fix(layout): leave a pinned tab's preview mark to the window until main opens files

Changing the shared pin command would change what a paired client's pin does in every build.

* fix(layout): write a folded legacy row in its loaded form when a layout.apply command touches its claim

A legacy terminal row the load folds into a tab-bar tab stays stored as is. Closing the tab it
repeats, or giving it a tab-bar entry by moving it, let the next load resolve the claim the other
way: the row came back or took the other tab's terminal. Such a command now writes the row as
the load had it; every other record still stays as stored.

* feat(layout): accept tab moves, group splits and group sizes on layout.apply

Group sizes have no tab, so they go to the workspace's routed partition.

* refactor(layout): move the window's layout.apply sender out of the terminal pane folder

Tab groups send through it too; it now also acts as today when main lacks the group.

* feat(layout): reorder, move and split tab groups, and save group sizes, through main behind ORCA_LAYOUT_OWNER

A tab drop and Move Tab to Split wait for main's answer and then show main's placement, with main's
new group id; group divider sizes are drawn while dragging and saved by main when the drag ends.
Closing an empty group needs no command: main never holds an empty group.

* docs(layout): wrap the layout-owner sender's doc comment

* fix(layout): review fixes for tab moves through main

- Read a folded row's terminals with the loader's own claim rule, so rows sharing only an SSH
  session are covered too, and drop the row's remote session entry with it.
- The record-patch test allows a folded row only in its loaded form.
- A tab drag ends at once; main's answer shows the move after.
- A lone group main moved whole is placed so only when this window's group is lone too.

* refactor(layout): adopt only a folded row's pane layout beside its row

A row's remote session entry is carried through load and save unchanged, so adopting it from the
load changed nothing (the earlier commit's message overstated it).

* feat(layout): record a window's terminal tab close through layout.apply

closeTabs can now record each terminal tab it closes, or does not find, as closed for the given
reason (pruned as a partition's records are), so a start that commits later is refused, as today's
close records it. layout.apply takes one tab's close with the window's reason: main forces it in
every holder partition, marks a user close's launches, stops no terminal (the window already did,
sparing any another tab shares), and leaves a workspace it lacks to today's close. A sibling the
load supplied that the close only renumbered is no longer written.

* feat(layout): send the window's terminal tab close to main behind ORCA_LAYOUT_OWNER

The window still closes the tab and stops its terminals itself; main records the close through
layout.apply instead of the close intent, which stays for paired servers and when main lacks the
workspace.

* fix(layout): review fixes for the tab close through main

- The window's close sends through a store-free sender, so the store no longer imports itself.
- Main marks a user close's launches by the terminal's own id.
- A recorded close prunes the whole partition's close records, as today, not one workspace's.

* feat(layout): open and close browser tabs and pages through main behind the layout-owner build switch

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

* refactor(layout): name the browser sender's no-op so the window bundle keeps its names

* feat(layout): move a window's dragged-out pane into a new tab through layout.apply behind ORCA_LAYOUT_OWNER

* refactor(layout): keep the topology commit under its line limit

* fix(layout): keep a served browser tab's open, address and close in order while main writes it

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

* fix(layout): review fixes for the window's pane drag-out through main

* docs(layout): note what a refused served-tab close leaves

* refactor(layout): re-key a moved pane from the move alone

* refactor(layout): share one record-only layout.apply sender and inline the id schema

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

* Restore the browser layout-apply hook dropped by the main merge

---------

Co-authored-by: Claude <noreply@anthropic.com>
…a possibly reused PID (stablyai#29548)

* fix(orcad): reclaim a pre-stablyai#28125 lock whose PID another user now owns, and explain a possibly reused PID

Records written before stablyai#28125 carry no start ticks or boot id, so on Linux a
wall-clock mismatch is unproven and a reused PID kept the lock held forever.
A PID owned by another non-root uid cannot be the holder (orcad never changes
uid), so that now proves reuse in both lock reclaim and managed stop. When the
verdict stays unproven, the error names the live process and the orcad.lock
path that is safe to remove once no orcad is running.

* fix(orcad): treat the overflow uid as unproven and word the reused-PID error for any boot-less record

* test(orcad): keep the Linux-platform lock cases off Windows, where the platform hook also drives data-root privacy
…to install (stablyai#29556)

Every orcad start ran installPendingOrcadMigrationShellHistory, which paid a
process inventory with an empty worktree list. That inventory could leave a
surviving daemon session unfiled and drop its restored orchestration receipt
before any client connected. Return early unless a committed migration still
holds a received shell history, and refresh against the host's real worktrees
when one does.

Follow-up to stablyai#28902.
…, behind the per-host switch (STA-10765) (stablyai#29571)

* fix(ssh): relay-era terminals' orca orchestration commands work again, behind the per-host switch (STA-10765)

* fix(ssh): attest bridge-verified relay terminals for legacy records, replay settled receipts, cancel on disconnect

* fix(ssh): admit retained legacy coordinators, their delivery targets, and legacy receipt replays

* fix(ssh): reject emptied retry ids, match moved panes by leaf, replay completed retries after reassignment

* fix(ssh): answer --help in a relay-era terminal without running the command

* fix(ssh): exempt only matching mutation replays from relay CLI scope; keep legacy retries replayable after reassignment

* fix(ssh): relay CLI security review: pane-bound legacy coordinators only, revoke and cap calls, exact-hash replays, target-aware identity, consent copy

* fix(ssh): admit settled legacy workers only with a committed receipt; honor exact reply replays
…ablyai#29439)

* ci: remove Windows build and persistence test overhead

* ci: collect unit phase diagnostics during PR checks

* test(ci): seed hook reuse fixture from the canonical prebuild

The hook reuse regression first built into a missing private --output, so it
always reached Cargo (74s warm on Windows). Build through the default output
instead, which reuses a warm canonical prebuild, then copy its executable and
fingerprint into the private fixture root.

* fix(lint): clear inherited max-lines and broad object tab parameter after main merge

Combine the duplicate shared/execution-host imports in runtime-host-services
(301 -> 300 lines) and type the overlap test's tab mapper with the
RuntimeMobileSessionClientTab owner type instead of object.

* fix(ci): align inherited runtime and workflow contracts

* test: establish changed runtime probe signatures deterministically

* test: await asynchronous daemon profile cleanup

* test: shut down owned daemons before deleting profiles

* test: clean up oracle daemons during final app close

* test: recognize verified Linux zombie exit during daemon teardown

* fix: preserve folder SSH ownership when project groups conflict
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
…yai#29590)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
… reads, and name orchestration in the reset notice (STA-10765) (stablyai#29596)
…yai#29323)

* test: batch lint fixtures and remove repeated runtime scan

* test(cli): consolidate owner coverage and remove false decoding proof

* test(renderer): retain IPC lifecycle behavior without copied inventories

* test: share floating panel behavior suite startup

* test: retire synthetic runtime constructor probes

* test: share saved terminal title runtime setup

* test: share native chat render cost setup

* test: share terminal close continuity runtime setup

* test(runtime): prove mobile geometry through desktop restores

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* fix(agent-hooks): bound retained agent exits

Proven exits were only forgotten by a successor claim, so closed tabs and
retired panes kept theirs forever and every save rewrote them. Drop an exit
when its tab closes, its pane retires or its PTY is really torn down (not a
reconnectable disconnect), and prune exits older than the hydrate window
at save.

* fix(agent-hooks): closing a moved pane's former tab keeps its exit

* fix(agent-hooks): keep an aged exit while its retained resume row remains

* fix(agent-hooks): launch-authority retirement keeps a proven exit
…by default) (stablyai#29354)

* feat(layout): add the dark layout.apply channel and route pane splits through main behind ORCA_LAYOUT_OWNER

A build made with ORCA_LAYOUT_OWNER=main registers layout.apply: main applies the
window's layout command to the stored partition through the shared applyLayoutCommand,
minting new ids. A user split then waits for main's answer and mounts the pane id main
minted. Every other build substitutes 'window', so neither branch is compiled in.

* fix(layout): write only the records a layout.apply command touched

A load-then-save rewrite also normalizes records the command never touched. The command's
effect is now the diff between the save before and after it, written onto the stored
partition record by record. A split whose main reply says a paired server holds the tab
splits as today.

* fix(layout): write a load-supplied record whole and check that patched partitions reload to the command's result

* feat(layout): close, rename and equalize panes through main behind ORCA_LAYOUT_OWNER

In a layout-owner build the window's pane close, title change and equalize go through
layout.apply and the window shows main's answer. A window pane close keeps the legacy
close's rules: the tab's last pane is refused, a pane restarted since main read the close is
left alone, every partition holding the tab closes it, and the tab's remote session id follows
the remaining pane. The rules sit behind a command field only layout.apply sets.

* fix(layout): close the tab when a racing pane close leaves the window one pane

* fix(layout): leave an unmounted tab alone when main refuses its last pane

* feat(layout): move panes and save divider ratios through main behind ORCA_LAYOUT_OWNER

Claude-Session: 10569c9a-a0a1-4fc9-8469-0edfb7aed125

* test(layout): a taken pane drop whose pane closed does nothing

Claude-Session: 10569c9a-a0a1-4fc9-8469-0edfb7aed125

* test(layout): pin the window's pane move to main's saved tree, and the drops main cannot take

Claude-Session: 10569c9a-a0a1-4fc9-8469-0edfb7aed125

* fix(layout): close a user's pane even if its terminal restarted, as today

Claude-Session: 10569c9a-a0a1-4fc9-8469-0edfb7aed125

* test(layout): pin the restarted-pane close directly, not through a made-up profile state

Claude-Session: 10569c9a-a0a1-4fc9-8469-0edfb7aed125

* fix(layout): leave a split to today's path only when the workspace's row names a paired server

The split switch point asked which host to route to with a helper that falls back to the focused
server, so a focused server could claim a workspace whose row names no owner. Use the same owner
test the tab mirror uses.

* test(layout): type the split owner mock without a cast

* test(layout): type the layout-owner command mocks with the real signature

* test(layout): type the gesture command mock with the real signature

* fix(layout): close the dark path's gaps: unmounted split panes, refused or failed splits and moves, ratios during a move, release guard

- A pane main saved but the window could not mount (source pane or manager gone during the await)
  is closed again in main, so it does not come back on restart.
- A split main refuses because its copy lacks the workspace, tab or pane, or a failed call, splits
  as today; a refused or failed move moves as today.
- Divider ratios committed while a move is in flight wait for the window's move, then go to main.
- The orcad bundle gets the ORCA_LAYOUT_OWNER define when the switch is on.
- Packaging and the orcad template refuse ORCA_LAYOUT_OWNER=main and any output a dark build marked.
- workspace-layout-record-patch.ts: no non-null assertions; plain comment for the map depth.

* test(layout): count a split pane as mounted from the split mock's own results

* fix(layout): act as today whenever main lacks the target or the call throws, not just for splits

- sendPaneCommandToLayoutOwner runs asToday for workspace/tab/pane_not_found and a thrown call, so
  close, rename and equalize fall back like split and move; a handler that throws no longer runs the
  fallback too.
- A close main refuses as its last pane, while this window still has others, closes as today.
- A move whose local step throws still releases held divider ratios.
- Drop the orcad output marker: nothing read it; the template build refuses the switch itself.

* test(layout): plain names for the last-pane fallback test and the marker comment

* docs(layout): say plainly what an indeterminate write can leave behind

* fix(layout): finish the main merge with this branch's versions of the layout-owner files

* test(build): copy the layout-owner build guard into the compiler-cache checkout fixture

* feat(layout): send tab rename, color and pin through main behind the layout-owner build switch

In an ORCA_LAYOUT_OWNER=main build, the tab bar's rename, color and pin ask main's layout.apply
first; the window then runs today's action. When main lacks the tab or the call fails the
window acts as today; a paired server's workspace keeps today's path. Pinning a tab now also
ends its preview in main's copy, as the tab bar's pin does. Other builds compile the switch out.

* fix(layout): leave a pinned tab's preview mark to the window until main opens files

Changing the shared pin command would change what a paired client's pin does in every build.

* fix(layout): write a folded legacy row in its loaded form when a layout.apply command touches its claim

A legacy terminal row the load folds into a tab-bar tab stays stored as is. Closing the tab it
repeats, or giving it a tab-bar entry by moving it, let the next load resolve the claim the other
way: the row came back or took the other tab's terminal. Such a command now writes the row as
the load had it; every other record still stays as stored.

* feat(layout): accept tab moves, group splits and group sizes on layout.apply

Group sizes have no tab, so they go to the workspace's routed partition.

* refactor(layout): move the window's layout.apply sender out of the terminal pane folder

Tab groups send through it too; it now also acts as today when main lacks the group.

* feat(layout): reorder, move and split tab groups, and save group sizes, through main behind ORCA_LAYOUT_OWNER

A tab drop and Move Tab to Split wait for main's answer and then show main's placement, with main's
new group id; group divider sizes are drawn while dragging and saved by main when the drag ends.
Closing an empty group needs no command: main never holds an empty group.

* docs(layout): wrap the layout-owner sender's doc comment

* fix(layout): review fixes for tab moves through main

- Read a folded row's terminals with the loader's own claim rule, so rows sharing only an SSH
  session are covered too, and drop the row's remote session entry with it.
- The record-patch test allows a folded row only in its loaded form.
- A tab drag ends at once; main's answer shows the move after.
- A lone group main moved whole is placed so only when this window's group is lone too.

* refactor(layout): adopt only a folded row's pane layout beside its row

A row's remote session entry is carried through load and save unchanged, so adopting it from the
load changed nothing (the earlier commit's message overstated it).

* feat(layout): record a window's terminal tab close through layout.apply

closeTabs can now record each terminal tab it closes, or does not find, as closed for the given
reason (pruned as a partition's records are), so a start that commits later is refused, as today's
close records it. layout.apply takes one tab's close with the window's reason: main forces it in
every holder partition, marks a user close's launches, stops no terminal (the window already did,
sparing any another tab shares), and leaves a workspace it lacks to today's close. A sibling the
load supplied that the close only renumbered is no longer written.

* feat(layout): send the window's terminal tab close to main behind ORCA_LAYOUT_OWNER

The window still closes the tab and stops its terminals itself; main records the close through
layout.apply instead of the close intent, which stays for paired servers and when main lacks the
workspace.

* fix(layout): review fixes for the tab close through main

- The window's close sends through a store-free sender, so the store no longer imports itself.
- Main marks a user close's launches by the terminal's own id.
- A recorded close prunes the whole partition's close records, as today, not one workspace's.

* feat(layout): open and close browser tabs and pages through main behind the layout-owner build switch

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

* feat(layout): open editor tabs under the window's id, per split, and promote previews through layout.apply

openEditorTab takes the window's tab id (the same open again is a no-op, a held id is refused),
dedupes per group so a file open in another split gets its own tab, pins an existing preview on a
non-preview open, and places the new tab before closing the replaced preview so a group whose only
tab is its preview keeps its id. layout.apply accepts openEditorTab, promotePreviewTab and a
closeTabs with no terminal close record.

* feat(layout): record the window's editor tab opens, closes and preview promotions in main behind ORCA_LAYOUT_OWNER

The window still draws first (openFile returns at once and its callers act on the tab by id).
Main then records the tab under the window's ids and decides the group fallback and preview
replacement by the same shared rule. Switch points: the one editor tab create, the unified tab
close, preview promotion on activation, edit and make-permanent, and the reopened tab's position.

* refactor(layout): name the browser sender's no-op so the window bundle keeps its names

* feat(layout): move a window's dragged-out pane into a new tab through layout.apply behind ORCA_LAYOUT_OWNER

* refactor(layout): keep the topology commit under its line limit

* fix(layout): keep a served browser tab's open, address and close in order while main writes it

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

* fix(layout): review fixes for the window's pane drag-out through main

* fix(layout): review fixes for editor tabs through main

openEditorTab refuses a group main lacks instead of placing the tab in another group, and gives
a workspace with no group (the window keeps its emptied root group; main closes it) the sender's
group id. Tests pin close-all, the emptied root group, the refused open into a fresh split, and
that the window sends an edit-mode file's record exactly as its session save keeps it.

* docs(layout): note what a refused served-tab close leaves

* refactor(layout): re-key a moved pane from the move alone

* test(layout): assert the window drew the tab main refused

* refactor(layout): share one record-only layout.apply sender and inline the id schema

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
…9595)

* ci: reuse prepared natives in Windows serve-switch builds

* ci: route legacy relay SQLite fixtures through Node
)

* test: route legacy relay database fixtures through Node

* test: require process exit after daemon cleanup loses contact

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
…lyai#29324)

* test: batch lint fixtures and remove repeated runtime scan

* test(cli): consolidate owner coverage and remove false decoding proof

* test(renderer): retain IPC lifecycle behavior without copied inventories

* test: share floating panel behavior suite startup

* test: retire synthetic runtime constructor probes

* test: share saved terminal title runtime setup

* test: share native chat render cost setup

* test: share terminal close continuity runtime setup

* test(runtime): prove mobile geometry through desktop restores

* test(runtime): skip unused RPC catalog in bind-host coverage

* test: route legacy relay database fixtures through Node

* test: require process exit after daemon cleanup loses contact

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* test: consolidate terminal query ownership proof

* test: route legacy relay database fixtures through Node

* test: require process exit after daemon cleanup loses contact

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
…ts done (stablyai#29604)

* fix(runtime): keep a restored agent turn unverifiable instead of done after a server restart

* fix(runtime): retire restored turns by the synced leaf's title clock; no Stop for unverifiable mobile turns

* fix(runtime): capability-gate restored agent rows so older clients keep the legacy done projection

* fix(runtime): retire restored turns only on shell titles; restored rows never pierce live client status

* test(web): the browser client advertises the restored agent status capability
…ommitted move (D1b-7b) (stablyai#29597)

* feat(wsl-mover): the ledgered desktop rewrite stage that finishes a committed move (D1b-7b)

* fix(wsl-mover): a move finishes its rewrite only once every store exists (D1b-7b)

* fix(wsl-mover): chats never move per decision C, the journal enforces registry completeness, stores time out (D1b-7b)
…#29619)

* test: route legacy relay database fixtures through Node

* test: require process exit after daemon cleanup loses contact

* test: limit WebSocket budget suites to their RPC handlers

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
…inear/Jira without a restart (stablyai#29608)

* fix(orcad): tell the user when a locked macOS Keychain hides Linear and Jira, and recover without a restart

A managed macOS host reached only over SSH keeps its login Keychain locked
after a reboot, so the server's credential key failed closed and the app
asked the user to reconnect a token that was fine. Exit 36 from
security find-generic-password (errSecInteractionNotAllowed) is now a
'locked' lookup: the server logs it once, retries the Keychain on each
credential use and before each Linear/Jira status read, and status carries
an optional credentialErrorReason: 'keychain_locked' that the app shows in
place of the generic text. Once unlocked, Settings offers a one-click move
of the same key into the owner-only key file.

* fix(orcad): keep retrying a locked Keychain after a timeout, fence the key move to the host that asked, report the lock only for credentials that need the key

* fix(settings): offer the file-storage switch to a paired browser's implicit server too

* fix(settings): route the Keychain notice and key move by the host that reported the status; sweep stale key-record staging; say the moved key is not machine-bound

* fix(orcad): sweep only this machine's key-record staging, so a shared home's other hosts keep theirs
* test: batch lint fixtures and remove repeated runtime scan

* test(cli): consolidate owner coverage and remove false decoding proof

* test(renderer): retain IPC lifecycle behavior without copied inventories

* test: share floating panel behavior suite startup

* test: retire synthetic runtime constructor probes

* test: share saved terminal title runtime setup

* test: share native chat render cost setup

* test: share terminal close continuity runtime setup

* test(runtime): prove mobile geometry through desktop restores

* test(runtime): skip unused RPC catalog in bind-host coverage

* test: share transcript scroll suite startup

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
nwparker and others added 29 commits October 16, 2026 20:24
… as stale (stablyai#29957)

The preparation lock reason named only the owner's pid, so a host sharing the repo's disk
(an NFS home) read another machine's in-progress preparation as abandoned and discarded it.
The reason now carries the writer's machine identity (optional, appended after the fields
older builds read); a different or unknown machine is unverifiable, and a reason without
one is judged by pid only on a local disk.
…pen terminals (stablyai#29958)

* fix(orcad): replace a retired terminal daemon when a client returns during idle shutdown

* fix(daemon): drop idle-retired legacy generations when replacing the daemon

* fix(orcad): treat a lost or late daemon retirement reply as committed

* fix(orcad): settle a lost retirement reply with the daemon itself before replacing it

* fix(orcad): bind the replacement to the retired daemon and honor the router fence

* test(daemon): type the pending census

* fix(daemon): skip the owner check only for a proven-dead endpoint
… still starting (stablyai#29911)

The + agent menu waited 5 s for the new tab's terminal and otherwise showed
"Couldn't launch X — the terminal did not start." Any slow spawn (Windows,
a cold or busy terminal host, a queued spawn) passed through that state and
got the false toast before the agent came up. The pane's own spawn already
reports a real failure in the pane, so the guess is removed.
…er (stablyai#29946)

* fix(sidebar): order sidebar text the same on every device

Sidebar names, project labels, project-group names and folder workspace
names were ordered with localeCompare, whose result depends on the ICU data
bundled with Electron, the phone WebView or Node. Order them with one
comparator in src/shared/sidebar-list/text-order.ts instead: case-folded and
NFC-normalized, digit runs by value, the rest by code point, and equal text
broken by host-qualified id.

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

* perf(sidebar): fold each sort label once per sort; reuse the phone row identity helper

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

* fix(sidebar): ignore accents when ordering sidebar names

Accents and case now only break exact ties, so "Éclair" sorts beside "eclair".

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
…tablyai#29124)

* fix(native-chat): identify the owner when resume create is refused

* test(native-chat): consolidate transcript refusal cases

* test(native-chat): keep ownership coverage at the launch boundary

* test(native-chat): update adoption fixture for early ownership admission

* test(native-chat): align resume admission assertions with host ownership

* test(native-chat): keep RPC coverage focused on ownership proof

* test(native-chat): assert blank request presence before optional source

* fix(native-chat): admit the actual CLI source during chat creation

* fix(native-chat): satisfy source admission lint checks

* test(native-chat): provide the waiting hook prompt

* test(native-chat): isolate source admission fixtures from host homes

* test(native-chat): canonicalize adoption fixture directories

* test(native-chat): preserve early ownership admission in source stack

* fix(native-chat): preserve resume refusal behavior and honest transcript feedback

* fix(native-chat): retain import read evidence in typed result

* fix(native-chat): preserve admitted source and prepared execution home

* fix(chat): retain source evidence for dispatched adoption

* fix(chat): keep source capture within module limits

* test(chat): allow host evidence in create routing

* fix(native-chat): admit an unsaved transcript under a linked account folder

A missing transcript stayed unresolved while its account root resolved through
symlinks, so containment failed and a not-yet-saved conversation showed a load
error instead of the unsaved notice.

* refactor(native-chat): move the newest-item import window into its own module

Keeps journal-legacy-import.ts under the line limit after the source
identity observer landed on top of the streaming import.

* fix(native-chat): restore the refusal error import lost in the main merge

* fix(native-chat): admit Pi transcripts against the root Session History walks

A PI_CODING_AGENT_DIR that already ends in sessions, or any custom folder,
made admission expect <dir>/sessions while the lookup searched <dir>, so a
found conversation was refused.

* refactor(native-chat): admit and look up CLI transcripts in one store per account

Each agent's import described where an account keeps its conversations twice,
once for the path check and once for the lookup, and the two could disagree
(the linked-folder and Pi custom-root refusals). The import now names one
conversation store per account; the create path resolves it once and both the
hook-path check and the fallback lookup judge that same store. OpenCode's
store is its exact database.
…e like a sidebar delete (stablyai#23593)

* fix(workspace-cleanup): let select-all include the workspace you are in

Deleting the current workspace is allowed like any other, so select-all now
includes it; a live agent stays excluded. Cleanup removal now hands focus to a
surviving sibling after the batch, as the sidebar's batch delete does.

Linear: STA-4686

* test(workspace-cleanup): cover focus handoff after the dialog closes and after Delete anyway

* fix(workspace-cleanup): move focus off a deleted active workspace as soon as its row is gone

* fix(workspace-cleanup): don't reopen a workspace the user left while a cleanup batch runs

The per-row focus handoff fired whenever no workspace was selected, so
closing the successor's last tab mid-batch re-activated it (and spawned a
new terminal) on the next row. Move focus only when the workspace the
batch left the user on is actually gone and nothing else took focus.

* refactor(workspace-cleanup): move focus off the active workspace when a cleanup delete starts

Switch to the successor once, right after the batch rows are marked deleting, instead of
repairing an empty selection after each row settles. The active row is no longer active when
it is removed, so the store never clears the selection and no later handoff is needed.

* fix(workspace-cleanup): never hand focus to a workspace in the same cleanup batch

The scan qualifies local rows as host `local`, so their delete state lives
under a host-qualified key, while a sidebar row with no stored host only
matches an unqualified key. The successor picker therefore saw batch rows as
not deleting and could focus one about to be removed, leaving an empty screen
once it went. Exclude the batch's own ids directly.

* test(workspace-cleanup): use scan-shaped hosts in the active-workspace focus tests

Candidates now carry the `local` host the scan publishes, which is what
exposed the picker gap. The other-host case also stamps the viewed row with
its host so it resolves; before, the lookup missed and the test passed with
the host check removed.

* feat(workspace-cleanup): confirm before a cleanup delete stops a running agent

A cleanup delete (bulk, single row, or "Delete anyway") that includes a
workspace with a running agent now shows a step naming those workspaces
before anything is queued. The check reads live agent state at click time,
attributed like the sidebar so structured chats count too, and re-asks if
another agent starts while the step is open.

The approved candidate records which agents the user agreed to stop; the
per-row preflight that runs right before each delete refuses a row whose
agent started after confirmation. With the step in place, live-agent rows
join select-all like the active workspace.

* refactor(workspace-cleanup): name the agent-stop same-action check

* fix(workspace-cleanup): drop an open agent-stop step when the dialog reopens mid-batch

* style(workspace-cleanup): match the agent-stop step's heading to the delete step

* test(workspace-cleanup): type the cache-hydration mocks in the reopen test

* style(workspace-cleanup): let the agent-stop step's heading use the dialog's own typography

The changed-lines design-system gate rejects restyling DialogTitle and
DialogDescription; the delete step's overrides predate that gate.

* fix(worktree-delete): key delete state by the identity the sidebar card reads

A cleanup delete names local rows with the 'local' host, so their queued and
deleting state landed under 'local|<id>' while a card stored without a host
reads the bare id and never showed it. Every delete-state write now derives
its key from the store's own worktree record.

* refactor(workspace-cleanup): reuse the sidebar delete's focus hand-off

Drops the move-focus-before-delete path. Each confirmed row prepares the same
prepareActiveWorktreeFocusAfterDelete committer the sidebar delete uses and
runs it when that row's own removal succeeds, so the user stays on the
workspace while it is queued, lands on the most recently visited surviving
sibling once it is gone, and stays put if its delete fails.

* refactor(workspace-cleanup): reuse the close-terminal stop-agent wording

The stop step's single-agent title is the existing "Stop this agent?" string,
and its description follows the close-terminal prompt's wording.

* test(worktree-delete): cover a local-qualified removal failure on an unhosted card

* docs(workspace-cleanup): describe the after-delete focus hand-off in the bulk-select note

* fix(workspace-cleanup): name one agent in the stop step's button and keep one agent term per locale

* fix(terminal): keep starting terminals in a workspace that is only queued for deletion

A cleanup batch now marks its local rows with the same delete state the sidebar card reads,
so the terminal spawn guard sees them. The guard exists because removal just killed the
workspace's terminals; a queued row still has them and can fail its own delete and stay, so
a new terminal opened there (often the current workspace) stayed blank.

* refactor(terminal): name the teardown-started check the spawn guards share

* chore(workspace-cleanup): keep one agent term in ko/zh and refresh a spawn-guard comment

* fix(workspace-cleanup): refresh consent and focus per removal

* fix(workspace-cleanup): invalidate detected host evidence

* fix(workspace-cleanup): admit removal at transport send

* test(workspace-cleanup): expect the send-time admission callback and a published chat tab
…ames (stablyai#29966)

Records the post-stablyai#29946 text order for workspace rows (Name, Recent and
project-name sort), project group names and folder workspace names, ungrouped
and grouped by project, so a future ordering change shows up as a golden diff.
…t reported, not the capped status store (stablyai#29905)

* fix(native-chat): decide owed helper work from what the provider reported, not the capped store

Idle cleanup, replacing an outgrown agent, /clear, /compact, rewind,
command handover and the teardown resume marker decided whether a chat
still had helpers running by reading the agent-status store. That store
is a bounded display projection: it refuses records when it is full or
for per-record reasons, and a refused helper is still running.

The host now keeps an uncapped per-session set of the child work its
provider reported running and has not reported over, fed from every
child-work edge before the store sees it. Every owed-work decision reads
that set. An entry leaves on the child's own end (a late end of an
earlier run leaves the current run open, as the store's run verdict
does), on a Stop the store settled, or on the session's end.

The strip's "incomplete" note and Stop all's unlisted route are now
derived: open work that no live current-run record answers to. This
replaces the refused-for-room side list, so every refusal reason is
covered and nothing needs separate bookkeeping.

* test(native-chat): the real-store test sink reads recorded helper handles, as production's does

* fix(native-chat): keep helper run identity through delayed events and Stop

* fix(native-chat): name the host helper lifetime token

* test(native-chat): explain the Stop adapter fixture boundary

* fix(native-chat): derive helper coverage from running store records

* docs(native-chat): clarify running helper coverage

* test(native-chat): use an explicit helper end event

* Scope background Stop to captured child work

* Exercise refused helper Stop race through status store
…#29224)

* fix(mobile): remove the dead space around the chat composer

The Tools toggle floats over the transcript and shows only when the chat
has tool calls; the composer is one row (attach, message, mic, send) until
focused or drafted; Stop takes Send's slot while there is nothing to send;
the bridge lane's working dots move to the transcript's tail.

* fix(mobile): name the composer's content facts for their role

* fix(mobile): preserve composer controls and picker focus

* fix(mobile): typecheck working indicator test lookup

* test(mobile): find the floating Tools pill when checking a chat switch keeps it expanded
…, for Settings and chat (stablyai#29049)

* feat(acp): one install row shows and drives a managed runtime install, for Settings and chat

The row reads the shared host state: Install, byte progress with Cancel, extract and verify lines,
Retry after a failure or cancel, and in Settings Reinstall or Update once installed. A failed status
read says it could not check, never that nothing is installed. Strings in all six locales.

* fix(settings): the install row says when a copy is damaged, when a PATH copy is used, and when the host needs unzip

- A damaged copy reads "This copy of {agent} is damaged." with Install, which repairs it.
- A copy found on this host outside Orca's reads "Uses the copy of {agent} found on this host."
  with no Install, since a launch would never run the download.
- A host without the program to unpack the archive says so, with no Install.

* fix(settings): say why a host running its own copy is offered no download

* fix(settings): the install row says Cancelling only where Cancel was clicked, offers Orca's copy beside one found on the host, and keeps its status line after a failed install

The host no longer publishes a cancelling phase or a missing unzip, so the row drops both: the
row whose Cancel is still out says Cancelling and holds its button until the host's cancel
settles. A copy found on PATH or in an install folder still gets Install of Orca's copy in
Settings (the chat keeps its composer). An older installed release reads Installed. with Update.
A failed first install keeps Not installed. beside the failure and Retry. The status line is a
polite live region.

* fix(settings): the install row says it is retrying a failed status check, and names the step after the download

* fix(settings): a Cancel that fails for no stated reason says the cancel failed, not the install
…ect groups (stablyai#29962)

Relinking projects the server already held still re-imported this desktop's
retained copy of their project groups, so a group renamed on the server while
it was managed failed the reconnect with orcad_migration_project_group_id_conflict.
A relinked move now leaves to the server every group its projects sit in (read
from repo.list's projectGroupId) and those groups' ancestors unless a moving row
needs one, journals those ids, and keeps them out of the exported rows while
still reading them for folder-workspace host ownership. Later delta moves
subtract them as they do moved groups.
…self (stablyai#29964)

* fix(runtime): the host stops a removed project's terminals itself

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

* fix(runtime): a runtime-stamped project still stops the shells that run here

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

* fix(runtime): refresh the PTY inventory and sweep again after a project's removal

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

* fix(runtime): bound the project inventory refresh and recheck the WSL move fence

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

* fix(runtime): fence a project's removal sweep by its SSH connection and wait out in-flight spawns

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

* fix(runtime): bound the project stop's lease wait by the removal deadline

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

* fix(runtime): refuse spawns into a removed project and skip a stop a same-id sibling shares

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

* fix(runtime): bound each project stop by the removal deadline

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

* fix(runtime): pass the removal deadline to the lease so a timed-out waiter is dequeued

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

* test(runtime): type the project-removal fixtures instead of casting

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
…s again (stablyai#29984)

Partially reverts stablyai#27540 and the plain-http part of stablyai#27552, which broke web
pairing on upgrade for 1.4.227 users who open Orca at a LAN or Tailscale address:

- The host no longer refuses a first pairing from a plain-http page on another
  machine (insecure_origin, ORCA_ALLOW_INSECURE_WEB_PAIRING).
- Such a page keeps its pairing code in local storage and signs in with it, as
  in 1.4.227, so reloads, new tabs and bookmarks stay paired; no notice.
- Session-only browser keys (pairing.device-key-ephemeral.v1, the idle sweep)
  stay for browsers without Ed25519.
- A web client whose credentials are refused shows the host's reason instead
  of an empty workspace and "Session restore failed".
Cut point: merge-base of v1.4.229 and upstream/main (2026-10-16), six days
old at merge time. Window: 1.4.199's cut point (6108ce6) .. de1f6e3.

Reverts landed upstream after the cut point, all targeting work outside the
window: stablyai#30875 (reverts stablyai#30851), stablyai#30572 (reverts stablyai#30134), stablyai#30207 (reverts
stablyai#30185).

Known open upstream bugs accepted with this merge:
- stablyai#31767 terminal_pane_owner_changed recurs on 1.4.229; fix stablyai#30448 landed
  after the cut point.
- stablyai#31545 Claude account switch relaunches sessions in the wrong folder
  (regression from 1.4.224).
- stablyai#29899 memory saturation / UI freezes with many concurrent agents.

Conflicts resolved (17): fork capability gates, release-repo plumbing and
the yolo-default gate kept alongside upstream's new imports and defines;
the fork's failed-local-exit teardown re-expressed on upstream's
keepsUntouchedFreshPane; release-mapping.json and windows-process-
enumeration.md follow upstream's deletion.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit a0cd164)
Upstream added background push (push.onorca.dev) after the fork compiled out Orca Mobile, and starts it on both the desktop and orcad paths with no relation to the mobile capability. A build without mobile has no phone to push to, so it must never construct the gateway client.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…mpiled out

Upstream's native chat seeds new chats as Full access from a default independent of DEFAULT_TUI_AGENT_ARGS, so emptying those args for yolo-default left chats launching with every permission bypassed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Also lists the files that now carry fork patches in FORK_OWNED_PATHS: the build defines helper, the split updater modules, and the two new capability gates.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…e 1.4.229 merge

Three things undid `fix(terminal): tear down failed local exits after input` in the merge, with typecheck green:

- The port onto upstream's new keepsUntouchedFreshPane helper read the raw lastTerminalInputAt instead of the input generation, so a late ack for input typed into a previous PTY made its fresh replacement look user-interacted.
- Upstream moved its PTY suites into register-style .spec.ts files (stablyai#29193), which brought back the five upstream tests asserting the behaviour the fork inverts and dropped the fork's replacements. Those five are removed from the spec; the fork's versions now live in pty-connection-failed-local-exit.test.ts so the upstream suite stays byte-comparable.
- The fork's `!session.connectionId` guard named a field upstream renamed to worktreeConnectionId; the session is untyped, so it was always true. Upstream dropped that guard itself in stablyai#27489 (a pane is now local or remote-runtime, and an SSH pane is the latter), so the fork does too, along with its unreachable direct-SSH test.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…-lines budget

register-app-menu.ts and pty-input-forward.ts sit at exactly 300 counted lines upstream, so every fork line pushed them over. The crash-report gate now spreads ifCapabilityEnabled() in one line, markTerminalInputAttempted() returns the generation it credited, and pty-input-forward claims the viewport once before its two synchronous branches instead of in each. Also lists pane-host-capability-flags.ts in FORK_OWNED_PATHS; it carries the input-generation state.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Takes the runtime half of upstream eb9d2f9 (stablyai#31687, 2026-10-21), which landed after the 1.4.229 cut point: the Node that Orca installs on SSH hosts as their managed server moves from 24.21.0 to Node's October security release. Only the pin, the tests asserting it, and the node-server-tests image the change-scope test checks against are carried; the remaining CI, cloud and Dockerfile bumps arrive with the next sync.

(partially cherry picked from commit eb9d2f9)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ease

erli-linux-release.yml becomes erli-release.yml: one dispatch builds the Linux x64 .deb and the Windows x64 NSIS installer from the same commit, and publishes both into a single release only once both succeeded. Two workflows cutting separate releases would leave one platform's updater resolving /releases/latest/download against a release without its manifest.

The Windows build is unsigned, because ERLI has no Authenticode identity. ERLI builds therefore drop upstream's SignPath publisherName and verifyUpdateCodeSignature, as upstream's dev channels do: electron-updater checks every downloaded installer against the publisher baked into the installed app, so keeping it would make each ERLI build reject the next one.

Both platforms now also need two things this sync introduced. The orcad deployment template, built by upstream's node-server-tests.yml exactly as release-cut does, is required since upstream deleted the SSH relay (stablyai#27215); without it no SSH host can connect. And mobile/node_modules, since build:release always builds the mobile web bundle, even with mobile compiled out.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The other half of upstream eb9d2f9's runtime change: setup-node's check-latest, so a cached 24.21.0 on the runner does not build the orcad template against the newer pin.

(partially cherry picked from commit eb9d2f9)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@damianjarosz-erli
damianjarosz-erli merged commit b0a1bd3 into erli/linux-release Oct 23, 2026
47 of 53 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants