Commit Graph
4440 Commits
Author SHA1 Message Date
OrcaWin 2f73775ffc fix(terminal): bound fullscreen atlas recovery (#12061) 2026-08-01 23:38:46 -07:00
Neil 4f963fd279 fix(browser): remove unsafe window close bypass (#12040)
* fix(browser): remove unsafe window close bypass

* test(browser): strip legacy close policy on hydration
2026-08-01 21:07:58 -07:00
Neil 8ab85c9bfc fix(quit): stop durable state writes from parking the main thread on quit (#11931)
* fix(quit): stop durable state writes from parking the main thread

will-quit ran stats.flush() and store.flush() synchronously, before
preventDefault(). Both fsync and rename a multi-MB file on the profile
directory. When that directory sits on a stalled network mount the
syscall enters an uninterruptible wait: the app stops repainting and
stops responding to Force Quit, because a process blocked in the kernel
ignores SIGTERM and SIGKILL alike.

The existing 20s teardown deadline could not bound this. Its timer runs
on the very thread the syscall parked, so it never fires. The fix is to
make the quit path awaitable rather than to try to bound it — a quit
that is slow but responsive stays killable by the OS.

- preventDefault() now runs first, so every teardown step is free to await
- stats and state gain flushAsync() twins that use node:fs/promises
- both join the existing teardown barrier, which can now actually bound them
- the pass-2 will-quit re-entry returns early instead of re-running teardown
- quitFlushStarted makes the quit flush the last write, so a teardown step
  touching the store cannot arm a debounce that races process exit

Making the swap async cost the atomicity of check-generation-then-rename:
a writer parked on await rename has already cleared the guard, so a later
synchronous flush could be clobbered by stale state. Both async writers now
claim their temp path, and the sync writers delete it, turning that swap
into a swallowed ENOENT.

Atomic temp+rename is unchanged, so a write cut short by the deadline
leaves the previous file whole — bounded loss, never corruption.

* fix(quit): harden async persistence finalization

* fix(persistence): bound best-effort flushes
2026-08-01 19:22:39 -07:00
Jinjing dbfffa6530 Add first user prompt to AI Vault session history row (#12006)
* Add first user prompt to AI Vault session history rows

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

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

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

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

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

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

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

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

Remounting the card on session switches prevents late responses from
a previous load from writing stale data into the component's refs.
Also improves conversation-turn key stability.
2026-08-01 18:38:49 -07:00
闲人 8fc892dd02 fix(i18n): add missing Editor Font Family translations for es, ja, ko, zh (#11573) 2026-08-01 16:22:39 -07:00
Jinjing 2f104d8713 Tier GitHub PR lookup polling to prevent quota exhaustion (#12013)
* Tier GitHub PR lookup polling to prevent quota exhaustion

The selected worktree (O(1)) checks per-minute; card list (O(N)) per-15-minutes.
Introduce process-wide cache to collapse concurrent polling and gate lookups on
available rate-limit budget with exponential backoff on failure.

- Preserve last-known review during backoff
- Invalidate cache when Orca opens a PR
- Stop coordinator from double-charging

* Tier GitHub PR lookup polling to prevent quota exhaustion

- Return the latest reset time when both GitHub API buckets are rate-limited, preventing premature retries against still-blocked buckets.
- Serve the last known review on transient lookup failures, preventing reviews from blinking out on temporary errors.
- Discard in-flight lookups that predate an invalidation so stale answers cannot overwrite newly opened reviews.

* fix: give rate-limit reset tests unique titles

oxlint vitest/no-identical-title was failing static analysis because two
cases shared the same describe title.
2026-08-01 15:50:54 -07:00
JinjingandOrca a7d769e13d Watcher explorer relay regressions (#12012)
* fix(watch/relay): bound remote watcher fan-out and read the live relay grace

Three P1 fixes from the SSH/remote freeze audit:

- Remote watchers now debounce on the same 150/500 window as local ones
  (finding D), and every teardown path drops the trailing flush timer
  instead of letting it fire into a dead watch. The deferred send is
  wrapped so a frame disposed mid-window can't escape as a fatal
  main-process exception.
- File Explorer refreshes are scheduled and concurrency-capped rather
  than fanned out unbounded over expanded dirs (finding C). Local
  transports use a zero window, since main already coalesced the burst.
- relay.startGrace reads ptyHandler.configuredGraceTimeMs instead of the
  launch-time argv closure, so a grace raised after launch is honored.
  The branch selection moves to relay-grace-branch.ts because relay.ts
  has no exports and calls main() at import, making it untestable.
  Consequence: a host-sleep relay holding zero PTYs now exits after the
  idle cap. Pinned by test and documented in
  docs/reference/relay-grace-time-reconfiguration.md.

Also drops the duplicated 150/500/5000 constants in the runtime-RPC
batcher in favor of the shared window module.

* docs(relay): correct grace-reconfiguration line numbers after the relay.ts edit

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

* refactor(file-explorer): use useMemo for paths; remove relay reference

Replace manual ref-based caching with proper React hooks for content-stable path memoization. Remove outdated relay grace-time reference documentation from code review cycle.

* rm design doc

* fix(watcher/explorer/relay): coalesce POSIX paths by byte identity; pres

- Remote watcher event coalescing now keeps NFC/NFD-distinct POSIX paths separate while still folding Windows path spellings, fixing cache invalidation when recreating directories with different Unicode compositions.
- Relay grace reconfiguration now preserves shutdown-deferred state when grace is set to zero, preventing premature shutdown when the grace timer is reconfigured mid-flight.
- File explorer refresh refactored from fixed-wave batching to concurrent task execution with `forEachWithConcurrency`, batching results every N settled reads instead of every wave, and reporting whether cancellation discarded pending work.
- Watch handler now resyncs when events arrive after disposal, ensuring refresh requests aren't lost when events race cleanup during worktree switches.

* fix(watcher/explorer): resilient commit batches on callback error

Cache writes precede callbacks, so a throwing callback cannot strand
the batch with stale marks. All callbacks complete despite errors,
with the first error thrown after.

- Fix commitBatchSize calculation for empty dirs
- Add scheduler discard-report test
- Move disposed variable before handleFsChanged
- Expand batching-strategy comments

* test(activity): stabilize portal readiness latch release under CI load

The fixed 9-flip budget could miss enough MutationObserver deliveries under
shard load for sibling DOM to still report loading. Drain readiness rAF after
each flip and wait until latched unavailable is actually observed.

* fix(watcher/explorer): stop batches on commit error; verify drain settle

Enhance resilience under CI load and error conditions:

- Portal readiness drain now returns a boolean confirming settlement; test expects verify the drain completed before proceeding.
- File explorer commit batches stop executing after a callback throws, preventing stale writes after caller-observed rejection.
- Watch hook requires worktree ID upfront in effect guard, eliminating redundant checks inside loops.

* fix(relay): re-arm grace on configured change

Refactor grace reconfiguration into a dedicated decision function. startGrace
samples the configured grace at arm time, so a raised value landing mid-window
would still fire at the old deadline without re-arming. Extract the logic with
proper tests to ensure shutdown-deferred state is preserved across re-arms.

* refactor(relay): extract grace reconfiguration to testable function

Extract `applyRelayGraceTimeConfiguration` so the grace-time re-arming
logic can be tested independently. relay.ts runs `main()` on import and
exports nothing, making the call site (including retryDeferredShutdown
hand-off into startGrace) otherwise untestable.

---------

Co-authored-by: Orca <help@stably.ai>
2026-08-01 15:45:45 -07:00
JinjingandOrca c5c10e1203 Fix POSIX path coalescing and file explorer watch regressions (#12004)
* fix(watch/relay): bound remote watcher fan-out and read the live relay grace

Three P1 fixes from the SSH/remote freeze audit:

- Remote watchers now debounce on the same 150/500 window as local ones
  (finding D), and every teardown path drops the trailing flush timer
  instead of letting it fire into a dead watch. The deferred send is
  wrapped so a frame disposed mid-window can't escape as a fatal
  main-process exception.
- File Explorer refreshes are scheduled and concurrency-capped rather
  than fanned out unbounded over expanded dirs (finding C). Local
  transports use a zero window, since main already coalesced the burst.
- relay.startGrace reads ptyHandler.configuredGraceTimeMs instead of the
  launch-time argv closure, so a grace raised after launch is honored.
  The branch selection moves to relay-grace-branch.ts because relay.ts
  has no exports and calls main() at import, making it untestable.
  Consequence: a host-sleep relay holding zero PTYs now exits after the
  idle cap. Pinned by test and documented in
  docs/reference/relay-grace-time-reconfiguration.md.

Also drops the duplicated 150/500/5000 constants in the runtime-RPC
batcher in favor of the shared window module.

* docs(relay): correct grace-reconfiguration line numbers after the relay.ts edit

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

* refactor(file-explorer): use useMemo for paths; remove relay reference

Replace manual ref-based caching with proper React hooks for content-stable path memoization. Remove outdated relay grace-time reference documentation from code review cycle.

* rm design doc

* fix(watcher/explorer/relay): coalesce POSIX paths by byte identity; pres

- Remote watcher event coalescing now keeps NFC/NFD-distinct POSIX paths separate while still folding Windows path spellings, fixing cache invalidation when recreating directories with different Unicode compositions.
- Relay grace reconfiguration now preserves shutdown-deferred state when grace is set to zero, preventing premature shutdown when the grace timer is reconfigured mid-flight.
- File explorer refresh refactored from fixed-wave batching to concurrent task execution with `forEachWithConcurrency`, batching results every N settled reads instead of every wave, and reporting whether cancellation discarded pending work.
- Watch handler now resyncs when events arrive after disposal, ensuring refresh requests aren't lost when events race cleanup during worktree switches.

* fix(watcher/explorer): resilient commit batches on callback error

Cache writes precede callbacks, so a throwing callback cannot strand
the batch with stale marks. All callbacks complete despite errors,
with the first error thrown after.

- Fix commitBatchSize calculation for empty dirs
- Add scheduler discard-report test
- Move disposed variable before handleFsChanged
- Expand batching-strategy comments

---------

Co-authored-by: Orca <help@stably.ai>
2026-08-01 14:49:33 -07:00
Jinjing 05206046f6 chore: condense code comments (#12008)
* chore: condense code comments

* chore: shorten more code comments

* clarify PTY agent session descendant cleanup behavior

Refine the comment on ptyAgentSessionIds to more accurately describe
when agent sessions sweep their descendant process trees and note the
exception on immediate Windows shutdown.
2026-08-01 14:24:31 -07:00
Neil 2b44e9ed9e fix(updater): notarize hourly macOS builds so TCC grants survive updates (#12007)
macOS anchors a notarized Developer ID app's TCC grants on identifier +
team, which is cdhash-independent and so survives an in-place update.
Without a notarization ticket there is no such stable identity, so every
hourly reads as a different client: the grant row stays but stops
matching, and file access under Documents/Desktop/Downloads fails with
EPERM and no re-prompt. `tccutil reset` fixes it until the next build —
and orca-hourly has shipped as many as 14 builds in a day.

Skipping notarization was chosen because Squirrel.Mac validates the
replacement bundle's signature, not its notarization. That is true, but
it is the wrong requirement; the in-place swap was never the problem.

Budgets grow to absorb the notary round trip (publish 2x45, job 150), and
the App token is re-minted after the build so its one-hour life starts at
the first call that uses it rather than during `pnpm install`.
2026-08-01 14:22:50 -07:00
36cc8495ef fix(terminal): stop the active-terminal repair loop from tripping React #185 (#11950)
* fix(terminals): make redundant tab activation idempotent (React #185)

setActiveTab always reallocated activeTabIdByWorktree, even when the tab was
already active for that worktree. Terminal's active-terminal repair effect
depends on that map, so when the repair cannot converge activeTabId -- which
happens when an earlier-scanned worktree reuses the tab id -- the effect
re-triggers itself every commit until React throws #185.

Crash cluster A: 12 reports, boundary terminal.workbench, 1.4.162/1.4.163.

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

* fix(terminals): converge activeTabId when a tab id is owned by two worktrees

Prefer the active worktree when resolving a terminal tab's owner. First-match
ownership left activeTabId permanently unconvergeable under a duplicated tab
id, so the active-terminal repair effect re-triggered itself into React #185.

Breadcrumb the duplicate-ownership state (once per tab id) so a crash bundle
can prove or kill the production origin of the precondition.

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

* fix(crash-reporting): coalesce the duplicate-tab-owner breadcrumb

Its renderer guard is once-per-tab-id, so the stale worktree map it exists to
diagnose duplicates every tab id at once and could evict the whole 30-entry
ring. Also drops two keyed re-reads of tabsByWorktree that would throw for a
prototype-named worktree id, and pins the activeTabIdByWorktree guard with a
test that fails without it.

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

* fix(crash-reporting): key the duplicate-tab-owner crumb on its convergence flag

Name-only coalescing keeps only the newest payload, so a resolvedToActiveWorktree
false sample — the one value saying the activation still could not converge — was
erased by any later benign true in the same 30s window. Keys on the flag instead,
mirroring the WebGL name:kind branch; two keys still bound the burst.

Also: the previous coalescing commit had no test at all (removing the name from
both sets broke zero of 2701 tests), the resolver's activeWorktreeId truthiness
check was a hole rather than a guard for a '' active id, and the resolver test
file failed oxfmt --check.

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

* fix(crash-reporting): keep the non-converging duplicate-tab verdict

The two earlier commits contradicted each other. Splitting the coalesce
key existed so a `false` verdict could not be erased by a later benign
`true` — but the renderer guard was keyed on the tab id alone, so for
any one id only the first verdict was ever emitted.

A duplicated id that first resolves benignly, then stops converging when
the user switches worktrees, dropped the `false` sample at the source.
That sample is the whole reason the breadcrumb exists: it is the only
value saying the activation could not converge activeTabId.

Key the guard on id plus verdict. At most two crumbs per tab id, and
the main process still folds each verdict into its own ring entry, so
the flood bound is unchanged.

* fix(crash): correct the duplicate-tab verdict rationale, pin and cap the guard

Three comments said `false` is the verdict that matters because it is the
only one showing the activation could not converge. That is backwards.
The repair effect activates a tab drawn from tabsByWorktree[active], so
the React #185 path can only ever emit `true`; `false` is what a
deliberate jump-to-agent into a background worktree emits from a fully
converged state. A reader of the next bundle would have discarded the
exact sample the breadcrumb exists to capture.

The mechanism was right, only its stated reason was wrong: the real
justification for keying on the verdict is symmetric, since coalescing
keeps only the newest payload and either verdict would erase the other.

The suite also did not pin the "at most 2 per tab id" bound - a guard
keyed on `${tabId}:${activeWorktreeId}` passed all 11 tests while
emitting once per worktree, the storm the guard exists to prevent. Adds
a count-pinning test that kills it.

Caps the never-pruned guard set at 256 distinct verdict keys (~85KB),
mirroring MAX_COALESCE_KEYS. Measured 330 B/entry; a realistic thousand
duplicated tab ids is ~0.6MB, negligible but unbounded in principle.

* perf(crash): scan worktree tabs by key, and soften the verdict rationale

Round 6 corrected my own round-5 comment. I had written that `true` is the
repair-loop signature and `false` covers a deliberate background activation. The
repair effect can emit `false` too: its closure holds the worktree from its render
while the guard runs against live state, so a worktree switch landing in between
reattributes the tab. The verdict hints at the caller; it does not prove it, and
neither value should be discarded. Comment-only.

Also take the free scan win the perf review measured: Object.entries allocates a
pair array per worktree on a path that runs per tab activation. Own keys are safe
to index by, so Object.keys plus an indexed read is behaviour-identical
(16.1us -> 5.0us at 170 worktrees x 10 tabs).

* fix(terminal): keep a duplicated tab id from re-sorting the active worktree

setActiveTab now prefers the active worktree when a tab id is held by more
than one, but terminals.ts has a second, older owner resolver:
getTerminalTabOwnerWorktreeId, a memoized map built last-writer-wins. Two of
its callers — setRuntimePaneTitle and clearRuntimePaneTitle — use the result
for the same "is this pane in the active worktree" gate, so under a duplicate
the two resolvers disagree: the cache names whichever worktree it saw last,
which can be a background one for a pane the user is looking at. The gate then
fails open and every classified OSC title frame bumps sortEpoch, reinstating
the click-driven sidebar re-sort #209 removed — 20 title frames measured 20
bumps, each one a store write that re-renders every sortEpoch subscriber.

isTabInActiveWorktree answers from the active worktree's own tab list instead
of a tie-break. It stays behind the cheap id equality so the common
non-duplicated path is unchanged, and it is a hasOwn lookup plus one scan of
that worktree's tabs rather than resolveActiveTabOwnerWorktreeId, whose full
scan would run per title frame and whose breadcrumb would fold a second caller
into one verdict.

Leaves updateTabTitle and clearTabLaunchAgent on the cache: they pick which
copy of a duplicated tab to mutate, where no answer is defensible until the
duplication itself is fixed.

* test(terminal): pin the SSH-hydration origin of the duplicate tab id

Drives the duplicate from real hydration rather than constructing it: a
direct-SSH snapshot is keyed by worktree path, so renaming the worktree on
the host (or re-adding the repo, which mints a fresh id) re-resolves it to a
new worktree id while replaceHydratedRecordKeys retains the old key verbatim.
Nothing de-dupes across keys.

Fails on unfixed origin/main with converged=false after 200 passes; the two
precondition assertions pass on both sides, so the red is the non-convergence
itself and not a setup divergence.

The reconnectPersistedTerminals stub is load-bearing and marked as such: with
no registered PTY the orphan sweep cleans the duplicate up before the repair
effect sees it.

* docs(test): record the end-to-end #185 reproduction method on the regression test

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

* test(terminal): pin the null-active-worktree guard in isTabInActiveWorktree

Dropping the `activeWorktreeId === null` early return was killed by nothing:
`Object.hasOwn(map, null)` coerces to the string key 'null', so a worktree
literally named 'null' would answer for "no active worktree". An untested
guard reads as dead code and gets deleted.

* rm triage context

* rm triage context

* rm context files

* refactor(terminal): extract repair logic into reusable hook and guard ag

Extract the active terminal repair effect from Terminal.tsx into `useActiveTerminalRepair`
hook to enable reuse in tests and clarify responsibilities. Replace falsy coercion guards
(`obj[id] ?? []`) with explicit `Object.hasOwn()` checks to handle edge cases: empty-string
worktree ids (valid but falsy), prototype-named ids like 'toString', and duplicated tab ids
across worktrees. Remove the now-unused `isTabInActiveWorktree` helper. Simplify the
isActive logic in terminals.ts to rely solely on owner-equality since the repair now uses
proper membership checks.

---------

Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
2026-08-01 13:53:14 -07:00
Jinjing 036b1e78ba fix(terminal): add replacement policies for repeated same-handle stream (#12003)
Recovery logic strengthened with replacement-policy tiers (reuse/prefer-replacement/require-replacement) and bounded same-handle end cycles. Prevents infinite flapping by capping reuse attempts and inventory wait windows. Tracks ready evidence to reattach from prior snapshots when inventory becomes unavailable.
2026-08-01 13:45:49 -07:00
JinjingandOrca 5c0195af64 Bound remote watcher fan-out and defer File Explorer refreshes (#11908)
* batch remote watcher events and defer File Explorer refreshes

Remote filesystem watcher events now batch with the shared 150ms trailing and 500ms
max-wait window, coalescing per-path like local events. File Explorer tree and
directory refreshes are scheduled with debounce and transport-aware concurrency caps
(16 local, 8 runtime, 4 SSH). Stale directory cache tracking prevents trusting
collapsed listings skipped by full refresh; they are re-read on re-expansion. Relay
implements a 15-minute idle-only grace cap for zero-PTY relays via PTY pool
lifecycle tracking, independent of explicitly configured grace time.

* fix(watch/relay): bound remote watcher fan-out and read the live relay grace

Three P1 fixes from the SSH/remote freeze audit:

- Remote watchers now debounce on the same 150/500 window as local ones
  (finding D), and every teardown path drops the trailing flush timer
  instead of letting it fire into a dead watch. The deferred send is
  wrapped so a frame disposed mid-window can't escape as a fatal
  main-process exception.
- File Explorer refreshes are scheduled and concurrency-capped rather
  than fanned out unbounded over expanded dirs (finding C). Local
  transports use a zero window, since main already coalesced the burst.
- relay.startGrace reads ptyHandler.configuredGraceTimeMs instead of the
  launch-time argv closure, so a grace raised after launch is honored.
  The branch selection moves to relay-grace-branch.ts because relay.ts
  has no exports and calls main() at import, making it untestable.
  Consequence: a host-sleep relay holding zero PTYs now exits after the
  idle cap. Pinned by test and documented in
  docs/reference/relay-grace-time-reconfiguration.md.

Also drops the duplicated 150/500/5000 constants in the runtime-RPC
batcher in favor of the shared window module.

* docs(relay): correct grace-reconfiguration line numbers after the relay.ts edit

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

* refactor(file-explorer): use useMemo for paths; remove relay reference

Replace manual ref-based caching with proper React hooks for content-stable path memoization. Remove outdated relay grace-time reference documentation from code review cycle.

* rm design doc

* fix(remote-watcher): prevent stranded timer after close

An in-flight provider receive can land after the batch is torn down.
Without a guard, pushing events to a closed batch would re-arm a timer
that would never be cleared, stranding the task indefinitely. Track the
closed state and skip pushes after close().

Relay.ts comment clarifies why pool watches remain registered during
grace-period shutdown deferral — the socket server stays listening so
a reconnecting client can cancel the grace and resume.

---------

Co-authored-by: Orca <help@stably.ai>
2026-08-01 11:55:58 -07:00
Neil 16c5526dfd fix(daemon): cover in-flight sleep in PAM watch (#11921)
* fix(daemon): rebaseline in-flight PAM suspension

* test(activity): await portal readiness commits
2026-08-01 03:35:34 -07:00
Neil 1f307afa6d fix(terminal): preserve follow output through streaming refocus (#11915) 2026-08-01 03:26:30 -07:00
Neil c79b859758 fix(browser): prevent window.close guest crashes (#11910)
* fix(browser): prevent window.close guest crashes

* fix(browser): guard close before inline scripts

* fix(browser): preserve explicit window close policy
2026-08-01 03:08:11 -07:00
Neil 340faaa839 fix(workspaces): use the emojibase shortcode preset for emoji suggestions (#11888)
Swap the worktree-name emoji picker from emojibase-data's `github` shortcode preset to `emojibase`, which carries both `flag_kr` and `south_korea` style flag names, and drop the hand-maintained `kr` entry that patched around the gap. Filter skin-tone aliases so they neither crowd the suggestion list nor clobber base-emoji branch names.

Search now matches anywhere in the shortcode, ranked exact > prefix > word-start > substring, so `:korea` surfaces both Koreas.

Emoji-derived branch names now prefer spelled-out aliases: flags use country names (japan, germany, south-korea) and cryptic stubs are skipped (thumbsdown over no, victory over v).
2026-08-01 00:40:47 -07:00
Neil 278a4b28c8 fix(terminal): close async capability review gaps (#11887) 2026-08-01 00:00:12 -07:00
OrcaWinandNeil c8a22ad0a6 fix(terminal): make snapshot capability lookup async (#11881)
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-07-31 23:38:26 -07:00
Brennan Benson 402e49203d fix(codex): keep persistent panes logged in after home routing (#11720) 2026-07-31 18:33:26 -07:00
072133fb27 fix(preflight): route landing banner through the runtime-aware preflight slice (#11390)
* fix(preflight): route landing banner through the runtime-aware preflight slice

Landing called window.api.preflight.check directly, which always probes the
local client. The preflight slice is the only caller that consults
getActiveRuntimeTarget and forwards to preflight.check on the active runtime
environment, so while connected to a remote runtime the landing banner
reported the client machine's git/gh state instead of the server's.

Delegate to refreshPreflightStatus and derive the issue list from
state.preflightStatus. This also drops Landing's duplicate probe: the slice
dedupes concurrent and forced checks, so the mount/focus/poll paths now share
one in-flight request with the rest of the app.

* fix(preflight): refresh landing status across runtime sessions

* test(preflight): cover paired runtime session races

* test: make landing preflight oracle behavioral

* fix(preflight): scope runtime session invalidation

* test(preflight): cover headed runtime switching

* test(preflight): isolate runtime status toast

---------

Co-authored-by: Marty <marty@localhost>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-07-31 18:01:24 -07:00
OrcaWin 33ad64b1c8 fix(runtime): bound persisted graph hydration (#11832) 2026-07-31 17:56:40 -07:00
Brennan Benson 377b580bab Restyle voice dictation feature tip (#11842)
* Restyle voice dictation feature tip

* Remove workspace name from dictation preview

* Refine voice dictation tip instructions

* Clarify voice dictation setup action

* Fix voice dictation tip keyboard behavior

* Use neutral voice dictation demo prompt
2026-07-31 17:51:03 -07:00
Neil 5738d61fe0 fix(workspaces): guarantee Korean flag kr shortcode (#11858) 2026-07-31 17:33:35 -07:00
fsdwen 1a0a1ce4d0 fix(i18n): correct mistranslation of Grace/grace period in Chinese (#11505) (#11507) 2026-07-31 17:18:36 -07:00
Neil e79304ccca feat(workspaces): add Korean flag shortcode (#11845) 2026-07-31 17:16:49 -07:00
Neil 3c05e03c6f Revert "feat(workspaces): add Slack emoji aliases (#11837)" (#11843)
This reverts commit adc56a71f2.
2026-07-31 17:09:41 -07:00
Neil adc56a71f2 feat(workspaces): add Slack emoji aliases (#11837) 2026-07-31 16:39:38 -07:00
Brennan Benson 9bf05b0a9c Prevent Agent sleep while orchestration dispatch is active (#11808)
* fix(agent-sleep): keep active dispatch workers awake

* fix(agent-sleep): harden background work detection
2026-07-31 16:35:33 -07:00
OrcaWin 139f756064 Add recursive sleep actions for workspace descendants (#11810) 2026-07-31 16:15:20 -07:00
Brennan Benson e4937956b3 Improve disconnected server recovery copy (#11827) 2026-07-31 16:03:59 -07:00
475f63ea1b fix(remote): scope renderer throttling to paired terminal publication (#11581)
* fix(remote): unthrottle host renderer while serving a paired client

A paired desktop host left in the background could not open or close
agent sessions for its remote/relay client: the action stalled and
eventually failed with the host-side "Timed out waiting for terminal
surface after creation" (10s) error, while an already-live terminal's
keystrokes stayed fast.

Root cause: creating/closing a session routes through the host
renderer's setTimeout-coalesced graph sync to publish the terminal
surface, but the host window runs with Electron background throttling
(the hidden-window default, reaffirmed on macOS). When the window is
backgrounded/occluded, those renderer timers are throttled to a crawl
and the surface publication misses the 10s deadline. Live keystrokes are
unaffected because PTY I/O flows through the main process, never the
renderer.

Keep the authoritative renderer unthrottled while at least one remote
client is connected and restore the throttled power-saving default once
the last one disconnects. Connect/disconnect are driven from the shared
MobileSocketWiring onReady/onClose, so both direct-WS and cloud-relay
clients are covered; headless serve has no window and is a safe no-op.

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

* refactor(remote): tidy renderer-throttle comment and test per review

Address automated review nits on #11581:
- Trim the module-level rationale comment to the non-obvious contract,
  matching the repo's concise-comment guideline.
- Drop the dead `detachedThrottle` variable from the reapply test; the
  detached-target scenario is already covered by the lazy-resolution
  test, so the case now asserts only what it exercises.

No behavior change.

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

* fix(remote): scope paired terminal publication throttling

Keep headed paired terminal creation and close renderer-owned so host inventory, input routing, ACK recovery, and cleanup retain the established lifecycle. Hold a reference-counted background-throttle lease only while the renderer publishes a paired operation, and epoch-fence async resolution so renderer reloads reject before any request or PTY spawn. Preserve headless main ownership and prevent paired clients from falling back to a local terminal.

* test(e2e): verify minimized host terminal repaint

* fix(remote): preserve paired terminal inventory through graph gaps

---------

Co-authored-by: fanyunqian.1 <fanyunqian.1@bytedance.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-07-31 16:01:44 -07:00
Brennan Benson fcb23e19cd fix(sidebar): stabilize cross-host project grouping (#11805)
* fix(sidebar): stabilize cross-host project grouping

* fix(runtime): suppress outage toast on disconnect

* fix(runtime): dismiss intentional outage toast

* fix(runtime): keep offline retry toast visible
2026-07-31 15:38:08 -07:00
Brennan Benson 4205d5f31f Refine macOS access prompt guidance (#11822)
* Refine macOS access prompt guidance

* Capitalize MacOS in access prompt

* Use official macOS styling in prompt
2026-07-31 15:13:41 -07:00
Brennan Benson 751b6b119b fix(scm): keep git-status pacing across scheduler rebuilds (#11820)
* fix(scm): keep git-status pacing across scheduler rebuilds

* fix(scm): order shared refresh pacing updates
2026-07-31 15:13:04 -07:00
Brennan Benson 4d044c47dc Revert "Clarify macOS access prompt guidance (#11807)" (#11821)
This reverts commit 7d24dad48a.
2026-07-31 14:43:30 -07:00
Brennan Benson 7d24dad48a Clarify macOS access prompt guidance (#11807)
* Clarify macOS access prompt guidance

* Make settings target highlight visible
2026-07-31 14:38:27 -07:00
Neil 90963f8ee3 [P1] fix(routing): keep the paired-HUB owner for the active workspace selection (#11818)
* [P1] fix(routing): keep the paired-HUB owner for the active workspace selection

#11346 made `activeWorkspaceExecutionHostId` authoritative for the active
workspace, but the short-circuits that read it never see the owner record, so a
project reached through a paired runtime HUB (`hostId: ssh:<target>` plus
`runtimeOwnerEnvironmentId: <hub>`) loses its transport and its repo:

- `resolveWorktreeOperationRouteResult` returned `runtimeEnvironmentId: null`
  for any non-`runtime:` selection, bypassing the HUB recovery in
  `resolveExactWorktreeRoute` — every owner-routed terminal/git/filesystem
  operation on the active workspace lost the HUB that proxies the SSH target.
- `selectRepoByIdForActiveWorkspace` filtered `state.repos` by the selected host
  with no fallback, but `withRepoHostOwnership` deliberately keeps the SSH host
  on the worktree while the repo row stays HUB-owned — so the active repo read
  as `null` and `useGitStatusPolling` stopped polling the whole workspace.

Route resolution now recovers the HUB owner from the owner rows on the selected
host, and drops it only when rival HUBs project the same host. The repo selector
opens exactly one hole in #11346's fail-closed rule — an `ssh:` selection over a
`runtime:` repo row — and every other host mismatch still returns `null`.

`worktree-owner-route.ts` is a pure extraction of the existing owner-route
helpers; no behavior moved with it.

* fix(routing): fail ambiguous paired-HUB repo selection closed
2026-07-31 14:38:08 -07:00
NeilandOrca cd2b62ed14 feat(updater): name hourly releases by version, build number, time, and sha (#11817)
* feat(updater): name hourly releases by version, build number, time, and sha

Hourly releases were titled with their raw tag
(`v1.4.163-hourly.202607312054`), which reads as one opaque digit run and
does not say which commit it came from.

Title them `1.4.163 • 01 • 07-31 13:54 • e698241` instead, and show that
same string in the in-app build picker by having the picker render the
release's stored name rather than deriving its own label. Composing it in
one place means the two surfaces cannot drift.

The build number is monotonic across the channel. It is read as the
highest number already in use rather than as a count of releases: the
prune step trims to 72, so a count would roll backwards after three days
and reissue numbers. Drafts count toward it — unlike in the freshness
check, which asks whether a commit shipped, this asks whether a number is
free, and a stranded draft still holds one.

Times are Pacific while the tag's stamp stays UTC. The stamp is a sort
key and a local one would repeat an hour at every DST fall-back, making
two distinct builds compare equal; the title is only ever read.

* fix(updater): fail the hourly build when the release name is missing

The workflow checks out `ref: main`, but a workflow_dispatch runs the
workflow file from whatever branch was dispatched. A branch that edits
this step while main still carries the old script produces an empty name
and an untitled release — silent, and only visible once someone opens the
releases page. Verified by hitting exactly that on run 30665586904.

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

---------

Co-authored-by: Orca <help@stably.ai>
2026-07-31 14:29:20 -07:00
NeilandOrca bd490b14aa [P2] fix(terminal): always close the bracketed-paste frame and hold the pty lock through submit (#11816)
Co-authored-by: Orca <help@stably.ai>
2026-07-31 14:05:07 -07:00
NeilandOrca ea40c87315 [P2] fix(skills): keep the npx preflight in the forced-PowerShell setup terminal on Windows (#11814)
Co-authored-by: Orca <help@stably.ai>
2026-07-31 14:03:26 -07:00
NeilandOrca 81fee4d6a4 [P2] fix(checks): give the PR page and work-item dialog the shared check-count labels (#11813)
Co-authored-by: Orca <help@stably.ai>
2026-07-31 14:03:03 -07:00
NeilandOrca b998f7b13e [P2] fix(updater): route hourly release-notes links to the hourly repo and let the disabled channel tooltip open (#11812)
Co-authored-by: Orca <help@stably.ai>
2026-07-31 14:02:30 -07:00
Brennan Benson e698241aab fix(mobile): remember custom pairing addresses (#11741)
* fix(mobile): remember custom pairing address

* fix(mobile): stabilize custom pairing address sync

* fix(mobile): update pairing refresh refs after commit

* feat(mobile): manage saved custom pairing addresses

* fix(mobile): harden custom address selection
2026-07-31 13:03:11 -07:00
129d8b32bb fix(codex): trust extended-length resume paths (#10337)
* fix(codex): trust extended-length resume paths

* test(codex): cover both sides of extended-length resume folding

The provenance fix folds the extended drive spelling on the rollout path and
the trusted sessions root, and runs per entry in the legacy id scan. Only the
rollout-path side had coverage, so pin the rest:

- normal-form rollout under an extended-length trusted home (root side)
- compressed .zst sibling derived from an extended-length persisted path,
  where a folded comparison copy could leak into the returned path
- legacy id-scan entries: extended-length accepted, device namespace refused

All three fail with the production change reverted.

* docs(codex): state accurately what the resume path fold rejects

The helper's comment called the rejected spellings "arbitrary device
namespaces", but \?\UNC\ is the extended-length form of \server\share\ —
a network share, not a device. Reading it as a security decision hides that
UNC is simply not folded yet, so say what is actually excluded.

Also trim the two new test comments to one line to match the file.

* fix(codex): reject rollout alternate data streams

* fix(codex): preserve resume provenance guard

---------

Co-authored-by: OrcaWin <alpha-eng@stably.ai>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-07-31 11:59:05 -07:00
Brennan Benson f3f5a928f6 Fix duplicate terminal renderers after layout restore (#11726)
* Fix duplicate terminal PTY layout ownership

* Fix rootless duplicate PTY replay

* Normalize mirrored terminal PTY ownership

* Handle repeated terminal layout leaf ownership

* Preserve terminal ownership through duplicate layout repair

* Repair dangling PTY layout selection

* Preserve terminal ownership repair metadata

* Preserve rootless pending terminal focus

* Bound duplicate terminal layout pruning

* Preserve agent authority during layout repair

* Fix hydrated terminal pane authority repair
2026-07-31 11:25:30 -07:00
NeilandOrca 79251d7a98 [P2] fix(release,settings): restore signing preflight portability, bootstrap diagnostics, and skill re-check (#11692)
* fix(release): restore the SignPath composite action when cutting from an older ref

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

* fix(startup): record a durable diagnostic before the bootstrap fatal-exit guard exits

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

* fix(settings): make agent-skill Re-check rescan skill freshness

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

* fix(startup): keep the bootstrap fatal diagnostic when the log override is unwritable

Create the parent directory an overridden ORCA_BOOTSTRAP_FATAL_LOG names and fall
back to the default location when that path still cannot be opened, so a missing
parent no longer costs the only account of the failure. Also pins the Re-check
freshness rescan to the completed install scan rather than the click.

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

* refactor(settings): move the post-recheck surface sync out of the panel

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

* fix(startup): retain diagnostics without node fs

* fix(skills): keep freshness scoped to the local runtime

* fix(settings): register freshness status translations

* fix(settings): scope and sequence skill freshness refreshes

* fix(settings): refresh freshness across runtime transitions

---------

Co-authored-by: Orca <help@stably.ai>
2026-07-31 06:08:26 -07:00
NeilandOrca fdb58695e9 [P1] fix(checks): stop skipped and manual checks reporting as failures (#11700)
* fix(checks): stop skipped and manual checks reporting as failures

Route every check-classification surface through one shared helper so
desktop renderer, desktop main and mobile agree on the same verdict.

- GitLab `manual` jobs and pipelines are neutral again, not action_required/failure
- `skipped` counts as passed everywhere, including mobile
- a neutral check no longer demotes a summary that has passing checks

* fix(checks): move the check-classification parity test into the renderer project

The parity table lived in src/shared but imported a renderer module, and both
config/tsconfig.node.json and config/tsconfig.cli.json are composite projects
that include src/shared without that renderer path, so `pnpm typecheck` failed
with TS6307 on two of its three projects. Only the web project spans both trees.

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

* fix(checks): stop the Tasks-grid pill contradicting its own verdict

The checks pill's label, tone and icon all read one ProviderCheckSummary, but
getChecksLabel short-circuited on the raw `neutral` counter while the tone and
icon key off `state`. After the classification fix a PR with 19 success + 1
neutral renders an emerald CheckCircle2 pill that reads "1 unresolved", and
mobile's own label (which keys off `state`) reads "19/20 passed" for the same
summary.

Move the label into src/shared/provider-check-summary.ts so desktop and mobile
cannot fork it again, and key it off `state`.

Also covers deriveWorkItemCheckSummary, the desktop-main producer of the summary
that reaches the Tasks grid and the relay-paired mobile client. It was rewritten
here with no test at all; the parity table stands in derivePRCheckStatusFromRollup,
which is a different normalizer. The new main-process test drives getWorkItem with
a real statusCheckRollup fixture, pinning the StatusContext `state` fallback that
would otherwise be deletable with the whole suite still green.

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

* fix(gitlab): route the pipeline job-array rollup through the shared check classifier

The array path in derivePipelineStatus kept its own copy of the rollup rules, so
manual-only read green and one unrecognized job status demoted a passing pipeline
to neutral — both disagreeing with every other check surface.

Also retry the packaged-CLI smoke temp cleanup on Windows: the copied Orca.exe can
still be locked by AV/indexers after every assertion passed, failing the package job.

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

* fix(gitlab): stop the skipped pipeline string diverging from the Checks tab

- classifyPipelineString now counts a skipped pipeline as passing, matching
  the per-check classifier; canceled stays neutral and is pinned as an
  explicit, sign-off-pending divergence.
- Pin the production string path (head_pipeline.status) in the parity table
  and note that the job-array branch has no production caller yet.
- Count skipped checks in the Checks panel's passing header so it agrees
  with the checks pill.
- Correct the packaged-CLI smoke retry comment: the EBUSY is the smoke's own
  just-exited Electron process, not AV/indexers.

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

* fix(checks): finish cross-surface check parity and back out the skipped MR-card flip

Review follow-ups on the check-classification PR.

- PullRequestPage and GitHubItemDialog kept private copies of getCheckCounts /
  getChecksSummaryLabel that still counted only `success` as passing, so a
  2-success/3-skipped PR read "2 passing · 3 skipped" there and "5 passing" in
  the sidebar. Both copies move to pr-check-counts.ts, which routes the passing
  bucket through classifyCheckOutcome; action_required keeps its own amber
  bucket. The summary icon now keys off passing count, so an all-neutral PR
  stops painting a green tick above "0 of N checks passing".
- The sidebar checks header and triage strip still called
  `{status: completed, conclusion: null}` pending, contradicting the grey
  "Unresolved checks" pill. Both now read summarizeProviderChecks and render an
  unresolved chip/strip instead of an amber spinner that can never resolve.
- classifyPipelineString('skipped') is reverted to neutral. That flip painted
  MR cards green for pipelines that never ran, on the only GitLab path with
  production callers, and contradicted the same function's deferral of
  `canceled`. Both tone changes stay deferred, pinned by one test.
- classifyPipelineString('manual') resolves to pending rather than neutral: a
  blocked pipeline is outstanding, and neutral let the worktree card fall
  through to its emerald `open` default while GitLab still refuses the merge.
- TaskPage's checks pill helpers move to task-page-checks-pill.ts so the
  "1 unresolved on a green pill" fix is actually pinned by a test.
- smoke-packaged-cli no longer lets an EBUSY cleanup replace the real failure.

* fix(checks): stop completed unknown checks from spinning

---------

Co-authored-by: Orca <help@stably.ai>
2026-07-31 04:58:15 -07:00
NeilandOrca bc6a6e9ff0 [P2] fix(daemon): keep audit-only evidence off the main thread and out of false negatives (#11695)
* fix(daemon): keep audit-only evidence off the main thread and out of false negatives

Three audit-only daemon evidence defects:

- The Windows CIM probe emitted the "absent" marker whenever $p was empty, so a
  non-terminating Get-CimInstance failure (Winmgmt down, corrupt WMI repository,
  access denied) exited 0 with {"exists":false} and was recorded as proof the
  daemon process was gone. The script now sets $ErrorActionPreference and reports
  query failure explicitly; only a query that ran and found nothing yields
  'missing', everything else stays 'unavailable'.
- probeMacosProcess read the process start time through execFileSync('ps') on the
  Electron main thread. It now uses the async readMacosProcessStartedAtMs, matching
  the async command-line sibling in the same probe.
- daemon_audit_eligibility fired on every successful inventory, so routine
  listProcesses traffic could drain the shared 1,000-event per-session telemetry
  ceiling. Repeated identical observations now collapse to one heartbeat per
  5 minutes; a changed observation still emits immediately.

The evidence stays strictly audit-only — no lifecycle, routing or recovery path
consumes the observation.

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

* fix(daemon): take the linux audit start-time read off the main thread

The linux branch of the audit-only evidence probe still bottomed out in
getProcessStartedAtMs, i.e. two readFileSync calls plus an
execFileSync('getconf', ['CLK_TCK']) spawn on the Electron main thread —
the same hazard the darwin half of this change removed. The `async`
wrapper hid it from the type signature and from the main-thread
regression test, which only covered darwin.

That path is reached whenever the pid record carries no start ticks or
boot id: legacy bare-integer pid files (the upgrade population this audit
exists to observe) and any host where /proc boot-id or start-tick reads
fail.

readLinuxProcessStartedAtMs now reads procfs asynchronously and caches
CLK_TCK for the process lifetime (it is fixed for the kernel's lifetime),
retrying only after a failed probe. The main-thread test now covers linux
with a ticks-less pid record.

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

* fix(daemon): measure the audit heartbeat window on a monotonic clock

A backward wall-clock jump (NTP correction, VM resume) made elapsed time negative, which read as "still inside the window" and suppressed daemon_audit_eligibility heartbeats until wall time caught up. Rate-limit arithmetic now runs on performance.now() and treats any backward movement as eligible.

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

* fix(daemon): guard the audit rate limiter and scope the CLK_TCK cache

The rate-limited tracker is the only production emitter, but its payload
serialization and clock read sat outside the guard that keeps audit
telemetry from affecting daemon availability — a throw there propagates
out of listProcesses' try and turns a successful inventory into a
reported inventory failure.

Also key the CLK_TCK cache by the runner that produced it: the value
belongs to the host executing getconf, not the module.

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

* test(daemon): make audit CI synchronization deterministic

---------

Co-authored-by: Orca <help@stably.ai>
2026-07-31 04:56:14 -07:00
LailyandOrcaWin 60d2493bbb fix: route server project adds to selected runtime (#11346)
* fix: route server project adds to selected runtime

* fix: preserve selected runtime for nested project scans

* fix: retain nested review runtime ownership

* fix: preserve nested runtime owner through completion

* fix: preserve captured add repo owners

* refactor: isolate add repo nested review controller

* test: cover selected runtime project adds

* fix: preserve selected project host ownership

* fix: pin SSH nested import completion

* fix: fence missing SSH repo refreshes

* test: cover selected runtime reconnect routing

* test: register selected runtime project routing gate

* fix: preserve selected host across all project adds

* fix: isolate selected-host project catalogs

* fix: preserve host-qualified workspace identity

* test: expect local folder host identity

* test: preserve host-qualified activation assertions

* fix: fence folder refreshes by host identity

* fix: preserve runtime owner across project refresh events

* test: fence selected-runtime reconnect oracle

* fix(runtime): preserve selected host during session activation

* test(runtime): force same-id paired terminal activation

* chore(reliability): register prior selected-runtime evidence

* test(runtime): seed isolated Git identity

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-07-31 04:32:23 -07:00