Repository navigation
Conversation
…p-free (stablyai#9881) Co-authored-by: Orca <help@stably.ai>
…idebar rows (stablyai#9850) * fix(agent-status): keep Claude in-process teammates visible as idle sidebar rows Claude Code 2.1.21x runs named Agent-tool agents as turn-based in-process teammates: SubagentStop and TeammateIdle fire at every TURN end while the teammate stays alive awaiting mail (verified live on 2.1.217). Treating those events as finish signals deleted the child row seconds after each burst, so the sidebar showed no subagents for most of a teammate's life. Root-cause fix: the roster now tracks a working/idle state per child. - One-shot children (hyphen-free ids) keep remove-on-stop: their SubagentStop is a true finish. - Teammate-shaped rows park as idle on SubagentStop/TeammateIdle and revive to working via the next SubagentStart (same lifecycle id, first-observed startedAt preserved). - Idle rows never gate the pane 'working' (stablyai#8825's done-gate rule). - Only TeammateIdle-confirmed idle rows survive a complete lead-Stop fold; a stopped workflow lane wearing a teammate-shaped id is reaped there (or immediately, once a fold tagged it listedAsSubagentTask), so the pre-stablyai#8825 idle pile cannot rebuild. - At the wire cap, the oldest idle row is evicted to admit a working spawn; working children are never displaced. - Hydrate keeps pruning idle snapshots: idle-teammate liveness cannot be proven across a restart, and a live teammate re-earns its row. * fix(agent-status): restore inventory-confirmed workflow lanes
* feat(skills): land remaining hybrid stubs * fix(build): exclude skill stub sources from packages
…gration (stablyai#9798) Opening the Quick Commands sheet right after connecting over relay races the relay->direct cutover, which rejects the in-flight one-shot settings.getTerminalQuickCommands with LogicalClientCutoverError while connState stays 'connected'. The sheet then strands on "RPC interrupted by connection migration" with an empty list until closed and reopened. The read is side-effect-free, so replay it on cutover (capped at 5, cancelled if the sheet closes or the client is replaced). Same failure class and pattern as stablyai#9794 (capability probe) and stablyai#9796 (terminal create).
…rch terms (stablyai#9967) The Language setting's native word (语言 / 언어 / 言語 / Idioma) only reached settings search via the localized title in that word's own UI locale — so a Chinese speaker on the default English UI could not find it by typing 语言. Always-index the native word for 'language' in every supported language (plus the previously-omitted Spanish native name Español), so speakers can locate and switch to their language from any starting locale. Native words are locale-invariant constants, so they are plain keyword literals with reviewed localization-coverage allowlist entries. Co-authored-by: Orca <help@stably.ai>
stablyai#9861) * fix(agent-status): map codex request_user_input questions to waiting Codex 0.145 asks user questions via the auto-allowed request_user_input tool (experimental default_mode_request_user_input): PreToolUse fires while blocked on the answer with no Stop, so Orca showed the pane as working/idle instead of Needs You. Map that PreToolUse to waiting (mirrors grok's ask_user_question), exempt question waits from the codex yolo auto-approval suppressor, and deliver native-chat answers to the digit-commit selector by option number (typed labels are ignored and Enter commits the highlighted first option). Older codex versions emit no such event and are unchanged. * fix(native-chat): preserve codex question answer semantics
…n mobile (stablyai#9801) * fix(mobile): persist per-device tab selection so worktree return restores the last open tab A phone's tab selection lived only in the host's in-memory ClientSessionTabSelectionStore. Any host restart wiped it, and the per-device projection then fell back to deterministic topology, so returning to a worktree on mobile always landed on the first tab instead of the tab last opened on the phone. Persist the per-device selections in the Store (keyed deviceId -> worktreeId), hydrate them when the runtime constructs, and guard projection so an early empty snapshot after restart cannot wipe a hydrated selection before tabs arrive. Selections are pruned with the worktree/repo and on device revoke, and malformed persisted payloads degrade to empty instead of throwing. * fix(mobile): harden persisted tab selection cleanup * fix(mobile): preserve tab selection across worktree rename
…ed (stablyai#9780) * feat(mobile): mount ProtocolBlockScreen when protocol compat is blocked ProtocolBlockScreen existed since PR stablyai#1440 but was never mounted: on a 'blocked' compat verdict the only output was a console.warn, so a future MIN_COMPATIBLE_RUNTIME_CLIENT_VERSION bump would have silently shown a broken host UI instead of the update screen. Add HostProtocolGate — a choke point in app/h/_layout.tsx above every /h/[hostId] route — that consumes useHostStatusGates and replaces the blocked host's entire UI (sidebar + detail stack) with ProtocolBlockScreen. The host list and other hosts stay usable; the screen's own 'Back to hosts' escape hatch routes to '/'. Both block reasons render their respective CTAs (mobile-too-old → App Store, desktop-too-old → GitHub Releases). Compat logic stays in the src/shared mirror contract — no fork. * fix(mobile): fence incompatible host routes efficiently * fix(mobile): route Android updates to releases
* feat(source-control): show current branch in header * fix(source-control): keep header focused on branch * fix(source-control): compact detached head identity * fix(source-control): make branch identity keyboard accessible * fix(source-control): keep create review in checks
…erer OOM (stablyai#9872) (stablyai#9936) The live `agentStatusByPaneKey` map had no size cap. `setAgentStatus` rewrites it with a full spread copy on every status ping and keeps rows until a pane/tab teardown event removes them; a missed teardown (agent killed without a Stop hook, pane/tab closed while its status lingered) orphans a row forever. On long multi-agent sessions orphaned heavy rows accumulate without bound, and because each ping spread-copies the whole map, once it is ~1.9 GB one ping transiently doubles it past the 3586 MB old-space limit -> renderer crash (exit -36861). Cap the live map at MAX_LIVE_AGENT_STATUSES=500, shedding only rows whose pane is provably gone (a mounted tab's rooted layout no longer lists the leaf) or long idle, and never an open pane's row (any state, incl. needs-input waiting/blocked). Rootless/empty-snapshot, not-yet-hydrated, and no-renderer-tab (orchestration worker) rows are unprovable: kept while a fresh agent could own them, shed only past the stale window or by a hard-cap fallback that guarantees the bound. Eviction bumps the status/sort epochs so the retention sync snapshots disappeared done rows. Cost is one Object.keys length check on the reducer's already-O(n) spread under the cap; the layout walk + sweeps run only on the rare over-cap ping. Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
…ablyai#9782) * feat(mobile-pairing): surface unpaired-device auth failures instead of silent 4001 loops Desktop: when a phone repeatedly fails direct-transport E2EE auth with a token missing from the device registry (pre-v1.4.106 pairing-path bug left desktops that regenerated their registry rejecting paired phones forever), throttle to one notification per session and show an actionable toast pointing at Settings -> Mobile to re-pair. Mobile: map a bare 4001 close onto the existing auth retry budget (the encrypted e2ee_error is undecryptable when the desktop keypair changed, so the close code is the only surviving signal) instead of looping the generic reconnect forever, and make the auth-failed verdict say 'Pairing invalid - re-pair with your desktop' instead of a bare 'Auth failed'. * fix(mobile-pairing): handle stale keys and startup notification races * fix(mobile-pairing): isolate auth notification failures * fix(mobile-pairing): keep recovery alert actionable
* perf(mobile): gate host polling on foreground The mobile host screen ran two 3s polls (routed + embedded), each firing worktree.ps AND repo.list, with no foreground/background gate — so a connected phone kept pinging every 3s (worktree.ps is a full multi-repo process scan) plus a radio wakeup, including brief background windows while the socket stays parked. Consolidate both into one startHostWorktreeRefresh lifecycle and AppState-gate the interval so BOTH polls stop while backgrounded and refresh immediately on foreground return. worktree.ps keeps its 3s cadence while foregrounded (it carries live agent status/preview/unread that no push event replaces). repo.list stays on the interval as an AppState-gated, self-throttling (REPO_METADATA_REFRESH_MS=60s) convergence safety-net — desktop Settings repo edits notify only the renderer, not the runtime clientEvents stream, so it can't be made purely event-driven without going stale — and additionally gets a reposChanged/worktreesChanged fast-path and reconnect-replay refetch. Verified in a deps-installed mobile checkout: full mobile suite 2232 pass, typecheck, oxlint (within the frozen max-lines budget), and oxfmt --check all clean. Co-authored-by: Orca <help@stably.ai> * chore(mobile): drop stale fetchRepoMetadata dep from the reconnect effect Address CodeRabbit nitpick: the reconnect effect no longer calls fetchRepoMetadata (that refetch moved into startHostWorktreeRefresh), so it shouldn't remain in the effect's dependency array. Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Orca <help@stably.ai>
stablyai#9979) * fix(mobile): keep quick-commands button steady while capabilities load The tab-row quick-commands button only rendered once the capability probe resolved true, so it popped in after the row was already visible (and vanished during reconnect re-probes). Render it whenever support is not confirmed absent and disable it until the probe settles — pre-quick-commands hosts strip agentPrompt, so the action (not the button) must wait for confirmation. Confirmed-unsupported hosts still hide it entirely. * fix(mobile): explain unsupported quick commands on tap instead of hiding Per feedback on the disabled/hidden states: the button now always renders and stays tappable. Tapping against a desktop that confirmed no support shows "Desktop update required for quick commands" (mirroring the browser streaming copy); tapping while the capability probe is still resolving says to try again in a moment. The sheet still opens only once support is confirmed, since pre-quick-commands hosts strip agentPrompt. * docs(pr): add QA screenshots for quick-commands button states * test(mobile): lock quick-commands button stability Add a focused source-contract test for the always-mounted tab action and confirmed-support sheet gate. Keep the non-obvious safety comment concise, and remove PR screenshots now hosted as GitHub user attachments. * test(mobile): structurally guard quick-command action mount
…9843) react-markdown's <Markdown> has no internal memoization: it rebuilds the whole unified remark->rehype->highlight->katex processor and re-parses the document on every render. MarkdownPreview re-renders on internal state that does not affect the rendered output — most visibly, every keystroke in Find (query/match-index state) — so a large doc re-ran the full parse + syntax-highlight + KaTeX pass per keypress, making Find laggy. Hoist the two fully-static plugin arrays to module scope (a fresh array identity per render would defeat the memo) and render the body through a React.memo'd MarkdownBody keyed on content + components. The pipeline now re-runs only when the rendered content or the components map actually changes; Find/review-pulse/copied- note re-renders skip it. The components map was already memoized, so its identity is stable across those re-renders. Behavior unchanged: 106 existing MarkdownPreview tests pass. Co-authored-by: Orca <help@stably.ai>
…i#9842) The iOS MJPEG and Android scrcpy device streams are gated only on the pane being the active tab (isActive, PR stablyai#7382). When the emulator tab is frontmost but the whole Orca window is hidden/minimized/occluded/display-asleep, the full-fps pipeline keeps running: main-process socket read + JPEG/H.264 decode + IPC + renderer decode. Renderer background-throttling (stablyai#9395) cannot stop it because the pipeline is IPC-push driven from main. Gate showStream additionally on window visibility via a new occlusion-safe hook that honors the terminal stale-visibility latch (so a display-sleep occlusion wedge can't freeze the emulator on a black frame) and delays the visible->hidden park by 500ms so a quick Cmd+Tab round-trip doesn't renegotiate the device stream. Co-authored-by: Orca <help@stably.ai>
…#9995) Zone.js patches the global Promise with a non-native thenable. When a bare `new Promise(...)` crosses the Electron executeJavaScript boundary, it's serialized as-is, losing { page, target } and exposing __zone_symbol__* fields instead. Wrap in an async IIFE to return a native promise that Electron always unwraps correctly.
…blyai#9985) Production crash diagnostics measured ~128 `git worktree list` execs/min (9,400 in one 80-minute session, ~16% of wall-clock in git subprocesses): the resolved-worktree scan fans out over every registered repo on a 30s cache TTL, and most registered repos on the affected installs were agent-CLI scratch repos (~/.codex-tmp capsules, vendor imports, skill checkouts) that need no freshness. Classify agent-scratch repo roots with a curated shared matcher and stamp their scan-cache entries with a 5-minute TTL instead of 30s. Orca-driven mutations still bypass the TTL via the per-repo generation bump, so only passive pickup of external changes slows for scratch repos. Expected steady-state reduction on the measured install: ~82% fewer git spawns.
…7936) (stablyai#9826) * fix(daemon): retire macOS daemons whose login session died (stablyai#7936) A daemon that survives a full macOS logout is unsalvageable: its PAM context can no longer host login(1) spawns (every new PTY becomes a 'Login incorrect' prompt zombie) and its Mach bootstrap namespace has lost the system DNS resolver, so terminals it hosts have no egress. Today it also keeps the stablyai#9301 preflight's cached 'accepted' verdict, so it keeps wrapping spawns in login(1) forever; only a manual daemon restart recovers. GUI-spawned daemons now watch for login-session death from the inside: a fresh cache-bypassing PAM probe (triggered by PTY-exit bursts, fresh client hellos, and a slow periodic timer) must conclusively reject three consecutive times AND the in-process system resolver must be degraded; then the daemon exits crash-style so session meta stays unclean and the replacement daemon cold-restores scrollback. A conclusive rejection also flips the spawn-wrapper cache off immediately. Headless serve/SSH daemons never get the watch (they must survive their spawning session ending), and a session that never conclusively accepted login(1) never arms it — a PAM anomaly alone can't kill a healthy daemon (fast user switching keeps accepting, so switched-away sessions are preserved). * test(daemon): e2e seam to drive login-session death oracles from a verdict file A dead macOS login session cannot be fabricated without root (PAM owns audit-session teardown), so live lifecycle QA drives the death watch's probe and resolver oracles from ORCA_E2E_LOGIN_SESSION_PROBE_FILE: 'alive' → accepted/healthy, 'dead' → rejected/unhealthy, anything else inconclusive — with compressed watch timing. Mirrors the existing ORCA_E2E_DAEMON_INIT_DELAY_MS seam; inert unless the env var is set. * fix(daemon): close the hang-shaped gap in login-session death detection The conclusive-PAM-verdict trigger had one blind failure shape: login(1) hanging at the prompt past the probe bound (killed → inconclusive forever → the watch never fires). Three changes close it: - The death-watch probe gets its own 4s bound (the 500ms preflight bound exists for spawn-path latency, which doesn't apply off-path), so a slow-but-answering PAM stack isn't misread as a hang. - An inconclusive pipe probe escalates to a PTY-hosted probe via script(1) — a dead session's PAM stack may only misbehave under a real tty (the pipe-vs-PTY fidelity limit the preflight documents). - A streak of timeout-killed probes (which a live session never produces) is a second retirement trigger, at a higher threshold (5) and still gated on the degraded resolver, logged with a distinct cause so field logs discriminate the two paths. Every dead-session behavior — fast reject, prompt-then-EOF, or hang — now fires retirement; all inconclusive states still fail toward preserving the daemon. * fix(daemon): keep login-session retirement conclusive * fix(daemon): stop login watch before clean shutdown * fix(daemon): make login-session PTY probe reliable * fix(daemon): close login-session watch races * fix(daemon): ignore health probes for login watch activity
…ai#9980) * test(e2e): verify Claude is prefilled with issue URL on start Regression test for stablyai#6613: when starting a workspace from a newly created GitHub issue, ensure the issue URL is passed to Claude via `--prefill` and `--dangerously-skip-permissions` flags. This prevents context loss after issue creation. * test(e2e): fix GitHub-issue-start prefill test flakiness - Reorder mock API handlers to ensure `/labels` and `/assignees` paths match before the specific issue endpoint - Replace regex heading matcher with exact string for more reliable assertions - Refactor terminal content polling to capture text once and reuse in subsequent assertions
* fix(terminal): defer remote output ACKs until parse * test(terminal): document synchronous credit claims --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
…ablyai#9475) * fix(codex): promote [tui] settings so they survive the managed-home remirror Codex TUI preferences (/statusline, theme, terminal title) are written into the [tui] table of the managed runtime config.toml, but the write-back promotion allowlist only covered four top-level scalars — so the next mirror pass rewrote the runtime config from ~/.codex and silently discarded them. Extend promotion to the [tui] keys the Codex TUI persists (status_line, status_line_use_colors, terminal_title, theme), keyed as structured tui.* paths so the same three-way merge (runtime vs baseline vs ~/.codex) applies: in-Codex changes promote into ~/.codex before the mirror, and outside edits to ~/.codex still win over stale runtime values. The byte-preserving upsert moves to codex-config-settings-upsert.ts (max-lines) and learns [tui] placement: replace an existing bare or dotted key in place, insert into the first [tui] body, insert dotted beside existing dotted tui.* keys, or create one [tui] table at EOF — never defining tui twice, including when the system config holds an inline tui = {...} table. * Add codex-config-settings-upsert to the CLI tsconfig file list * fix(codex): keep tui upserts out of array tables * fix(codex): handle quoted tui config paths during promotion * fix(codex): harden tui promotion writes
… c5d87873) (stablyai#10024) Co-authored-by: Orca <help@stably.ai>
Coalesce identical and sub-pixel fallback overlay measurements so ResizeObserver and xterm fit cannot sustain a render feedback loop, while preserving precise committed geometry. Adds regression coverage for stable measurements, sub-pixel jitter across integer boundaries, and genuine resizes.
…s 7f93d89c, 362b566d) (stablyai#10028)
…+ add glibc/libstdc++ packaging gate (stablyai#9902) (stablyai#10019) * fix(linux): restore Ubuntu 20.04 launch by pinning node-pty glibc symbols (stablyai#9902) The bundled node-pty pty.node is compiled from source in release CI on ubuntu-latest (glibc 2.39). glibc's 2.32-2.34 libpthread/libutil merge relocated openpty/forkpty (GLIBC_2.34) and pthread_sigmask (GLIBC_2.32) into libc under new symbol versions, so the from-source build bound to versions absent on Ubuntu 20.04 (glibc 2.31). The main process imports node-pty at startup, so the app crashed on launch. pty.node is the sole blocker (Electron needs GLIBC_2.25; other native modules <= 2.17). - Patch node-pty: a .symver shim pins the 3 symbols to their pre-merge version (GLIBC_2.2.5 x64 / GLIBC_2.17 arm64), and Linux-only ldflags force libutil.so.1/libpthread.so.0 back into DT_NEEDED. Guarded to Linux; macOS/Windows untouched. - Add a packaging gate (verify-linux-glibc-floor.cjs, afterPack): reads each bundled native binary's objdump -p version needs and fails the Linux build if any strong GLIBC_/GLIBCXX_/CXXABI_ node exceeds stock Ubuntu 20.04 (glibc 2.31 / GLIBCXX_3.4.28 / CXXABI_1.3.12). Catches GLIBC_ABI_DT_RELR, rejects GLIBC_PRIVATE, skips weak needs, fail-closed. - Docs + tests; the lazy sherpa-onnx speech prebuilt (GLIBCXX_3.4.29, never loaded at launch) is a documented libstdc++-floor exemption. * fix(linux): assert DT_NEEDED provider deps in the glibc-floor gate Harden the packaging gate (flagged in adversarial re-eval): the version-floor check alone can false-pass if the patch's forced `-l:libutil.so.1` ever silently drops — the pinned openpty@GLIBC_2.2.5 still resolves from libc's compat alias at build time, but fails to load on Ubuntu 20.04 where openpty/forkpty live only in libutil. The gate now also asserts that any binary importing openpty/forkpty keeps libutil.so.1 in DT_NEEDED. Validated on a real symver-pinned .so with libutil dropped (now fails) vs. present (passes). Documents the recommended real-host smoke-test follow-up.
Update desktop experimental settings, mobile settings/onboarding, i18n (en/zh/ja/ko/es), and user-visible error strings. Keep internal APIs and identifiers as nativeChat.
…, not failed (stablyai#10021) * fix(mobile): report interrupted native chat sends as delivery-unknown, not failed A terminal.send interrupted mid-flight showed a definite "Message not sent" even when the desktop may have already delivered the text. Three paths were misclassified as definite failures: - Logical relay/direct cutover: migrateTo rejects in-flight requests with LogicalClientCutoverError, which mapped to 'rejected'. Now maps to 'unknown' (held unconfirmed + transcript-echo verification; never retried since terminal.send is non-idempotent). - Suspend/close of a half-open session: the stable logical client blanket- rejected in-flight pendings with plain 'Client suspended'/'Client closed', preempting the physical layer's delivery-unknown marking. It now lets the physical close settle them, so post-write failures stay marked and pre-write failures stay definite. - Relay path: mobile-relay-rpc-session never marked delivery ambiguity at all (timeout, close, link failure). Post-write rejections are now marked; pending entries only exist after the frame reached the authenticated link. Permission, ask-answer, and cancel-Escape surfaces now show "unconfirmed — check chat before retrying" instead of a definite "not sent" on ambiguous outcomes (still not-accepted, never retried). Also consolidates a private copy of isLogicalClientCutoverError in worktree-create-retry. Co-authored-by: Orca <help@stably.ai> * chore(skills): regenerate skill-bundle manifest artifacts --------- Co-authored-by: Orca <help@stably.ai>
…#10040) The daemon health-check guard logs during main-process startup, which can complete before the renderer window resolves. Moved stderr listening to the launch options so early logs aren't missed. Also made the assertion regex pattern-based instead of exact-string matching to tolerate benign log rewording, and added a check that the replace path stayed off.
…tablyai#9988) * fix(mobile): keep native chat from resizing the covered terminal PTY Native chat reads the agent transcript stream and never renders the terminal grid, but two paths still pushed phone dimensions into the covered PTY, reflowing the desktop terminal for no benefit: - The covered lease-only subscribe carried the cached viewport, and handleMobileSubscribe phone-fits the PTY whenever a viewport is present. The lease now omits the viewport so the host keeps the desktop baseline and late-binds on return to the terminal tab. - useTerminalViewportRefit measured the still-mounted WebView under the chat overlay and sent terminal.updateViewport on rotation, keyboard, text-scale, reconnect, and iOS-resume triggers. Refits are now suppressed while native chat covers the active terminal; the triggers already mark the viewport stale, and the return-to-terminal resubscribe re-measures. * fix(mobile): harden native-chat resize suppression
….ma" (stablyai#10047) Co-authored-by: Orca <help@stably.ai>
…tablyai#10050) Grok transcripts carry no timestamps. Previous logic excluded these rows from matching, leaving pending sends and launch prompts unmatched and causing the seeded bubble to appear rank-pinned at the list tail — which reads as conversation reordering. Now pending sends, launch prompts, and their pruning rules treat null timestamps as matching-eligible, allowing echo suppression and proper cleanup of delivered messages.
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
The type-aware switch-exhaustiveness lint rule requires explicit cases for 'current', 'duplicate', and undefined; they fall through to the existing generic skipped message, so behavior is unchanged. Co-authored-by: Yuris Auzins <zuz666@users.noreply.github.com>
…(hardening, stablyai#9191 investigation) (stablyai#10069) Co-authored-by: Orca <help@stably.ai>
…tablyai#10027) (stablyai#10070) Adds a keyboard command that opens the "Send notes to an agent" picker for the active worktree's AI diff-review notes, enabling a fully keyboard-driven review flow. Unbound by default; users assign it in Settings → Keyboard Shortcuts. - New `sourceControl.sendReviewNotes` command (scope global, unbound). Set `conflictGroup: 'editor'` so Settings warns on collisions with editor chords (e.g. Add Review Note), not just global ones. - Dispatched from App.tsx's existing global capture handler so it respects the terminal-shortcut policy, the shortcut-recorder guard, and defaultPrevented. - Store thunk `openDiffNotesSendMenuForActiveWorktree` reveals Source Control and requests the notes send menu open; no-op when there are no unsent notes. - Menu opens via a nonce-based store request consumed on mount, TTL-bounded so a request the menu never consumed can't reopen it on a later remount. Co-authored-by: Orca <help@stably.ai>
…stablyai#9849) * perf(main): park worktree pollers while hidden The per-repo worktree metadata pollers ran 24/7 with no window-visibility gate (~50 fs.stat/sec at 20 repos on macOS while the window is hidden). Park the poll timers while the window is hidden and resume losslessly (immediate fresh-vs-retained snapshot diff) on reveal, via an injected WorktreePollerWindowVisibility mirroring the ssh-port-scanner pattern. Only the poll timers are gated; the darwin native worktrees/ fsevents watch and its teardown/re-arm lifecycle stay always-on (push-based, ~0 idle cost; stablyai#8732 race). A window is parked only once it has actually been shown and is now hidden: a live never-shown window (ORCA_E2E_HEADLESS keeps one) and null/destroyed windows stay always-visible, so a windowless/headless host never permanently parks the poller. Co-authored-by: Orca <help@stably.ai> * fix(main): keep start-to-start poller cadence after the interval→timeout change Parking the pollers moved them from setInterval to a one-shot setTimeout chain, but scheduling the next tick a full pollIntervalMs AFTER each scan completed turned the cadence into gap-after-completion — every visible refresh landed ~one scan-duration late per tick. Measure from tick start and schedule max(0, interval - elapsed) so the cadence is start-to-start like the original interval, while keeping the one-shot chain that park/resume needs. Co-authored-by: Orca <help@stably.ai> * fix(main): clamp poller reschedule delay to [0, pollIntervalMs] Date.now() is not monotonic: a backward wall-clock jump (NTP correction) during a scan makes (now - startedAt) negative, so the start-to-start delay pollIntervalMs - elapsed would exceed one interval by the adjustment — suppressing visible metadata refreshes for minutes/hours, unlike the former setInterval. Cap the computed delay at pollIntervalMs (upper) as well as 0 (lower) in all three pollers. Co-authored-by: Orca <help@stably.ai> * refactor(main): drop redundant notifyTimer guards to stay under max-lines The visibility-parking change pushed worktree-base-directory-watcher.ts to 303 code lines (limit 300). clearTimeout tolerates null/undefined, so the two truthy guards around it are redundant — remove them (coalescing null→undefined for the type). No behavior change; back under the limit without an eslint-disable. Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai>
…re-open of stablyai#9882) (stablyai#10074) * perf(main): gate idle git-common polling Co-authored-by: Orca <help@stably.ai> * fix(main): enumerate git-common worktrees every tick to catch coarse-FS add/remove The worktrees-dir readdir was gated on the dir's mtime:ctime:ino:size signature, so on a coarse-mtime/FAT filesystem a same-granule add+remove (all four fields collide) went undetected until the ~30s index backstop. A single readdir of a small dir is negligible next to the per-entry structural stats that already run every tick, so always enumerate — the listing is the authoritative add/remove signal. The expensive per-entry index read stays gated on each entry's own dir signature; onFullScan now reflects the ungated index-metadata backstop fan-out (the real periodic cost) rather than the readdir. Co-authored-by: Orca <help@stably.ai> * fix(main): don't fabricate worktree deletions on a transient git-common readdir failure Follow-up to always-enumerating the worktrees dir: the readdir catch-all treated ANY error as an authoritative empty listing, so a transient failure (EIO/ESTALE/EMFILE, network/SSH hiccup) emitted a false delete for every linked worktree (and a false create next tick) — and enumerating every tick widened that exposure. Only ENOENT (dir truly absent) now yields an empty listing; other errors retain the known entries so per-entry stats still run and a real removal surfaces as that entry's own stat miss. Adds a regression test (readdir → ENOTDIR) asserting no false delete. Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai>
…can (stablyai#9841) * perf(startup): overlap catalog/session disk reads with the worktree scan App.tsx hydrated repos, project-groups, folder-workspaces, worktrees, then session-get strictly serially, even though only worktrees depends on repos. Once repos is loaded, run fetch-worktrees, session-get, and the (internally ordered) local project-group/folder-workspace catalog chain concurrently so the two disk reads hide behind the O(repos) worktree git scan (the startup long pole). list-runtime-session-hosts now overlaps the repo scan too. Ordering preserved: repos before worktrees/session; project-groups before folder-workspaces; hydrate-session-stores still runs only after all settle. fetchAllWorktrees({hydrationPurge:'defer'}) returns before its folderWorkspaces read, so it needs no catalog ordering at startup. Co-authored-by: Orca <help@stably.ai> * perf(startup): don't serialize the worktree scan behind host discovery Address CodeRabbit review: awaiting runtimeHostsPromise before the Promise.all made fetch-worktrees + the catalog chain wait on list-runtime-session-hosts, even though only session-get needs the host ids. Chain session-get off the host promise inside the Promise.all instead, so the worktree scan and catalog reads start immediately and the host-list IPC only gates session-get. Co-authored-by: Orca <help@stably.ai> * test(startup): assert concurrent hydration graph in source-order guard The #18 startup change runs worktrees/session-get/catalog concurrently in a single Promise.all, so the guard's old serial folders<worktrees assertion (and its session-get slice terminator) no longer describe the code. Assert the real invariants: UI hydrates before any local read, the catalog chain stays ordered, worktrees+session start after repos, and all three are joined in one Promise.all. Co-authored-by: Orca <help@stably.ai> * fix(startup): join concurrent hydration with allSettled so recovery can't race in-flight tasks The concurrent worktrees/session/catalog join used fail-fast Promise.all, so a fast rejection from one branch dropped into the catch/recovery path (which reconnects terminals and flips readiness) while a sibling hydration task was still in flight and mutating catalog/worktree state — a race the old serial flow could not hit. Use Promise.allSettled and surface the first rejection only after all three settle, so recovery still triggers but nothing is left writing to the store. Guard test updated. Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai>
…emote-server host (stablyai#9790) * fix(agents): scope Settings agent list and quick-launch menu to the remote-server host With a paired Remote Server as the Active Server, Settings → Agents and the tab-bar + quick-launch items always ran agent detection on the local client's PATH, so a Windows client showed its own agents while worktree-create correctly listed the server's. - Extract TabBar's ssh/runtime/local owner resolution into a shared useAgentDetectionTargetForWorktree hook and use it in QuickLaunch, which previously resolved only SSH connections and fell back to local for paired-runtime worktrees. - Scope AgentsPane detection (and its Refresh button) to the Active Server, with an "on <server>" badge showing which host the list came from. Enable/disable/default toggles remain client-side settings. - Split runtime detection into store/slices/runtime-detected-agents.ts and add refreshRuntimeDetectedAgents: preflight.refreshAgents over the relay (login-shell PATH re-read), falling back to preflight.detectAgents on servers that predate the refresh RPC, keeping the last known list when the runtime is unreachable. * fix(agents): avoid redundant runtime refresh fallback * fix(agents): dedupe SSH agent refreshes * fix(agents): preserve remote host boundaries * fix(agents): prevent remote detection refresh races * fix(agents): harden remote detection failures * fix(agents): keep unresolved detection off local host * fix(agents): keep cold remote ownership unresolved
…blyai#10007) (stablyai#10029) * Revert "Enable accessibility tree (`ax`) command on iOS emulator sessions (stablyai#10007)" This reverts commit 43ae014. * fix(emulator): expose iOS accessibility tree * fix(emulator): support device-only iOS AX * fix(emulator): normalize iOS ax to 0..1 and heal missing axUrl serve-sim's helper /ax reports element frames in absolute pixels, but tap/gesture take normalized 0..1 coords. Normalize the raw AX node tree into a compact nested shape whose frames are 0..1 over the device screen (first root's frame), mirroring serve-sim's own normalizeAxTree, so agents can feed ax output straight back into input commands. Also heal sessions that were registered without an axUrl: stablyai#9924 only derived /ax at parse time, so already-active sessions had no endpoint. The bridge now derives it from the session's mjpeg stream URL, guarded to the /stream.mjpeg suffix so a non-mjpeg URL never fabricates a bogus /ax. * docs(emulator): mark ax working on iOS with correct raw-AX-tree shape Both skill guides and the CLI summary described iOS ax as unsupported (or, via the reverted stablyai#10007, as a normalized "screen + elements" shape that never matched the endpoint). ax works on both backends: Android via uiautomator, iOS via the serve-sim helper. Document the real iOS output — a raw AX node tree (labels, roles, nested children) with frames normalized to 0..1 — and regenerate the bundled skill guides. * chore(skills): regenerate skill bundle manifests CI verify failed because generated skill artifacts were stale after version/skill revision bumps. * fix(emulator): read ax from explicit device without active session Fall back to udid-keyed session lookup when a worktree has no active emulator, allowing `--device` targeting to work the same way for ax as it does for tap/type. Also clarify in docs that AX frames are normalized 0..1 with top-left origin, and show how to tap an element at its frame center (x+width/2, y+height/2). * fix(emulator): cap iOS AX tree at 500 nodes Unbounded accessibility trees can flood agent output. Enforce a 500-node limit (matching serve-sim's snapshot cap) and mark truncated parents so consumers know the tree was cut. --------- Co-authored-by: 5Hyeons <ohs2251@naver.com>
Long single-line and structured files wrap by default and misalign. Surface Word Wrap on the editor more-actions menu for normal file tabs (diff already had it) and add editor.toggleWordWrap (Alt+Z) so users can unwrap without opening Settings. Closes stablyai#9974
CodeRabbit: Alt+Z previously always flipped editorWordWrap, leaving diff panes out of sync with the markdown actions menu.
Cover editor/diff setting callbacks and the cross-platform Alt+Z binding.
Owner
Author
|
Upstream stablyai#10086 merged on stablyai/orca — closing portfolio mirror. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Upstream
Summary
Summary Long single-line / structured files wrap by default in the editor, which misaligns table-like content (stablyai#9974). Diff surfaces already had a Word Wrap checkbox; normal file tabs only had Settings → Editor Word Wrap. This PR: - Always shows Word Wrap on the editor
Note
innocarpe/orcamainuntil the upstream PR is merged.