* fix(ai-vault): support session scanning in SSH worktrees
Add relay-native aiVault.listSessions scanning that discovers agent
sessions on SSH hosts. Includes fallback to filesystem crawl for
legacy relays, full cancellation support, result validation, and
scan coalescing to reduce redundant work.
* fix(ai-vault): scan sessions in SSH worktrees with coordinated cancellat
- Extract batching logic to `mapRemoteScanBatches` for reuse and proper cancellation checkpoints
- Move `AiVaultScanCoordinator` from relay to main to handle concurrent same-key requests with individual cancellation signals
- Report scope path truncation consistently across relay and SSH fallback paths
- Gracefully degrade relay handler on unsupported platforms instead of aborting startup
- Refactor issue display to separate blocking errors, scope notices, and skipped transcript counts
* fix(ai-vault): stabilize SSH session scan CI
Swallow async WSL relay stdin EPIPE so the live hook-relay shard no longer
fails after all tests pass. Merge main, resolve scan/relay conflicts, and
align cancellation/host-issue reporting with IPC expectations.
* fix(ai-vault): harden session scan cancellation, relay timeouts, and preemption
Thread the abort signal through every scan and parse path so superseded or
cancelled scans stop promptly instead of parsing every remaining transcript
for a caller that already left. Replace the fragile message-text relay
timeout check with a typed error code so unrelated errors carrying the
phrase "timed out after" no longer suppress the filesystem fallback. Fix
scan coordinator preemption so a forced Refresh in one window no longer
re-enters as a spurious cancellation in another. Add a host-leg cache for
the all-hosts view and cap filesystem concurrency so a single slow remote
home cannot stall the whole merge.
Co-authored-by: Orca <help@stably.ai>
* fix(ai-vault): use stable React keys for scan issue banners
Drop array-index keys so react-doctor/no-array-index-as-key passes.
Uniqueness comes from host, kind, agent, path, and message.
* fix(ai-vault): SSH session scanning with configurable depth limits
Implement depth-aware caching and proper scan boundaries to make SSH session
scanning reliable in worktrees. Users can now select between faster (250
sessions) and comprehensive (unlimited) history scans. The scanner:
- Deduplicates scans across relay, host leg, runtime, and renderer layers
- Reuses larger scans to serve smaller depth requests
- Properly bounds in-scope discovery per-limit
- Fixes timeout enforcement when SSH providers ignore abort signals
* Move sessionLimit ref update to useLayoutEffect
Keep render pure for React Doctor by deferring ref updates to
a layout effect, which still executes before render-dependent
effects that consume the ref.
* fix(adhoc): stamp version prefix from main, not the feature branch
Adhoc builds check out arbitrary refs whose package.json often lags
version bumps (e.g. 1.4.165-rc.0 while main is 1.4.168-rc.1). Hourly
always builds main so it already tracks the product line; adhoc now
resolves the base version from origin/main (or ORCA_ADHOC_BASE_VERSION)
so branch builds share that prefix.
* Revert "fix(adhoc): stamp version prefix from main, not the feature branch"
This reverts commit a26a18eb3fd83f7e7d2db9a6a7c3e02e0f79089a.
* fix(ai-vault): fix scoped backfill and coordinator race conditions
Resolve race where the last waiter leaving could abort an already-settled scan (add `settled` flag). Redesign scoped session backfill to keep searching through newer files until the scope reaches its requested session quota instead of stopping at the candidate limit; out-of-scope files no longer consume the scope budget. Centralize scan limit normalization and fix error classification for cancelled scans using the proper helper instead of checking Error.name. Disambiguate cache keys using JSON and add cancellation check after scope discovery phase.
---------
Co-authored-by: Orca <help@stably.ai>
Bound exclusive host navigation to a generation-aware latest-wins
single-flight so bulk open and switch fan-out stay responsive on large
remote fleets. Add freeze repro harnesses and navigated settlement.
* fix(terminal): expand variables in Windows PATH
* fix(terminal): preserve expanded Windows PATH at spawn
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* Open SSH host add/edit form in modal dialog
Form moves from inline to a viewport-stable modal (STA-3067) so fields stay
accessible with long host lists. Includes sticky header/footer, dirty-state
protection against outside click, and session-aware Advanced state reset on
cancel/reopen.
* fix: add missing SshTargetForm localization keys
Sync en.json catalog for modal title/description strings so
verify:localization-catalog passes in static analysis.
* fix: translate SshTargetForm modal strings in es/ja/ko/zh
Add non-English catalog entries for the new modal title and
description keys so localized UIs match English.
* Prevent SSH form double-submit and fix dismissal detection
Adds a saving state to prevent concurrent saves when a user double-clicks
the submit button. Fixes outside-click dismissal by correctly tracking form
state across re-renders using refs. Extracts session termination logic to
a reusable module.
* fix: stop mutating formRef during render in SshTargetForm
React Doctor fails the static-analysis gate when refs are written during
render. Sync form into formRef in an effect so render stays pure.
* feat(voice): allow selecting a microphone for dictation
Persist a preferred audioinput device in Voice settings and pass it into
getUserMedia, falling back to the system default when the device is gone.
* fix(voice): resolve mic preference by label and detect mid-capture loss
Drop Chromium's 'default'/'communications' aliases from the picker — pinning
one behaved exactly like system default and silently defeated the setting.
Resolve a stored preference against the live device list before capturing:
a unique label match heals an id that Chromium re-salted, a known-missing
device skips the doomed getUserMedia attempt that clipped the first words,
and an unreadable list no longer reads as "unplugged".
Surface the input ending mid-dictation instead of feeding silent zeros, add
a permission affordance so the picker is not empty before mic access, and
toast the fallback once per preference rather than once per utterance.
Co-authored-by: Orca <help@stably.ai>
* add e2e tests
* add e2e tests
---------
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
* fix(ssh,relay): stop remote connections from being killed by backoff and frame caps
Three independent connection killers found in the SSH/remote freeze audit.
FINDING A - the reconnect ladder never escalated for post-handshake drops.
scheduleReconnect() used the single published state.reconnectAttempt for both
the delay index and the give-up test, and runReconnectAttempt() zeroed it
before connecting (ssh.ts gates the relay redeploy on 0-at-connected). Every
post-handshake drop therefore re-entered at 1000ms forever, ~3600 relay
redeploys/hour, and 'reconnection-failed' was unreachable for a flapping host.
New SshReconnectLadder splits the delay index (advanced by every retry) from
the failure streak (advanced only by a failed handshake), so flaps back off
while give-up semantics stay byte-identical to shipped.
FINDING B - notify() closed the client whenever a frame exceeded the producer
frame capacity, conflating a permanently un-sendable frame with transient
backpressure. A 5000-event fs.changed is 425KB against a 49KB cap, so the
watcher flood killed the link and re-killed on every reattach+replay. notify()
now drops and logs once per generation; fs.changed is chunked to each sink's
capacity with a control-lane overflow marker as the resync fallback; agent-hook
envelopes shed lastAssistantMessage/interactivePrompt/subagents to fit.
FINDING B2 - sendResponse routed >1MB responses to a lane whose admission
ignores the frame cap and closed the client on rejection, so a large
fs.listFiles dropped the SSH host. It now substitutes a JSON-RPC error so the
request fails instead of the connection.
Also moves fs.streamEnd/fs.streamError to the control lane so a terminal frame
cannot be dropped by the producer-lane check.
Co-authored-by: Orca <help@stably.ai>
* fix(relay): stop the overflow marker from re-killing the link it protects
Round-1 review fixes on the P0 freeze work.
The control-lane overflow marker could reinstate the exact failure this P0
removes: dispatcher-client-writer closes the client when control-lane
admission fails, and admitControl is the only lane that returns an error, so
one marker per failing batch accumulated to the 256-frame/1MB bound and
dropped the link. Markers are now deduped to one outstanding per
(client, root), cleared on settle.
Chunking also defeated the renderer's per-payload directory dedupe -- events
are now stable-grouped by parent directory so one directory lands in one
chunk -- and the halving walk overshot the byte minimum ~1.7x while the fast
path paid three JSON encodes; both are fixed by publishing first and sizing
from a measured bytes-per-event estimate.
Agent-hook shedding now surrenders the blocking interactive prompt LAST
rather than first, so a degraded envelope cannot strand a pane at
state=waiting with no answerable question card.
The dropped-notification log now distinguishes over-capacity from producer
queue backpressure and no longer lets the first dropped method silence every
other producer for the life of the connection.
* fix(relay,ssh): keep status delivery and terminal frames from trading one freeze for another
Round-2 review fixes.
The round-0 change from close-on-rejection to silent drop removed the only
redelivery path for agent.hook envelopes: they are fire-and-forget and the
per-pane cache only replays on handler install, so a saturated link stranded
a pane on a stale Working spinner until reconnect. Closing used to guarantee
delivery by forcing that replay. Envelopes now publish per client and pend
for bounded latest-wins redelivery when the producer queue rejects them.
Shed fields are now named on the wire. The subagent roster is not cosmetic --
the renderer replaces rather than merges it, and hibernation gates on its
length -- so an unmarked shed could sleep a live pane.
fs.streamEnd rode the control lane because it must not be dropped, but that
lane kills rather than drops. The stream's concurrency slot is now held until
the terminal frame settles rather than until the fd closes, capping queued
terminal frames well under the control budget; overflow costs one refused
read instead of the connection.
The watcher chunk walk now stops while producer retention sits past its
reserve and degrades to a resync, so a 5000-event flood cannot fill the queue
that interactive PTY traffic shares and stall every remote terminal.
The reconnect ladder caps its flap-path delay so delay plus handshake timeout
cannot cross the relay grace floor and let the remote daemon kill live PTYs.
Also: the suppression key no longer embeds a NUL byte, which had made the
file binary to git and grep; producerEnvelopeBudget no longer reports
infinite capacity for a departed client; the drop logger no longer encodes a
frame it will not log; and an over-capacity response substitution no longer
settles as if the result had been delivered.
* fix(relay,ssh): restore relay-shed status fields and scope backpressure per client
Round 3 + 4 review fixes.
Watcher chunking is now gated on the *client's* retention reserve rather than
the dispatcher-wide one, so one stalled peer no longer forces a healthy client
into a full file-tree resync. The relay-lost redeploy ladder no longer burns its
6-attempt budget while the SSH transport itself is down: it holds at the 15s step
with a non-terminal status and rearms, so a laptop that slept past the ladder
comes back instead of landing on a terminal "give up" banner.
The shedFields wire marker had no consumer, so an agent-hook envelope whose
subagent roster was dropped to fit the frame read as "roster cleared" on the Orca
side: live child rows blanked and a done pane became hibernation-eligible while
its teammates were still running. ingestRemote now restores shed fields from the
cached payload (interactivePrompt deliberately excluded — a stale answerable
question card is worse than none).
Also: stream terminal-frame slots are counted per client, since the control queue
they protect is per client; the chunking fast path no longer logs a drop for a
batch it goes on to deliver in full; -32010 is now RelayErrorCode.ResponseOverCapacity.
Test debt from the review: pending-pane eviction, per-client stream isolation, and
the reconnect budget are now asserted rather than assumed; four fragile exact-byte
pins dropped in favour of the tier comparisons that carry the requirement.
* fix(relay,ssh): restore relay-shed status fields and scope backpressure
- Oversized relay responses now fail their request instead of closing the connection,
preventing one frame from killing every pane on the host
- Restore subagent state for correct hibernation; don't resurrect stale prose
across turns
- Account for relay re-establishment and PTY reattach time in SSH flap delay caps
- Only log drops of final unsendable envelopes, not temporary rejections during
measurement probes
- Fix watcher overflow marker release race when notification admission rejects
without settlement; use precise byte counting for event batching
* Restore relay-shed fields with digest validation and scoped backpressure
Validate that shed subagent rosters match their wire digest and turn identity before
restoration, preventing stale roster resurrection. Compact interactive prompts for waiting
states instead of dropping them. Demote control-queue overflow to non-fatal rejection so
clients can retry on capacity recovery, keeping the link alive during transient backpressure.
* fix(relay): correct ResponseOverCapacity error code
ResponseOverCapacity should use -33008 to stay in the -33xxx range
for relay protocol errors, not -32010.
* fix(relay): close client when pty.replay overflows control queue
Replay is never retried, so it uses the control lane where overflow
is fatal — the writer closes the client and reconnect reloads history
rather than stranding a short buffer.
* fix(relay): prevent infinite redeploy on flapping SSH transports
Charge reconnect attempts when connection restores mid-backoff, preventing
infinite loop on transports that flap between states. Refactor control overflow
handling to use entry property instead of WeakSet marker for clarity.
---------
Co-authored-by: Orca <help@stably.ai>
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).
Remove one-off incident docs and committed test-results noise, move
dev/repro/bench tools under tests/tools, and relocate i18next config
into config/ so the GitHub root scrolls to the description faster.
* 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>
* 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>
* Fix Create PR preparation with unavailable lookup
* test(source-control): align dirty+unavailable intent expectation
Create PR preparation is allowed when review lookup is unavailable; only final create stays fail-closed. Update the local-blocker snapshot test to match.
* Keep Create PR intent running when hosted review lookup fails
A failed or timed-out hosted-review eligibility lookup no longer aborts
Create PR intent mid-run. Local prep (stage/commit/push) continues, the
branch-ahead refresh is deferred until after eligibility resolves, and the
final create preflight still fails closed to prevent duplicate reviews.
Also gate generated PR title/body on eligibility and thread the provider
through the intent run token so an unavailable lookup falls back to the
inferred remote host.
* fix(source-control): align dirty+unavailable intent expectation
Local preparation (stage/commit changes) is safe without review-lookup authority; remote actions stay blocked. Prevents dirty trees from dead-ending at sync-first when lookup is unavailable.
* fix(source-control): distinguish loading state from unavailable lookup
Require head branch presence in shouldAttemptCreateHostedReviewForIntent to
separate real unavailable-lookup results from loading placeholders, which
share the same outcome/reason pair but lack a branch name.
* test(activity): drive portal readiness latch release with explicit rAF
Wall-clock setTimeout waits for requestAnimationFrame were flaky under
CI load (shard 15/16), leaving status stuck at loading instead of ready.
* Distinguish expected absence from git errors in remote removal
Why: swallowing all errors silently masks genuine git failures.
Check presence explicitly instead, so setup/teardown can still
skip when origin is absent while letting real errors surface.
* Virtualize workspace board lanes and defer card render for instant open
* fix(review): harden kanban virtualization interaction edges
Cancel deferred card mount on close, re-apply marquee preview after remounts,
query drag styles from live DOM, drop re-exports/casts, and cover edge cases.
* Remove virtualizer from effect deps to avoid unnecessary re-runs
* Fix stale closure in kanban area-selection and card-drag handlers
- Refresh area selection measurements on pointer up to avoid stale cache
- Read worktree IDs ref directly in drag handler to close stale closure
* Remove itemIds ref for correct virtualizer layout memoization
The card list kept itemIds in a mutable ref that was manually synced on every render, so the layout registration effect never re-ran when the lane contents changed, leaving stale measurements. Read itemIds directly and declare it as an effect dependency so layout registration stays in sync with the items.
* fix(browser): scope Cmd/Ctrl+F find to the focused split (#11348)
The browser pane's renderer-path Find handler is a window-global
capture-phase keydown listener, but it armed on `isActive` (the active
tab within its own group) rather than on whether its split holds focus.
In a terminal+browser split, the browser was therefore `isActive` even
while the terminal held keyboard focus, so it swallowed Cmd/Ctrl+F and
opened find-in-page in the browser instead of find-in-terminal.
Thread a focused-split signal (`isFocused`) from BrowserPaneOverlayLayer
— derived from `activeGroupIdByWorktree` — down to the Find handler and
gate the listener on it. This mirrors how terminal leaves already gate
global shortcuts via `focusedGroupId` in TabGroupSplitLayout. Floating
browser panels omit the prop and fall back to `isActive`, preserving
their behavior. The IPC path (webview guest focused) is unchanged; it
only fires when the guest genuinely has focus.
Not platform-specific: the chord resolves through `keybindingMatchesAction`
(Mod -> metaKey on macOS, ctrlKey elsewhere), so the same path is fixed on
macOS, Linux, and Windows.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(browser): preserve Find before split focus settles
* fix(browser): handle stale focused split IDs
* fix(browser): route guest Find to source page
* test(browser): wait for split address bar
* test(browser): focus split before Find routing
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* Revert "fix(terminal): avoid flash while restoring parked terminals (#10871)"
This reverts commit 5a6a9e0b28.
Reverted for terminal rendering regressions (flashing, lost content).
Conflict resolution preserves the forwardRef signature from #10433 and
drops the parked-presentation gating #11016 fed with its effective set.
Co-authored-by: Orca <help@stably.ai>
* Revert "fix(terminal): limit pre-paint WebGL resume to macOS (#10794)" and "fix(terminal): stop switch bold flash and Windows lag (#10692)"
This reverts commits 4681edb520 and
8f5a45401f.
#10794 was itself a partial revert of #10692, so both are reverted
together: the Windows retained-WebGL LRU and the macOS pre-paint
(layout-phase) visibility transition that survived it. Terminal
visibility resume returns to passive disposal and recreation on every
platform, and the WebGL context ceiling returns to a flat 128.
Co-authored-by: Orca <help@stably.ai>
* Revert "fix(terminal): release an abandoned synchronized-output frame on reveal (STA-2694) (#10907)"
This reverts commit 97cb32c1cc.
---------
Co-authored-by: Orca <help@stably.ai>