* feat(mobile): start a workspace from a branch, issue/PR, or Linear ticket
Unify mobile workspace creation with desktop. The "+" Create Workspace
modal now has a primary "Start from" field that opens a tabbed search
drawer (Branch · GitHub · GitLab · Linear), letting a user start a
workspace from an existing/new git branch, a GitHub issue/PR, a GitLab
issue/MR, or a Linear ticket — in addition to the default blank workspace.
No new backend is required: the search RPCs (github.listWorkItems,
gitlab.listWorkItems, linear.searchIssues/listIssues, repo.searchRefs) and
the worktree.create linked-item params were already used by the mobile
Tasks screen. This surfaces them in the create flow, reusing the existing
pure modules (buildTaskWorkspaceCreateParams, shouldResolveHostedReviewStartPoint,
filterAvailableTaskProviders).
Details:
- New pure modules: workspace-source-selection, use-workspace-source-search,
source-workspace-create, worktree-create-retry, blank-workspace-create
(the blank/retry path extracted from the modal for reuse + line budget).
- New UI: WorkspaceSourcePickerDrawer (+ row) and SetupHookTrustDrawer
(extracted from the modal).
- Older paired desktops (missing the mobile.tasks.v1 capability) degrade to
Branch + Blank only; GitLab/Linear tabs appear only when available.
- GitHub/GitLab sources pin their repo; switching repos resets the source.
PR/MR sources resolve their base branch at create time; SSH repos gate
search until connected (Linear search is repo/SSH-independent).
* fix(mobile): hydrate settings/trust before availability probes settle
Review fixes for #7985: setTrustedOrcaHooks/setRuntimeSettings no longer
wait on status.get/preflight.check/linear.status (a first-open
preflight.check can take seconds, widening the spurious setup-trust
re-prompt window). Also adds param-parity tests for createBlankWorkspace
and a GitLab MR base-resolve test.
* feat(mobile): match desktop's Smart source picker exactly
Rework the mobile create-workspace source picker to be a faithful port of
desktop's Smart picker instead of the earlier divergent "Start from" drawer.
The mobile field is now the workspace-name input AND the source search, with the
exact desktop tabs — Smart · GitHub · Linear · GitLab · Branch · Name. "Smart"
fans out across GitHub + GitLab + Linear + branches, prepends a "Use '<name>'"
row, and resolves pasted URLs / #123 / STA-42 to exact items (with a cross-repo
switch prompt). Selecting a source shows a pill and moves the editable name into
Advanced. The invented "Blank workspace" concept is removed — the neutral state
is just a typed/empty name (blank submit still yields a creature name).
DRY: the pure desktop logic (smart-workspace-source-results, -command-value,
github-links, gitlab-links, work-item-link-query-bounds, github-work-item-identity)
moves to src/shared/new-workspace/ with re-export shims at the old renderer paths,
so both renderer and mobile share one implementation. composer-branch-selection
and workspace-name were already shared and are reused directly.
Two read-only lookup RPCs are allowlisted for mobile so pasted GitLab URLs and
cross-repo GitHub URLs resolve to exact items (github.workItemByOwnerRepo,
gitlab.workItemByPath).
New mobile modules are split for max-lines: use-mobile-composer-source (selection
state + desktop-parity handlers, PR/MR base resolve), use-smart-workspace-source
+ smart-source-fan-out/-search-requests/-paste-intent (RPC orchestration),
composer-linked-work-item / work-item-lookup-text / mobile-smart-source-modes
(pure logic), and SmartWorkspaceSourceField/Drawer/Row + SmartWorkspaceAdvancedFields.
Replaces WorkspaceSourcePickerDrawer/Row, workspace-source-selection,
use-workspace-source-search, and MobileWorkspaceNameInput.
Reviewed by three adversarial agents + re-reviewed after fixes: GitHub search now
returns issues AND PRs (not issues-only), Linear defaults to assigned, create-branch
preserves slashy names, cross-repo PR base resolves against the item's own repo,
displayName is suppressed for user-edited names, and the smart-mode GitHub fan-out
respects availability. tsc/oxlint/max-lines-ratchet clean; 1328 mobile tests pass.
* fix(mobile): keep smart source drawer fully visible
* refactor: share workspace creation behavior across clients
* fix: address workspace creation review findings
---------
Co-authored-by: Brennan Benson <brennanbenson@Brennans-MacBook-Pro.local>
Reply state now tracks a comment id instead of a group id, and reply composers/handlers are threaded through each comment row (root and replies alike) so any comment can receive an inline reply rather than only the thread root.
* fix(terminal): stop stale PTY resize after worktree reveal
The visibility-resume size readback captures xterm's pre-reveal grid as
its resize target while the applied-size read is in flight. A reveal fit
or snapshot-restore resize can change the grid mid-flight without
queuing a newer request, so the resolved callback "repaired" the PTY
back to the pre-reveal grid — an idle TUI (Claude Code) then redraws
for the wrong grid until a manual resize (refs #7951, #7240).
Instrumented traces show the stale capture on every reveal; correctness
relied on the read winning FIFO against the fit's resize, which busy
daemons and SSH/relay round-trips lose.
Re-measure xterm at resolve time and re-run against the fresh grid
instead of forwarding a stale target. Adds an e2e seam that delays the
readback dispatch to reproduce the losing ordering, unit repros that
fail without the guard, and a hidden-resize/reveal-cycle e2e spec.
* test(terminal): cover single-flight convergence under an oscillating grid
* docs(e2e): note the WINCH bar is illustrative, not a Windows ground truth
The reveal repro asserts on pty:getSize convergence; the bottom-bar TUI
only makes the pane WINCH-reactive. Record that a long-lived process can
miss Node's stdout 'resize' event under Windows ConPTY even when the OS
PTY was resized, so future readers don't add a flaky bar-content check.
* Remove finished and killed claude subagents from the sidebar roster
Finished one-shot subagents stayed in the pane roster as permanent idle
rows (dozens per ultracode/workflow session), and a subagent killed
without its SubagentStop hook stayed 'working' forever, pinning the pane
working. SubagentStop now removes one-shot entries (teammates only idle:
they are alive and resumable), and a lead Stop's present background_tasks
list is treated as authoritative for non-teammates - unlisted entries are
removed. Teammates are identified by their name-embedding agent ids
(a<name>-<hex>, verified against live hook captures).
* Fix Claude subagent roster reconciliation
* Drop named workflow lanes that share the teammate id shape
Workflow/named one-shot agents report name-embedding lifecycle ids
(afinder-C-<hex>, agent_type = the label), indistinguishable by shape
from resumable teammates - so the roster retained them as idle rows
(observed live: a 32-row 12h-old pile). Unlike teammates they ARE
listed id-exact as subagent-typed background tasks, including inside
their own SubagentStop payload. Use that corroboration to remove them
on stop, reclassify task-listed teammate-shaped entries as one-shots,
and reap teammate-shaped leftovers when a complete inventory lists no
teammate-typed task at all (a teams session always lists its teammates,
even idle ones).
* fix(native-chat): retry not-yet-flushed transcripts instead of settling into a permanent error
A freshly-created session's transcript .jsonl lands on disk seconds to
minutes after the process starts. Native chat's one-shot read raced that
first flush: a miss became a permanent "No transcript found" error and
the live-tail subscription silently degraded to a no-op, so the pane
never recovered even after the file appeared.
- transcript-reader/read-cache: mark the miss with notFound so callers
can tell "not flushed yet" from a real parse/IO error (never cached).
- transcript-watch: poll resolve+install (500ms backoff, 5s cap) for the
subscription's lifetime instead of returning a dead no-op watcher.
- use-native-chat-live-session: retry a notFound read with backoff for
up to 60s while staying in the loading state, and let live appends
render over a stale initial-read error.
Fixes#8401
Claude-Session: https://claude.ai/code/session_01HA5g3X7wCakBttBpDru9Fp
* fix(native-chat): address CodeRabbit review — ENOENT retryable, content over spinner, blank-id guard, unref poll timer
- transcript-reader: an ENOENT after a successful resolve is the same
first-flush/rotation race as an unresolved path — mark it notFound.
- use-native-chat-live-session: live appends landing mid-retry render
instead of the loading state (mirrors the stale-error gate).
- transcript-watch: bail out for a blank session id with no explicit
file (nothing to resolve-poll), and unref the poll timer so headless
serve shutdown is never held open by an unresolvable session.
Claude-Session: https://claude.ai/code/session_01HA5g3X7wCakBttBpDru9Fp
---------
Co-authored-by: kaynan <kaynan.camargo@terceiro-sky.com.br>
* Fall back to a merge when a divergent pull has no reconciliation strateg
- Git 2.27+ refuses `git pull` on divergent branches unless pull.rebase or
pull.ff is configured. Retry with `--no-rebase` (Git's historical default)
so pulls succeed out of the box on fresh hosts.
- Skip the fallback whenever the caller already specified a reconciliation
strategy (e.g. --ff-only, --rebase) so explicit policies still fail as
expected on divergence.
- Applied identically in the local git pull path and the relay/SSH git
handler so both surfaces behave the same way.
* Refactor divergent-pull merge fallback into shared helper
Extracts the retry-as-merge logic (duplicated between local git and
relay SSH pull paths) into `runPullWithDivergenceFallback` in
git-remote-error.ts, so both callers share one implementation and
test coverage.
Fixes#8553
'跑步' (jogging) was used in computing/automation contexts where
the correct term is '执行' (execute). 11 entries fixed across:
- TabBarQuickCommandsButton: Run
- Scripts settings: When to run
- quick.commands.search: run (keyword tag)
- ReviewPRViewAnimatedVisual: Running
- AutomationRunHistory: Run (column header)
- AutomationRunPageFrame: Back to runs
- AutomationsPage: Runs, run
- ExternalAutomationManagers: run
- ExternalAutomationRunTable: Runs, run
Note: key e66091daf4 already correctly translated as '运行'.
* fix(startup): bound serve WSL reconciliation wait
* test(startup): cover WSL barrier fail-open on early reconciliation rejection
Co-authored-by: Orca <help@stably.ai>
* feat(serve): surface managed WSL reconciliation status to headless clients
Expose reconciliation state ('pending'|'settled'|'failed') in the
orca_server_ready payload and a wsl-cli-barrier startup milestone, so
headless/SSH agents can tell the fail-open barrier outlived its budget
and a WSL PTY launch may still race an un-migrated registration.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
An index rewrite cannot move HEAD, yet every quiet local Git-common status
burst was calling readGitCommonHeadIdentities, which scans the primary
checkout plus every linked worktree sequentially (~1,500 file reads for 500
worktrees) even when only index files changed.
Split the conflated git-status signal into two: index churn stays
`gitStatusRepoIds`, while logs/HEAD (and other reflog head moves) become a
distinct `headIdentityRepoIds` signal. Source Control is still notified for
both, but readGitCommonHeadIdentities now runs only for true head triggers
(and structural ticks, which re-baseline silently). Index-only bursts do zero
head-identity reads.
Adds deterministic tests: index-only bursts read zero head identities across
linked and primary checkouts; logs/HEAD still refreshes identities for both;
plus coalescing and debounce coverage.
* Keep running-agent workspaces visible under "Hide sleeping" (#7197)
The "Hide sleeping" sidebar filter judged a workspace active only when it
had a live PTY (or a browser tab), so a workspace with a running agent
whose live-PTY entry was momentarily absent — an SSH reconnect grace
window, an unmounted pane, a remote surface not yet `ready`, or an
orchestration worker reporting before its tab is mirrored — was
classified as "sleeping" and hidden while its session was still open.
The smart sort already treats a fresh `agentStatusByPaneKey` entry as
"working" independent of live-PTY, so the filter and sort disagreed. Add
`getWorktreeIdsWithLiveAgent`, which derives the worktrees with an open
agent session from the live agent-status map (sleep/teardown drop those
entries via dropAgentStatusByWorktree, so slept/hibernated workspaces
still hide), and consult it in `hasActiveWorkspaceActivity`. Wire it
through the sidebar list, Cmd+J jump palette, and kanban board.
* fix(agent-status): align live workspace attribution
* fix(mobile): preserve live-agent workspace activity
* fix(mobile): prefer newest agent status source
* chore: restore unrelated benchmark formatting
* fix(mobile): resolve projected agent worktree ids
* perf(mobile): index projected worktree summaries
* fix(mobile): preserve projected activity under limits
* fix(mobile): preserve POSIX path identity
* perf(mobile): cache projected summary fallbacks
* fix(mobile): preserve remote path and priority contracts
* perf(mobile): index projected paths by host flavor
* test(mobile): prove projected path index keys
* perf(mobile): bound projected path fallback
* perf(mobile): reuse projected repo platforms
* test(mobile): enforce projected lookup bounds
* chore(runtime): remove review instrumentation
* perf(mobile): skip unresolved repo platform scans
* perf(mobile): batch represented project runtimes
* perf(mobile): batch cold project runtime scans
* fix(mobile): couple worktree platform snapshots
* fix(sidebar): prioritize attributed headless agents
* fix(sidebar): activate smart sort for headless agents
* fix(sidebar): prefer mirrored agent ownership
* fix(mobile): follow mirrored agent ownership
* fix(sidebar): resolve mirrored unstamped agents
---------
Co-authored-by: Brennan Benson <brennanbenson@Brennans-MacBook-Pro.local>
* fix(e2e): repair release e2e suite — parking regression tests, stale/flaky specs, profile switcher gate
Diagnosed 20 failing tests across the release e2e shards. Most are test debt,
plus two genuine product-side issues.
Product fixes:
- OrcaProfileSwitcher: the PROD gate hid the "Switch profile" button in the
e2e build (electron-vite build bakes NODE_ENV=production). Exempt
MODE==='e2e' so the specs render it while packaged prod builds stay hidden.
Parking cluster (8 tests): #8262 intentionally keeps the most-recently-hidden
tab warm (exempt from cold-park). The specs hid exactly one tab — always the
exempt one — so it never parked. Open a throwaway decoy tab that absorbs the
last-active exemption so the target parks. (terminal-hidden-view-parking,
terminal-pane-close-layout-consistency)
Stale tests updated to match intended product behavior:
- rich-markdown-link-bubble: match Edit link by aria-label (title dropped in #8307)
- terminal-codex-hidden-startup-background: drop the dead hiddenRendererSkipCount
poll (Phase-4 main-side delivery gate #7214 bypasses that renderer path)
Brittle threshold/geometry/timing hardening (no product regression):
- agent-session-log-tail-stability: assert full-model length instead of a
machine-specific word-wrap pixel baseline
- artificial-opencode revisit: dedicated under-backpressure latency bound
- terminal-history-size-typing-latency: gate p90 not max (tolerate one
checkpoint-in-window spike; median stays strict)
- combined-diff-scroll-restore: assert viewport barely moved vs exact anchor key
- terminal-shortcuts: idempotent kitty-flag reset instead of a racing stack pop
- agent-session-live-force-exit-resume: drive the product quit-capture path
- renderer-crash-recovery-terminal-input: poll the transport probe over the
recovery budget (still flags a permanently frozen pane)
terminal-push-delivery-loss-recovery left unchanged (no safe test-only
improvement; recovery is wall-clock bounded with ample slack).
* Extract shared parking helpers into terminal-hidden-parking.ts for e2e s
- Deduplicate waitForTabParked/parkHiddenTabBehindDecoy, previously
copy-pasted across the parking and layout-consistency specs
- Parameterize parkDelayMs so the helper no longer depends on a
file-local PARKING_DELAY_MS constant
* fix(e2e): second pass — fix link-editor Escape regression + deeper test failures
CI validated round 1 (parking + 5 areas green). This fixes the tests that were
still red because the first fix cleared only the first assertion or the root
cause was deeper.
Product fix (real regression found by the test):
- RichMarkdownLinkBubble: Escape while editing a link dismissed the whole bubble
instead of cancelling the edit. #8307 added a container-level Escape→onDismiss
with stopPropagation, but the edit input's older Escape→onEditCancel never
stopped propagation, so both fired. Add e.stopPropagation() in the input's
Escape branch so editing Escape only cancels the edit.
Test fixes:
- agent-session-live-force-exit-resume: wait for hydrationSucceeded (not just
workspaceSessionReady) before persisting — shouldPersistWorkspaceSession gates
the writer on it, so the record write was a silent no-op until hydration.
- terminal-shortcuts: clear the shell line deterministically (Ctrl-U + Ctrl-C)
then send the kitty flag reset as its own settled command, so the reset byte
isn't swallowed mid line-edit.
- agent-session-log-tail-stability: allow a 25MB GC-noise margin on the
append-vs-replacement peak comparison. The append path provably allocates less
than the replacement control (which also encode/decode/setValue), so a peak
above it is uncollected-transient noise, not a regression; the deterministic
retention budget and bench are untouched.
- artificial-opencode hidden-restore: 1500→2000ms for whole-buffer serialize-poll
overhead under reveal (still 2x stricter than main's 4s).
- terminal-push-delivery-loss-recovery: assert the observable watchdog healCount>0
instead of 'wedged-123' in the pane. In headless e2e a desktop-only local pty
has no main headless emulator, so getMainBufferSnapshot falls back to the
blackholed renderer xterm and the repaint cannot carry the wedged bytes.
* fix(e2e): third pass — harden the last 4 chronic/flaky e2e gates
- agent-session-live-force-exit-resume: raise persisted-record poll 15s→30s
(two-stage debounced write + main scheduleSave needs headroom under the CI
event-loop starvation that also drifts renderer timers ~1s in this shard);
on miss, dump store vs disk state to distinguish a lost write from slow flush.
- artificial-opencode-terminal-load: add MAX_TIMER_DRIFT_UNDER_LOAD_MS (2.5s)
for the injected-load scenarios, mirroring MAX_WORST_KEY_LATENCY_UNDER_LOAD_MS;
baseline single-terminal gate stays at 250ms.
- combined-diff-scroll-restore: converge the after-tab-switch anchor via bounded
retry (Monaco restores scroll over several layout passes) before asserting;
a genuine restore miss still fails since the last anchor is returned on timeout.
- terminal-reattach-mouse-mode-leak: poll rAFs until the enable-mouse-events
class lands after re-arming instead of a single frame (batched xterm render).
Co-authored-by: Orca <help@stably.ai>
* Widen timer-drift and scroll-restore budgets for loaded/slow e2e scenari
- Add maxTimerDriftUnderLoadMs budget so multi-pane opencode redraw
scenarios aren't judged against the unloaded timer-drift ceiling
- Start the combined-diff scroll-restore poll window after the initial
viewport anchor settles, since that settle can itself take up to 15s
* fix(e2e): round-2 — gate mouse-probe on arm capability; align revisit budgets
- terminal-reattach-mouse-mode-leak: xterm binds the enable-mouse-events class
and the motion listener together in one _handleProtocolChange; some headless CI
renderers never bind it on a warm reattach (core mouseTrackingMode still flips),
so the positive control cannot arm. Poll a bounded window for arming, then skip
when it never arms (matching the pane-manager/shell guards) instead of failing.
- artificial-opencode-terminal-load: the worktree-revisit scenario sampled worst-key
and timer drift under ACK-gate-held load but asserted the strict unloaded budgets
(worst seen ~2s); switch it to the under-load budgets like its siblings.
Co-authored-by: Orca <help@stably.ai>
* Expand timer-drift budget test coverage to all scenario branches
- Splits the pass/fail assertions into separate it blocks and adds
it.each over all four isUnderLoadTimerDriftScenario matches (two
exact, two prefix) so a predicate regression can't silently fall
back to the unloaded 150ms ceiling for any of them.
---------
Co-authored-by: Orca <help@stably.ai>
* Fix PTY shutdown crash on Windows by dropping unsupported signals
- node-pty's Windows agent throws when kill() is called with a signal
argument; route all pty.kill() calls through killPtyProcess(), which
drops the signal on win32 and forwards it on POSIX
* Expand Windows PTY kill-signal-drop test coverage to all call sites
Replace the single graceful-shutdown test with a suite covering every
place killPtyProcess is invoked (graceful/immediate shutdown, stale-spawn
cleanup, SIGKILL fallback timers, and dispose), so a future regression
that reintroduces a signal argument on any Windows code path is caught.
* Fix hook scripts to drain stdin before any early-exit path
Generated agent hook scripts and missing-script launchers could exit
successfully before consuming the payload written to their stdin,
leaving the writer with a broken pipe (EPIPE/ERROR_BROKEN_PIPE) once
the reader closed early. Capture stdin (or drain it via a shared
epilogue/fast-path guard) before any whole-script success exit across
all POSIX, batch, PowerShell, and Git Bash launcher variants, and add
a cross-agent lifecycle test suite plus a live Electron verification
script to guard the contract going forward.
* Harden hook scripts against unreadable managed scripts and add a Claude/
- Extend the POSIX launcher guard to also require `[ -r ]`, not just `-f`/`-x`,
so an executable-but-unreadable managed script still drains stdin instead of
erroring or silently misbehaving.
- Add a verifier case (`verifyClaudeDevinSkip`) that spins up a local HTTP
server and confirms the Claude hook never forwards a request that Devin
already imported, catching accidental double-forwarding.
- Update installer-utils tests and stdin-lifecycle docs to match the new
readable-file guard and the added verification case.
* Fix hook-launcher verification to derive script paths from the installed
Extract the quoted path from the launcher's `if [ -f '...'` clause instead of
reconstructing it via join(home, ...), so missing/failing-script test cases
can't silently fall through to the real script if the install layout changes.
---------
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
* Redesign Agents sidebar cards for cross-worktree scanning.
Lead with project and workspace identity, keep stable task titles across follow-up turns, and stop user replies from replacing agent status previews.
* fix(activity): honor generated-title setting and follow the active turn
Activity sidebar task-title/preview fixes surfaced in review of the
cross-worktree redesign:
- Apply generatedTitlesEnabled instead of ignoring it, so a disabled
tabAutoGenerateTitle no longer resurfaces auto-titles (mirrors
resolveTerminalTabTitle's gate).
- Guard orchestration labels with orchestrationLabelsMatchLiveDispatch so
sticky completed metadata can't pin the title over new work.
- Prefer a substantive live prompt over history, and pick history by most
recent startedAt (not longest), so the row title follows the active turn.
- Clear a stale status preview on a new substantive turn; only bridge a
previous preview across a terse follow-up.
- Drop the duplicated workspace name in thread search text; index the
branch instead.
Fixes two previously-red buildActivityEvents tests and adds coverage for
the generated-title gate and orchestration staleness.
---------
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
* Add a Docker SSH watcher-isolation E2E test to verify remote relay-watch
- Covers two scenarios: crashed watcher children are respawned under the
same relay without dropping the terminal PTY or file-explorer view, and
a missing deployed relay-watcher.js artifact is repaired on reconnect
- Extracts shared connect/disconnect/reconnect logic out of the perf spec
into docker-ssh-relay-connection.ts, and adds docker-ssh-relay-processes.ts
for inspecting/signaling remote relay and watcher PIDs
- Wires the new spec into a dedicated CI job and pnpm script
* Fix Windows and Linux-only issues in Docker SSH watcher-isolation E2E ha
- node-gyp override only applies on Linux runners now, since the CI job
moved to ubuntu-latest but shares the workflow with non-Linux jobs
- spawn the e2e runner scripts through a shell on win32 to satisfy
Node's CVE-2024-27980 restriction on unshelled .cmd spawns
- harden relay process row parsing against empty pid/ppid fields so a
vanished /proc entry fails loudly instead of coercing to pid 0
- dedupe the reconnect helpers and export shellQuote for reuse across
the docker-ssh-relay test helpers
* Prevent index churn from refreshing worktrees
* Cover IPC contract in worktree reliability gate
* Refresh background worktree heads without re-entering structural fanout
External commits, amends, and soft resets in non-active worktrees now reach
store rows through spawn-free Git metadata reads diffed in the watcher's
existing debounce, emitted only on real head moves. HEAD reflog appends become
status-only triggers, config.worktree becomes structural for sparse-flag
freshness, and the non-darwin poller gains a periodic ungated index re-stat
so in-place rewrites on coarse-mtime filesystems cannot be missed forever.
* Reject unsafe symref paths and validate object ids in the head reader
Ref content comes from repo files an attacker can craft. Backslash segments
traverse on Windows where join treats them as separators, and colons are
forbidden in Git ref names; both now fail isSafeRefName before any path is
built. Resolved values are additionally emitted only when they match a hex
SHA-1/SHA-256 object id, so no file content can leak through the identity
event even in principle.
---------
Co-authored-by: Brennan Benson <brennanbenson@Brennans-MacBook-Pro.local>
Co-authored-by: Brennan Benson <>
* Hide console window for Windows agent foreground-process scan
Agent foreground-process inspection re-forks powershell.exe (or the wmic fallback) to detect which agent runs in each terminal. Both spawns omitted windowsHide, so on Windows each fork popped a fresh conhost console window that flashed and stole keyboard focus from the foreground app — including Orca's own terminal — recurring roughly once every few tens of seconds while an agent session was open (and more often under continuous agent output).
Add windowsHide: true to both probes (matching the codebase-wide convention) plus a regression test asserting the spawn options.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(windows): include scan root in foreground fixtures
---------
Co-authored-by: xucongwei <xucongwei@bytedance.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Jinwoo Hong <73622457+Jinwoo-H@users.noreply.github.com>
* fix(cli): migrate managed WSL launchers to native exe
* refactor(cli): reconcile managed WSL registrations
* fix(cli): harden WSL registration reconciliation
* fix(cli): serialize and harden WSL CLI registration lifecycle
- Cap per-distro concurrent repairs and add a filesystem lock so two
Orca installs can't race the same distro's launcher/bridge files
- Skip re-adopting a legacy managed launcher when its bridge is user-
owned, avoiding a doomed install retried on every startup
- Track per-distro reconciliation against launcher target + app
version so unchanged registrations aren't re-probed each boot
- Replace command install with a single atomic rename instead of a
move-based backup, closing a window where no orca-ide exists
- Remove the registry invalidate-on-persist-failure path now that
writes are atomic and repair is disk-authoritative
- Add brief negative caching to WSL distro discovery to bound
wsl.exe respawns on persistently broken hosts
- Extract keyed-promise-queue as a shared serialization primitive
Replace the raw <button> in the task list row with the shadcn Button
component so Open/Start gets consistent styling and hover/focus states.
Open uses the solid variant to read as the stronger action (resuming
existing work) versus outline Start (new workspace).
* fix(orchestration): bind lifecycle sends to dispatched pane
* fix(orchestration): bind injected worker messaging
* Fix worker_done pane identity gating and rename SSH linear CLI files
- Lifecycle reconciliation now returns an explicit `rejected` action
(instead of `ignored`) for persisted sender_not_assignee markers, and
only treats a payload's `_orcaLifecycleRejection` as trusted state
when it exactly matches that reserved shape, preventing caller-
supplied markers from spoofing a rejected send as success.
- SSH legacy fallback CLI now fails closed with
`no_active_sender_terminal` when a worker_done/heartbeat send has no
resolvable sender identity, rejects mixed raw/structured payload
flags, and reconstructs structured lifecycle payload fields
(task/dispatch id, files-modified, report-path, phase) instead of
dropping them — extracted into ssh-remote-orchestration-send.ts.
- Renamed ssh-remote-linear-argument-error.ts to
ssh-remote-cli-argument-error.ts since RemoteCliArgumentError is now
shared beyond the Linear CLI.
* Add re-read logging for already-converted lifecycle rejections
Send-path reconcile converts worker_done/heartbeat rejections with a
no-op logger, so the coordinator's later re-read is the only chance
to surface the rejection message. Log it there instead of dropping it.
* fix(e2e): repair release e2e suite — parking regression tests, stale/flaky specs, profile switcher gate
Diagnosed 20 failing tests across the release e2e shards. Most are test debt,
plus two genuine product-side issues.
Product fixes:
- OrcaProfileSwitcher: the PROD gate hid the "Switch profile" button in the
e2e build (electron-vite build bakes NODE_ENV=production). Exempt
MODE==='e2e' so the specs render it while packaged prod builds stay hidden.
Parking cluster (8 tests): #8262 intentionally keeps the most-recently-hidden
tab warm (exempt from cold-park). The specs hid exactly one tab — always the
exempt one — so it never parked. Open a throwaway decoy tab that absorbs the
last-active exemption so the target parks. (terminal-hidden-view-parking,
terminal-pane-close-layout-consistency)
Stale tests updated to match intended product behavior:
- rich-markdown-link-bubble: match Edit link by aria-label (title dropped in #8307)
- terminal-codex-hidden-startup-background: drop the dead hiddenRendererSkipCount
poll (Phase-4 main-side delivery gate #7214 bypasses that renderer path)
Brittle threshold/geometry/timing hardening (no product regression):
- agent-session-log-tail-stability: assert full-model length instead of a
machine-specific word-wrap pixel baseline
- artificial-opencode revisit: dedicated under-backpressure latency bound
- terminal-history-size-typing-latency: gate p90 not max (tolerate one
checkpoint-in-window spike; median stays strict)
- combined-diff-scroll-restore: assert viewport barely moved vs exact anchor key
- terminal-shortcuts: idempotent kitty-flag reset instead of a racing stack pop
- agent-session-live-force-exit-resume: drive the product quit-capture path
- renderer-crash-recovery-terminal-input: poll the transport probe over the
recovery budget (still flags a permanently frozen pane)
terminal-push-delivery-loss-recovery left unchanged (no safe test-only
improvement; recovery is wall-clock bounded with ample slack).
* Extract shared parking helpers into terminal-hidden-parking.ts for e2e s
- Deduplicate waitForTabParked/parkHiddenTabBehindDecoy, previously
copy-pasted across the parking and layout-consistency specs
- Parameterize parkDelayMs so the helper no longer depends on a
file-local PARKING_DELAY_MS constant
* fix(agent-status): surface Claude tool failures
* Fix compact sidebar hiding tool-failure errors behind stale tool name
Extract a shared clearActiveToolFieldsUpdate() helper and apply it to
Cursor's postToolUseFailure, Copilot's PostToolUseFailure/ErrorOccurred,
and Grok's post_tool_use_failure events, matching the existing Claude
behavior so the failure message surfaces instead of the last tool name.
* fix(agents): bundle agent icons instead of loading them from Google's favicon service (#8451)
Agents without a hand-authored SVG glyph loaded their icon live from
Google's favicon service (www.google.com/s2/favicons). That service is
unreachable in some regions (e.g. mainland China) and offline, so ~23
agent icons rendered as broken images on the agent settings page, the
terminal title bar, and the status bar.
Bundle each favicon as a build-time asset under resources/agent-icons/
and render it via a new agent id -> URL map (agent-favicon-assets.ts).
The remote favicon service now only serves as a last-resort fallback for
any future agent that lacks a bundled icon. Follows the same pattern as
#7373, which bundled the OpenCode mark.
* fix(agents): bundle mobile agent icons too; drop dead omp faviconDomain (#8451)
Mobile had the same offline/region bug: MobileAgentIcon rendered every
non-glyph agent from Google's favicon service. It actually affected more
agents than desktop, since mobile lacks hand-authored glyphs for
Copilot, OpenCode, Kilocode, Droid, and OpenClaude — all fell through to
the favicon path.
Bundle the 28 favicon-path icons under mobile/assets/agent-icons/ and
render them via a Metro static require() map (mobile-agent-icon-assets.ts).
A node-env invariant test asserts every favicon-path agent ships a
bundled PNG and is wired into the map.
Also remove omp's vestigial faviconDomain from the desktop catalog — omp
renders the hand-authored OmpIcon glyph, so the favicon fallback was
never reachable.
* refactor(agents): share one set of bundled agent icons between desktop and mobile
Desktop and mobile each shipped their own copy of the favicon PNGs (23 +
28, with 23 byte-identical duplicates). Consolidate them into a single
source of truth at src/shared/agent-icons/, reachable by both bundlers:
- Desktop (Vite) imports them via `?url`.
- Mobile (Metro) requires them; Metro already watches src/shared via
metro.config.js sharedRoot, so no config change is needed.
The two per-platform maps stay separate because the import syntax differs
(`?url` string vs `require()` asset ref), but they now point at the same
files. Verified with a real `expo export`: Metro bundles all 28 shared
icons from src/shared/agent-icons.
* fix(grok): prefer auth.x.ai session and mention Grok in OSC52 toast
auth.json can hold multiple issuer keys; Object.values order could pick a
stale entry before the live auth.x.ai OIDC session. Prefer auth.x.ai keys
and fall back only when none exist. Also list Grok in the OSC52 blocked
clipboard toast so Grok TUI copy failures point at the same setting.
* fix(grok): harden preferred auth session selection
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
* fix(ssh): isolate relay filesystem watchers
* Fix relay watcher fault-harness pid file and in-process fallback isolati
- Use exclusive ('wx') creation for the fault-harness pid file so a leaked
ORCA_WATCHER_CHILD_PID_FILE env var can't clobber an existing file, and
have the harness remove the file after reading a replacement pid.
- Force useInProcessVitestFallback to false in the relay watcher pool so a
leaked VITEST env var can never load the native watcher addon in-process
on the relay; fail closed instead when the isolated child is missing.
- Thread an injectable RelayWatcherProcessPool into FsHandler/
RelayFilesystemWatchRegistry for tests, and add coverage for both fixes.
* fix(agent-status): label Cursor by identity, not a bare "cursor" token
The worktree card, status bar, and mobile all derive an agent label from the
terminal title via getAgentLabel / resolveTerminalTitleAgentType. Both matched
Cursor with `titleHasAgentName(title, 'cursor')`, a whole-token match. But
`cursor` is ordinary editor vocabulary, so a Claude/Codex tab working on Orca's
own code (title like `⠋ preserve cursor visibility across replays`) got
mislabeled as Cursor. The generic braille-spinner Claude fallback even had a
`!lower.includes('cursor')` guard that then dropped the title to no label at
all.
Gate Cursor on its closed identity title set (`isCursorAgentTitle`) instead —
the same predicate @cursor orchestration routing uses. A real cursor-agent
terminal still resolves as Cursor across working/idle/permission; a non-Cursor
tab that merely mentions a text cursor reverts to its true agent. Relax the
braille guard to the same predicate so those titles land on Claude, not null.
Makes display consistent with routing (the follow-up flagged in #8436).
* refactor(agent-status): address review on Cursor identity labeling
- Trim the four Cursor `// Why:` comments in both parallel resolvers
(agent-title-identity.ts, terminal-title-agent-type.ts) to AGENTS.md's
one-to-two-line rule; use identical wording so future drift is visible.
- Add a direct isClaudeAgent assertion in terminal-title-agent-type.test.ts
pinning that file's parallel copy (previously only covered transitively),
plus Cursor Agent / "Cursor - action required" activity-facet assertions.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* Fix crash-isolated file watcher process pool for orca-serve SIGSEGV afte
Replace the worker-thread runtime file watcher with a forked, crash-isolated
@parcel/watcher child process pool so a native FSEvents fault can no longer
take down the main/serve process, and add bounded event batching, delivery
backpressure, and quarantine-based recovery for faulty watch roots.
* Fix crash-isolated file watcher teardown and shutdown leaks
- Fault harness could throw before mkdtemp/realpath completed, skipping
cleanup; now tracks each temp path independently and races an async
watcher-callback error so it can't escape the try/finally unhandled.
- In-process fallback swallowed unsubscribe failures via a bare rejection
handler that could still throw; use .catch() instead.
- Watcher process entry's cancel-subscribe handler now reuses the async
unsubscribe path when a crawl already finished, releasing the native
handle instead of leaking it (blocks worktree unlock on Windows).
- Runtime watcher process pool exposed no real dispose(); shutdown now
kills pooled children so they don't outlive the main process.
* Fix disposeSlot double-iteration bug in file watcher pool teardown
Remove the unnecessary array snapshot in dispose(): disposeSlot mutates
allSlots by deleting the slot being visited, and deleting the
in-progress element during Set iteration is well-defined, so the spread
copy was dead weight left over from prior debugging.
* Fix pending file watcher installs not aborting on unsubscribe
- Local/WSL watcher installs and SSH fs.watch setup now honor the
in-flight AbortSignal, so the last unwatch cancels a slow native
subscribe or remote setup instead of waiting for it to finish.
- Thread signal through IFilesystemProvider.watch and SSH-backed
file explorer watches for the same early-cancel behavior.
* Fix crash-resubscribe hangs and SSH watch teardown races in file watcher
- Add a bounded deadline for post-crash resubscription crawls so one
stuck root quarantines instead of pinning its whole shard forever.
- Report FSEvents overflow as recoverable so delivery continues after
a dropped-events error instead of surfacing as terminal.
- Make WSL watcher abort errors real DOMException instances so
AbortSignal-based cancellation checks recognize them.
- Rework SSH watch registration so ownership of the shared setup
request (not just the first caller) decides teardown, preventing
one caller's abort from cancelling another's shared watch and
guaranteeing exactly one fs.unwatch per registration.
- Reformat reliability-gates.jsonc arrays and refresh WSL/SSH coverage
entries and evidence runs to match the above.
* Add CI gate to run the file-watcher SIGSEGV fault harness under Electron
- The reliability gate and release workflows (mac, Linux) previously only
exercised the crash-isolation harness under vanilla Node, which doesn't
catch runtime differences in the actual Electron binary that ships to
users.
- Adds an `ELECTRON_RUN_AS_NODE=1 pnpm exec electron ...` run of the same
harness alongside the existing Node run, so #8212's SIGSEGV-survival
contract is proven against both runtimes before packaging.
* Add CI gate blocking Linux/macOS release packaging on watcher fault reco
Adds a contract test asserting release-cut.yml and release-mac-build.yml
run the runtime-file-watcher-fault-harness after building and before
publishing artifacts, so a regression in watcher process fault recovery
fails release packaging instead of shipping silently.
* Fix use-after-clear crash in failAllWatcherSubscriptions
Snapshot the records map before iterating, since onTerminalError
hooks can dispose the supervisor and clear `records` mid-loop,
causing a crash. Also update the matching test to assert against
the shared buildParcelWatcherIgnoreOptions helper instead of a
loose arrayContaining match.
* Fix use-after-clear crash in failAllWatcherSubscriptions
Snapshot watcher records with Array.from instead of spread, since
spread syntax over an iterator that's mutated mid-loop by
onTerminalError hooks can produce inconsistent results.
#7598 pushed client-ui.ts to 301 lines, one over the max-lines limit, which
breaks repo lint for every PR that merges current main (verify does not run
on main pushes). Follow the existing browser-schemas.ts/computer-schemas.ts
pattern: zod schemas move to a sibling module, the method registry stays.
Co-authored-by: Brennan Benson <>
* Keep long live session logs stable while updating
* fix(editor): normalize content before append sync
* chore(editor): export e2e probe type and link gate motivator
Post-review cleanup: env.d.ts referenced the probe's method shape as an
inline literal, so a probe rename would only surface in the e2e spec;
the new reliability gate's motivatingLinks pointed at the repo root.
---------
Co-authored-by: Brennan Benson <brennanbenson@Brennans-MacBook-Pro.local>