Commit Graph

7855 Commits

Author SHA1 Message Date
Jinjing e08eba674c
test(terminal): cover live macOS Korean syllable flush (#12284) 2026-08-03 03:07:52 -07:00
OrcaWin 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
OrcaWin 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
OrcaWin 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
Him188 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
KimYounghwan8750 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
KimYounghwan8750 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
Neil 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
OrcaWin 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 Qamar 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
Neil 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
OrcaWin 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
Jinjing 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
Jinjing 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
Jinjing 616d22406e docs: add WeChat group 7 QR with overflow guidance
Groups 5/6 filled; point community join flow at group 6 + group 7.
2026-08-02 19:47:46 -07:00
Brennan Benson 922268af10
fix(native-chat): stop clipping assistant text blocks at the tool-preview cap (#12159)
* fix(native-chat): stop clipping assistant text blocks at the tool-preview cap

Long assistant messages read over a paired connection (headless orca
serve viewed from desktop or mobile) were cut at 4,000 chars with a
'… (truncated)' marker and no way to read the rest. The mobile payload
diet in nativeChat RPC applied the tool-preview char cap to text blocks,
which are the fully rendered message body. Give text blocks their own
64k safety ceiling so real replies pass through whole while pathological
multi-hundred-KB blocks still can't freeze the phone.

Fixes STA-3230

* test(native-chat): cover long text stream frames
2026-08-02 19:17:31 -07:00
OrcaWin 525ffc5ae0
fix(worktree): stop the PTY gate from permanently wedging workspace removal (#12153)
Destructive worktree removal proves every PTY is dead before touching the filesystem. When a stop
RPC failed, it re-listed the provider to check whether the PTY had already exited — but on the
same deadline the sweeps had just spent, so it timed out without ever asking and read "could not
verify" as "still live". The sweep spends that budget every run, making the refusal deterministic;
--force never reached the gate, so the workspace was unremovable forever.

- Verification gets its own budget instead of an exhausted remainder.
- Verdicts split into exited / live / unverifiable; the error names the blocking PTY ids and why.
- A reachable escape hatch: allowUnverifiedPtyStop, set only by genuine Force Delete affordances
  and the CLI's --force — never by the force the ordinary delete confirmation already sets — with
  an 'unstopped-pty' classifier reason so the desktop actually offers the button.
- Force also survives a sweep that cannot complete; the non-force path still fails fast.

Fixes #11960
2026-08-02 19:16:58 -07:00
Brennan Benson 1e46121504
fix(codex): make quota probes credential-safe (#12145)
* fix(codex): make quota probes credential-safe

Codex OAuth uses rotating refresh tokens, and Orca's quota probes spawned
real codex app-server processes inside live credential homes, hard-killed
them at a 10s deadline (cold starts run 10-25s), re-probed every inactive
account on each switch, and deselected accounts on torn auth.json reads.

- arm the RPC read deadline only after initialize responds (30s/40s boot
  budget), and terminate probes via stdin EOF + SIGTERM with a bounded
  drain before any hard kill; resolve only once the child exits
- serialize Orca-spawned codex processes per credential home (probe vs
  probe, probe vs commit-message/PR-fields/branch-name/model-discovery)
- keep the inactive-probe debounce across account switches and stagger
  inactive probes; the active account still refreshes immediately
- grade credential reads (present/missing/unreadable/no-credential) and
  require absence to outlive a grace window before deselecting

* fix(codex): close remaining credential races

* fix(codex): keep failed probes under home lock

* fix(codex): observe probe pipe failures

* fix(codex): await Windows generation tree kills

* fix(codex): preserve incomplete shared credentials
2026-08-02 19:06:43 -07:00
Brennan Benson bcf7d1df1b
fix(codex): execute accepted account-switch restarts for unmounted panes (#12146)
* fix(codex): execute accepted account-switch restarts for unmounted panes

Accepting the Codex account-switch restart prompt queued every awaiting pane
but only mounted TerminalPane instances executed the queue, so background-tab
and parked panes stayed input-blocked on the old account with no prompt.

- Add a detached store-level driver (codex-detached-pane-restart) that
  kill-and-respawns any queued pane no mounted transport claims, rebinding
  tab/layout state so a later mount reattaches to the replacement PTY.
- Re-offer the prompt when a detached execution fails, and clear the notice
  when the pane is gone, so input is never silently blocked.
- Sweep restored PTY ids at startup so stale panes in never-mounted tabs are
  re-offered after an app restart.
- Carry launchAgent codex on the restart respawn so it waits for managed-auth
  readiness and records the pane's launch account.

* fix(codex): fence detached restart ownership races

* fix(codex): unblock detached restart handoffs

* fix(codex): contain detached restart cleanup

* fix(codex): detach restart progress from cleanup

* fix(codex): avoid detached restart size wait

* fix(terminal): scope Codex restart prompt to pane

* fix(codex): contain detached restart sweep failures
2026-08-02 18:54:31 -07:00
Brennan Benson a20165a43d
fix(browser): import __Host- cookies host-only so Chromium keeps them (#12166)
* fix(browser): import __Host- cookies host-only so Chromium keeps them

The cookie file/JSON import path passed a Domain attribute for every
cookie. Chromium rejects any __Host--prefixed cookie that carries a
Domain (the prefix requires host-only, path=/, Secure), so file import
silently dropped session cookies like GitHub's __Host-user_session_same_site
and users stayed logged out after importing.

Mirror the browser-native import path, which already shapes __Host-
cookies host-only: omit domain and force path=/ when the name is
__Host--prefixed. Add a regression test covering both a __Host- cookie
and a normal domain cookie in one import.

* test(browser): cover __Host- cookie payload constraints

* test(browser): preserve ordinary cookie paths
2026-08-02 18:53:22 -07:00
Jinjing 45a78e13da
feat(editor): preserve PDF scroll position across tab switches (#12163)
* feat(editor): preserve PDF scroll position across tab switches

PDFs snapped back to page 1 on every tab switch — the one scrollable
editor viewer never wired into the shared scroll cache.

A raw scrollTop is the wrong unit here: PdfViewer resets zoom to
page-width on each mount, and page-width resolves to a different
absolute scale for a different container width, so a cached pixel
offset restores to the wrong place. Store the pdf.js location
({ pageNumber, top, left }) instead — PDF user space, scale
independent — and restore via scrollPageIntoView with the same XYZ
destination pdf.js uses for its own scale-change restore.

Scoped to the single-pane edit path: diff and conflict-review mount
several viewers on one path, so they pass no key and keep no memory.

Closes #12117

* fix(editor): defer PDF scroll recorder arm until the restore settles

pdf.js dispatches the init `updateviewarea` synchronously from `update()`
right after the `pagesinit` handler, so arming there recorded the restore's
own provisional landing. On a mixed-page-size document that landing uses
page-1 geometry and is wrong, and a tab switch before `pagesloaded` flushed
it over the good cached position.

Arm at the first of `pagesloaded` or first user input instead; arm
immediately only when nothing was cached. If neither ever happens the
recorder never arms and the cached position survives untouched, which is
correct because the reader never moved.

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

* fix(editor): keep pdf.js location live after the PDF scroll re-apply

Two defects in the restore half, both from calling scrollPageIntoView
outside pdf.js's own scale-update path.

`#scrollIntoView` nulls `_location` when `currentScaleValue` is unset, and
it stays unset because 'page-width' resolves against a page list that is
still empty at load. At `pagesinit` that is harmless (`_location` is
already null and the init `update()` repopulates it), but the `pagesloaded`
re-apply runs with a live `_location` and nothing recomputes it — on a
uniform-page document the re-apply moves nothing, so no scroll event fires.
The next zoom then found no location, fell back to the page top, and the
recorder persisted that page-top position over the reader's offset. Call
`update()` after the re-apply to recompute it.

The user-moved guard also missed find: `PDFFindController` scrolls to a
match programmatically, and the find bar is not inside the watched
container, so neither the input listeners nor the arm gate saw a search.
Searching during the pagesinit-to-pagesloaded window yanked the reader back
to the cached page. Treat a 'find' dispatch as reader movement.

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

* test(editor): pin the PDF scroll cache wiring and drop a dead seam

The key's journey from EditorContent through ImageViewer to PdfViewer had
no coverage, so dropping the prop anywhere along it would have shipped as
silently amnesiac scrolling with a green suite. Assert both directions:
forwarded when supplied, null when omitted. Verified the tests fail when
the forwarding is removed.

Also pin the trailing-from-first-record debounce, which every existing
test advanced past and so could be mutated into a restarting debounce
undetected, and delete the `timers` injection seam — it had no callers,
so only its two fallbacks ever ran.

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

* fix(editor): do not arm the PDF recorder in a hidden worktree pane

Editors in background worktrees stay mounted under display:none so their
layouts survive worktree switches. There the container has no layout box,
so pdf.js bails out of scrollPageIntoView on a null offsetParent and
update() early-returns with zero visible pages, leaving its location null.
Arming anyway meant the reader's first zoom after switching to that
worktree scrolled to the page top and persisted it over the cached
position — turning a pane that merely failed to restore into one that
destroys the saved position.

Stay disarmed and keep the input watcher attached while the container has
no height, so the cached entry survives until the reader is actually
looking at the pane. A pane that gains layout between pagesinit and
pagesloaded still restores normally.

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

* defer PDF scroll restore until hidden pane becomes visible

Why: a display:none pane can't scroll or update pdf.js's internal location, so
the reader's initial zoom persists over the cached position. Use ResizeObserver
to detect when the pane regains a layout box, then re-apply the saved scroll.

---------

Co-authored-by: Orca <help@stably.ai>
2026-08-02 18:48:39 -07:00
Jinjing a000839465
Add first prompt to agent session history rows (#12085)
* Add first user prompt to AI Vault session history rows

Re-parse transcripts on demand to extract and display the untruncated first
user prompt for copy/reuse. List scans omit the body (payload/perf); UI loads
it when session details expand. Grok sessions extract the typed ask from
<user_query> envelope, skipping injected <user_info> bootstrap rows. Supports
Claude, Codex, Grok, and OpenCode agents.

* fix(ai-vault): split SessionTime out to pass max-lines lint

AiVaultSessionDetails exceeded the 400-line oxlint limit after adding
first-prompt UI; move SessionTime into its own module.

* fix(ai-vault): handle corrupt transcripts and fix OpenCode prompt captur

Corrupt transcripts now resolve null instead of rejecting the IPC call, matching behavior for other unavailable cases. OpenCode SQLite parsing now correctly captures all text parts from the earliest user message only, fixing truncation of large prompts and padding of small ones. Add stale-response guard in the UI to prevent late results from overwriting the current session when tabs switch. Consolidate text slicing via `sliceAtCodeUnitLimit` to avoid surrogate-pair splits across all callers.

* test(ai-vault): add first-user-prompt UTF-16 safety tests

Ensure truncation at safety limits doesn't split UTF-16 surrogate pairs,
preventing corruption of astral characters in captured prompts.

* fix(ai-vault): key first-prompt-card by session.id

Remounting the card on session switches prevents late responses from
a previous load from writing stale data into the component's refs.
Also improves conversation-turn key stability.

* fix(ai-vault): preserve first prompt after preview truncation

* refactor(ai-vault): improve first user prompt capture robustness and per

- Add 15s timeout to full-prompt load to prevent indefinite loading states
- Extract seedFullFirstUserPrompt helper for reuse across parsers
- Prevent AI-generated summaries from becoming the copyable first prompt
- Fix truncation detection in OpenCode SQLite by probing for N+1 rows
- Optimize text bounding to apply safety limit before toLowerCase
- Gate synthetic OpenCode path detection on agent type, not just # presence
- Add test coverage for remote execution host handling

* Fix FirstPromptCard loading state stranded by stale promise reuse

Clears loadPromiseRef during cleanup to prevent the dedupe handle from
causing StrictMode remounts to await stale in-flight requests. Stops loading
when session becomes non-loadable mid-request. Adds tests for StrictMode
double-invoke resolution and main-process timeout scenarios.

* refactor(ai-vault): split session parsers into modular files

Split secondary-parsers into individual files per agent type (copilot,
cursor, hermes, opencode) for improved modularity. Add test coverage
for first-user-prompt envelope handling: unwrap user_query tags and
reject bare user_info dumps.

* fix(ci): clear max-lines and flaky portal readiness check

Collapse an accidental multi-line regex wrap in ssh-connection-utils that
pushed counted lines to 301. Harden the latched-readiness test's ready
transition so CI load can re-observe attach after MutationObserver gaps.

* fix(ssh): extract proxy command helpers to pass max-lines

Move resolveEffectiveProxy/spawnProxyCommand out of ssh-connection-utils
so oxfmt line wrapping cannot push that file over the 300-line lint cap.

* capture first user prompt by ordering OpenCode messages by creation time

- Add `readOpenCodeMessagesInOrder` to rebuild transcript by timestamp, handling
  corrupt/partial files gracefully instead of discarding sessions
- Extract SSH proxy command tests to dedicated file; add backpressure handling
  and stderr draining to prevent proxy process stalls
- On Windows, reject unsafe characters in ProxyCommand values instead of
  pretending to escape them; properly format cmd.exe invocation with verbatim
  arguments
- Expand ProxyJump chains into -J plus final hop, mirroring OpenSSH behavior
- Decouple portal readiness reapply budget from flip-count budget via explicit
  constant
2026-08-02 18:45:55 -07:00
Brennan Benson 3658165119
chore(audit): pin the dead-code audit to knip@5.88.1 (#12165)
pnpm dlx knip@5 downloads and runs whatever the newest 5.x is at
invocation time, outside lockfile integrity review — flagged P2 by the
v1.4.165-rc.0 release scan. Pin the exact version, matching the
react-doctor@0.9.1 pattern one line up.

Why dlx rather than a devDependency: knip 5.x peer-depends on
typescript@^5, and this repo is on typescript 7.0.2 — installed as a
devDependency, pnpm resolves knip against TS 7 and knip crashes at
module load (verified: same knip against a TS 5 peer runs clean). The
dlx sandbox auto-installs knip's own TS 5, which is the environment the
original #12077 sweep actually ran in.
2026-08-02 18:38:30 -07:00
OrcaWin 95c431f5c3
fix(orchestration): worker-start launches the configured agent CLI, not the raw agent id (#12148)
Worker-start passed the Orca agent id straight to the shell as the worker terminal command, so `--agent cursor` ran `cursor` — which on Windows resolves to Cursor IDE's cursor.cmd and opened the desktop app, leaving a blank shell that timed out at agent_readiness. The same gap hit every agent whose CLI binary differs from its id (continue/aug/kiro/qwen-code/mistral-vibe/antigravity/trae/mimo-code/hermes/command-code/claude-agent-teams).

Adds TerminalCreateOptions.startupAgent so callers name the agent outright; createTerminal then builds the launch from the TUI agent config (command, agentCmdOverrides, default args/env, preflight trust) instead of sniffing the command string. Also covers repo-less folder workspaces, which previously skipped resolution entirely, and fails loudly instead of spawning a bare shell when an explicit agent cannot resolve.

Fixes #11926
2026-08-02 18:00:20 -07:00
Rod Boev 8c5371ebad
fix(worktrees): respect Windows shell for setup runners (#6967)
* Honor configured shells during worktree setup

* Align setup launch paths with selected Windows shells

* Carry setup shell selection through deferred launches

* Prove Windows setup shell routing at its real adapters

* Ground remote PowerShell proof in the real writer

* Preserve Git Bash across deferred setup launches

* Harden Windows setup runner shell selection

- Resolve remote PowerShell binary without local pwsh probe: for SSH/remote
  Windows worktrees, isPwshAvailable() reflects only the LOCAL host, so an
  'auto' implementation could route the remote runner to a pwsh.exe the remote
  lacks. Add resolveSetupRunnerShell(..., { probeLocalPwsh: false }) so remote
  auto keeps the always-present powershell.exe; explicit pwsh.exe still honored.
- Preserve native exit codes in the PowerShell runner by checking
  $LASTEXITCODE before $?, so a failing native command surfaces its real code
  instead of a generic exit 1; $? still catches cmdlet soft-failures.
- Write the PowerShell runner with a UTF-8 BOM so Windows PowerShell 5.1 (the
  new default powershell.exe) reads it as UTF-8 instead of ANSI, preventing
  non-ASCII setup-script corruption.
- Add unit tests for the remote-probe behavior.

* Restore setup-shell scope narrowing over the rebase

The force-pushed rebase dropped five review-fix commits that were already
on this branch; this reapplies their combined effect on top of the new
base and the hardening commit:

- Keep SSH setup shell selection remote-owned (no local terminalWindowsShell
  or pwsh routing for remote hosts; supersedes the probeLocalPwsh guard)
- Preserve cmd setup compatibility outside POSIX shells (no .ps1 runner
  family, so the BOM/exit-code hardening is no longer applicable)
- Route WSL setup runners from the project runtime
- Avoid blocking PowerShell probes during setup creation
- Correct SSH and WSL background setup fixtures

* Satisfy the changed-code gates for the setup-shell runner

- createWorktreeRunnerScript took 7 positional parameters, tripping the
  changed-code max-params gate; move it to a single options object.
- hooks-runner.test.ts deep-equals the createSetupRunnerScript result, so
  assert the cmd shell now returned for native Windows worktrees.

* Carry the setup launch shell through observed and issue runners

- buildObservedSetupCommand takes the runner's launch shell so WSL-routed
  Windows-drive setup replays use /mnt/c instead of Git Bash /c
- resolveSetupRunnerShell gates the posix runner on the same Git Bash
  resolution the PTY uses, so a missing or non-MSYS bash keeps the cmd runner
- issue-command runners carry their launch shell, and the renderer passes it
  when building the queued command
- treat a bare `bash` shell setting as POSIX like `bash.exe`

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

* fix(worktrees): close counsel P1 gaps for Windows setup shells

Route windowless/headless creates through the shell-aware setup runner when a
PTY controller is available, existence-check explicit Git Bash paths before
committing to .sh runners, thread the resolved shell into issue-command
runners, and document the intentional Git Bash interpreter flip with a narrow
scope table.

* Convert setup env to MSYS form and harden the bare cmd runner launch

C3: a Git Bash setup runner now receives ORCA_*/CONDUCTOR_*/GHOSTX_* path
values in /c/... form, matching the runner path and the shell's own HOME/PWD.
C5: extension-less `bash` resolves to Git Bash everywhere, matching how
resolveWindowsShellStartupFamily already classifies it.
C7: runner paths carrying characters that cannot be quoted on a cmd command
line launch through a delayed-expansion PowerShell shim instead, and the batch
runner disables inherited delayed expansion so `!` in setup lines survives.

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

* docs: note MSYS ORCA_* paths and bare bash Git Bash resolution

Keep the setup-shell release note aligned with C3 env conversion and C5 bare
bash resolution so the published claim matches runtime behavior.

* revert: drop windows-setup-shell doc allowlist and AGENTS link

Keep the counsel P1/P2 product fixes without expanding the docs allowlist
or AGENTS.md guidance surface.

* fix(plugins): contain Parcel unsubscribe rejections under Vitest

Dev plugin watchers fire-and-forget unsubscribe, and in-process Parcel
can reject when temp watch roots are already deleted. Catch those
rejections so they cannot fail the suite as unhandled errors.

* fix(plugins): keep in-process unsubscribe rejection surface

Swallowing Parcel unsubscribe errors broke mocked unsubscribe tests
that return non-Promises and expect rejections. Contain failures only
in PluginDevWatcher fire-and-forget paths.

---------

Co-authored-by: OrcaWin <alpha-eng@stably.ai>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
2026-08-02 17:40:58 -07:00
Yuri Chukhlib d426e35be3
fix(gitlab): count diff lines whose content begins with -- or ++ (#12133)
* fix(gitlab): count diff lines whose content begins with -- or ++

countDiffLines skipped every line starting with ---/+++ as a file header,
but a removed line whose original text began with -- (SQL/Lua/Haskell
`-- comment`) becomes a diff line `---<content>`, colliding with the
`--- a/file` header — so its deletion was silently dropped from the
+N/-N shown in the GitLab MR dialog. Same collision for an added line
whose content began with ++ (+++ flag).

Track hunk state: ---/+++ are file headers only before the first @@;
inside a hunk every +/- is content, matching the unified-diff rule git
itself uses to disambiguate headers from content.

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

* fix(gitlab): validate countDiffLines with actual diff format

GitLab's /diffs endpoint returns json_safe_diff starting at @@ without
file headers. Add comprehensive test coverage validating the collision
fix correctly handles this format: content lines beginning with -- or ++
are counted as additions/deletions.

Tests cover binary files, empty diffs, no-newline markers, and content
beginning with @@ or C-style ++. Clarify function contract: requires
hunk headers to distinguish headers from content lines.

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
2026-08-02 17:12:37 -07:00
Jinjing 1685b96a97
fix(workspace-cleanup): read reflog timestamps to avoid git maintenance (#12131)
* fix(workspace-cleanup): read reflog timestamps to avoid git maintenance

Workspace activity detection now reads the reflog to find the newest HEAD movement,
avoiding false activity signals from `git gc` and `git status` restamping logs/HEAD.
Extraction of git mtime probes to exclude files that maintenance rewrites (gitdir,
index, logs/HEAD), and instead read commit markers (COMMIT_EDITMSG, ORIG_HEAD) and
reflog entry timestamps. Expands the scan with a renderer-side activity estimate to
reconcile against the Resource Manager button's fast count. Adds deletion phase
tracking (queued vs deleting) and a mismatch notice when the two counts diverge.

* fix(workspace-cleanup): parse reflog timestamps with fewer digits and im

- Regex now accepts 1-11 digit timestamps (was 9-11); trailing timezone anchor makes digit-count floor unnecessary
- Add `removalInFlight` state to prevent duplicate removal batches; UI checks this flag alongside `removalProgress`
- Filter scan errors by selected repos; only show estimate-mismatch notice when scan is complete and error-free
- Mark candidate rows as non-selectable while deleting, even if `removing` flag is omitted
2026-08-02 17:06:11 -07:00
Brennan Benson ccb5850390
fix(cmd-j): center palette row icons on the first text line (#12144)
The leading-icon gutter used self-start with a hand-tuned pt-0.5 nudge,
leaving status dots ~2px and lucide icons ~1px above the 20px title line
box. Give the gutter h-5 to match the line box so icons center on the
first line for single- and two-line rows alike.
2026-08-02 17:01:49 -07:00
Neil 040734b18e
fix(sidebar): show collapse chevron on pinned section headers (#12147) 2026-08-02 16:56:08 -07:00
Kyle 7c7167028c
feat(voice): allow selecting a microphone for dictation (#12119)
* feat(voice): allow selecting a microphone for dictation

Persist a preferred audioinput device in Voice settings and pass it into
getUserMedia, falling back to the system default when the device is gone.

* fix(voice): resolve mic preference by label and detect mid-capture loss

Drop Chromium's 'default'/'communications' aliases from the picker — pinning
one behaved exactly like system default and silently defeated the setting.

Resolve a stored preference against the live device list before capturing:
a unique label match heals an id that Chromium re-salted, a known-missing
device skips the doomed getUserMedia attempt that clipped the first words,
and an unreadable list no longer reads as "unplugged".

Surface the input ending mid-dictation instead of feeding silent zeros, add
a permission affordance so the picker is not empty before mic access, and
toast the fallback once per preference rather than once per utterance.

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

* add e2e tests

* add e2e tests

---------

Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
2026-08-02 16:25:56 -07:00
github-actions[bot] 3c329c43b1 release: v1.4.165-rc.0 2026-08-02 23:03:25 +00:00
Brennan Benson 2efc6e5476
fix(mobile): stop support modules from registering as routes (#11652)
* fix(mobile): keep support modules out of Expo routes

* test(mobile): parse Expo route exports

* test(mobile): reject platform-specific API routes

* test(mobile): reject platform API routes unconditionally
2026-08-02 15:53:06 -07:00