Commit Graph
7873 Commits
Author SHA1 Message Date
OrcaWin 70ea5fb5d6 fix(updater): re-prove the retained Linux package before a privileged retry (#12395)
* fix(updater): re-prove the retained Linux package before a privileged retry

The recovery card's "Try Automatic Install Again" handed electron-updater the
cached .deb/.rpm path with no re-verification. That path is user-writable, so
the digest proven when the card rendered says nothing about the bytes dpkg or
rpm would read as root minutes later — and the card's other actions (copy
command, reveal) validated while the one that actually installs did not.

Re-hash the artifact immediately before the install, ahead of any destructive
quit prep, and abort with copy that tells the user to download again. This
narrows the window rather than closing it; only an immutable handoff would
close it, which is a larger change.

Also fix a macOS-only failure this suite gained with the platform-conditional
pre-commit copy: the expectation hardcoded the non-Darwin string, so the suite
was red on any Mac.

* fix(updater): re-prove the retained Linux package on every install path

Review findings on the original fix:

1. The abort force-sent its error status with no staleness guard, so a
   verdict from a hash that outlived its cycle overwrote whatever card had
   replaced it (a fresh 'available' from Check for Updates became a stale
   "package no longer matches" error). Now keyed on an install-cycle
   signature, the same protection failLinuxPackageRecovery already had.
2. 'read-failed' (EMFILE/EIO/EACCES mid-stream) was described as a digest
   mismatch and tore down the recovery card. It now reuses the accurate
   per-reason copy and keeps the card, exactly as the Copy/Show paths do
   for the same reason. It still fails closed: chmod 000 on a swapped file
   would otherwise be a one-line bypass, since root can read what we cannot.
3. The check was keyed on the recovery status, so it only covered the retry.
   The primary 'downloaded -> Restart to Update' install, whose window is
   hours rather than seconds, handed the same user-writable path to dpkg/rpm
   unverified. Moved into performQuitAndInstall keyed on the tracked
   artifact, so both paths are covered; non-Linux keeps its exact timing
   through a synchronous artifact guard.
4. The async prologue had moved the "quit timer is always cleared"
   invariant out of a try/finally. The re-proof now owns a flag cleared in
   finally, and a rejection fails closed instead of wedging the updater.
5. The install re-proof no longer joins an in-flight validation, so its
   proof cannot predate the click that asked for it.
6. The retry button gained the pending affordance the other actions have,
   since the click now streams the whole package before anything happens.

Tests: real packages are staged in a real updater cache for the whole Linux
block (a path that never existed would now abort every install); new cases
cover the swapped primary install, the stale-verdict drop, the preserved
card on read-failed, the rejecting re-proof, the concurrent second click,
and the fresh-hash guarantee. Each was verified to fail with only its
source change reverted.

* fix(updater): tell the renderer when a stale verdict abandons the install

The cycle guard that stops a stale digest verdict from clobbering a newer
card also withheld the only signal the renderer has that the restart was
called off. The preload abort relay keys on an 'error' status, so with the
status suppressed the window stays restart-prepared for the rest of the
session: Terminal/Settings skip their unsaved-work prompts and the shutdown
checkpoint stays deduped, so a later real quit stages no fresh snapshot.

Push the abandon from performQuitAndInstall's single return-false site, so
it cannot depend on what the reporter decides about the status text, and
relay it to the existing relay.abort() (a no-op unless the renderer armed
a restart). The status stays cycle-guarded exactly as before.

