Commit Graph

8268 Commits

Author SHA1 Message Date
Asurada de4f272b31
fix(i18n): standardize Chinese status bar usage labels (#12881)
* fix(i18n): standardize Chinese status bar usage labels

Signed-off-by: ousugo <dkzyxh@gmail.com>

* fix(i18n): align Antigravity usage description

Signed-off-by: ousugo <dkzyxh@gmail.com>

* fix(i18n): standardize the zh status bar usage labels the menu actually renders

The status bar item menu renders "<Brand> Usage" for eight providers. Claude,
Codex and Gemini read 使用情况; Antigravity, OpenCode Go, Kimi, MiniMax and Grok
read 使用量, so one dropdown showed two words for one concept.

Register the decision where the repo already keeps it — the zh block of
locale-value-overrides.mjs already pins Claude/Codex/Gemini Usage — so the
repair pass enforces it instead of the catalog drifting again, and add the
missing Kimi entry to BRAND_MISTRANSLATIONS so 基米 can no longer come back.

---------

Signed-off-by: ousugo <dkzyxh@gmail.com>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
2026-08-07 22:08:22 -07:00
Neil c3bf22b9a8
[P2] perf(windows): stop the capability poll respawning blocking wsl.exe probes (#11698)
* perf(windows): stop the capability poll respawning blocking wsl.exe probes

#11295 added a 30s renderer interval to `useWindowsTerminalCapabilities` whose
early-return only fires when WSL is available with at least one distro, so on the
common Windows host (no WSL) it re-ran a full capability read forever. Each read
IPCs four probes whose main-process handlers were synchronous `execFileSync` calls
to wsl.exe/pwsh.exe, blocking the Electron main event loop for up to 5s a time.

The un-latching intent is kept: a host that answers "no WSL" is still re-checked,
now on an exponential backoff (30s, +60s, +120s) that parks once the answer stops
moving, re-arms on window focus, is shared by all consumers of an owner key, and
stops entirely when the last consumer unmounts. The wsl/pwsh IPC handlers now use
async twins that share the existing caches and back off identically.

* fix(windows): classify async wsl/pwsh probe failures with the execFile error shape

The async twins feed `execFile` callback errors into classifiers written for
`execFileSync`: a non-zero exit lands on `error.code` as a number rather than
`error.status`, and a timeout is a SIGTERM kill rather than ETIMEDOUT.

So a Windows host without WSL (wsl.exe ships in System32, so it exits non-zero
instead of ENOENT) was cached as retryable, shrinking the shared window from
10min to 45s and making the still-sync callers re-pay their blocking spawn ~13x
more often; and a pwsh cold start past 5s cached "pwsh missing" for 30s,
demoting the user's PowerShell 7 preference — the exact case the ETIMEDOUT
branch exists to prevent.

Also drops a literal NUL byte from the new re-probe module's signature
separator, which made the file binary to git, and seeds `lastProbeAt` at
registration so focus churn right after mount cannot defer the first re-probe
indefinitely.

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

* perf(windows): route relay host-capability probes through the async wsl/pwsh twins

A paired web/mobile client resolves `useWindowsTerminalCapabilities` to a local
target (TabBar's `isWebClient` gate, and `useSettingsNavigationMetadata` forces
`{kind:'local'}`), so the new re-probe arms there too. But `window.api.wsl/pwsh`
on a web client is not the ipc/app.ts channel — it is `host.wsl.*`/`host.pwsh.*`
over the runtime RPC, which still ran the sync probes and blocked the desktop
main event loop on `execFileSync('wsl.exe' | 'pwsh.exe')` for up to 5s per call.

Switch those handlers and the relay preflight capability probe to the async
twins added here; they share the same caches, dedupe and backoff, so remote
callers see no behavior change.

* fix(windows): harden async capability reprobes

* fix(windows): dedupe PowerShell shell probes

---------

Co-authored-by: Orca <help@stably.ai>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-08-07 21:59:24 -07:00
Jinjing 0d29497f82
Expand Cmd+J and interleave open tabs with worktrees on search (#13120)
* Expand Cmd+J palette and interleave tabs/worktrees on query

Increase palette dimensions (900x600) and remove redundant secondary
labels ("Terminal tab", "Mobile Emulator tab") that crowded rows. When
a typed query matches both open tabs and worktrees, use a soft-split
layout: leading section preview followed by trailing section floor so
neither primary is buried under ~50 rows. Trailing section no longer
truncates to hard cap when paired with a larger leading section.

* Add type-alias search and fix multi-primary palette ordering

Add searchable type aliases (e.g. "terminal tab", "mobile emulator") so users can find items by type without cluttering the row display. Refactor multi-primary palette layout into orderMultiPrimaryPaletteItems to prevent selection/render order drift, simplify selectableItems derivation, and track trailing hard-overflow count separately from scrollable rest.

* fix(cmd-j): pin multi-primary layout generic for mixed item types

Typecheck failed because the ternary lead/trail arrays inferred a
WorktreePaletteItem[] | OpenTabPaletteItem[] union that could not
satisfy layoutMultiPrimaryPaletteSections' single T parameter.
2026-08-07 21:32:40 -07:00
Neil f968583e95
fix(remote): stop a reachable Orca server with a closed workspace window from reading Ready (#12477)
A remote Orca server whose workspace window is closed keeps answering status RPC, so Settings > Available Hosts showed "Ready" and the status bar showed "Connected" while every graph-backed operation failed. Adds the shared predicate `isRuntimeWorkspaceWindowClosed` (`graphStatus !== 'ready' && desktopWindowStatus === 'openable'`) and one host-health derivation with a new `workspace-window-closed` state, consumed by both surfaces. Hosts that omit `desktopWindowStatus` are unaffected, so the connected-host count and overall dot do not regress.

Fixes #12350

Co-authored-by: gatsby74 <gatsby74@users.noreply.github.com>
2026-08-07 21:11:31 -07:00
Neil 6ea99f6607
fix(runtime): isolate same-path folder workspace PTY identity (#12474)
Folder-project workspace ids (`repoId::/path::workspace:<uuid>`) were compared via the suffix-stripping `splitWorktreeIdForFilesystem`, so every workspace sharing one directory compared equal at ~35 runtime call sites — PTYs leaked between siblings and paired/mobile clients hung on "Loading terminal". Both identity helpers now use the suffix-preserving `splitWorktreeId`, `stopTerminalsForWorktree` routes through the shared helper, and `findResolvedWorktreeIdForPath` gains a `targetWorktreeId` tie-break.

Co-authored-by: dgk-dev <dgk-dev@users.noreply.github.com>
2026-08-07 21:11:01 -07:00
OrcaWin ce20a109da
Persist the Linear issue list view and per-workspace filters (#12710)
* Persist the Linear issue list view and per-workspace filters

Layout, grouping, ordering, columns, and attribute filters survive a restart.
Facet ids are workspace-scoped, so filters are kept per Linear workspace and the
active filter is *derived* from the selected workspace rather than reset by an
effect on switch — no ordering race can apply workspace A's facets to B, and an
unresolved or cross-workspace selection reads as unfiltered without erasing
anything.

A single shared catalog backs the renderer state, `TaskResumeState`, and the
strict `ui.set` schema, so a new view option cannot leave paired web/mobile/relay
clients rejecting the whole payload. Persisted values are normalized as untrusted
input: a corrupt preference or a single bad workspace entry is dropped without
taking the rest of the resume state with it.

Deriving the filter also removed the guard that used to make three neighbouring
behaviours safe, so they are re-scoped here:

- The primary-team facet reset now fires only on an in-workspace team change.
  A workspace switch also changes the primary team, and clearing there wiped the
  filter that had just been restored for the workspace being switched *to*.
- The list-read force check no longer fires on the session's first read, so a
  restored filter serves warm cache instead of forcing a network round trip
  behind a blocking spinner on every cold start.
- The filter dropdown derives "no single workspace" from `workspaceId` alone.
  With an unresolved workspace it previously rendered the statically populated
  priority section, whose clicks now have nowhere to be stored.

* Harden Linear view persistence against the failures review surfaced

Five issues, each found by a reviewer and reproduced before fixing:

- The filter dropdown's prune effect only ran when the user opened the popover,
  because the filter was always empty at startup. Restoration makes it run on
  mount, where `availableTeams` may still be the issue-scraped fallback rather
  than the real fetch. Metadata complete for a *partial* team set passes every
  R12 guard, so it pruned facets belonging to teams it simply hadn't seen — and
  the write persisted, deleting them permanently. Gated on `teamsSettled`.

- `canonicalize` dedupes but enforces none of the transport bounds; only the
  throwing parser does. So `serialize` could emit a 101-label filter that the
  strict `ui.set` schema rejects, which drops the WHOLE taskResumeState — github,
  jira and linear query included — on every subsequent write, since the renderer
  resends the merged object each time. Added `boundLinearIssueAttributeFilter`
  and a round-trip test built from serializer output rather than a literal, which
  is the only kind that can catch renderer/schema drift.

- `linearIssueView` now carries `.catch(undefined)`: value tolerance stops at the
  top level, so any future instance of the above is a cosmetic reset of the view
  instead of silent loss of every other resume field.

- A workspace switch forced an uncached list read in both directions. The switch
  is a later observation, so the null-baseline fix didn't cover it; the cache is
  already workspace-keyed, making the force pure cost.

- Recency for the 20-workspace cap came from object key order, which is wrong
  twice: re-filtering an existing workspace left it at the head (first evicted,
  though just used), and an array-index-like key enumerates first regardless of
  insertion, so a write could evict the very entry it added. Recency is now an
  explicit ordered key list.

Also adds the nested parity assertion — the top-level one compares only
TaskResumeState's own keys, so a field added to LinearIssueViewResumeState stayed
invisible to it, which is exactly what `.strict()` rejects.

The wiring test was blind: deleting the hydration guard outright left all four
assertions green. The gate is now `shouldPersistLinearIssueView`, unit-tested
directly, and the file is renamed to the repo's `*-boundary.test.ts` convention
with an assertion that fails on that mutation.

* Log discarded Linear views and fix empty-filter serialization

- Schema now logs when linearIssueView is discarded, making validation failures visible
- Fixed serialization: filters that become empty after bounding are now omitted
- Added AssertNoExtraKeys type check for bidirectional schema/type parity
- Refactored view option catalogs to use canonical constants, preventing UI/schema drift

* Remove workspace persistence limits and LRU eviction

Stop capping persisted Linear workspace filters at 20 and evicting
least-recently-used workspaces. Simplify persistence to store all
workspace filters, gate persistence only on resume state application,
and remove tests that pinned implementation details. Users can now
persist filters for all their workspaces without arbitrary limits.

* add test for linear persistence

* Improve Linear filter test clarity and fix e2e overlay dismissal for CI

- Convert parameterized filter-pruning test to sequential assertions
- Fix dismissOverlayChrome to toggle overlay triggers instead of
  force-clicking inert page elements in headless CI

* Prevent TaskPage from stealing Escape from Radix menus

- Add check to detect open Radix dropdown menus and popovers; return
  early from Escape handler to respect their capture-phase ownership
- Update overlay dismissal in e2e tests to use keyboard.press('Escape'),
  now that TaskPage no longer interferes

* The capture-phase Escape guard in TaskPage bailed out for open dropdown menus and popovers, but an open Radix Select matches none of those selectors: the shared SelectContent wrapper (src/renderer/src/components/ui/select.tsx:60) renders data-slot="select-content" and Radix gives its content role="listbox", not role="menu". So with a select open, the window-level capture handler ran first, called preventDefault() and closeTaskPage() — closing the whole task page instead of just the select. Added [data-slot="select-content"] to the guard, as suggested. I did not add [role="listbox"]; the reviewer explicitly notes it's too broad, and the data-slot selector covers every select rendered through the shared wrapper.

---------

Co-authored-by: m4air <m4air@MacBook-Air.localdomain>
Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
2026-08-07 20:41:59 -07:00
Jinwoo Hong 4b5157b147
fix(codex): bound state DB recovery retries (#13109)
Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com>
2026-08-07 20:37:50 -07:00
OrcaWin c3939ebf0e
fix(mobile): allow reachable Hyper-V pairing addresses (#13107)
* fix(mobile): allow reachable Hyper-V pairing addresses

* fix(mobile): keep host-local Hyper-V addresses filtered

* fix(mobile): preserve explicit address on empty refresh

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-08-07 20:37:47 -07:00
Jinwoo Hong 2f30eb9af5
fix(ai-vault): block deletion of live sessions (#13108)
* fix(ai-vault): block deletion of live sessions

* fix(ai-vault): retain external session authority

---------

Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com>
2026-08-07 20:36:23 -07:00
OrcaWin 6b547cf2ea
fix(ai-vault): contain WSL session deletion (#13106)
* fix(ai-vault): contain WSL session deletion

* fix(ai-vault): close approved-root traversal race

* test(wsl): restore fixture permissions before cleanup

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-08-07 20:35:22 -07:00
Jinjing e7d288c58c
fix(cmd-j): re-rank Recent when terminal entities hydrate late (#13105)
* Fix Cmd+J recent order when terminal entities hydrate late

Unified tabs can appear before tabsByWorktree entities on restore, which
latched an all-IDLE ranking and buried blocked chats until reopen. Keep a
provisional freeze, then re-capture once entities arrive.

* Harden Cmd+J incomplete hydration re-rank latch

Clear the provisional order latch when the ranked list goes empty so a
brief tab wipe cannot freeze an empty Recent section, and assert that a
user-moved selection survives the incomplete→complete re-rank.

* Fix Cmd+J ordering to not compare focus ordinals across worktrees

focusOrdinal is a per-worktree sequence, so comparing rows from different worktrees corrupts their relative order. Preserve input (positional) order instead.

Also: refactor test helpers to use makePaneKey() utility for pane-key construction, and clarify a test description about CJK character handling in relevance scoring.
2026-08-07 20:21:17 -07:00
BingZ 523feda462
fix(commit-message): use Kimi --prompt instead of Claude --print (#11674)
* fix(commit-message): use Kimi --prompt instead of Claude --print

kimi-code rejects --print (suggesting --prompt). Deliver the generation
prompt as the --prompt argv value so branch auto-rename and commit
message generation work when Kimi is the selected agent.

Fixes #11669

* test(commit-message): cover Kimi argument defaults
2026-08-07 20:19:45 -07:00
Neil a7e31e5e10 chore(mobile): apply the supply-chain release-age gate to mobile
mobile/ had no .npmrc, so unlike the root workspace it would resolve
packages published moments ago. #13113 surfaced this concretely: it
pulled nanoid 3.3.18 and postcss 8.5.26 at 0.4 and 1.7 days old, both
newer than anything the root gate would have allowed.

Copies the root's minimum-release-age=4320 (3 days). Deliberately not
shamefully-hoist -- that one is Electron-specific and would change how
mobile hoists.

Re-resolves nanoid to 3.3.17 and postcss to 8.5.25 in the same commit
because the gate is otherwise unusable: pnpm install fails with
ERR_PNPM_NO_MATCHING_VERSION on the locked nanoid 3.3.18. Both picks stay
above their advisory floors (CVE-2026-67213 needs >=3.3.17,
CVE-2026-69153 needs >=8.5.23), so this is not a security regression.

Co-authored-by: Orca <help@stably.ai>
2026-08-07 20:03:46 -07:00
Neil 757b785e43
fix(deps): resolve Dependabot security alerts across root and mobile (#13113)
Clears 47 of 49 open Dependabot alerts across the root and mobile lockfiles.
The 2 remaining (image-size) have no patched upstream release.

Direct bumps: pdfjs-dist 5.7.284 -> 6.2.108 (CVE-2026-16633), mermaid
11.16.0 -> 11.16.1 (root + mobile), dompurify 3.4.12 -> 3.4.13.

In-range re-resolves: brace-expansion, fast-uri, hono, ip-address,
js-yaml 4.3.1/3.15.1, nanoid, postcss, tar, undici 6.28.0/7.29.0.

Drops the @modelcontextprotocol/sdk>@hono/node-server override by bumping
shadcn's transitive SDK to 1.30.0, which widens its range to
^1.19.9 || ^2.0.5 so @hono/node-server resolves to a patched 2.1.0 on its
own. The other two overrides must stay: monaco-editor hard-pins dompurify
3.2.7 and xcode wants uuid ^7.0.3, both vulnerable.

pdf.js 6 removed PDFDocumentProxy.destroy(); PdfViewer now tears the
document down via the loading task it was already destroying.

Supersedes #13074, #13090, #12960, #12952.

Co-authored-by: mondaychen <monday.chen@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Orca <help@stably.ai>
2026-08-07 19:56:26 -07:00
Jinjing 4c26ef626c
Extract GitHub task search commit debouncing into a hook (#13112)
* Extract GitHub task search commit debouncing into a hook

Move debounce logic from TaskPage into useGitHubTaskSearchCommit to
prevent excessive GitHub API calls on every keystroke. Uses a 750ms
idle window before committing search values. Add tests for the new hook.

* Keep task rows visible while typing search query

Removed premature row-hiding logic from the search input handler that was
triggering before debounced queries fire. The handler now only updates the
input state; debouncing and query timing are handled by a dedicated hook.
Added e2e test verifying search idles before fetching and Enter doesn't
double-fetch.

* Test that GitHub task search commits cancel on disable and unmount

Verify the useGitHubTaskSearchCommit hook properly cleans up pending
commits when disabled or when the component unmounts. This prevents
unnecessary GitHub API calls during normal user interaction.

Also fix e2e test instrumentation to find the active repo through the
worktree relationship rather than assuming the first repo with a path.
2026-08-07 19:18:29 -07:00
Neil 597c84f36c
fix(rate-limits): read Kimi usage credentials from the configured WSL runtime (#12475)
* fix(rate-limits): read Kimi usage credentials from the configured WSL runtime

Kimi's usage fetch always read the Windows host's ~/.kimi-code, so a Kimi CLI running inside WSL rotated only the WSL-side token and the status bar was stuck on 'Run Kimi to refresh'.

Resolve the Kimi home from the local-account runtime target (mirroring Codex's getDefaultWslDistro()/getWslHome() UNC pattern), pinned to host off Windows, and keep KIMI_CODE_HOME host-only.

Fixes #12370

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

* fix(rate-limits): bound and offload the Kimi credentials read for WSL homes

Adopted from @cengiz-io's #12372: read credentials through
createAuthFilesystemOperation (async, per-path dedup, AbortSignal bound) so a
stopped distro degrades to an error instead of parking Electron main on a UNC
read. ENOTDIR joins ENOENT as "not signed in" to keep existsSync parity, and the
WSL runtime target is now probed with the async wsl.exe helpers.

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

* test(rate-limits): build Kimi credential-path expectations with path.join

The host-home assertions hardcoded POSIX separators, so they only passed on
a POSIX runner — on a Windows dev machine `join` emits backslashes and all
four cases failed (three assertion mismatches plus a WSL-suite fixture whose
map key never matched the path the fetcher read).

---------

Co-authored-by: Orca <help@stably.ai>
Co-authored-by: OrcaWin <alpha-eng@stably.ai>
2026-08-07 18:59:42 -07:00
Jinjing 2c865cda66
Add open-tab search to the new-tab omnibox (#13100)
* Add open-tab search to the new-tab omnibox

The omnibox now searches workspace, browser, and simulator tabs alongside file creation. Tab results rank first, so users jump to existing tabs before creating new ones. File entries for open editor tabs are suppressed to avoid duplication.

* feat(tab-bar): include focused tab in open-tab search

The omnibox no longer hides the tab a column is already showing when
searching. The + menu can be opened from any column, so filtering by the
focused column's visible tab broke search when you tried to find the tab
on screen. Now every tab in the worktree is offered, matching how Cmd+J
lists the tab you're on.

Removes the groupId parameter from useOpenTabSearch since all tabs are
now included regardless of which column opened the menu.

* refactor(tab-bar): snapshot open-tab search entries and consolidate rank

Extract entry building to a pure function in open-tab-search-entries.ts; simplify the hook to snapshot once at menu open rather than subscribing to store changes. Consolidate three ranking functions into a generic rank helper and merge activation routing into a single function with a shared failure handler. Improves code clarity and hook efficiency.

* refactor(tab-bar): make open-tab search reactive instead of snapshotted

- Tab search results now reflect changes while the menu is open, using a
  shallow store selector instead of a static snapshot
- Separate state selection from entry building for cleaner composition
- Use the public API method getKnownWorktreeById instead of internal helpers
- Remove tests for snapshot behavior that no longer applies

* refactor: extract test fixture and thread execution host for remote work

- Extract duplicated lucide-icon stub into shared test fixture
- Fix open-tab-search path matching to be editor-only (diff/review tabs have
  different destinations)
- Thread execution host ID through simulator tab palette activation for
  remote-hosted worktrees

* Gate cmdk selection until deferred query updates

cmdk reports selections before useDeferredValue commits the new query, leaving the old first result selected. Ignore selection changes until the deferred query catches up.
2026-08-07 18:14:39 -07:00
Avichal Dwivedi 96a6c93757
fix(orchestration): allow fresh agents to take over legacy runs (#12896)
* fix(orchestration): allow fresh agents to take over legacy runs

* test(orchestration): cover forged takeover evidence

* fix(orchestration): retain renderer launch authority

---------

Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com>
2026-08-07 18:10:27 -07:00
Neil 940f2ff1e4
fix(terminal): quote agent resume for the tab's real Windows shell (cmd.exe) (#12476)
* fix(terminal): quote agent resume commands for the tab's real Windows shell

Cold restore and sleeping-agent resume built their launch line without the
host shell family, so win32 fell back to PowerShell argv quoting. On cmd.exe
tabs those quotes arrived literally and agent CLIs rejected the resume argv
and permission flags after a reboot ("unexpected argument ''<uuid>'' found").

Both call sites now share resolveAgentResumeLaunchTarget, which resolves the
launch platform and the live shell family together via
resolveLocalWindowsAgentStartupShell, honoring a per-tab shell override for
cold restore and leaving SSH / remote-runtime / WSL workspaces on their own
default quoting.

Fixes #12320

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

* test(shared): cover cmd.exe resume quoting at the plan layer

Adopted from #12321 by @CountClaw.

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

---------

Co-authored-by: Orca <help@stably.ai>
2026-08-07 17:57:15 -07:00
Jinjing 094d6821ef
feat(native-chat): add model and effort pickers for grok (#12780)
* feat(native-chat): add model and effort pickers for grok

Grok had no session-option catalog, so the native chat composer showed no
pills and every launch ran the CLI's own defaults with no way to change them.

Adds a `GROK_SESSION_OPTION_CATALOG` (model via `-m`/`/model`, reasoning
effort via `--reasoning-effort`/`/effort`) and the discovery plumbing behind
it. Grok's selectable ids depend on the signed-in account and on `[model.*]`
config, so the seed carries only `grok-4.5` and a runtime `grok models` probe
supplies the rest as authoritative — a retired id must be droppable, since
launching one is a fatal exit rather than a warning.

Because `grok models` publishes `Default model:` and marks the row
`(default)`, the picker can name the model a fresh session is actually
running: `defaultModelIsCliDefault` plus an untracked record means no `-m`
was ever emitted, so the CLI is on its own default. That default scopes the
effort row but is never written to persisted settings — that field is what
authorizes `-m` on every later launch, and adopting a model the user never
picked would pin today's default forever, fatally so on an account without
it. `grok --help` publishes no default for `--reasoning-effort`, so the
effort value stays unnamed until something sets it.

Known gap: that refusal to persist is also a limit. An option set while on
the CLI default is dispatched and honored in-session, but reaches no later
launch — it persists under the default's id with `model` left unset, and
both `resolveNativeChatSessionOptionDefaults` and
`resolveAgentSessionOptionLaunch` bail without that key. Picking a model
explicitly persists normally. Closing this means teaching both to resolve
options from the default model while still refusing to emit `-m`, which is
the launch-args path and wants its own review.

Known gap: the picker infers "no `-m` was emitted" from its own in-memory
record, so a model reaching argv from outside it — the user's own
`agentDefaultArgs`, or a renderer reload that drops the record while the
flagged PTY lives on — leaves the pill claiming the CLI default while
another model runs. No wrong model is persisted.

Extracts `hasFlag` and `labelFromModelId`, and splits the model-probe spec
out of the commit-message registry so discovery no longer implies an agent
can write commit messages.

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

* docs(native-chat): note the invariant keeping modelIsCliDefault agent-safe

The flag is computed without checking the catalog, so it reads as unsafe for
the four agents with no CLI default. It is safe only because `persist` bails
unless `modelId` is truthy, which for those agents implies a tracked model.
Widening that guard would silently change persistence for every agent.

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

* fix: retire persisted models on mount and handle -- terminator

- When a pane mounts after model discovery has already settled, it now checks
  the cache and retires persisted models that are no longer available.
- CLI flag detection now respects the `--` option terminator, treating
  everything after it as positional arguments rather than flags.

* Fix: persist grok session options under probe-confirmed defaults

Options set under the CLI default were silently lost on restart.
Distinguish seed guesses from probe-confirmed defaults by renaming
`modelIsCliDefault` to `modelIsUnverifiedDefault`. Once confirmed,
adopt the default as a persisted flag so options survive restarts.

* fix(native-chat): close the retired-model fatal-launch paths from counsel review

Counsel report C1/C2 (High), C3, P1, C4:
- Untrack a session model an authoritative discovery dropped and gate every
  persist path, so option writes can never re-adopt a retired id (C1).
- Resolve launch defaults through the enrichment cache: a persisted model
  missing from every settled probe no longer becomes a fatal `-m` (C2).
- Serialize retirement and picks on one settings write queue that re-reads
  live state at apply time (C3).
- Stabilize onSwitchToTerminal so the session-option surface is not rebuilt
  every TerminalPane render (P1), and cap the enrichment host map (C4).

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

* Store agent in enrichment entry and extract token utilities

Refactor enrichment to store the agent field directly instead of
parsing it from a composite key, and extract CLI flag token filtering
into a shared utility. Use a dedicated function for tracked model ID
lookup. Improves code reuse and reduces parsing overhead.

* Rename modelIsUnverifiedDefault to adoptModelAsLaunchDefault

Move the model adoption gate into the core session-options module, where probe confirmation and discovered-model status are known. This ensures adoption decisions are gate-checked before persisting to avoid fatal launch flags, and simplifies the picker surface by moving the logic to where it belongs.

* Keep model probe evidence by agent, not host

Store probed model IDs in agent-keyed cache independent of host cache, so
evidence persists across host eviction. Prevents retired models from being
treated as valid when host cache entries are evicted.

* Store agent in enrichment entries instead of separate proof-evidence map

Model probe evidence is now tied to enrichment entries rather than maintained in a separate per-agent map, eliminating the need for eviction logic that could disconnect proof from entries.

---------

Co-authored-by: Orca <help@stably.ai>
2026-08-07 17:48:27 -07:00
Neil 8cdfa64e68
fix(grok): keep the Windows hook safe under an inherited /v:on (#12479)
A bare `setlocal` inherits delayed expansion from the caller, so every `!`
in a percent-expanded value on the curl line is eaten as a delayed
reference -- mangling paneKey and dropping worktreeId. `!` is legal in a
Windows path.

Also adds the test file the script never had: structural checks that run on
every platform, plus real cmd.exe runs covering unset/empty/normal/trailing
backslash/envelope/`!`/inherited `/v:on`.

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-08-07 17:48:14 -07:00
Jinjing e6e197feed
Add Recent Chats & Terminals to Cmd+J palette with digit shortcuts (#13076)
* Add Recent Chats & Terminals to Cmd+J palette

On empty query, the palette leads with recently accessed chats and terminals,
addressable via ⌘1–⌘9 shortcuts. Digit chords are intercepted while the
palette is open, preventing workspace switches behind the overlay. Status
dots reflect agent blocking and activity.

* Fix Cmd+J recent order capture and backfill behavior

- Capture unfiltered recent tab order when palette opens, avoiding frozen filtered subsets on search reopen
- Backfill recent section when rows drop out (mid-open narrowing) instead of rendering empty
- Move create-workspace action to the end as a fallback, not competing with real matches
- Add test coverage for order freezing, backfill, and create-action ranking

* Fix Cmd+J selection backfill when recent tabs hydrate late

Track auto-selected item to distinguish user-moved selections from auto-picks. When recent tabs arrive after the palette opens, reset selection to the top item unless the user explicitly moved it. Use useLayoutEffect to capture the pre-hydration worktree order before tabs render, preventing flashing. Fix worktree budget cap for sessions with no open tabs. Add test coverage for late hydration and user-moved selection preservation.

* Rank Cmd+J results by match relevance, lead with stronger section

Adds match-relevance scoring to rank search results by which field matched and
match position (prefix > word-start > mid-word). When a typed query has results
in both worktrees and open tabs, whichever section holds the stronger hit now
leads the list, matching user intent — a prefix hit in a tab beats a mid-name
hit in a worktree.

Decouples live status dots from the palette body subscriptions to prevent the
whole list from re-rendering on every agent transition or pane-title change.
Dots now own their subscriptions via PaletteLiveStatusProvider, while the body
reads status maps as a snapshot, refreshed only when the palette opens or tabs
change. Freezes the snapshot identity during animation to keep the selection
stable even while closing.

* Handle decomposed accents in Cmd+J match relevance scoring

Include Unicode combining marks (\p{M}) in word-boundary detection so
decomposed characters like café are treated as word boundaries instead
of mid-word matches.
2026-08-07 17:21:47 -07:00
Brennan Benson 38275c2aa2
fix(codex): publish Windows system-default sessions (#12611)
* fix(codex): publish Windows system-default sessions

* fix(codex): close launch-scheduling races in session migration scheduler

* fix(codex): bound repeated session migration audits

* fix(codex): preserve delayed session publication passes

* fix(codex): bound failed session audit events

* fix(codex): fence stale session migration markers

* chore: preserve main formatting after merge

* perf(codex): bound launch session migration scans

* perf(codex): preserve coalesced migration scope

* fix(codex): preserve scheduled migration recovery

* fix(codex): preserve session migration recovery

* fix(codex): close session migration launch races

* fix(codex): harden session migration completion
2026-08-07 16:53:41 -07:00
Brennan Benson 20aeb0cb99
Prepare mobile 0.0.42 and fix TestFlight CI hang (#12961)
* Bump mobile app.json to 0.0.42

* fix(mobile-ios): stop TestFlight CI from waiting on ASC processing

0.0.42 builds 1–2 uploaded successfully then hung for hours polling
processing with no Ready build and no Apple email. Exit after upload
and cap the job at 90m so the next cut does not repeat that hang.

* fix(mobile-ios): fully skip Pilot wait (no changelog)

Pilot only returns immediately after upload when changelog is nil;
passing notes re-enters the ASC build-list poll.
2026-08-07 16:52:37 -07:00
Jinjing 7a867f12aa
Revert "Fix grok stale pane width (#13060)" (#13098)
This reverts commit 58531e4caa.
2026-08-07 16:09:53 -07:00
Jinwoo Hong 9b70ce0424
fix(computer-use): settle macOS permission checks (#13025) 2026-08-07 15:59:00 -07:00
Jinwoo Hong 2a93cae293
fix(crash-reporting): stop filing Windows OS shutdown as renderer crashes (#12938) 2026-08-07 15:51:24 -07:00
Neil f8786d5224
fix(agent-history): fold trailing-slash, NFD/NFC, and project-fallback folder group keys (#12458)
Co-authored-by: Orca <help@stably.ai>
2026-08-07 15:06:37 -07:00
Jinwoo Hong 2396e5e3e5
fix(browser-pane): reschedule remote stream restart with bounded backoff (STA-3483) (#12787) 2026-08-07 15:01:11 -07:00
Brennan Benson 360c496b9b
fix(agent-map): draw every visible orchestration edge, and let the filter hide them (#13087)
The agent map dropped real parent -> child dispatch edges and mislabeled the
ones it kept.

- Remove the `child.y <= parent.y` gate on agent lineage. Both endpoints are
  drawn nodes, so the relationship is real whatever the layout ranked them;
  the gate silently hid edges that packing pressure placed side by side.
  `lineagePath` is now direction-aware so an upward edge does not exit the
  wrong side and draw back through both nodes.

- Fix the relation attribute. `parent.card.parentPaneKey ? 'subagent' : ...`
  asked whether the PARENT has a parent and said nothing about the child, so
  a 3-deep chain rendered solid as if it were an in-process subagent. Every
  map node is a top-level pane agent -- build-dashboard-snapshot folds
  subagent rows into the parent card's roster and never makes them cards --
  so every card-to-card edge is an orchestration edge. Dead CSS removed.

- Add a "Orchestration links" toggle under Filter > Map content, default on,
  hiding both same-worktree and cross-worktree edges while keeping the nodes.

Lineage still comes from the existing active-or-recently-settled dispatch
context; making it historical would be a main-process change and is out of
scope here by design.

Extractions are max-lines pressure, not drive-by refactors: the toolbar hit
404/400 and AgentMapCanvas 403/400, and the repo forbids new max-lines
suppressions.
2026-08-07 14:36:22 -07:00
github-actions[bot] 9e948fbdf4 release: v1.4.177-rc.0 2026-08-07 21:21:43 +00:00
Pongsakorn Paetrakul 6aafb1d318
fix(gitlab): include bridge/child pipeline jobs in Checks (#12863)
Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com>
2026-08-07 14:11:31 -07:00
Brennan Benson f0443c326a
fix(codex): recover interrupted state DB backfills (#12617)
* fix(codex): recover interrupted state DB backfills

* fix(codex): detect mixed-case backfill timeout

* fix(codex): harden backfill recovery review findings

* fix(codex): keep process identity retries safe
2026-08-07 14:06:29 -07:00
Jinwoo Hong 8c3e9535c7
fix(terminal): remove permanent link tooltip gap (#13075) 2026-08-07 12:29:00 -07:00
Wooseong Kim 2e199cb2dd
fix(ai-vault): tolerate unknown session agents (#12999)
* fix: tolerate unknown AI Vault agents

* fix: tolerate unknown AI Vault scan issues

* test(ai-vault): cover malformed unknown-agent rows

---------

Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com>
2026-08-07 12:18:49 -07:00
seoo2001 6382b8a053
fix(file-explorer): report the saved filename in the download toast (#12959)
* fix(file-explorer): report the saved filename in the download toast

The success toast named the remote node, so renaming a file in the native
save dialog left the label disagreeing with its own Open action, which
opens the real destination. Folder downloads had the same gap whenever
sanitizeLocalDownloadFilename rewrote the remote basename.

* fix(file-explorer): respect local download path semantics

---------

Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com>
2026-08-07 11:50:38 -07:00
5hseok a77002c42b
feat(ai-vault): delete a provider session from the AI Vault list (#10249)
* feat(ai-vault): validate session-delete targets for single-file providers

Add the pure judgement layer for deleting an Agent Session History entry.
`validateAiVaultSessionDeleteTarget` decides whether a session may be removed:
the agent must be one of the nine providers where a single file is the whole
session (gemini, copilot, cursor, hermes, devin, openclaw, droid, pi, omp),
the host must be local, and the renderer-supplied path must resolve inside
that agent's own session roots and match its discovery predicate.

To keep the delete roots from drifting from the scanner's own roots, the
WSL-expansion helper moves to session-scanner-root-dirs.ts and the OpenClaw
root derivation + session predicate become shared helpers that
discoverOpenClawFiles itself consumes.

The result is path-only and never touches the filesystem; a returned
`allowed: true` still requires an lstat/realpath re-check in the executor
(S-2) before removal, documented as a caller contract on the result type.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XDLggjSAjDnaWi3Y8U622i

* feat(ai-vault): move a validated session transcript to the trash

Add the filesystem executor behind session deletion. It calls the S-1 path
validator, then performs the fs-side guards that validator documented it
could not: lstat().isFile() rejects a directory or symlink, and realpath is
re-fed through the validator so a regular file reached through a symlinked
parent that escapes the agent's roots is rejected too. Only then is the file
moved to the OS trash via shell.trashItem, with ENOENT treated as success so
a delete racing an external removal stays idempotent.

WSL UNC paths (no Recycle Bin) are delegated to tryDeleteWslUncPath before the
Windows-local fs guards, mirroring fs:deletePath. Any non-ENOENT error is
returned as a failure result rather than thrown, since IPC payloads are
untyped at runtime.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XDLggjSAjDnaWi3Y8U622i

* feat(ai-vault): delete-session IPC handler, preload bridge, cache invalidation

Wire the S-2 delete executor to an IPC endpoint and expose it on the preload
bridge. The renderer calls aiVault:deleteSession with { agent, filePath,
executionHostId }; the handler fetches WSL homes, delegates to the executor
(which re-validates and trashes), and on a real delete invalidates the caches
that could otherwise keep serving the deleted session.

Cache invalidation is generation-guarded: a scan already in flight when the
delete lands carries an older generation and must not write its pre-delete
result back into the cache. Without this, an in-flight scan resolving just
after the delete would resurrect the deleted session for the 15s TTL — and
force-refreshing the panel only masks it for the desktop, not for the paired
mobile client or runtime RPC that share the same cache module. Both the shared
local-scope cache and the desktop multi-host cache carry the guard, with
regression tests for the in-flight race.

The delete result type moves to shared/ai-vault-types.ts so the renderer can
import the same contract the executor returns. To keep ai-vault.ts within the
max-lines budget after adding the delete wiring, two cohesive pieces are
extracted to their own files: the delete orchestration (ai-vault-delete.ts)
and listAiVaultSubagentSessions (ai-vault-subagent-list.ts). The latter is the
only handler with no dependency on this module's private cache state, so it is
the one piece that moves verbatim without threading state through a seam.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XDLggjSAjDnaWi3Y8U622i

* feat(ai-vault): renderer judgement for whether Delete is offered

Add the renderer counterpart to the main-side delete validator: given a
session, decide whether the row menu shows Delete enabled, or disabled with a
reason a tooltip can render. It reuses the shared deletable-agent set and
unsupported-reason map so the two sides can never disagree about which agents
are deletable, and reuses the existing local-host / synthetic-path renderer
helpers.

This is intentionally not a security boundary — it validates neither the path
root nor the file predicate. Those are the main process's untrusted-input
defense; the renderer only picks the affordance, and the main side re-checks
on delete regardless.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XDLggjSAjDnaWi3Y8U622i

* docs(ai-vault): correct deletability parity claim; test multi-reason agent

The renderer deletability check runs host -> synthetic -> agent, while the
main validator runs agent -> host -> synthetic. The two layers agree only on
deletable-or-not (renderer-false is a subset of main-false), not on the reason
code a doubly-failing session carries. Document that explicitly instead of
implying the orders match, and add the antigravity case (two reason codes) so
the agentReasonCodes array shape is actually exercised.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XDLggjSAjDnaWi3Y8U622i

* feat(ai-vault): add Delete to the session row menu with a confirmation dialog

Wire the delete affordance into AI Vault. Both the dropdown and the context
menu gain a destructive Delete item; a session that can't be completely deleted
(remote host, synthetic OpenCode-SQLite path, or a directory/registry-backed
agent) shows the item disabled with a reason surfaced both as a tooltip and as
an aria-label so keyboard and screen-reader users learn why. Confirming opens a
dialog that names the session and states it will no longer be resumable from
the provider's own CLI, then calls the delete IPC and force-refreshes the list
for immediate feedback (the main side has already invalidated its caches).

The confirmation copy says the session "will be deleted" rather than "moved to
the trash": on Windows a WSL session is deleted with rm inside the distro (no
Recycle Bin), so promising recoverability would be a lie on that platform.

Deletability is computed once per row and shared by both menus so they can
never disagree. New pure logic — the reason-to-tooltip mapping (including the
multi-reason join) and the delete action hook's deleted/rejected/failed
branches — is covered by unit tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XDLggjSAjDnaWi3Y8U622i

* fix(ai-vault): state that Delete is unavailable without naming the cause

The disabled Delete item explained a provider's storage layout to the user
("Claude sessions can't be deleted here: stores sessions as a folder, not a
single file"). That is Orca's problem, not the reader's — the tooltip now says
which sessions are affected and stops there. The non-local-host string stays as
it was: it states scope, not a cause, and tells the user what would work.

The reason-code plumbing existed only to compose that tooltip, so
AI_VAULT_UNSUPPORTED_DELETE_REASONS, AiVaultUnsupportedDeleteReasonCode, and the
renderer result's agentReasonCodes field go with it. Why each agent is excluded
moves into the comment above AI_VAULT_DELETABLE_AGENTS, where a reader looking
up the deletable set will find it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KGuzChimmQ1dYX2raecrH7

* feat(ai-vault): delete claude, rovo, and grok sessions by their directory

These three were excluded only because the delete unit was one file. Their
sessions are directories — claude keeps Task subagent transcripts in a sibling
`<uuid>/subagents/`, rovo and grok keep everything under `<sessionId>/` — and
nothing in them is shared with another session, so a directory-aware delete is
still a complete delete. Supported goes from 9 agents to 12; the four that
remain (antigravity, kimi, codex, opencode) are blocked by a registry or a
SQLite row, which no delete unit fixes.

Validation now returns an ordered removal plan instead of a single path. Each
removal carries the kind it must be on disk and the roots its realpath must
stay inside, so the executor's guard is the same shape for a file and for a
directory. Companions come first and the transcript last: the transcript is
what puts the row on screen, so a part-way failure leaves the row to retry
from rather than dropping it and stranding the rest on disk.

Claude's `session-env/<uuid>/` goes with the transcript — it holds that
session's generated shell exports and nothing else. Its sibling
`file-history/<uuid>/` deliberately does not: it is the rewind buffer holding
earlier versions of the user's own files, and retiring a session is no reason
to take away the only copy that can restore them.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KGuzChimmQ1dYX2raecrH7

* fix(ai-vault): remove a claude session's own directory, not just its subagents

Deleting a claude session trashed `<uuid>/subagents/` and left `<uuid>/` behind
as an empty directory — one per deleted session, accumulating under every
project. The directory is named after the transcript, so it belongs to that
session as a whole; take it rather than the one subdirectory inside it. Still
derived from the scanner's own subagents path, so the two cannot drift.

Reaching the parent means a degenerate stem now matters: `..jsonl` passes the
extension check and its stem is `.`, which would resolve the session directory
to the project directory holding every session. Reject it instead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KGuzChimmQ1dYX2raecrH7

* fix(ai-vault): keep a session row collapsed when a menu action is chosen

Radix portals the row's dropdown and context menus out of its DOM, but React
still bubbles their clicks back through the component tree, so every menu
selection also hit the row's own click handler and expanded it. The trigger
button already stopped propagation, which is why opening the menu looked fine
and only choosing an item misbehaved.

It shows worst on Delete: the row expands behind the confirm dialog, so
cancelling leaves the list rearranged under a dialog the user just backed out
of. Toggle details only for clicks that land in the row's own subtree — that
covers the context menu and any future portalled surface, not just this one.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KGuzChimmQ1dYX2raecrH7

* fix(ai-vault): harden the delete-confirmation flow against IPC rejection and mid-delete dismissal

Two robustness gaps flagged in review:

- handleConfirmDelete only branched on result.outcome. The main handler
  resolves with a 'failed'/'rejected' outcome rather than throwing, but the
  IPC invoke itself can still reject on a transport/serialization error, and
  the caller fires it with `void`. That reject would surface as an unhandled
  rejection with no toast. Catch it and show the same generic failure toast.

- handleDialogOpenChange cleared sessionPendingDelete on every open=false.
  The Cancel button is disabled mid-delete, but Radix still fires its
  Escape/outside-click/X close, which could dismiss an in-flight delete out
  from under itself. Ignore close requests while deletingSession is true.

Both covered by regression tests (verified failing without the fix).

* fix(ai-vault): route WSL UNC directory removals through the WSL rm branch

Directory-shaped deletes (claude's subagents/session-env dirs, rovo/grok's
session dir) gated the WSL branch on kind === 'file', so on Windows a session
under a WSL distro home fell through to shell.trashItem — which can't trash a
WSL-volume item (no Recycle Bin) and throws, or worse is silently stranded when
the 9P filesystem's unreliable lstat false-reports ENOENT and the executor
treats that as success. Single-file deletes predate the directory kinds, so the
file-only gate was correct until directory removals were added.

tryDeleteWslUncPath already supports recursive removal; pass recursive for
directory removals so they take the same WSL rm path as files instead of
shell.trashItem. Covered by two regression tests (file: non-recursive,
directory: recursive), verified failing without the fix.

Also drops the internal ledger-ID references (D-*, S-*) from comments in these
two files; they pointed at a private design doc a reader can't see.

* docs(ai-vault): drop internal design-ledger IDs from shipped comments

Comments across the session-delete feature cited decision/slice IDs (D-1..D-7,
S-1..S-5) from a private design document. Those references are meaningless to
anyone reading the code without that doc, so remove the IDs while keeping the
reasoning each comment carried. No behavior change.

* test(ai-vault): e2e-cover the real on-disk session delete

The unit tests mock lstat/realpath/trashItem, so nothing proved the whole IPC
path actually removes files. This spec seeds sessions into the E2E harness's
isolated HOME and deletes them through window.api.aiVault.deleteSession:

- a single-file session (gemini): the transcript is gone from disk and drops
  out of the list.
- a directory-shaped session (claude): the transcript, the <uuid>/ session
  directory (subagents included, no empty shell left), and the session-env
  companion are all gone, while the file-history rewind buffer is preserved.

Verified failing when the executor's removal is stubbed out. Runs on Linux CI.

* fix(ai-vault): address review findings on the session-delete flow

Three points raised in review:

- Disable Delete for a still-running session. resolveAiVaultSessionDeletability
  now gates on liveState (working/blocked/waiting) last — an otherwise-deletable
  session that is mid-run shows "wait for it to finish" instead of an enabled
  Delete, so trashing a live agent's transcript can't drop writes it is still
  appending. Unsupported/remote sessions keep their permanent reason.

- Realpath the roots, not just the target, in the executor's escape check. The
  roots were only resolve()'d (text), so a session under a symlinked root
  (~/.claude -> /Volumes/…) was falsely rejected; realpath each root (falling
  back to its text form when it can't be resolved) before the membership check.

- Invalidate the parse cache with the raw filePath, not resolve(filePath). The
  cache is keyed by the exact path the scanner discovered, so resolve() could
  normalise it away from the stored key and miss. Drops the now-unused import.

Also moves AiVaultDeleteSessionArgs/Result out of ai-vault-types.ts (which the
upstream merge pushed over the max-lines limit) into the ai-vault-session-deletion
domain module they belong to, and updates importers.

Regression tests added for the live gate, the symlinked-root accept, and the
reason string; verified failing without each fix.

* fix(ai-vault): type the deleteSession preload bridge as its real result

The bridge declared Promise<unknown> while AiVaultApi.deleteSession promises
AiVaultDeleteSessionResult, so the preload object leaned on the api-types
declaration to stay honest instead of being checked against it.

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

* refactor(ai-vault): tighten the session-delete code to house style

Comments across the delete flow explained HOW alongside WHY and ran to a dozen
lines; they now carry only the non-obvious reasoning. The excluded-agent
rationale, the caller contract on the validator, and the file-history carve-out
are kept — those are knowledge, not narration.

Also removes three duplications the feature introduced:
- AiVaultSessionDeleteExecutionResult was an alias for AiVaultDeleteSessionResult
  whose comment pointed at a module the type no longer lives in.
- The synthetic-path predicate existed twice under near-identical names; the
  renderer now re-exports the shared one it already had a sibling import of.
- The delete-failure toast was written out verbatim in both the rejected and
  the thrown branch.

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

* refactor(ai-vault): use a design-system dialog width and a stable row selector

The confirm dialog pinned an arbitrary sm:max-w-[440px]; every other dialog in
the right sidebar uses a scale token, and md (448px) covers the role.

The row-expand test selected the row by [draggable="true"], which stopped
naming the row when draggable moved to the title element upstream. It still
passed by bubbling, so the comment was the only thing wrong — now it selects
the title deliberately and says why the query is first-match (Radix's asChild
trigger repeats the subtree, so screen.get* sees duplicates).

Also types the e2e delete helper as AiVaultDeleteSessionResult instead of a
hand-written { outcome: string }, now that the preload bridge returns it.

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

* refactor(ai-vault): consolidate agent sources and use system dialog

Discovery and deletion now share the same agent source definitions, eliminating the risk of them drifting apart. A single `AI_VAULT_AGENT_SOURCES` table declares each agent's root directories, file extensions, and acceptance predicates. Replaced the custom delete confirmation dialog with the system dialog, simplifying the delete action hook and removing boilerplate state management.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
2026-08-07 11:47:26 -07:00
Jinjing 58531e4caa
Fix grok stale pane width (#13060)
* fix(terminal): restore canonical terminal rows from unified sessions

During session hydration, non-canonical terminal rows (closed/stale tabs)
were being restored alongside canonical ones. Filter to only canonical
terminal rows from unified sessions, treating legacy rows as terminal
backing data only.

* fix(terminal): preserve independent legacy PTY rows
2026-08-07 11:42:40 -07:00
Jinjing 2a2b517a85
Prevent empty agent-launch fallback on automation dispatch (#13068)
The automation session owns the prompt-bearing agent tab.
Passing undefined for createdWithAgent prevents spurious
empty agent tabs from opening when dispatching automations.
2026-08-07 11:41:50 -07:00
Brennan Benson bc1e049b3f
fix(terminal): defer metric option writes to unmeasurable panes (#12944)
* fix(terminal): defer metric option writes to unmeasurable panes

Writing fontSize/fontFamily/fontWeight/lineHeight makes xterm re-measure
cell size against the pane's current box. A hidden or mid-layout pane can
measure a wrong-but-nonzero size, which latches (hasValidSize) and mis-keys
the shared WebGL glyph atlas until a manual resize — the stuck variant of
the P0 bold/blurry-font reports.

Metric writes now land only on measurable panes; otherwise the latest
values park per-pane and flush on the next safe fit or reveal (with a refit
on the light tab-resume path, which otherwise skips fitting). Measurability
helpers move to pane-fit-measurability.ts to stay under the pane-fit.ts
line cap.

* fix(terminal): key metric deferral by terminal, not pane view

getPanes() returns a fresh toPublicPane() wrapper per call, so a
WeakMap keyed on ManagedPane never matched across call sites: deferred
metric options were dropped, not deferred. Key on pane.terminal, which
is carried by reference and dies with the pane.

Also from review:
- flushDeferredPaneMetricOptionsIfMeasurable checks the pending WeakMap
  before the measurability probe, so the common no-deferral case costs
  zero forced style/layout on every reveal.
- applyTerminalAppearance skips the apply (and the probe) when all five
  values are already live and nothing is parked; any settings write
  re-runs the pass over every mounted pane, and arming a no-op deferral
  would trigger a refit on the next reveal.
- fitRevealedPane flushes first: its pixel/grid checks can both no-op
  and return without fitting, stranding parked options.
- Font zoom folds its direct fontSize write into any pending deferral so
  the flush inside safeFit cannot clobber the user's zoom.

Corrects comments that asserted a cell-size re-measure mechanism xterm
does not have: CharSizeService measures via OffscreenCanvas TextMetrics,
independent of the pane box, and only fontSize/fontFamily re-measure.

Test fixtures now allocate a fresh pane view per getPanes() call, which
is what production does and what hid the keying bug.

* fix(terminal): re-check the fit floor after a metric flush

performSafeFit evaluated the min cols/rows gate with the pre-flush cell
size, then flushed and fit unconditionally. A large font jump on a
narrow pane passes the gate at the old size and lands under it at the
new one, so fit() pinned the PTY to the tiny grid the floor exists to
reject. Re-check after a flush that actually landed.

The parked values still apply, so the pane is never stuck on stale
metrics; only the fit is skipped.

* fix(terminal): route a reveal metric flush through the stable fit

fitRevealedPane's new flush branch called safeFit directly, which is
exactly what the function's contract forbids on reveal: resumeRendering
has just re-attached WebGL, whose cell metrics transiently differ from
the DOM renderer's, so a raw fit can propose a one-column-off grid and
reflow — and xterm's wrap/unwrap is not a perfect inverse, leaving a
diff-painting inline TUI corrupted.

A landed flush leaves pixels unchanged with a diverged grid, the same
shape as a snapshot resize, so it takes the same steady-grid repair.
A real resize still fits synchronously, after the flush.

Reachable via window wake, which calls fitAllRevealedPanes with no
pre-flush loop.

* fix(terminal): gate metric writes on the pixel box, not the fit floor

canApplyPaneMetricOptions reused canMeasurePaneForFit, whose >=8 cols /
>=4 rows floor exists to stop a fit pinning the PTY to a sliver. But the
divider clamp is 50px, which clears the 48px pixel floor and proposes
~5 cols — so a pane dragged to the clamp deferred every font change and
never flushed: it never hides, and its box never changes, so no reveal
and no ResizeObserver entry ever arrives. It rendered a stale font until
widened, where pre-PR the write was unconditional.

Gate metric writes on display plus the pixel box only. Hidden panes and
the transient worktree-switch overlay are near-zero, so they still
defer — the deferral's purpose is unchanged. The cols/rows floor stays
on the fit, including the post-flush re-check in performSafeFit.

Apply and flush share the same predicate, so no "applies but never
flushes" state can open up.

* fix(terminal): flush heavy reveal metrics after WebGL resume
2026-08-07 10:14:37 -07:00
Jinjing 6c9215a127
fix(worktrees): keep local base refresh failure toast sticky and named (#13059)
* fix(worktrees): keep local base refresh failure toast sticky and named

Create-time local-base refresh failures are easy to miss when the toast
auto-dismisses. Stick the warning until dismiss, name the new workspace,
surface the dirty owner path, and localize full detail sentences so ja/ko
copy is not mid-clause English.

* fix(i18n): use native commit terminology in local-base refresh toasts

Address CodeRabbit feedback: replace mixed English "commits" with
locale-native wording in ja/zh/ko failure detail strings, and shorten
the sticky-toast comment.
2026-08-07 10:10:53 -07:00
Jinjing 46b9d3b13a
Break out test and generated lines in branch line total (#13057)
* rm comments

* reduce comment
2026-08-07 09:55:54 -07:00
Neil 02a1251c2d
fix(native-chat): classify diff lines whose content begins with -- or ++ (#12459)
* fix(native-chat): stop diff colouring from misreading -- / ++ content lines as file headers

diffFromText skipped every line starting with --- / +++ as a file header, so a
deleted SQL/Lua '-- comment' (git emits '---<content>') or an added '++flag' fell
through to gray context with its marker still attached — and when it was the only
change, the two-marker gate dropped the coloured diff entirely.

Detect real headers structurally instead: an adjacent '--- <old>' / '+++ <new>'
pair outside any hunk. A hunk header or 'diff --git' line now also proves the text
is a diff, so a genuine single-line change renders while prose keeps the guard.

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

* test(native-chat): adopt #12335 diff-collision vectors and add mobile parity

Pulls in @YuriNachos's test vectors from #12335 (header-less --- deletion, an
adjacent --x/++y content pair, mobile re-export parity) and adds the spaced
-- / ++ pair inside a hunk, which the pair-only rule in that PR misreads.

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

* fix(native-chat): keep bare --- / +++ rules out of the diff marker count

Dropping the `---`/`+++` prefix exclusions made a bare `---` — a Markdown
thematic break or YAML document separator — classify as a deletion. Tool
results routinely carry those, so `---\na: 1\n---\nb: 2` went from correctly
rejected to rendering as a red diff.

A bare rule is never a file header (those need a path after the marker) and is
only content inside a hunk, so treat it as meta when outside one.

Fold the separate `isStructuredDiff` scan into the same pre-pass and skip
non-marker lines early, so the added guard costs no extra traversal: 5.1 -> 4.3
us per 120-line prose result, diff path unchanged.

---------

Co-authored-by: Orca <help@stably.ai>
2026-08-07 03:13:14 -07:00
Brennan Benson 419151dddd
fix(browser): present Firefox UA on Google auth hosts to end ~1h sign-in staleness (#12884)
Google binds a signed-in session to the browser identity that created it, and
rejected Orca's Electron/Chrome-shaped UA on accounts.google.com — so sessions
died at ~1h and re-copying cookies only reset the same losing clock.

Present a Firefox identity scoped to the Google/YouTube sign-in hosts at every
layer Google probes: the request headers (with sec-ch-ua* stripped, since real
Firefox sends none), the per-WebContents UA that auth JS reads, and the CDP
emulation override a viewport preset installs. All three must agree — a
disagreement between them is a sharper bot signal than any single wrong UA.

Native-UA profiles opt out at every layer, including popup child windows and
after a browser-native cookie import.

Validated live against a real account: freshness cookies issued and rotating on
a ~10-min cadence, session still signed in and self-renewing past 10 hours,
against a pre-fix baseline of death at ~1h.

Closes #11518.
2026-08-07 01:29:37 -07:00
Jinwoo Hong ddf58d6d6a
fix(terminal): restore preserved remote PTYs after host relaunch (#12990)
* fix(terminal): foreground preserved daemon PTYs

* fix(terminal): keep snapshot sequence domains distinct

* test(terminal): use active reconnect control

* test(terminal): await reconnect control activation

* test(terminal): validate reconnect with fresh control

* test(terminal): tighten host restart evidence

* fix(terminal): retry preserved PTY attach after inventory

* fix(terminal): retry attach after overlapping inventory

---------

Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com>
2026-08-07 01:25:18 -07:00
plotarmordev 9e4e6ddae5
feat(native-chat): render omp transcripts (#11523)
* feat(native-chat): render omp transcripts

omp already ships as a first-class launchable agent with session_id resume, but
its transcripts had no decoder, so native chat could not render it — the agent
runs and the conversation stays a raw terminal. This adds the decoder and wires
it through the same path Claude, Codex and Grok use.

omp writes one envelope per line, `{ type, id, parentId, timestamp, … }`, where
conversation turns are `type: 'message'` and the rest is session bookkeeping.
Reasoning arrives as a `thinking` content block inside the assistant turn, so
the mapping follows Claude rather than Codex: thinking becomes a text block on
an assistant message, where Codex and Grok emit a separate reasoning role only
because their transcripts carry dedicated reasoning records.

  - toolCall -> tool-call, arguments passed through as the object omp writes
  - toolResult -> tool role, isError preserved
  - developer -> system, matching the Codex non-user/non-assistant fallback
  - blob-handle images drop, as the Claude mapper drops an image record with
    neither path nor url
  - bookkeeping and unrecognized types skip rather than throw

Session files are `<ISO timestamp>_<session id>.jsonl` under a per-cwd directory,
so the resolver matches the id as a base-name suffix the way Codex rollout files
are matched, and honors OMP_CODING_AGENT_DIR through normalizeAgentSessionsDir
so it stays consistent with the AI Vault scanner.

omp records no interruption or abort event, so unlike Claude and Codex there is
no NATIVE_CHAT_INTERRUPTED_STATUS_TEXT path.

Verified against 94,603 lines of real omp transcripts across four sessions:
50,546 records decoded, zero malformed, zero thrown.

* fix(native-chat): complete omp record coverage and gate remote transcripts

Review fixes on the omp transcript decoder.

omp writes several record types with no `content` field, so they decoded
to zero blocks and disappeared from the chat view entirely:

- `bashExecution` / `pythonExecution`: TUI `!command` runs, now a tool turn
- `fileMention`: `@path` attachments, listed by path (never `files[].content`,
  which is an auto-read dump)
- `custom_message` and legacy `custom` / `hookMessage` rows, gated on
  `display` the way omp's own renderer gates them

Also:

- `stopReason: 'aborted'` turns now surface as the interrupted row, matching
  the Claude and Codex decoders. An abort carrying partial content keeps it.
- A cancelled command cell now reads as errored. Every omp cancel path emits
  `exitCode: undefined`, which JSON drops, so an `exitCode !== 0` check read a
  cancelled run as a clean success.
- omp joins Grok in requiring a locally readable transcript. Its hook reports
  no transcript path, so under Model-A SSH the chat view opened against a disk
  this process cannot read and never loaded. Applies on mobile too, which
  shares the same allowlist.
- The session-file walk prunes omp's per-session subagent artifact
  directories, matching the AI Vault scanner. It was returning a subagent
  transcript instead of the parent session, and cost a full recursive readdir
  on every resolve.

* style(native-chat): apply oxfmt to the omp review fixes

Mobile CI gates `oxfmt --check`; the two root files were unformatted too,
just ungated there. Line wrapping only, no behavior change.

---------

Co-authored-by: plotarmordev <299844489+plotarmordev@users.noreply.github.com>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
2026-08-07 00:58:29 -07:00
Wooseong Kim f057cbc85f
fix(serve): recognize CLI-form serve args on the Electron process (#12818)
* fix(serve): recognize CLI-form serve args on the Electron process

When the binary is launched as `… serve --port …` without the CLI rewrite
that injects `--serve`, normalize argv so isServeMode, headless GPU flags,
and serve option parsing all engage.

Preserves existing `--serve*` flag behavior for the CLI-spawned path.

Fixes #12677

* fix(serve): treat only CLI subcommand position as serve

Parse bare `serve` as the first positional token after flags/values so an
option value named `serve` cannot enable headless mode.

Addresses CodeRabbit on #12818.

* fix(serve): keep CLI redirects ahead of the serve argv rewrite

Rewriting argv before maybeRedirectAppImageCliLaunch replaced the `serve`
positional with `--serve`, so the redirect's command-name lookup saw a port
number and bailed — dropping AppImage serve launches out of the CLI path.

Also translate `--port=6768` (the CLI accepts it, getServeOptions only reads
the next token) and the mixed `--serve --port` form, so a security-shaped flag
like `--no-pairing` can no longer read as accepted while pairing stays on.
Map lookups replace `in` on object literals, which turned a stray `serve
toString` positional into a function spliced onto argv.

* fix(serve): close the CLI-form serve gaps found in review

second-instance: shouldActivateDesktopForSecondInstance matched only `--serve`,
so a duplicate `<binary> serve --port …` — the ExecStart shape documented in
docs/reference/headless-linux-server.md — promoted the live headless server to a
desktop window, un-fixing #11935 on exactly the launch shape this PR legitimizes.

findServeSubcommandIndex consumed a flag's value unconditionally while the
rewrite consumed it only when the next token was not flag-shaped. The two could
disagree and swallow the `serve` token, leaving `--serve` uninjected: #12677
again in a new shape (`--port --port serve`, `--port -- serve`). Both scans now
share one definition of value consumption.

`<binary> serve --help` / `serve help` bound a network-exposed runtime server
with pairing on and printed nothing; the AppImage redirect already routes those
three tokens to the CLI, so refuse them here too.

`--no-pairing=false` translated to `--serve-no-pairing` with the value dropped,
disabling pairing for an operator who asked for the opposite. The CLI reads its
serve booleans as `flags.get(name) === true`, so a boolean is now translated only
in its bare form and the `=` form rides through as the CLI treats it.

Tests: spec-derived parity between src/cli/specs/serve.ts and the rewrite,
covering both ends of the contract (serveOrcaApp and getServeOptions); a
source-text lock on the index.ts redirect/rewrite ordering, which reverted
silently green before; an exhaustive self-consistency property test; and the
real GUI launch argv shapes that must never enter serve mode.

---------

Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com>
2026-08-06 23:56:34 -07:00
Shani Singh 595097b5fc
fix(clipboard): stream the temp root when sweeping expired staged files (#12917)
cleanupExpiredRemoteClipboardFiles read the entire OS temp root with
readdir({ withFileTypes: true }) and mapped every entry into Promise.all,
so the prefix filter only ran after a promise already existed per entry.
The sweep is fire-and-forget from registerClipboardIpcHandlers at startup,
so a large %TEMP% froze the main process before the window came up.

Stream the root with opendir, skip foreign entries before allocating, and
cap in-flight removals at 8.
2026-08-06 22:50:51 -07:00
Jinwoo Hong 2b42de1f52
fix(orchestration): wake coordinators with mail pointers (#12988)
Wake idle Run coordinators with durable orchestration mail pointers while keeping message payloads in the store until check consumes them. Preserve waiter, Cursor, restart, real Codex title, and PTY replacement behavior.\n\nPart of #12953.
2026-08-06 22:50:09 -07:00
Jinwoo Hong 7e60b338ea
fix(persistence): fail closed when safeStorage cannot encrypt (#12983)
Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com>
2026-08-06 21:19:04 -07:00