Commit Graph
5146 Commits
Author SHA1 Message Date
Brennan Benson bf13ae12c8 fix(terminal): retain sleeping agent records until spawn 2026-08-24 03:17:49 -07:00
Neil afd76a4df9 fix(terminal): preserve synchronized frames on reveal (#16026) 2026-08-24 00:01:53 -07:00
Neil 95633a7883 Fix stale task-source flashes in new workspace input (#16145)
* fix(new-workspace): prevent stale GitHub URL selection

* fix(new-workspace): guard all task URL transitions

* test(e2e): make task URL frame proof runner-safe

* fix(new-workspace): guard Enter during task URL lookup
2026-08-23 22:18:39 -07:00
Jinwoo Hong 4ee41fede2 fix(automations): reveal full prompt from detail view (#16067) 2026-08-23 19:59:30 -07:00
Brennan Benson 4bc9e6b00c fix(workspace-cleanup): stop a wheel tick setting a filter, and land every filter patch (#15298)
A profile carried `activity.idleMinDays = 20` that the user never set, hiding 253
of 799 workspaces on open. Chromium mutates a *focused* number input on every wheel
tick, and before #14629 the facet panel could not scroll, so the natural response --
cursor into the panel, spin the wheel -- walked the threshold up and persisted it.

Two fixes:

- `FacetNumberField` renders `type="text" inputMode="numeric"`. A wheel cannot
  mutate a text input, and the parser already takes strings. `preventDefault` on a
  focused number input would also work, but it blocks the wheel's default action --
  which includes scrolling the nearest scrollable ancestor -- and would re-break the
  panel scrolling #14629 just fixed, in exactly the reported gesture. All five
  numeric facets share this one field.
- `patchFilters` closed over the render's `browse` snapshot, so two patches in one
  tick dropped one. It now writes through a functional update against the latest
  store state. `toggleSortField` and `clearFilters` had the same defect.

Both tests were confirmed to fail against the unfixed source before being kept.
2026-08-23 18:39:48 -07:00
Neil d9aa09cc8c fix(runtime): retire terminal snapshot probes before waiter timeout (#16149)
* refactor(renderer): use terminal paste sanitizer directly

* fix(runtime): order visible probe timeout settlement

* fix(runtime): skip unorderable visible probes
2026-08-23 18:38:52 -07:00
Brennan Benson 41ddcbde06 fix(workspace-cleanup): stop pre-selecting workspaces for deletion (#15152)
* fix(workspace-cleanup): stop pre-selecting workspaces for deletion

'Ready' is Orca's verdict about the user's own work. The dialog already
refuses to display the tier as a workspace fact, then pre-checked rows based
on it anyway — acting on the verdict more loudly than showing it would. Open
with an empty selection and let the user decide.

Removing auto-select unmasks two selection defects it was hiding, both fixed
here because they become reachable the moment select-all is the primary path:

- The header checkbox compared a canQueue-scoped selected count against a
  canSelect-scoped selectable count, so hand-picking review-tier rows flipped
  it to fully-checked and the next click cleared the entire selection.
- A filter change silently dropped selected rows; auto-select used to refill
  them instantly, so the loss was invisible. It is now reported.

Also drops the scan toast's 'N cleanup suggestions' clause, which published
the same verdict outside the dialog, and labels select-all with the count it
actually takes (the deletable subset, not every matched row).

* chore(i18n): sync the catalog for the cleanup selection strings

* fix(workspace-cleanup): preserve explicit selections
2026-08-23 18:35:27 -07:00
Brennan Benson 4c44116197 fix(workspace-cleanup): name the local context instead of totalling it (#15070)
* fix(workspace-cleanup): name the local context instead of totalling it

The delete confirmation showed 'Context: 2', a sum of five unrelated things
(terminal tabs, clean editor tabs, browser tabs, diff notes, finished agents)
rendered as plain text with no icon or tooltip. A reader cannot tell what the
number counts, which is the one thing that screen exists to tell them.

Reuse the breakdown the expanded row already renders ('Terminal tabs: 1,
Browser tabs: 1') so the confirmation names what deleting would discard. No new
strings: the per-kind labels already exist and were already translated.

* fix(workspace-cleanup): keep context labels legible
2026-08-23 18:35:05 -07:00
Jinwoo Hong 3d74f5fe03 test(remote): preserve HTML inventory RPC failures (STA-5210) (#16056) 2026-08-23 16:09:49 -07:00
Neil 0753f0a8dc fix(crash-reporting): stamp React #185 boundary attribution as unreliable (#16153) 2026-08-23 15:56:27 -07:00
Neil 0926c25854 refactor: reuse canonical regex escaping (#16150) 2026-08-23 15:45:53 -07:00
Neil b89b58bf77 fix(renderer): keep navigation responsive under terminal load (#16138) 2026-08-23 15:07:36 -07:00
Neil f0c718ecd5 refactor(renderer): use canonical attachment labels (#16135) 2026-08-23 14:46:22 -07:00
Neil 8af02d658c Preserve code editor selections across tab switches (#16132)
* Preserve editor selections across tab switches

* Defer editor selection caching to tab lifecycle
2026-08-23 14:25:03 -07:00
Jinjing 7a72f341f7 Split pty-connection.ts into focused modules under 400 lines (#15166)
* refactor: split pty-connection.ts under 400 lines

* rm design doc

* refactor(pty-connection): extract reattach payload handlers as factories

- Replace bindApplyReattachPayload with createReattachPayloadHandlers factory that returns handlers instead of mutating session directly, enabling better composability and testing
- Extract waitForUserInitiatedSshConnect as standalone function for reuse across deferred session attach flows
- Create ReattachPayloadSession type to document and isolate required session capabilities
- Add test coverage for overlapping reattach payload attempts
- Clean up comments to remove redundant prefixes (session.pane → pane, session.transport → transport)

* fix(pty-connection): correct sequencing and state bugs in spawn and reat

- Fix terminal tail slice to take prefix instead of suffix, preserving escape
  sequence markers needed by next scan
- Clear pending pane serializer when direct SSH retry PTY is unclaimed
- Initialize interrupt status baseline to undefined so first input advances
  sequence counter
- Bump reattach generation only after confirming current attempt owns the stream,
  preventing superseded results from canceling in-flight prepaint

* fix(pty-connection): correct sequencing and state bugs in spawn and reat

- Fix terminal tail slice to take prefix instead of suffix, preserving escape
  sequence markers needed by next scan
- Clear pending pane serializer when direct SSH retry PTY is unclaimed
- Initialize interrupt status baseline to undefined so first input advances
  sequence counter
- Bump reattach generation only after confirming current attempt owns the stream,
  preventing superseded results from canceling in-flight prepaint

* fix(test): increase poll iterations to prevent Node 26 test leakage

Increase event loop turns from 40 to 200 in the timer settlement loop.
Node 26's libuv poll phase can briefly starve when concurrent workers
transform tests, causing cleanup to leak into the next test. The higher
iteration count ensures async operations complete before returning.

* fix(foreground-output-budgets): use >= for budget window boundary check

At the exact window boundary, the budget should roll over. Change the
comparison from > to >= so the window resets when now equals
windowStart + FOREGROUND_BUDGET_WINDOW_MS, not just after. Add tests
to verify budget rejection and rollover behavior.

* refactor(pty-connection): add status observations and routing improvemen

- Track agent status observations with origin and transition metadata
- Separate interactive redraw input timing from general terminal input
- Restore pane authority on bind and reattach
- Refine routing trust and confirmation state handling
- Invoke queued startup callbacks when PTY is bound
- Resolve Windows shell overrides with user settings

* refactor: extract resolveLaunchAgentCandidate helper

Consolidate duplicated launch-agent resolution logic into a shared helper to prevent future divergence between paneExpectsLaunchAgent and resolveExpectedLaunchTuiAgent.

* refactor(pty-connection): use model snapshot for direct SSH reconnects

Direct SSH reconnects now restore from the full SSH model snapshot (complete scrollback) when dimensions are compatible, instead of the bounded relay tail. Falls back gracefully when incompatible or alternate-screen was exited.

* refactor(pty): retry unverifiable SSH reattaches via preserved bindings

Preserve deferred SSH session IDs longer when they serve as the only retry binding,
allowing the system to attempt recovery through direct SSH retries or PTY remounts
when reattach fails in an unverifiable way. Simplify reconnect model restoration
by removing the conditional model snapshot probe and using relay replay directly.

* test: poll terminal readiness in expectSingleOwningPty

Retry the terminal list assertion with polling to account for timing
delays in PTY state reporting from the runtime.
2026-08-23 13:55:20 -07:00
Isaque Coelhoandm4air 853afdf80e fix(status-bar): remove pet menu reserved space (#13067)
* fix(status-bar): remove pet menu reserved space

* test(status-bar): add pet segment layout validation tests

- Unit test guards against pr-[6.5rem] padding reintroduction
- E2E test measures trailing overhang instead of total width delta
  for more accurate layout validation
- Extract enableExperimentalPet helper for test clarity

---------

Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
2026-08-23 12:21:15 -07:00
NeilandMelih b2902cb61e fix(agent-resume): restore Kimi Code sessions after restart (#15883)
Co-authored-by: Melih <mberatsanli@gmail.com>
2026-08-23 02:26:52 -07:00
OrcaWinandOrcaWin 4c984d4c1b Fix Windows Git Bash console-capacity failures (#16045)
* fix(terminal): retain failed local console panes

* fix(terminal): preserve failed pane restart context

* fix(terminal): scope capacity recovery to PTY binding

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-08-23 01:24:37 -07:00
Neil 0e9f02baf3 fix(settings): an empty agent detection must not erase the saved default (#16043)
Three bugs in one screen, reported in #15256 with a diff of the user's
orca-data.json showing defaultTuiAgent going from "claude" to null.

1. The Auto pill's handler writes null, and it was rendered as the ACTIVE
   choice whenever the stored agent was merely not detected right now. So the
   pill that already looked selected was destructive: one click erased the
   setting, and a later successful detection did not bring it back. Auto is now
   active only when null is actually stored. Detection is a transient fact; the
   stored value is not, and this control reports the stored value.

2. With zero agents detected there were no agent pills at all, so the stored
   choice was both invisible and unrecoverable -- nothing to click to put it
   back. The stored agent is now always offered, labelled as saved but not
   currently detected.

3. Refresh lived inside the Installed section, which only renders when at least
   one agent was found, so the only retry control vanished in exactly the state
   that needs it. An empty result now renders its own Refresh.

This matters more now than when it was filed: #16028 makes WSL detection
legitimately return an empty set on machines where the only agent was a Windows
binary reached through interop, so the empty path is about to get more traffic.

Each of the three tests was verified to fail with its own fix reverted.
2026-08-23 00:56:36 -07:00
Brennan Benson 9ea2636b38 fix(remote): keep editor focus after unfocused HTML side preview (STA-5001) (#15716)
Opening a remote HTML preview to the side created an empty split and made it
the active group. The next host session-tab snapshot still had the terminal
active, so the client treated that empty group as a terminal focus change.

Do not activate the empty split for unfocused remote previews, and when a
reserved preview group is still empty, keep the sibling editor as the
visible tab instead of following the host terminal.
2026-08-22 22:37:40 -07:00
Neil 0cb076f03c fix(dictation): address visualizer review feedback (#16035) 2026-08-22 22:34:38 -07:00
erish b851a5e13f fix(github): add GHES avatar fallback to PR and task views (#13981)
* fix(pr-page): route remaining user avatars through GitHubUserAvatar

On a private-mode GitHub Enterprise instance the stored avatar URL 302s to
/login, and the renderer's default Electron session carries no cookie, so the
image never loads. #8784 added GitHubUserAvatar for exactly this — it degrades
to an initials placeholder via onError — but three call sites in
PullRequestPage kept a bare <img>: the reviewer picker, the comment author,
and the @ mention suggestions.

Each only guarded on avatarUrl being absent, so on GHE the URL is present, the
placeholder branch never runs, and a broken image is left on screen. The
authorAvatarUrl type comment already documents the intended contract ("falls
back to the login URL and finally an initials placeholder").

GitHubUserAvatar was already imported in this file for the PR author, so this
makes all five avatars in the page consistent. Note the three switched slots
now carry the shared border/bg styling, matching the two that already did.

Add a boundary test that fails if any avatar is rendered through a bare <img>
again.

Fixes #13976

* fix(task-page): route GitHub avatar cells through GitHubUserAvatar too

Auditing the rest of the GHE avatar path turned up the same bare <img> in
TaskPage: GitHubAssigneeAvatar, GHAssigneesCell and PRReviewCell. Fixing only
the PR page would leave half of #13976 in place.

GitHubAssigneeAvatar is the clearest case — ReviewChipAvatar directly above it
already renders through GitHubUserAvatar, so two adjacent functions disagreed
on how a GitHub user avatar is drawn. Its border also moves from
border-border/40 to /50, matching the neighbour.

Linear member avatars in this file are left alone; they use their own provider
path and are out of scope here.

Move the regression assertions into the existing repro-8784 file rather than a
new boundary test — that file already guards PullRequestPage and TaskPage
together, so it is where this belongs. The PR-page check matches the <img>
pattern instead of specific field names, so a rename or a newly added avatar
slot cannot slip past it; the TaskPage check is scoped per function to avoid
catching the Linear cells.

* test(github): scope the avatar guard per call site and cover TaskPage names

Addresses review feedback on the regression guard.

The field-name regex missed aliases and resolver expressions, and the PR-page
assertion did not require GitHubUserAvatar in each migrated slot — deleting all
three would have passed. Reject any bare <img> within the component scope
instead, which is safe now that every assertion is scoped to one function.

Drive all six slots from one table so each gets its own named case, and extend
the display-name contract to TaskPage, which previously went unchecked. The
ConversationTab entry carries displayName: null because PRComment has no
display-name field.

Reverting the fix now fails 11 cases instead of 3.
2026-08-22 22:11:55 -07:00
Jinjing 36dfcc3955 fix(terminal): arm the restore baseline only on a snapshot that painted content (#16009)
The restored-snapshot baseline permanently drops every delivery chunk at or
below the snapshot's seq, on the model's claim that those bytes are already
painted. reconcileChunkAgainstRestoredSnapshot recovers when the baseline
under-reports (a gap ahead re-restores; a rawLength mismatch re-restores) but
has no path for a baseline that over-reports: those chunks return
drop-duplicate forever, and an idle shell never re-sends them.

Gate arming on whether the snapshot painted printable cells. A snapshot
claiming seq > 0 while painting nothing cannot be the rendering of the output
it claims to cover, so the claim is disproven and the redelivery is the only
remaining copy.

Also pins disposeHeadlessTerminal's two-part write ordering, which was
previously unpinned and silently reversible.

Refs STA-5179
2026-08-22 22:01:45 -07:00
Neil dee48498b2 feat(dictation): add sound-reactive grape visualizer (#16017)
* feat(dictation): add sound-reactive grape visualizer

* perf: scope dictation meter updates
2026-08-22 21:40:47 -07:00
Neil 6785dc092d fix(composer): close the Create Workspace dialog on the first Escape (#16027)
* fix(composer): close the Create Workspace dialog on the first Escape

The modal copied the page-level "Esc blurs the focused field, then closes"
rule from TaskPage/Automations. On a page that rule protects a focus the
user chose; this dialog auto-focuses the name input on open, so its
capture-phase handler preventDefault'd every first Escape (which also
suppressed Radix's dismissal, since DismissableLayer skips a
defaultPrevented event) and the dialog could only be closed with two
presses.

Drop the Escape branch and let the dialog's dismissable layer own it.
Radix dismisses only the topmost layer, so nested popovers, selects and
dialogs still consume their own Escape first.

* test(e2e): pin the composer's auto-focus as the reason one Escape must close it
2026-08-22 21:33:54 -07:00
NeilandMelih 7ce11dcf55 fix(agent-resume): restore Copilot sessions after restart (#15879)
Co-authored-by: Melih <mberatsanli@gmail.com>
2026-08-22 21:24:44 -07:00
Neil ac103f5d90 fix(project-groups): route rename and delete to the group's owning host (#15889) 2026-08-22 21:15:31 -07:00
Neil e9e238c883 refactor(wsl): delete the environment-policy layer the reviews kept failing on (#16007)
* refactor(wsl): delete the environment-policy layer the reviews kept failing on

A design council (Opus, Grok, GPT-5.6-Sol) reviewed the merged runner after it
took eleven review rounds to land. All three reached the same conclusion: the
invocation half is sound, the environment/probe half is not, and every round had
been debugging the second one.

The finding that settled it, from Opus: `environmentResolved` had **54
references, all in tests and the runner itself. Not one production reader.** The
safety mechanism the strict default existed for was never wired to anything, so
all 19 degrading sites reported absence with full confidence anyway -- #9725
live at every one, under comments claiming it was handled. Two of those comments
say so out loud; I wrote them.

Root cause, in one line: every knob existed only because a failed probe was
fatal. So it no longer is.

- `allowDegradedEnvironment` and `WslGuestEnvironmentUnavailableError` are gone.
  A missing login PATH is a fact in the result, not an exception. That deletes
  23 opt-outs, six catch-and-remap blocks, the transient/rejected cooldown
  split, `probedWithBudget`, and the 1.5x re-probe heuristic -- none of which
  had a reason to exist once the case stopped throwing.
- `lane` + `allowDegradedEnvironment` collapse into `loginPath: 'none' |
  'preferred'`. 19 of 23 sites passed the opt-out, and two said in comments that
  they did not want the login PATH at all: the flag had become the `'none'` the
  union was missing.
- The `interactive` lane is deleted. It had zero production callers and kept ~30
  lines of fence plumbing alive for tests only.

Net -98 production lines; the runner itself sheds 86 for 38.

Also carries three fixes from the W3 orphan-PR sweep I had not done:
- `WSL_UTF8=1` in the runner. My relay migration deleted the only place setting
  it, so wsl.exe's own error text arrived UTF-16LE and read as NUL-riddled.
  A regression I introduced. Credit: #9010 (Chang-Jin-Lee).
- `GITLAB_HOST` is now named in WSLENV, so a ported self-hosted host actually
  crosses into a distro-routed glab (#12557). Credit: #12558 (makoto-developer).
- The WSL skill-setup command pipes into `sh` instead of `eval "$(...)"`, whose
  nested quoting produced `word unexpected (expecting "in")` (#14292). Credit:
  #14785 (innocarpe).

* fix(wsl): restore the login PATH for the Codex availability lookup

loginPath:'none' on a PATH lookup reports an nvm-installed codex as absent,
which is #9725. A miss without a resolved environment is now 'could not
check', not 'not installed'.

Also hardens the guards that should have caught it:
- bashism ratchet is per-call, not per-file, and fails closed on lexer desync
- blankStringContents handles regex literals (an apostrophe in /'/g desynced
  the lexer, so the scan silently found zero calls)
- windowsHide allowlist 85 -> 80, stale once the lexer parsed those files

Credit: Grok (P0), GPT-Sol (ratchet gaps).

* test(wsl): close the two ratchet gaps that let planted spawns pass

- variable-indirected wsl.exe (`const b = 'wsl.exe'; spawnProcess(b)`) is now
  tracked, so the 5 files recorded only in a comment become real allowlist
  entries. Three actually spawn that way; the other two never spawned wsl.exe
  at all, so the prose record was wrong by three in the hiding direction.
- promisify(renamedAlias) is now resolved, so `const run = promisify(execFile)`
  behind an `execFile as x` import can no longer skip windowsHide.

Each verified by planting the violation, watching it fail, restoring, watching
it pass. Credit: GPT-Sol.

* fix(source-scan): stop the regex-literal reader from eating block comments

At index 0 there is no preceding token, so a file opening with a banner
comment had its `/*` read as a pattern and swallowed to the next slash --
110k characters of preload/index.ts, in the direction that hides offenders.

Measured across the tree, old lexer vs new: worst-case over-blanking drops
from -110564 to -1116 characters, and files that desync drop from 51 to 22.
The remaining extra blanking is regex interiors, which is the intent.

Regression tests for both lexer bugs, each verified to fail with its fix
reverted. The first draft of the comment test did not bind -- it asserted on
text after the swallowed span.

* fix(wsl): restore the unverifiable signal on the two remaining probe sites

Round 2. Three call sites used to throw when the login-PATH probe failed;
the redesign rewired one (Codex) and left two reporting confident absence.

- skill-wsl-provider-detection: the script ends in `|| true`, so a lookup
  without the login PATH exits 0 with empty stdout -- identical to 'nothing
  installed'. Callers skip the ~/.codex and ~/.claude skill roots on an empty
  list, losing an nvm-installed provider's skills.
- wsl-cli-installer: the dead catch is replaced by an explicit check. Its
  `case ":$PATH:"` probe otherwise answers from the distro default PATH and
  Settings states as fact that the CLI is not on PATH. Timeout is checked
  first, since a timed-out run also leaves the environment unresolved.

Also narrows the regex-literal prev-token set. '!', '+', '-', '>' and '}' are
value terminators as often as operators, so postfix `n-- / 2` and JSX
`<A size={14} /> : <B` were read as patterns and their spans blanked -- 13
live JSX spans, and one swallowed execFile call that left no desync behind.
False negatives only risk a desync, and desync fails closed.

Plus: WSL_UTF8 on the probe spawn (#9010 reached the runner, not the probe),
and the allowlist header I shuffled by sorting comments along with entries.

Credit: Grok (both P1s), Opus (lexer false positives).

* docs(wsl): drop the lane comments the redesign made false

The interactive lane is gone, so 'both lanes' and the fenced-stdout note
described code that no longer exists. Also states plainly that
environmentResolved is always true under loginPath:'none' -- the field cannot
rescue a PATH lookup that was mislabelled, which is how #9725 came back.

Credit: Grok.

* fix(wsl): stop piping user scripts into the shell's stdin

The W3 migration moved hooks from `wsl.exe --exec bash -c <script>` to a
script piped into `bash -s`. Anything the script runs that reads stdin then
drains the rest of the script, bash hits EOF and exits 0, and the caller logs
success -- an orca.yaml hook of `ssh -T git@github.com || true` followed by
`pnpm install` silently never installs.

Scripts now travel in argv by default, which is what the pre-migration code
did and what --exec makes safe. `scriptDelivery: 'stdin'` stays for the one
caller that needs it: the hook-relay installer embeds a base64 JS bundle far
past any command-line limit, and reads no stdin.

A runner test already described this exact EOF hazard -- for the login shell,
not for the guest command it was itself creating.

Credit: code review.

* fix(skills): make the unverifiable check unconditional, and stop double-probing

Round 3.

- provider detection threw only on an EMPTY result, so a degraded partial hit
  slipped through: `claude` visible on the default PATH via Windows interop
  plus an nvm-only `codex` returns a plausible ['claude'], and the caller then
  skips the ~/.codex skill roots for a provider that is installed. The
  installer already got this right with an unconditional throw.
- three sites asked for 'preferred' without needing it. The GROK_HOME probe
  runs its own `"$login_shell" -lc`, so the runner's probe was a second login
  shell eating up to half an 8s budget; the two skill scans are
  find/base64/head/printf/stat over $HOME.
- the indirection binder missed `private readonly x = 'wsl.exe'` (the
  modifier was captured as the name), backtick literals, and
  `spawnProcess(this.x)`. Commit 2bbbd99 claimed that gap closed; it now is,
  verified against all three shapes.

Credit: Grok.

* fix(child-process): keep the tail of output whose failure lands last

Two console-flash bugs the ratchet was carrying on its allowlist rather than
catching: daemon-process-inspection execs powershell.exe and the gemini
extractor execs `where gemini`, both console-subsystem, both without
windowsHide (#10488). Allowlist 80 -> 78.

And a migration regression: the hook-relay install used to keep a rolling
tail of stderr (`slice(-MAX)`), while runProcess's maxOutputBytes keeps the
head. A guest install that fails after pages of apt warnings therefore
reported the warnings instead of `mv: Read-only file system`. runProcess
takes retainOutput: 'tail' for output whose meaning is at the end.

Credit: code review.

* test(wsl): close the last two indirection shapes in the binder

`this.binary = 'wsl.exe'` has no declarator keyword, and a helper that just
returns the literal is a spawn one hop away that no regex can follow. The
return case fails closed only when the file also spawns something --
local-windows-terminal-runtime.ts returns the name as terminal metadata and
never spawns, so a blanket rule flagged it wrongly.

Verified against both shapes: planted, failed, restored, passed.

Credit: Opus.

* fix(preflight): stop reporting installed WSL CLIs as absent (#9725)

The last two probe sites that turned an unresolvable login PATH into a
confident negative. The native branch of detectInstalledAgents already
consults install dirs for exactly this reason ('PATH may still be unhydrated
on a cold GUI launch'); the WSL branch had no equivalent, so a cold distro
made an nvm-installed claude/codex read as not installed and told the user to
install a CLI their own terminal runs.

Ports that fallback to the guest: agent detection checks the version-manager
bin dirs for commands the PATH lookup missed, and the preflight command runner
APPENDS them to PATH -- append, never prepend, so a resolved login PATH stays
authoritative and a stale nvm version cannot shadow the real binary.

Tested by executing the generated scripts through /bin/sh against planted
binaries, since the behaviour is shell globbing and [ -x ]. Both the
nvm-discovery and the no-shadowing tests were verified to fail when reverted.

* fix(codex-accounts): hide the console on the legacy active-home migration

execFileSync('wsl.exe') with no windowsHide flashes a conhost and steals
foreground on a GUI-launched Orca (#10488). Sibling WSL spawns got this in
earlier commits; this one only had its quoting rewritten. Allowlist 75 -> 74.

Credit: code review.

* fix(windows): close the shell:true hole that made windowsHide a no-op

I un-allowlisted the gemini extractor after adding `windowsHide: true` to an
`exec()` call. `exec` implies `shell: true`, which this repo's own chokepoint
documents as silently making windowsHide a no-op (#14543) -- so the site still
flashed a conhost while reading as guarded. Now execFile('where.exe', …),
matching the relay sibling that already did it right.

The ratchet could not see that, which is why it passed. It now treats a call
that resolves to exec/execSync, or any `shell: true`, as unguarded regardless
of windowsHide -- including through renamed imports and a renamed promisify.

Also: a script over 8000 chars now falls back to stdin. Windows caps a command
line at 32767 and a user's orca.yaml hook is the one unbounded script Orca
runs (`run-both` concatenates two; a vendored installer is ~15KB), so argv
would fail to spawn outright. Degrading beats failing.

And the binder now sees `let p: string` ... `p = 'wsl.exe'`.

Each verified by planting. Credit: Grok.

* test(wsl): an opaque payload must declare its interpreter

My per-call bashism guard REPLACED the file-wide one, and that was a strict
regression: the real payloads are built in a separate function and passed as a
bare `script,`, so the bashism is never inside the call literal and the
per-call arm cannot fire. Deleting `shell: 'bash'` from skill-discovery-wsl
-- `done < <(find ...)` and `read -r -d ''`, the #14292 signature -- passed on
this branch and failed on main.

Reading through the identifier is guesswork. Requiring the call to name its
shell when the payload is not a literal is not, so seven POSIX call sites now
say `shell: 'sh'` -- no behaviour change, sh was already the default.

Two earlier attempts at this were wrong and are worth recording: a whole-file
BASHISM test blamed codex-accounts/service.ts, which correctly pins bash on its
four inline payloads and correctly leaves printf/mkdir unpinned; and excluding
call text still caught a bash payload belonging to a non-runner execFileSync.

Also: runProcessSync now refuses retainOutput:'tail' instead of silently
keeping the head, and the union docblock no longer describes stdin delivery.

Verified against both of the plants that exposed this. Credit: Opus.

* test(wsl): judge an opaque payload by the file, not by whether shell is set

Round 5. My previous rule -- opaque payload must have `shell:` -- was the
third guard fix in a row that came out weaker than what it replaced:
`shell: 'sh'` on a bash payload satisfied it, which is #14292 with extra
steps. Flipping skill-discovery-wsl's pin from bash to sh shipped green.

Now: strip the text of every call that already names bash, and if a bashism
survives anywhere in the file while a script-carrying call is not bash-pinned,
flag it. Stripping the bash-pinned calls is what keeps codex-accounts clean.

Also closes four ways to hide a call from the collector, each verified by
planting:
- `script: \`${bashism}\`` -- a template literal read as a visible literal
- `runWslProcess({ ...spec })` -- a spread hides script AND shell
- `Object.assign({ a }, { script })` -- the collector took the first `{`, so it
  now takes the whole argument list
- `import { runWslProcess as runWsl }` -- a renamed callee collected nothing,
  and zero calls read as zero violations

Not fixed, recorded instead: a computed `shell:` in the console guard. Matching
any non-false value also flags `shell: spawnConfig.shell`, a pass-through that
is false in every branch, and a false positive there costs an allowlist entry
that disables the guard for a whole correct file.

Credit: Grok.

* fix(preflight): make the guest fallback match the native one it claims to mirror

Three defects in the #9725 fix from earlier today, all found by executing the
generated scripts under real dash rather than reading them.

- $HOME containing a space word-split the unquoted dir list into a relative
  path, so every CLI read as absent -- the exact symptom the fix exists to
  remove. Each entry is quoted now; the nvm entry quotes only its prefix so the
  glob still expands.
- A directory passes `[ -x ]`, so ~/.local/bin/gemini/ was reported as an
  installed CLI that then fails to launch with EISDIR. The PATH half of the
  same script already guarded this, and so does the native twin.
- The header called this the "guest-side twin" of the native fallback while
  omitting four of its directories: volta, asdf, fnm and mise. A WSL user on
  any of those still had #9725 while the same user on native did not -- and
  asdf and mise are named in the motivating comment. The claim is now true.

Credit: Opus.

* test(wsl): mask bash-pinned calls by position, not by String.replace

`rest.replace(text, '')` with a string pattern removes only the FIRST match,
so two identically-written pinned calls left one behind and its bashism then
counted against an unrelated unpinned call in the same file. A body that also
occurred earlier as a substring would blank the wrong region entirely.

The collector now returns ranges and the mask is applied by index. Verified
both directions: two identical pinned bodies plus one unpinned call flags, and
the same file with all three pinned stays clean.

* test(wsl): fail closed on call shapes a regex cannot attribute

Round 6. Rather than widen the pattern again, treat the shapes it cannot
reason about as unreadable.

A regex cannot tell which object a key belongs to, so every round produced
another way to put the pin in one place and the payload in another:
`cond ? {pinned} : {unpinned}`, `{...} as WslSpec`, `Object.assign({a},{b})`.
A call whose SPEC is chosen by a ternary or spread -- one appearing before the
first `{` -- or which carries an `as` assertion is now flagged whenever the
file has a bashism, with no `shell: 'bash'` escape, because the substring test
that would grant the escape is exactly what cannot be trusted on these shapes.

A ternary INSIDE the object is not exotic: claude-accounts/service.ts:977 uses
one to choose a script line in a call that is already pinned, and treating that
as opaque would demand a second pin it already has. Nor is a nested call --
`script: `x ${shellQuote(p)}`` is how every payload here is built, and flagging
it would demand bash on POSIX payloads that must not have it.

Also follows `const run = runWslProcess`, generics and optional chaining, and
counts collected calls against mentions so a shape that slips the pattern reads
as unreadable rather than clean.

I tried the TypeScript parser first, which would remove the class outright.
TypeScript 7 is the native port and exposes no JS compiler API; oxc-parser
works but is transitive, and declaring it surfaced an unmet peer warning.
Recorded here so the next person does not repeat the detour.

Credit: Grok.

* fix(wsl): fish is a PATH lookup, and my lint check could not fail

Two things Opus caught that I had verified wrongly.

`wsl-fish-history-cleanup` passes `program: 'fish'` -- a bare name, so a PATH
lookup by definition, the exact class the earlier rounds hunted. I mapped it to
'none' and then defended that in an audit, because I read
`allowDegradedEnvironment: true` as "does not need the login PATH". It does not
mean that: it means "do not fail when the probe fails". The old call still USED
the login PATH whenever it got one, which is 'preferred'. Under 'none' a fish
from linuxbrew or nix is invisible and the cleanup throws. The truncated
comment left behind when the flag was deleted is finished too.

And `pnpm lint` has been failing on this branch while I reported it clean: I
grepped for `error eslint|error oxlint`, but oxlint prints the rule category
(`error typescript(array-type)`, `error unicorn(prefer-ternary)`). The grep
could not match, so it never failed. Checking the exit code instead surfaced a
third violation hidden behind the first two.

Credit: Opus.

* chore(wsl): clear the round-7 P2s

- Formatting: the branch owned 22 of the tree's 26 oxfmt failures because I
  never ran the formatter. Branch files now own none.
- resolveScriptDelivery was computed twice, in two places that must agree
  about argv shape and stdin payload. Resolved once and threaded through.
- The allowlist header said the list only shrinks while the branch added three
  entries. It grew because the scanner learned to follow a variable-bound
  'wsl.exe'; those three were previously recorded in prose, so the count was
  wrong by three in the direction that hides offenders. The header now says so.
- Two test comments still explained behaviour via the deleted
  allowDegradedEnvironment flag; a stray triple blank line; two adjacent JSDoc
  blocks where only the second attached.

Not taken: platform-guarding addWslEnvKeys. WSLENV is inert off Windows, and
the guard broke a test that asserts the key directly -- more surface than the
tidy is worth, so the reason is recorded at the call site instead.

Credit: Opus.

* test(preflight): plant a fabricated CLI name, not a real one

CI caught what my local run could not: the runner has a real /usr/bin/gh, so
`command -v gh` resolved to it and the planted nvm stub was never reached. The
fallback APPENDS, so that is the code behaving correctly -- the test was
asserting a property of my machine.

Both real-shell suites now plant `orca-fake-cli`, which exists nowhere.
Re-verified the same way as before: with the PATH fallback disabled the test
fails, with it restored it passes.

I declared this branch merge-ready without looking at CI. Local green is not
the gate.

* refactor(wsl): delete two knobs and a duplicated fallback

Elegance pass. The branch had grown from a deletion into a net addition, and
most of the growth was optional axes with one caller each.

- `retainOutput` is gone. One production caller wanted the tail of a 64KiB
  buffer; head-truncation only hurt because of that cap. The caller drops the
  cap, keeps the default, and slices the tail itself -- which is what the live
  relay next door already does. Two mechanisms for one job became one.
- `scriptDelivery` is gone. The size rule was already the whole design:
  argv unless the script is too long for a Windows command line. The option
  existed so a small Orca script could opt into stdin, and no such caller ever
  appeared. Both behaviours stay pinned: a huge script still goes to stdin, an
  ordinary one still leaves the hook's stdin free.
- Agent detection no longer walks the fallback dirs itself. It prepends the
  same PATH prelude the preflight command runner uses and lets the ordinary
  lookup do the work. Its bespoke walk had duplicated the lookup script's
  `! -d` guard -- and had missed it once, which is how a directory read as an
  installed CLI.

All 17 detection tests still pass unchanged, including the $HOME-with-a-space,
directory-is-not-a-CLI, and volta/asdf/fnm/mise cases, so the collapse is
behaviour-preserving rather than assumed to be.

Credit: Grok.

* fix(wsl): never name a path-shaped variable in WSLENV

`buildHostEnv` forwarded every caller-supplied key into WSLENV. wsl.exe
translates path-shaped variables between Windows and Linux form, so a caller
passing PATH would have replaced the guest's own PATH with a translated
Windows one -- silently, and fatally for every lookup after it.

No caller passes PATH today. The point of a chokepoint is that it does not
depend on that staying true.
2026-08-22 20:35:59 -07:00
erish 063b804298 fix(i18n): match CheckRunJobs succeeded to its sibling count-label register (#16013)
succeeded rendered as casual declarative 성공했다 ('it succeeded') next to
skipped's polite 건너뛰었습니다 and pending's noun-phrase 보류 중, in a summary
that joins all three after a count: '3 성공했다 · 1 건너뛰었습니다'. Machine
translation read succeeded as a finished sentence instead of the noun label
the other two siblings use. Switch to 성공 and pin it in the key-override
file so the catalog regen script can't revert it; #15875 fixed five other
keys in the same family but its hardcoded regression map didn't cover this
one.
2026-08-22 18:19:05 -07:00
fb5c9a1fe6 fix(ui): clear agent attention icon when Floating Workspace tab activated via keyboard (#15745)
* fix(ui): clear agent attention icon when Floating Workspace tab activated via keyboard

When a tab in the Floating Workspace is activated through keyboard shortcuts
while the Floating Workspace is not the active worktree, the agent completion
notifications were not being acknowledged, leaving the yellow attention icon
visible.

The issue was that `useAutoAckViewedAgent` only checked the global `activeTabId`,
which doesn't change when the Floating Workspace is not the active worktree.

Now the hook also watches the Floating Workspace's active tab
(`activeTabIdByWorktree[FLOATING_TERMINAL_WORKTREE_ID]`) and acknowledges agents
when they become visible in the Floating Workspace, regardless of whether it's
the active worktree.

Fixes #15700

* fix(ui): gate Floating Workspace auto-ack on panel visibility

The floating-workspace scan added in the previous commit had no visibility
gate. The panel stays mounted while closed (use-floating-workspace-panel:
shouldMountPanel), so its layout still resolves an active leaf and the hook
acked a floating agent completion the moment it landed — silently killing the
minimized toggle's attention dot (selectFloatingWorkspaceHasUnread), which is
the only "unseen floating activity" signal a closed panel has.

- Scan the floating tab only while the panel is actually visible
  (isFloatingWorkspacePanelVisible), so a closed panel keeps its dot.
- Move the activeView filter onto the main-worktree target only: the panel is
  an overlay above every view, so it must ack from the activity/tasks views too.
- Carry the owning worktree with each target instead of re-deriving it by tab
  id, and keep the first entry on a tab-id collision, so a duplicate id can no
  longer clear the wrong worktree's unread. Drops the `as string[]` cast.
- Re-scan on TOGGLE_FLOATING_TERMINAL_EVENT (next frame, after aria-hidden
  commits) since panel open/closed is React state the store never sees —
  opening onto an already-active completed tab now acks.
- Cover the new resolveAutoAckTabTargets helper, including the closed-panel
  regression asserted against selectFloatingWorkspaceHasUnread.

* fix(ui): re-scan floating workspace auto-ack on every panel-open path

The visibility gate read the panel's aria-hidden and only re-scanned on
TOGGLE_FLOATING_TERMINAL_EVENT, so the two paths that open the panel without
that event — the floatingWorkspace.maximize keybinding and the default
floating-button toggle — left an already-active completed tab's attention icon
lit. Drive the gate from the committed `enabled && open` state instead: that is
what aria-hidden is derived from, it covers every open path, and it drops the
requestAnimationFrame that existed only to outrun the un-committed DOM read.

Adds a hook-level test (happy-dom) that fails both when the gate is removed and
when the open re-scan is removed.

* fix(ui): re-read store state per auto-ack target

Acking the first target writes to the store and re-enters the scan
synchronously, so the pre-write snapshot could re-ack a target the
nested pass already handled. Idempotent today; a footgun for the next
non-idempotent action.

---------

Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-08-22 17:22:49 -07:00
Neil 4828c6bed4 UX: compact native density for context + dropdown menus (#15924) 2026-08-22 15:30:23 -07:00
JinjingandNeil 8d021c0b5a test(automations): assert in-place language switch on mounted picker (#15977)
* fix(automations): localize schedule weekday names and labels

The Weekly Day picker rendered a hardcoded English tuple, and shared
schedule labels built copy as `${day}s at ${time}` from an OS-locale
Intl weekday, so a non-English UI showed Sunday…Saturday (or 星期五s).

Shared now emits deterministic English (the CLI contract) plus a
locale-free AutomationScheduleDescriptor; the renderer formats that
descriptor through translate() with Intl/CLDR weekday names resolved
from getIntlLocale(). Fixes #14404.

* test(automations): assert localized weekday copy in rendered DOM

The existing coverage walked the React element tree, so nothing proved the
Day dropdown and cron status row reach the DOM localized. Mount the picker
under happy-dom with the Radix Select swapped for a native <select> (the
pattern RepositoryWorktreeDefaultsSection.test.tsx already uses, since Radix
portals its content only once opened) and read real option text.

Also key the weekday SelectItems by index rather than by translated copy, so
a runtime language switch reconciles instead of remounting all seven items.

* fix(automations): keep the weekday SelectItem key off the array index

react-doctor(no-array-index-as-key) rejects `key={index}`; the localized
weekday name is already unique per locale, so keep it as the key.

* fix(automations): match the real AutomationDraft shape in the render test

The fixture invented `repoId`/`branchMode`/`enabled` fields; runtime ignored
them but `tsc` did not. Mirror AutomationSchedulePicker.test.ts's fixture.

* fix(automations): localize the custom-cron field chips

The five cron field headers rendered one row above the status row this PR
localizes were still hardcoded English, so a Chinese UI showed
Minute/Hour/Day/Month/Weekday. Same defect shape as the deleted DAY_OPTIONS
array. Chip keys move to stable field ids so a locale that renders two fields
with the same word cannot collide, and the truncated header carries a title so
longer copy (es 'Dia de la semana') stays readable.

* fix(automations): keep weekday option keys stable

* test(automations): assert in-place language switch on mounted picker

Add a test that changes the language while the weekly picker remains mounted,
then checks that the localized labels update while the underlying values
("0"–"6") stay stable. This validates the regression-prevention that stable
index keys (added in #15884) support — without them, a locale change would
unmount and remount options, breaking the persisted dayOfWeek value.

Wrap the picker in a LanguageAwarePicker harness that calls useTranslation(),
mirroring the root-level subscription in main.tsx that drives real
re-renders on language change.

---------

Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-08-22 11:25:19 -07:00
Jinjing 6c1286b592 Add Artifacts and Skills pages to navigation history (#15969)
* Add Artifacts and Skills pages to navigation history

- Record Artifacts and Skills visits in back/forward navigation like Automations
- Both pages properly rewind history when closed to the previous live entry
- Extract rewindHistoryIndexPastView() helper to deduplicate close-page logic across all page types
- Add test coverage for Artifacts/Skills navigation, separate entries, and shared link handling

* Add Artifacts and Skills pages to navigation history

Back/forward buttons now appear when navigating to Artifacts and
Skills pages, consistent with Terminal, Tasks, and Automations.
2026-08-22 11:00:01 -07:00
Jinjing 9ea1d28970 Fix Cmd+J Enter for worktree creation (#15970)
* fix(cmd-j): allow Enter to create worktree

* test: verify create dialog closes on Escape
2026-08-22 10:55:05 -07:00
NeilandJinjing 6e18c18e58 fix(automations): localize schedule weekday names and labels (#15884)
* fix(automations): localize schedule weekday names and labels

The Weekly Day picker rendered a hardcoded English tuple, and shared
schedule labels built copy as `${day}s at ${time}` from an OS-locale
Intl weekday, so a non-English UI showed Sunday…Saturday (or 星期五s).

Shared now emits deterministic English (the CLI contract) plus a
locale-free AutomationScheduleDescriptor; the renderer formats that
descriptor through translate() with Intl/CLDR weekday names resolved
from getIntlLocale(). Fixes #14404.

* test(automations): assert localized weekday copy in rendered DOM

The existing coverage walked the React element tree, so nothing proved the
Day dropdown and cron status row reach the DOM localized. Mount the picker
under happy-dom with the Radix Select swapped for a native <select> (the
pattern RepositoryWorktreeDefaultsSection.test.tsx already uses, since Radix
portals its content only once opened) and read real option text.

Also key the weekday SelectItems by index rather than by translated copy, so
a runtime language switch reconciles instead of remounting all seven items.

* fix(automations): keep the weekday SelectItem key off the array index

react-doctor(no-array-index-as-key) rejects `key={index}`; the localized
weekday name is already unique per locale, so keep it as the key.

* fix(automations): match the real AutomationDraft shape in the render test

The fixture invented `repoId`/`branchMode`/`enabled` fields; runtime ignored
them but `tsc` did not. Mirror AutomationSchedulePicker.test.ts's fixture.

* fix(automations): localize the custom-cron field chips

The five cron field headers rendered one row above the status row this PR
localizes were still hardcoded English, so a Chinese UI showed
Minute/Hour/Day/Month/Weekday. Same defect shape as the deleted DAY_OPTIONS
array. Chip keys move to stable field ids so a locale that renders two fields
with the same word cannot collide, and the truncated header carries a title so
longer copy (es 'Dia de la semana') stays readable.

* fix(automations): keep weekday option keys stable

---------

Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
2026-08-22 10:53:55 -07:00
OrcaWinandm4air 72f896d677 feat(automations): navigate table search results with arrows (#15805)
* feat(automations): navigate search results with arrows

* Set overview tab for external automations on arrow selection

External automations lack a runs tab, so the detail pane must default to overview when navigating via arrow keys to keep the tab selection valid when the automation is later opened.

---------

Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
2026-08-22 10:15:44 -07:00
Jinjing 80b2a02377 Move worktree palette search to top of sidebar nav (#15854)
* Move worktree palette search to top of sidebar nav

The Cmd+J search button is now displayed as the first item in the
sidebar navigation, improving discoverability. Styling is simplified
to match the nav item layout with flex-based display and consistent
spacing.

* Test: add guard clause for worktree palette search button

- Add explicit type annotation for the search button querySelector
- Guard against missing button with clear error message
- Use direct property access now that button existence is verified
2026-08-22 10:08:00 -07:00
Neil 990b23611e fix(i18n): correct five Korean strings that changed meaning in machine translation (#15875) 2026-08-22 01:40:08 -07:00
Jinjing 9725654855 Revert "Revert "Update Android download links to 0.0.44 (#15898)" (#15899)" (#15905)
This reverts commit a04fe24c80.
2026-08-22 00:47:07 -07:00
Jinjing a04fe24c80 Revert "Update Android download links to 0.0.44 (#15898)" (#15899)
This reverts commit 5ce356cc4a.
2026-08-22 00:18:20 -07:00
Jinjing 5ce356cc4a Update Android download links to 0.0.44 (#15898) 2026-08-22 00:06:42 -07:00
Jinwoo Hong 1354ff534f fix(cmd-j): host-qualify browser and simulator tab candidates (STA-4965) (#15686) 2026-08-21 23:17:54 -07:00
Hwanseok Choi bb8c3b6360 fix(agent-catalog): update outdated Claude Code doc links (#15387) 2026-08-21 22:27:14 -07:00
Jason BrashearandClaude Opus 5 b63292bee1 fix(editor): detect .liquid files as Liquid (#15213)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 22:25:42 -07:00
Jinjing 42a42281bd Remove agent map view from dashboard (#15853)
* Remove agent map view from dashboard

Removes the view toggle and simplifies the dashboard to show only the kanban board layout.

* Assert boardProps is initialized on drawer open
2026-08-21 21:43:54 -07:00
Denis Darii d5667376b0 feat(dashboard): add a keyboard shortcut to toggle the Agent Dashboard (#15353)
Adds a configurable, unbound-by-default `dashboard.toggle` action that toggles the Agent Dashboard (in-window drawer or pop-out, per the existing mode setting).

- Wired through window-shortcut-policy, main-window dispatch, browser-guest dispatch, preload, and the renderer IPC handler.
- Opening the in-window drawer reveals the sidebar first; closing leaves it alone.
- Gated on the `experimentalAgentDashboardPopout` experiment, and the Settings shortcut row is hidden while that experiment is off.
2026-08-21 21:12:12 -07:00
Jinwoo Hong e7b047f53a fix(codex): suppress false restart notices after reauth (#15835) 2026-08-21 18:09:41 -07:00
Yeray 013eb539d5 Change 'SEÑOR' to 'MR' in Spanish locale (#15594) 2026-08-21 17:27:11 -07:00
Jinwoo Hong da6b9d8065 fix(terminal): stop orphaning live agent terminals across host restarts and graph syncs (#15644) 2026-08-21 17:11:17 -07:00
OrcaWinandBrennan Benson 2a68b78bb3 fix(worktree): let a configured worktree base outrank a built-in visibility source (#15232) (#15430)
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
2026-08-21 12:20:04 -07:00