Tests: the stale-verdict and swapped-primary-install cases now assert the
push, a committed install asserts its absence, and the preload relay test
covers the new channel. Each fails with only its source change reverted.

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
(cherry picked from commit dbb7804421)
2026-08-04 02:28:05 -04:00
OrcaWin 3aefc413d8 fix(worktree): tell the truth about live PTYs, and offer force for a wedged sweep (#12394)
* fix(worktree): tell the truth about live PTYs, and offer force for a wedged sweep

Two gaps in the #11960 force path:

The delete toast described every unstopped-PTY failure as "could not confirm
every terminal has exited", including the case where verification positively
watched them running. Force Delete proceeds either way, so the user was being
asked to waive a doubt that did not exist while a running agent's uncommitted
work died with it. The live verdict now gets copy that says so.

A sweep that rejects before any per-PTY verdict exists (wedged daemon, dropped
SSH channel) fails with a teardown-timeout message that the force classifier did
not recognise, so no Force Delete button appeared — the exact dead end #11960
set out to remove. That error now carries the shared prefix and classifies.

* fix(worktree): close the sweep-rejection wedge and stop racing the delete

Review of #12394 found the fix covered only half the wedge it named, and
routed users into a force path whose own safety comment was untrue.

1. Only the outer deadline was classifiable. When a provider *rejects* the
   sweep — dropped SSH channel, erroring daemon — settleBeforeDeadline
   rejects with the provider's original error, which carries no marker, so
   classifyWorktreeForceDeleteReason still returned null and no Force Delete
   button rendered. That is the exact case #11960 named. A rejected sweep on
   the destructive path is now reworded through the existing unstopped-PTY
   prefix (provider text preserved, original kept as `cause`), so old and new
   clients alike classify it as 'unstopped-pty'.

2. Force could delete files while a sweep was still running. The deadline
   rejects without cancelling run(), so allSettled resolved with shutdown()
   still in flight — by construction the deadline error can only fire while
   something is in flight. Force then deleted the directory a live PTY still
   held open (EBUSY / half-delete on Windows and WSL). Sweeps are now tracked
   so the forced path waits for the abandoned work, bounded by a 2s grace;
   force never wedges, and when the grace expires the warning says handles may
   outlive the delete instead of implying the sweep finished.

3. The toast test named for the classifier passed the reason in as a literal,
   so it never exercised it. It now derives the reason exactly as the store
   does, and fails against main.

4. Added the missing unstoppedPtyLive key to the English catalog.

5. isProvenLivePtyRemovalError anchored the 'still live:' marker to the detail
   separator, so a worktree path can no longer spell out a live verdict and
   flip the toast to the destructive copy.

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
(cherry picked from commit 20a2901677)
2026-08-04 02:28:05 -04:00
OrcaWin acb37a2b99 fix(codex): stop a surviving descendant wedging the Codex home process lock forever (#12410)
* fix(codex): stop a surviving descendant wedging the Codex home process lock

The per-Codex-home process lock was released only by the child's 'close'
event. 'close' fires when the child AND every process that inherited its
stdio have exited, so a grandchild that outlives codex (an MCP server, a
helper, an `sh -c` wrapper) keeps the pipe open and 'close' never arrives.
killProcessTree only SIGKILLs the direct child on POSIX, so Stop and the
60s generation timeout both leave the lock held. Every later AI commit
message, PR field, branch name, and quota probe for that home then queued
forever with no error and no recovery until the app restarted.

Release the hold on 'exit' as well as 'close': once the codex process
itself is gone it can no longer rotate that home's auth.json, which is the
race the lock exists to prevent, so waiting on descendants bought nothing.
As a backstop for a child that never reports exit at all (a kill that does
not land), bound the hold: while another run is queued behind it, a hold
that outlives CODEX_HOME_PROCESS_LOCK_MAX_HOLD_MS releases the queue. The
cap is armed only when someone is waiting and only counts time the entry
actually holds the lock, so uncontended runs arm no timer and queueing
behind a slow predecessor never shortens a run's own budget.

* fix(codex): preserve home lock exclusivity

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
(cherry picked from commit 1ef83986a1)
2026-08-04 02:28:05 -04:00
OrcaWin 9e5906b222 fix(browser): keep the chrome inset when a page viewport shell is rebuilt (#12401)
The chrome inset that pushes a browser guest below the toolbar was applied
only from BrowserPane's layout effect, whose deps cover the first mount
(page id, active, paintable, slot readiness). Every path that rebuilds the
viewport shell without re-running that effect therefore produced a shell
with a zero-height inset: guest recovery replacing a lost guest
(replacePersistentWebview -> removeBrowserPageViewport -> render-time
ensureBrowserPageViewport), a session-profile switch that recreates the
guest for a new partition, and an overlay slot-root remount (STA-3228).
The recovered page then painted underneath the browser chrome until an
unrelated resize or tab switch forced a recompute.

Make the geometry a property of the viewport instead of the mount: remember
the last synced inset height per page and apply it whenever a shell is
built. This also fixes the ordering case where the chrome is measured
before the shell exists (the height used to be dropped on the floor).

Test: browser-page-viewport.test.ts covers the rebuild-after-replacement
and measured-before-shell cases; both fail without the change.

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
(cherry picked from commit bdafe840f7)
2026-08-04 02:28:05 -04:00
OrcaWin 70c91809d4 fix(editor): stop Cmd/Ctrl+Shift+T reopening a phantom editor tab (#12399)
`openFile` honored a recently-closed snapshot's `reopenId` whenever no
open file currently held that id, without checking whether the same
(path, mode, owner) was already open under a different id. Editor ids
are not symmetrical across worktrees: the first worktree to open an
absolute path gets the bare path as its id, and any other owner of the
same path gets a namespaced `editor:<wt>:<runtime>:<path>` id. So a
snapshot captured under one shape could be restored while the live tab
carried the other.

When that happened `openFile` took the `existing` branch and mapped over
`openFiles` looking for the stale id, matching nothing — no OpenFile was
created or updated — yet it still set `activeFileId`, the per-worktree
active id, the tab bar order and a unified tab entity to that stale id.
The user got a tab rendering nothing and a dangling active editor.

Resolve the id from the already-open file whenever one exists, so capture
and restore agree on canonicalization. This is a no-op for the non-reopen
path, where `resolveEditorFileIdForOwner` already returns `existing.id`
via the identical predicate.

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
(cherry picked from commit 0f052a5010)
2026-08-04 02:28:05 -04:00
github-actions[bot] 17df980b7d release: v1.4.168-rc.1 v1.4.168-rc.1 2026-08-03 21:22:26 +00:00
OrcaWinandOrcaWin e5174a452b fix(terminal): reattach panes across daemon protocol upgrade (#12351)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-08-03 14:19:44 -07:00
Jinwoo HongandOrcaWin a4944f5343 fix(orchestration): retain update settlement authority (#12336)
* fix(orchestration): retain update settlement authority

* test(orchestration): register update settlement gate

* fix(orchestration): close update settlement audit gaps

* test(orchestration): correct update settlement evidence

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-08-03 14:00:27 -07:00
Jinwoo HongandOrcaWin d3c34c7067 fix(mobile): redial when the app resumes mid-dial (#12344)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-08-03 13:53:59 -07:00
github-actions[bot] b04bd82394 release: v1.4.168-rc.0 2026-08-03 20:50:51 +00:00
OrcaWinandOrcaWin e25381cdd3 fix(relay): tolerate cell clock skew in pairing invite expiry validation (#12340)
The cell stamps invite expiry at exactly now+10min from its own clock while
the desktop rejected anything past now+10min from the local clock with zero
tolerance, so any cell clock ahead of the machine by more than network
transit made every Relay pairing code fail with an opaque toast. Same
defect class as the host-proof freshness incident; same 30s leeway.

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-08-03 13:15:30 -07:00
Brennan Benson 056c2d9496 fix(runtime): bind mobile WS listener to loopback until pairing (STA-2370) (#11956)
The runtime RPC WebSocket listener bound to 0.0.0.0:6769 at startup, so a
desktop with no paired device was reachable from the whole LAN before the
user opted in. Default the bind to 127.0.0.1 and widen to all interfaces
only on an explicit opt-in:

- createMobilePairingOffer / getRuntimePairingUrl widen (ensureNetworkExposure)
  before advertising a LAN endpoint; the rebind reuses the resolved port so an
  already-issued offer stays valid, and concurrent offers share one rebind.
- orca serve and E2E set exposeNetworkByDefault to bind wide at startup.
- A previously-connected device (lastSeenAt > 0) rebinds wide at startup so
  reconnect after restart keeps working; a pending/never-connected offer does
  not persist exposure across a restart.

The advertised pairing endpoint still resolves to a concrete interface address,
never the 0.0.0.0 bind host.
2026-08-03 13:06:11 -07:00
OrcaWin 2c6a9d1446 fix(browser): recover embedded guests after lifecycle loss (#11717) 2026-08-03 13:02:56 -07:00
814b87c421 fix(sidebar): route project adds to the intended host, not the global runtime (#9541)
* fix(sidebar): route local folder adds to the intended host, not the global runtime

Adding a local folder while connected to a remote runtime failed with
"<path> was checked on <host>, but that host did not report a usable folder"
because addRepoPath decides local-vs-remote purely from the global
settings.activeRuntimeEnvironmentId when no explicit host is passed.

Two local-add flows relied on that global fallback and got misrouted:

- useAddRepoLocalFolderFlow (native picker / drag-drop): the Add Project
  host selector can display "Local" (selectedRuntimeEnvironmentId = null, so
  the guard passes) while the global still points at an unavailable runtime.
  Native-picked/dropped paths are always local, so force local routing.

- AddProjectFromFolderDialog ("Add folder as project" on a subfolder): a
  subfolder lives on the active repo's host, so carry that host through the
  modal data and route by it — local for local projects, the owning runtime
  for runtime projects — instead of the globally-active runtime.

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

* fix(sidebar): route runtime server-path adds by selected runtime, not global

The Add Project "server path" step (reached only when a runtime host is
selected) called addRepoPath(path, kind) with no explicit host, so it
inherited the global settings.activeRuntimeEnvironmentId. When that global
diverged from the dialog's selected runtime, the add was misrouted off the
host the user picked — the same root cause as #9541, opposite direction.

Route the server-path add by the dialog's selected runtime explicitly.
Co-locate selectedRuntimeEnvironmentId in useAddRepoHostSelection next to
selectedSshTargetId so the dialog reads it from one place.

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

* fix(sidebar): route the pre-add git server-path scan by the selected runtime

For kind === 'git', scanNestedRepos runs before addRepoPath and can
early-exit the flow into the nested-repo review, but it still routed by the
global active runtime — so it could scan the wrong host even after the add
itself was correctly routed to the selected runtime (CodeRabbit).

- scanNestedRepos accepts an optional runtimeEnvironmentId in its controls;
  when present it routes by that host, else falls back to the global
  (existing callers unchanged).
- useAddRepoServerPathFlow passes the selected runtime into the scan and
  derives runtimeKind/streaming support from it instead of the global-reading
  getNestedRepoRuntimeKind(null), so telemetry and the nested review target
  the same host as the add.

Adds renderer- and store-level regression tests covering scan routing to the
selected runtime, the null-override local case, and the nested-review handoff.

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

* fix(sidebar): make nested-scan cancellation route by the scan's owning host

Follow-up to CodeRabbit review of the scan-routing change:

1. scanNestedRepos treated `{ runtimeEnvironmentId: undefined }` as an explicit
   local override via the `in` check. Only null or a string is now explicit;
   undefined falls back to the global (matches getAddRepoPathRouteSettings).

2. scanNestedRepos gained a routing override but cancelNestedRepoScan still
   routed by the global — an asymmetric contract where an override-routed scan
   could be un-cancellable if the global diverged mid-scan. cancelNestedRepoScan
   now takes the same override, and useAddRepoNestedReviewState remembers each
   scan's owning host by scanId (set when the scan is registered) so both stop
   and reset cancel on the host the scan actually ran on. The local folder flow
   routes its scan explicitly local so scan, cancel, and add all agree.

Adds store-level regression tests (explicit override wins, undefined falls back
to global, cancel routes by override) and a new useAddRepoNestedReviewState test
covering cancel-by-owning-runtime for stop and reset.

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

* fix(sidebar): keep subfolder adds on their owning host

* fix(onboarding): keep completion on captured host

* chore(review): drop unreachable onboarding recovery

* fix(sidebar): preserve paired runtime checkout ownership

* fix(runtime): index paired worktrees by logical owner

* fix: fail closed on worktree owner alias collisions

* docs(sidebar): clarify host-routing intent flagged in review

Two Greptile P2 notes, addressed as comments (no behavior change):
- project-added-default-checkout.ts: the runtime branch's `hostId === executionHostId`
  is NOT unreachable — a colliding repo id can carry a runtime-qualified hostId with
  no runtimeOwnerEnvironmentId (see the "repo IDs collide" test). Documented why the
  comparison is reachable and load-bearing rather than replacing it.
- AddProjectFromFolderDialog.tsx: note that omitting the runtimeEnvironmentId spread
  intentionally signals local (NonGitFolderDialog coerces absence to null).

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

* test(activity): control portal readiness observer delivery

---------

Co-authored-by: fanyunqian.1 <fanyunqian.1@bytedance.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
2026-08-03 12:53:11 -07:00
Brennan Benson 866bcda465 fix(terminal): recover degraded daemon spawn routing (#12277)
* fix(terminal): recover degraded daemon spawn routing

* fix(terminal): preserve fresh-session recovery semantics

* fix(terminal): avoid retaining exited recovery sessions
2026-08-03 12:47:41 -07:00
OrcaWinandBrennan Benson 34291f07e9 fix(runtime): skip unchanged worktrees when publishing mobile session snapshots (#12207)
* test(runtime): prove mobile session publication rebuilds every worktree

buildMobileSessionTabSnapshots consults its per-worktree cache after building
the content, so a republish saves the fanout but none of the work. With 300
worktrees, an unchanged republish still does 601 units of per-worktree work,
and a single changed worktree does 602.

Publication is keyed on agentStatusByPaneKey/agentStatusEpoch, so this runs on
every agent status tick. On a multi-client runtime host with 381 worktrees this
allocated ~350 MB/min and rode the renderer into repeated 4 GB OOMs.

Tests are marked it.fails so the branch stays green; drop .fails when the build
loop skips worktrees whose inputs are unchanged.

* refactor(runtime): make mobile session snapshot inputs explicit per worktree

Every per-worktree builder in buildMobileSessionTabSnapshots took the whole
AppState, so a worktree's real input set was the transitive closure of seven
helpers and could not be memoized safely. Introduce MobileSessionWorktreeInputs
— built once per worktree — and thread it through the group projection and the
terminal/markdown/file/browser tab builders so the compiler proves the input
set. Tab- and pane-keyed slices are narrowed to this worktree's tab ids, file
ids, browser workspace/page ids, and pane keys; agent statuses are bucketed per
worktree once per publication via a tab-id index.

No behavior change. Dropping AppState from the projection path also removes the
second per-worktree read of browserTabsByWorktree, so the publication-cost
counter falls from 601 to 1 per publication and its two cases now pass.

* fix(runtime): skip unchanged worktrees before building mobile session content

buildMobileSessionTabSnapshots consulted its per-worktree cache only after
building that worktree's three Maps, group projection, and full tab array, so
the cache suppressed the fanout but none of the computation. Every agent-status
tick therefore rebuilt every worktree, which drove sustained 4 GB renderer
working sets on a host holding 381 worktrees.

Cache MobileSessionWorktreeInputs alongside each snapshot and reuse the snapshot
when every input field is reference-equal, before any intermediate structure is
allocated. Worktrees with a mounted TerminalPane always rebuild: their live
DOM/PaneManager state is invisible to store references. Absent per-worktree
slices now resolve to shared empty values so an empty worktree can compare equal
to its last publication. jsonContentEquals stays as the backstop on the rebuild
path for inputs that churn by reference without changing output.

With 300 worktrees, per-worktree content builds go from 300 to 0 on an unchanged
republish and from 300 to 1 when one worktree changes.

* test(runtime): cover agent status publication cost

---------

Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
2026-08-03 12:09:32 -07:00
OrcaWin cd68a8b00c fix: preserve live agent PTYs through graph hydration (#11789) 2026-08-03 11:11:14 -07:00
OrcaWin 128e3e335e fix(packaging): prune non-target native binaries (#12174) 2026-08-03 10:54:00 -07:00
Jinjing e08eba674c test(terminal): cover live macOS Korean syllable flush (#12284) 2026-08-03 03:07:52 -07:00
40d5f36608 fix(native-chat): adopt IME deletions at composition end (#12281)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Co-authored-by: fengxinzi1814 <55725821+fengxinzi1814@users.noreply.github.com>
2026-08-03 02:56:35 -07:00
Neil caf6add53a test(terminal): cover leading Korean vowels (#12282) 2026-08-03 02:55:16 -07:00
Neil 035d8c2a54 fix(terminal): preserve macOS Korean composition (#12280) 2026-08-03 02:46:22 -07:00
f82d4100b7 fix(terminal): flush Hangul syllables before the next composition (#12278)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Co-authored-by: Hyunggyun Lyou <hg.lyou@miraeasset.com>
2026-08-03 02:45:08 -07:00
6f7a30ac2e test(terminal): preserve Windows IME Shift commits (#12276)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Co-authored-by: yoke233 <yoke2012@gmail.com>
2026-08-03 02:21:56 -07:00
Neil 339045b150 fix(runtime): coalesce concurrent host terminal focus (#11841)
Bound exclusive host navigation to a generation-aware latest-wins
single-flight so bulk open and switch fan-out stay responsive on large
remote fleets. Add freeze repro harnesses and navigated settlement.
2026-08-03 02:18:05 -07:00
Him188andClaude Fable 5 8e9640cb1d fix(terminal): stop cancelled IME compositions replaying stale preedit
Backspacing away an entire Pinyin preedit ended the composition with
empty data, no textarea residue, and no input/keypress events — yet
_sendPendingComposition fell back to the last non-empty
compositionupdate data and typed its first character into the PTY.
Only trust that fallback when observed input evidence corroborates it;
a composition with no evidence in any channel was cancelled.

Fixes the macOS Pinyin regression from #11293 (stray letter left after
deleting a preedit); same fix covers IBus/fcitx Backspace cancellation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 01:53:02 -07:00
KimYounghwan8750 173abf8b12 test(terminal): replay the native Windows IME Shift shape
Adapted from the physical Windows event trace contributed on #11878.
2026-08-03 01:30:58 -07:00
OrcaWin a5655321b5 style(terminal): keep IME ownership rationale concise 2026-08-03 01:30:58 -07:00
KimYounghwan8750andClaude Fable 5 bacdf3a31d fix(terminal): keep press evidence per Enter press, not per code
A single slot per code let a rapid second Enter press go unguarded: the
first release found a mismatched timeStamp, dropped the only entry, and
the second release then synthesized the Shift+Enter this guard exists to
prevent. Track one entry per press and drain exactly one per physical
release, so every press stays guarded until its own release. Auto-repeat
keydowns do not stack an entry, since the whole run ends in one release,
and the list is bounded so a press whose release never arrives cannot
grow it without end.

Refs #11878

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 01:30:58 -07:00
KimYounghwan8750andClaude Fable 5 ad0a70aa14 fix(terminal): require press-time evidence before synthesizing an IME Enter-keyup newline
On Windows, the Enter-keyup synthesis path inferred the modified-Enter
chord from release-time modifier state. A plain committing Enter
(Process/229, no modifiers) followed by a rolled-over Shift for the next
doubled consonant made the keyup report shiftKey=true and synthesized a
Shift+Enter the user never chorded; a directly-sent Shift+Enter could
likewise send a second newline from its keyup once the next composition
started. Record observed Enter keydowns (code -> timeStamp) and let the
keyup synthesis run only for presses whose keydown the IME swallowed
entirely; a balancing keyup that copies the keydown timeStamp keeps the
evidence for the later physical release.

Refs #11878

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 01:30:58 -07:00
Neilandholdn2 ca9562ceee fix(terminal): block IME-owned Process shortcuts
Adapted from #11616 commit 9f449d75b and #12120 commit 290f42a36.

Co-authored-by: holdn2 <club.makersfarm@gmail.com>
2026-08-03 01:30:58 -07:00
kunsanglee 8af6876510 fix(terminal): distinguish IME Process keys from Enter
Adapted from #11273 commits c690ee609, 506524085, and cc69fa7bf.
2026-08-03 01:30:58 -07:00
OrcaWinandOrcaWin 9e42b5fd41 fix(terminal): clear stale Windows IME modifiers (#12259)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-08-03 01:04:18 -07:00
Siddiqui QamarandOrcaWin f3c824bc28 fix(terminal): expand environment variables in Windows PATH (#11987)
* fix(terminal): expand variables in Windows PATH

* fix(terminal): preserve expanded Windows PATH at spawn

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-08-03 01:00:58 -07:00
NeilandOrca 1dbf55e4df Stop reporting supported Linux hosts as an unsupported remote platform (#12209)
Co-authored-by: Orca <help@stably.ai>
2026-08-03 00:50:20 -07:00
Brennan Benson 3d68212f7b fix(mobile): surface worktree catalog failures instead of silent 0 worktrees (STA-3123) (#12235)
* fix(mobile): surface worktree catalog failures instead of showing 0 worktrees (STA-3123)

A connected host whose worktree.ps request fails now shows an explicit
catalog-failure state (with the RPC error code) on the host page, and
'Worktree list unavailable' on the home host card, instead of silently
rendering as a healthy host with zero workspaces.

* fix(mobile): mark cached worktree catalogs unavailable
2026-08-03 00:48:30 -07:00
Neil 18dbcf001f fix(tabs): restore Cmd/Ctrl+Shift+T tab positions (#12236) 2026-08-03 00:08:09 -07:00
OrcaWinandOrcaWin d48cac7d0f fix(onboarding): run skill setup in the configured Windows runtime (#12103) (#12204)
* fix(onboarding): run skill setup in the configured Windows runtime (#12103)

Onboarding was the one skill-setup surface that did not route its install
command through the resolved runtime. Settings, the feature-wall panels and
the Linear prompt all wrap theirs as `wsl.exe -d <distro> -- sh -c ...` and
pass a matching shell override; onboarding spawned a bare terminal and handed
it the raw `npx skills add ...`. With Node inside WSL, npx is not on the
Windows PATH, so the install failed.

The runtime resolver had a second gap behind that: it only consulted
per-project settings, and onboarding runs before any project exists. With no
project it returned undefined and fell through to the Windows host, ignoring
a global WSL default entirely. `getLocalAgentPreflightContext` already had a
no-project fallback for PATH detection; the skill-install path had none.

- extract that fallback as `getGlobalWindowsExecutionRuntimeContext` and
  rewire the existing agent-preflight branch through it so the two cannot drift
- adopt it in `useActiveProjectSkillRuntime` when no project is active. WSL
  only: a windows-host default already matches the old no-project behavior,
  and resolving it would hand skill discovery a target where it had none,
  re-triggering scans for every host-default user
- build the onboarding terminal's command for the runtime and pass its shell
  override
- register the CLI in WSL rather than on the host, so `orca` lands on the PATH
  the install actually runs on, and wrap the copied command to match

* fix(onboarding): keep skill setup runtime consistent

* test(onboarding): satisfy runtime settings contract

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-08-02 23:28:34 -07:00
JinjingandOrca db5325204f Route terminal links based on pane ownership, not global state (#12233)
* fix(terminal): route remote-runtime link clicks to the system browser

Terminal link clicks classified ownership from the global
activeRuntimeEnvironmentId, which is null when runtimes are bound per
workspace, so a link clicked in a remote-hosted pane opened a local-only
Orca browser tab and never reached the host. Thread each pane's resolved
runtimeEnvironmentId into openHttpLink as sourceOwner across the OSC 8,
WebLinksAddon, and click-fallback paths.

Co-authored-by: Orca <help@stably.ai>

* fix(terminal): route link clicks based on pane ownership, not global sta

Clicking links on remote-hosted panes was routing based on global runtime state, causing unexpected reconnections. Now link routing decisions (where to open: Orca vs system browser) are based on the actual pane's owner — local, SSH connection, remote runtime, or unknown — regardless of whether any runtime is globally active. This ensures a local pane can route to Orca while another pane's remote runtime is active, and a remote pane always routes to the system browser.

---------

Co-authored-by: Orca <help@stably.ai>
2026-08-02 23:27:58 -07:00
Brennan Benson 93a2ad8fd8 fix(browser): bound retained webview guests across worktree switches (#12194)
* fix(browser): bound retained webview guests across worktree switches

* fix(browser): merge duplicate imports flagged by import/no-duplicates

* fix(browser): evict guests without unmounting the worktree surface

Unmounting the surface disposed every parked terminal byte watcher for the
evicted worktree (bell/title/agent-completion facts dropped for good after
the 15s handoff TTL) and skipped the force-park scrollback capture remote
panes rely on. Destroy the guests only: hidden slots mount no BrowserPane,
so nothing resurrects a destroyed guest before the next visit, and no slot
ever unmount-detaches a live guest (STA-3228). Terminal-state vetoes and
the eviction re-render bump are no longer needed; the only veto left is a
guest an automation/mobile controller is actively driving.

* fix(browser): veto eviction for downloading pages; keep zoom; add kill switch

Main cancels a page's active downloads when its guest unregisters (tab-close
semantics), so eviction now vetoes worktrees with a page still writing a
download, tracked app-wide because download state was pane-local and hidden
panes are unmounted. Eviction is not a user close: re-remember explicit zoom
past the destroy-path forget so a revisit reasserts the user's zoom instead
of resetting same-host siblings through Chromium's partition-wide HostZoomMap.
browserGuestWorktreeRetentionBudget mirrors the terminal budget kill switch.
2026-08-02 23:11:53 -07:00
Brennan Benson aa03e2475e fix(mobile): gate native chat approval card on a paused agent (STA-3144) (#12206) 2026-08-02 22:58:53 -07:00
Brennan Benson 153acf3156 feat(crash-reports): add byte attribution to renderer memory highwater breadcrumbs (#12198)
* feat(crash-reports): add byte attribution to renderer memory highwater breadcrumbs

Entry counts stay flat when a slice grows by value weight (97b9e86d leaked
~700MB while its biggest slice grew by 4 entries), so highwater breadcrumbs
now carry sampled per-slice KB estimates for the store plus a live pane
census (managers, panes, estimated scrollback KB) — the dominant heap cost
the store census cannot see.

* fix(crash-reports): bound renderer OOM profiling

* fix(crash-reports): total raw store estimate bytes
2026-08-02 22:51:25 -07:00
Brennan Benson b4d9ae44a5 fix(mobile): deliver the agent launch command when a create settles over a bare renderer PTY (#12197)
A mobile New Tab -> Codex create resolves the launch command and hands it
to the renderer, but when the renderer's startup queue is lost (the #7587
stall class) the pane spawns a plain shell and the create still settles
ready via PTY adoption - silently binding the phone to a bare terminal
forever, since the ready status also disables the #7837 activation-time
materialize recovery.

Record the resolved launch command on the pending create and, at every
renderer-backed settle point, deliver it to the adopted PTY when no spawn
command was recorded for it. Spawn commands are noted per PTY by both
spawn IPC handlers, so a missing record on the locally registered live
PTY proves the launch never ran; delivery types the command exactly like
the create would have, and the note prevents double delivery.

Fixes STA-3214
2026-08-02 22:47:44 -07:00
Brennan Benson 8b311ae984 fix(browser): preserve tab zoom across parent-drift webview repair (#12191)
The parent-drift repair path (destroyPersistentWebview with
preserveViewport: true, introduced in #12137) tears down and rebuilds the
webview under the same browserTabId, but unconditionally forgot the tab's
explicit user zoom. BrowserPane then re-seeds from the Settings default on
the next mount, silently resetting per-tab zoom.

Only forget explicit zoom on a real close; a preserveViewport rebuild keeps
the same logical tab, so its zoom must survive.
2026-08-02 22:15:50 -07:00
Brennan Benson c9a37f58d8 fix(ai-vault): label preview prompt honestly (#12178) 2026-08-02 21:16:49 -07:00
Brennan Benson f3e087ec06 fix(updater): use channel display labels in mac-only error (#12175) 2026-08-02 20:52:49 -07:00
JinjingandClaude Opus 5 5887b36eff fix(updater): recover Linux .deb/.rpm installs that fail escalation (#12183)
* fix(updater): recover Linux .deb/.rpm installs that fail escalation

A `.deb` install fails with `No authentication agent found` when the session
has no polkit agent. Orca reported "Quit and reopen Orca, then try again" —
wrong advice — and its only action was Retry Download, discarding a verified
160 MB package that was still in the updater cache.

Keep the one-click install path, but make a failed root-package install
recoverable without downloading again:

- Retain the downloaded package and its expected SHA-512 from the
  `update-downloaded` event, mirroring electron-updater's cache-name rule.
- Capture the child stderr that BaseUpdater logs but drops from the `error`
  event, redact it (ANSI, control bytes, `<home>`, `<package>`, `<user>`,
  1 KiB cap), and classify the failure. Classification reads the original
  text — redaction can rewrite a matched phrase.
- Send a structured `linux-package-install` recovery status and render a
  dedicated card: Copy Install Command / Try Automatic Install Again /
  Show Package.
- Revalidate on every action: cache containment, lstat, streamed SHA-512,
  timingSafeEqual. Concurrent requests coalesce into one hash pass.
- Build the command from fixed tokens plus one POSIX-single-quoted absolute
  path, resolving sudo and the package manager only from /usr/bin, /bin,
  /usr/sbin, /sbin. Orca never runs it.
- Disable `autoInstallOnAppQuit` for .deb/.rpm so an ordinary quit cannot
  trigger the same failing escalation after the UI is gone.

Extracts the error-card presentation into UpdateErrorCardContent so
UpdateCard does not absorb another stateful surface.

Lifecycle breadcrumbs carry package type, reason, exit code and version —
never a path, command, username or raw child output.

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

* Improve Linux package install recovery diagnostics

- Distinguish invalid-package-path errors from missing package manager
- Expand ANSI escape sequence stripping to handle OSC hyperlinks and DCS
- Prevent generic error logs from overwriting specific diagnostic verdicts
- Add error handling for shell.openUrl in update UI
- Fix test isolation with proper afterEach hooks

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 20:46:49 -07:00
Jinjing 565ef63bde feat(ssh): open the host add/edit form in a modal dialog (STA-3067) (#12172)
* Open SSH host add/edit form in modal dialog

Form moves from inline to a viewport-stable modal (STA-3067) so fields stay
accessible with long host lists. Includes sticky header/footer, dirty-state
protection against outside click, and session-aware Advanced state reset on
cancel/reopen.

* fix: add missing SshTargetForm localization keys

Sync en.json catalog for modal title/description strings so
verify:localization-catalog passes in static analysis.

* fix: translate SshTargetForm modal strings in es/ja/ko/zh

Add non-English catalog entries for the new modal title and
description keys so localized UIs match English.

* Prevent SSH form double-submit and fix dismissal detection

Adds a saving state to prevent concurrent saves when a user double-clicks
the submit button. Fixes outside-click dismissal by correctly tracking form
state across re-renders using refs. Extracts session termination logic to
a reusable module.

* fix: stop mutating formRef during render in SshTargetForm

React Doctor fails the static-analysis gate when refs are written during
render. Sync form into formRef in an effect so render stays pure.
2026-08-02 20:43:27 -07:00
Brennan Benson 4481b9b241 Keep your New Linear and Jira creation drafts after an accidental dismissal (#12167)
* Keep your New Linear and Jira creation drafts after an accidental dismissal

* Avoid draft store writes while typing

* Update draft retention refs after commit
2026-08-02 19:57:23 -07:00
Jinjing 6f28c118bb docs: drop WeChat group 5/6 QRs; keep group 7 only
Older groups are full; community join path is group 7 only.
2026-08-02 19:48:51 -07:00