[upstream #12822] fix(github-project): index fork upstream slugs for project row matching - #142
Open
innocarpe wants to merge 1106 commits into
Open
[upstream #12822] fix(github-project): index fork upstream slugs for project row matching#142innocarpe wants to merge 1106 commits into
innocarpe wants to merge 1106 commits into
Conversation
* fix(orchestration): retain update settlement authority * test(orchestration): register update settlement gate * fix(orchestration): close update settlement audit gaps * test(orchestration): correct update settlement evidence --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
…i#12351) Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
…er (STA-3291) (stablyai#12348) createExpandCollapseActions minted five fresh closures per TerminalPane render; four are deps of useTerminalKeyboardShortcuts, so its seven window listeners tore down and re-registered on every render and all effect-owned IME chord/modifier state silently reset. Memoize the actions via useExpandCollapseActions and pin registration stability with a render-churn regression test. Fixes stablyai#12269
…12356) Default 4ms maxTurnMs can defer later frames via setImmediate under CI load, so multi-frame assertions after a single feed were flaky.
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
…azy status loading (stablyai#12225) * Optimize worktree parent picker: conditional mount, virtualization, lazy - Mount WorktreeParentPickerPopover only when open to avoid hundreds of unmounted instances subscribing to lineage and worktree store updates. - Virtualize the candidate list and resolve activity statuses only for visible rows, eliminating redundant status subscriptions. - Extract filtering, placement calculations, and row rendering into separate modules for testability and clarity. * Memoize worktree parent picker search handler Wrap search state update in useCallback to stabilize the handler across re-renders. Reduces unnecessary effect runs and enables better memoization of child components. * Optimize worktree parent picker: defer unmount, memoize IDs - Defer unmount until exit animation completes (200ms) to prevent premature teardown - Memoize visibleWorktreeIds to prevent status hook from rebuilding its selector on every render
* Post fixing replies after launching PR comment resolution agents Orca now automatically replies to selected comments after launching an agent to resolve them. Review-thread comments get nested replies; review summaries and issue comments get top-level @-replies. Payloads are snapshotted at queue time and posted only after prompt delivery succeeds. Bounded concurrency (4) prevents SSH slowdown. * Batch conversation replies instead of posting per-comment Combine N unresolvable comments into a single PR conversation reply to avoid timeline spam. Review threads still get one nested reply each. Refactor helper functions to pr-comment-fixing-reply-body.ts and change replyAsConversation(comment, body) → replyAsConversation(body). * Handle bot account mentions in PR conversation replies Use buildPRCommentConversationReplyBody to properly handle GitHub App bot accounts, which don't resolve as @-mentions. Enforce delivery semantics with type-safe PendingPRCommentAiAck payloads and improve error handling around agent launch to prevent stranded state. * Fix React Doctor ref-mutated-during-render errors in ChecksPanel Move comment-resolution payload ref clears and latest-callback mirrors into effects so render stays pure for the static analysis gate.
* fix: preserve Codex launch drafts through setup * test: enforce single startup draft delivery * test: name startup draft release behavior
) * 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 a26a18e. * 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>
* chore(daemon): disambiguate audit observations * fix(daemon): reject future audit protocol roles * fix(telemetry): protect daemon audit observations
* fix(terminal): reconcile status after escape interrupt * fix(terminal): preserve absent interrupt baseline * fix(terminal): ignore stale input acknowledgments * fix(terminal): order interrupt acknowledgments
…minals (STA-905) (stablyai#12355) Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
…8) (stablyai#12359) * fix(renderer): drive agent working spinner on the compositor (STA-3328) The shared 12Hz JS clock wrote el.style.transform on every mounted spinner (41 live = ~490 main-thread style writes/s), keeping style perpetually dirty and delaying keystroke dispatch (measured typing inputDelay p90 363ms; 19ms with the writes stopped). Replace it with a steps(12) CSS transform animation so rotation runs on the compositor; one mount-time animation-delay write preserves the shared-epoch phase sync. * fix(renderer): lock agent spinner phases to document time * fix(renderer): tolerate missing spinner animation API
…ed (stablyai#12346) * fix(agent-status): restore hydrated nonterminal statuses as unconfirmed A hook transition that fires while Electron is down has no receiver and is discarded, so last-status.json can restore a stale 'working' as confirmed truth for up to the 7-day hydrate TTL. Stamp hydrated nonterminal rows with restoredUnconfirmed, carry it through both IPC paths, and treat such rows as never-fresh in the shared and renderer freshness gates so the sidebar, worktree.ps, and the raw snapshot all present the same degraded semantics. Terminal states restore as-is; any accepted live event clears the flag; the flag itself is never persisted. Interrupt/question inference refuses to fabricate transitions onto unconfirmed rows. * fix(agent-status): shed unconfirmed marker when the liveness sweep verifies done The restored-subagent reaper's reconciled entry spread carried restoredUnconfirmed onto a process-probe-verified 'done', making freshness gates suppress a legitimate completion. Keep the marker only while the reconciled state stays nonterminal. * fix(agent-status): let live evidence replace hydrated rows * fix(agent-status): keep restored rows degraded Sort accepted live evidence after hydrated rows even across wall-clock rollback. Let unconfirmed rows own their preserved pane titles without asserting live state, while retaining independently live sibling evidence. * fix(agent-status): suppress unmapped restored titles Treat a single runtime title as covered by the single restored hook row while layout identity is unavailable. Preserve ordinary age-stale fallback and mapped sibling-pane evidence.
* feat(ssh): add SSH config host picker for add-host form Users can now click 'Fill from ~/.ssh/config…' to browse available SSH config hosts in a picker, select one, and have the form automatically prefill with resolved connection details (hostname, port, username, auth). Previously, an 'import' button provided bulk sync on this form—confusing and unhelpful when everything was already synced. That action is now available as a secondary 'Add all' option in the picker. * fix(ssh): import filter preservation and label fallback - Reuse search loader on import completion to preserve active filter inside generation guard - Fall back to hostname when manual host has no label, not empty string - Make alias duplicate detection case-insensitive to match config picker behavior - Validate host availability when restoring project group selection - Add aria-selected attribute to picker options for accessibility * fix(ssh): harden config picker import, alias folding, and host targeting Review findings on the ~/.ssh/config picker + bulk add: - Guard config-host resolution with a generation counter so a late resolve cannot overwrite a later pick or a form the user backed out of; freeze the other rows while a pick resolves. - Stop "Add all N" from re-adopting deleted hosts — it now imports without reAdopt, matching the new-host count it advertises. Settings → Import keeps the explicit re-adopt path. - Fold SSH aliases through a shared normalizeSshConfigAlias for import ownership, delete tombstones, reclaim, picker search, and the save-time duplicate check, which now occupies configHost *and* label like the picker. - Persist GSSAPIAuthentication only when a parsed Host entry asks for it, not when `ssh -G` merely echoes the /etc/ssh system default. - Fail closed with unavailable/setup-not-found when an explicit projectHostSetupId names a non-actionable host instead of silently creating the workspace on a sibling host. - Cache the parsed config for the picker session (refresh on open/retry) so filter keystrokes no longer reparse and Include-expand the file, keep the filter usable during loads, add a Retry on load errors, explain an empty Identity file after a config fill, and drop the always-false aria-selected. * refactor(ssh): centralize host result limit and extract folder group val Move SSH_CONFIG_HOST_RESULT_LIMIT to shared types so the renderer's limit message cannot drift from the host's query limit. Extract findActionableFolderProjectGroup to avoid repeating the folder-host-availability check across the composer hook. * fix(ssh): pass -F to ssh -G when HOME differs from passwd home In E2E tests and sandboxes, isolated HOME can differ from the system passwd home. OpenSSH resolves the default config via getpwuid (passwd), while Node's loadUserSshConfig uses os.homedir() (HOME-aware). Pass -F to explicitly specify the config path when they diverge, so ssh -G and the picker resolve the same file. * fix(ssh): verify config host exists before resolving with ssh -G When a user edits ~/.ssh/config and removes a host, the import picker should not fall back to ssh -G's echoed response (which treats any alias as valid). Check the reloaded config file before resolving. - Force reload config on each resolve to catch user edits post-open - Reject aliases not in the current config before calling ssh -G - Add test for deleted alias edge case - Fix workspace-target fallback to honor explicit host selection * fix(ssh): let tombstoned aliases be re-picked in the config picker Allow users to reclaim a deleted SSH host by re-picking it from ~/.ssh/config. Tombstoned aliases now appear in the picker with a "Removed from Orca" badge and remain pickable, but don't count toward "Add all" operations — ensuring passive import never resurrects a deleted alias while still giving the user a recovery path.
…TA-3337) (stablyai#12362) * fix(mobile): bound terminal viewport resubscribe loop with backoff (STA-3337) An empty scrollback frame with absent host dims was coerced to 80x24, which never equals a phone viewport, arming a zero-delay unsubscribe/resubscribe loop (~25/s) that broke long-press gestures and drained battery. - Absent host dims now hold the stream instead of resubscribing. - Fit resubscribes are budgeted per handle (3 attempts, escalating backoff) with an absence-gated refill mirroring the chat-side rearm bound; on exhaustion the view degrades visibly via toast instead of hot-looping. - A fresh post-measure match counts as convergence instead of resubscribing. - setTerminalModes keeps the Map identity when the mode is unchanged, so same-mode frames no longer re-render the session route. - Host emits the subscriber viewport as scrollback dims when the snapshot and PTY size are both unavailable, so current hosts converge immediately. * fix(mobile): cancel stale viewport retries after convergence
…yai#12375) gh repo create leaves the repo empty, and publishing a release creates a git tag that needs a commit to point at. The adhoc build got all the way through notarization and upload before failing on "Repository is empty". Co-authored-by: Orca <help@stably.ai>
…son (stablyai#12376) main's version only moves on `release:` commits, and stable patches are cut from release branches that never merge back. On 2026-08-03 main read 1.4.165-rc.0 for twenty hours while 1.4.165, 1.4.166 and 1.4.167 all shipped, so every hourly built in that window was stamped 1.4.165-hourly.* while carrying code newer than 1.4.167 — and sorted below the stable its user was already running. Resolve the base from the main repo's published tags instead, taking the patch above the highest shipped stable. package.json stays a floor for the case where main leads the tags. Co-authored-by: Orca <help@stably.ai>
…load (stablyai#12245) * perf(runtime): withhold unchanged mobile snapshots from the graph payload Every graph sync structured-cloned all 222 worktree snapshots to main even when none had changed: 374 KB and ~5 ms per clone, paid twice because Electron clones on serialize and again on deserialize. That transport cost — not the renderer rebuild — is the bulk of a publication. The renderer now sends only the snapshots main has not acknowledged and names the rest in unchangedMobileSessionWorktrees. Detection is object identity, not a deep compare: an unchanged worktree already returns its cached snapshot object. Main seeds nextWorktrees from that list so its prune keeps withheld worktrees live instead of removing them. The call itself is unconditional. syncWindowGraph is not a one-way publish — its return value is the only channel carrying agentOrchestrationByPaneKey to the renderer, and the handler adopts pre-allocated handles, merges detached leaves, refreshes writable flags, and drains graph-sync callbacks on every sync. Skipping it would starve all of that. Two failure modes are closed explicitly. The memo advances only after main acknowledges, so a publication that throws is resent in full rather than silently withheld forever. And a worktree main dropped on its own — worktree metadata removal — comes back in mobileSessionResyncWorktrees, which also clears the accepted-revision record so the republish is not rejected as a no-op. Unchanged republish at 222 worktrees / 787 tabs: 374 KB to 3.4 KB, 5.08 ms to 0.02 ms per clone. One changed worktree: 5.3 KB. * fix(runtime): resync stale withheld mobile snapshots * fix(runtime): align accepted mobile snapshot membership --------- Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
…ts (stablyai#12161) The adhoc workflow checked out any requested ref and ran its scripts and electron-builder config with MAC_CERTS, the notary password, and the adhoc publisher token in reach — including refs/pull/* fork code a maintainer could dispatch in one innocuous-looking click. Vet the ref before checkout: PR refs are refused, branches/tags resolve in a bare tree:0 scratch fetch, raw SHAs must be reachable from a repo branch or tag (a partial clone lazily serves PR-only commits by SHA, so name resolution alone is not a trust test), and checkout pins the vetted SHA so a race push cannot swap the commit. Also reference an adhoc-mac-build environment so the secrets can later be fenced off from stale workflow copies via repo settings.
…nload veto on interrupt (stablyai#12232) Two robustness follow-ups from the stablyai#12194 safety-review loop (pass 3), staged by the reviewer before its session ended; both fail toward extra retention only: - recency bookkeeping now runs while the retention kill switch is off, so re-enabling evicts in true LRU order instead of worktree-list order - interrupted downloads release the eviction veto instead of pinning the worktree as downloading forever
…(STA-3328) (stablyai#12377) * perf(agent-status): coalesce live status bursts into one render pass (STA-3328) Each live agent-status event arrives as its own IPC task, so a multi-agent burst paid one full React render pass per event (200-488ms main-thread tasks under heavy orchestration). Same-task store commits batch to a single render pass, so buffer a burst for one 33ms window and apply it in one task. The leading event still applies synchronously (zero added latency for single events), only an applied event arms the window (dropped/pending events stay immediate), and both clear paths purge queued sets so a coalesced event cannot resurrect a removed status. * fix(agent-status): preserve queued completion on teardown
…g host (stablyai#12388) Destructive worktree removal swept PTYs by worktree id alone. Worktree ids are `repoId::path` and the store keeps one per host, so deleting an SSH worktree could stop a same-id local (or other-connection) workspace's terminals — or fail outright with `selector_ambiguous` when two hosts owned the id. Every destructive teardown now names its owner (resolvedWorktreeId plus the connection/runtime environment), matching the already-hardened forget-local path: - IPC `worktrees:remove` (git + folder workspaces) - runtime `removeManagedWorktree` (CLI/mobile `worktree.rm`, git + folder) - missing-worktree terminal reconciliation, including its no-provider fallback The stablyai#11960 allowUnverifiedStop force-delete gate is untouched.
…efore reporting success (stablyai#12387) The sync before-unload checkpoint staged renderer state and then queued store.flushPendingAsync() fire-and-forget, so reload/restart/update paths navigated while the staged session, scrollback and UI state were still only in memory. Quit is covered by the will-quit flush barrier; those paths were not. Keep staging synchronous (no sync durable writes), but record the flush outcome and expose it on app:await-before-unload-checkpoint. Restart, updater install and lazy-chunk recovery reload now join that write before navigating and abort the attempt when it fails or outlives a 20s deadline.
stablyai#12383) The per-worktree rebuild memo from stablyai#12207 refused to skip any worktree with a registered TerminalPane, because builders read live PaneManager/DOM state the memo could not witness. Hidden worktrees stay mounted (8 parked + 12 retained by default, unbounded with terminalHiddenViewParking off), so that set rebuilt on every publication — on the always-on hosts the memo targeted, ~20 worktrees paid the full content build plus the new inputs build and deep compare every tick. Capture the live reads instead: snapshot pane leaf ids, the live active leaf, the serialized pane tree, and per-pane numeric/pty bindings once per publication into MountedTerminalSurfaceCapture, compare captures by value in the memo, and have the builders read only the capture. Live state outside the capture is now unrepresentable in a builder, so the skip stays provably complete — and a mounted worktree whose panes did not change reuses its snapshot like any other.
) * fix(ui): always show Onboarding in the Help menu Users could not find "Show onboarding again" because it required holding Alt. Surface it on the default Help menu so replay is discoverable without reinstalling. * fix(ui): always show Restart Orca in the Help menu Surface Restart next to Check for Updates by default and drop the Alt-only admin gate, which no longer hid anything else.
…ever (stablyai#12390) GitLab's project-ref cache stored `null` forever and returned any cached value straight from the map, so a repo probed before `origin` was configured — or before `glab auth login` ran for its self-hosted host — kept hosted-review provider detection stale until app restart. The negative-TTL work that shipped for Azure DevOps / Bitbucket / Gitea skipped it. Mirror `createRemoteRefProbeCache`'s semantics: negatives expire on the shared interval, positives stay, the SSH provider generation joins the cache signature so a reconnect re-asks, and a probe abandoned as stale can no longer publish over its successor. Transient git/SSH failures stay uncached. Expiring negatives would otherwise turn `glab auth status --hostname` into one spawn per repo per interval on the hosted-review poll, since a non-GitLab remote reaches it too, so remember the unauthenticated answer per host — not per repo — on the same clock.
* fix(terminal): preserve restored snapshot fidelity * test(terminal): align legacy history handoff snapshot expectation * fix(terminal): keep legacy snapshot panes mounted * fix(terminal): refresh snapshot capability after startup * fix(terminal): refresh snapshot capability in degraded startup * fix(terminal): await snapshot provider authority
…tablyai#12842) - `truncate` has no effect on inline boxes, so long branch names would overflow their flex item and run under the line-total chip - Adding `block` display forces text truncation with ellipsis instead - Increase gap from 1.5 to 2 so ellipsis doesn't visually merge with chip
…i#12681) hasCursorAgentReattachPayloadScreenSignal built a char-by-char copy of the entire reattach payload so it could read the last header plus 5000 chars. On a 2MB daemon snapshot that cost 17.5ms of synchronous renderer main-thread work — ~75% of what xterm then spends parsing the same bytes — and the miss case paid it in full for a result that is always false. Two changes, both matching existing in-tree precedent: bound the scan to a 256KB tail (as the kitty tracker already bounds its own scan), and strip via the shared precompiled CSI_SEQUENCE_PATTERN instead of a hand-rolled loop, which is also faster in V8 because it copies spans rather than building a rope per character. 2MB snapshot, header hit 17.5ms -> 0.80ms (22x) 2MB snapshot, miss 8.7ms -> 0.52ms (17x) 200KB snapshot, header hit 1.5ms -> 0.62ms (2.4x) config/scripts/terminal-reattach-payload-scan-benchmark.mjs reproduces this and asserts every candidate agrees with the baseline before timing it. It also records a negative result: porting the daemon mouse mirror's includes() pre-filter to the kitty tracker makes reattach slower, because snapshots always contain the introducer. Adds guards for the two behaviours a future shortcut would silently break: a CSI-split header must still match, and a header behind the tail bound must not. Also byte-pins POST_REPLAY_REATTACH_RESET_KEEP_MOUSE, which shipped unpinned. Co-authored-by: Orca <help@stably.ai>
* Retire SSH worktree metadata an authoritative scan proved gone The metadata fallback's protection against resurrecting externally deleted worktrees lived only in renderer module state, so it died on every reload while the SSH WorktreeMeta it guarded against persists forever (gcStaleWorktreeMeta exempts any repo with a connectionId, because a local existsSync cannot probe a remote path). Repro: `git worktree remove` on the SSH host, let the authoritative scan purge the row, restart — the startup fetch runs before SSH connects and the fallback re-lists the deleted worktree as a ghost row. Chose option (a), deleting the stale persisted meta in main, over persisting the removal memory: the metadata is the thing that outlives the worktree, and Orca's own removals already delete it (removeWorktreeMetadataAndTransientState), so external removals now converge on the same end state instead of accumulating a second, parallel tombstone list that would itself need eviction. The in-session memory stays for the window before the async delete lands. New `worktrees:forgetRemovedForExecutionHost` only accepts SSH hosts, requires an exact repo owner, skips metas owned by another host, and refuses folder repos — a folder workspace's meta IS the workspace record (gcStaleWorktreeMeta skips those keys for the same reason) and no remote scan can retire one. The renderer only calls it from the authoritative-removal path, so a mere disconnect never deletes anything. Also: - hoist resetAuthoritativelyRemovedWorktreeMemoryForTests into a top-level beforeEach; removeWorktree writes that memory too, so suppression could leak across describes and silently hide a row. - cover the requireAuthoritative gate that skips the fallback, which had no test. - replace the raw NUL byte committed inside the coalesce-key template literal with a \0 escape; it made the file scan as binary to grep/ripgrep. * test(worktrees): verify non-authoritative fallback skips removal The non-authoritative fallback must not trigger worktree cleanup when it observes an absence — only an authoritative scan should. Tighten the expectation to ensure cleanup happens exactly once, when new data arrives after the connection state changes.
…12796) * refactor(mobile): demote address picker to optional disclosure on Relay Relay provides remote access without requiring a specific local address, so hide the picker behind a disclosure to keep the direct fast path accessible without visual clutter. Reposition Sign in between the Relay and LAN options to clarify it's Relay-specific. Keep custom addresses always visible and force the disclosure open when settings search targets the address picker. * refactor(mobile): improve relay pairing guide and interface ranking - Rank Docker/VirtualBox bridges below real LAN addresses so they're never auto-advertised as the default - Clarify UI copy: 'Local network address (optional)' → 'Direct connection on this network' - Better explain direct connection vs Relay roles and when each is used - Fix Relay unavailability to be a build property, not dependent on current selection * refactor(mobile): reframe local network address as optional in relay pai Demote the address picker from primary action styling to an optional disclosure with quieter visual treatment. Update messaging from "Direct connection on this network" to "Also use a faster local path" to clarify Relay is the default path and local addressing only applies when nearby. Add explanatory hint text to set expectations that Relay remains available when away.
… session (stablyai#12803) * fix(ai-vault): group OMP task subagent transcripts under their parent session OMP persists task-child transcripts inside the parent session's same-named artifact directory (<stamp>_<uuid>/), and discovery scanned them as ordinary top-level sessions - a coordinator's history drowned under its own workers. Extend the existing Claude subagent model to OMP, classifying purely by the artifact-dir layout (never by a transcript's parentSession field, which also describes non-task lineage): - prune artifact dirs from the top-level scan (name-pattern predicate) - count direct-child transcripts onto the parent row (local readdir; remote walks partition their listing instead, mirroring Claude's SSH posture) - list children on demand via the existing listSubagentSessions IPC, titled by their coordinator-given task label and linked to the layout-derived parent id - refresh the count on zero-turn cache reuse, matching Claude - extract session-scanner-roots.ts so the renderer-supplied-path allowlist for both agents lives in one module Fixes stablyai#9330 * review: harden OMP subagent classification and cover its uncovered branches Prune predicate now skips depth 0 (the workspace dir), so a workspace whose name happens to look like a session stem keeps its sessions. Drop degenerate OMP roots in ompSessionsRootDirs: OMP_CODING_AGENT_DIR='/' normalizes to '', which resolve()s to the process cwd and would have allowlisted it for the renderer-supplied subagent path. Rename session-scanner-omp-subagents.ts to -omp-subagent-transcripts.ts so it mirrors Claude's transcripts/lister split by role rather than inverting it. Correct two comments that asserted things the codebase contradicts: OMP task children do carry their own sessionId and would resume by path (OMP's own picker globs `*/*.jsonl`, so it never offers them either), and workspace dir names are not uniformly dash-prefixed. Cover branches the change added with no test: the remote/SSH partition wiring, the IPC `omp` gate and per-agent allowlist, the parse-cache zero-turn recount, and the executionHostId disk-ownership guard. Extract the remote scanner's in-memory provider into a fixtures module to stay under the max-lines cap. * review: note why child rows carry an unrendered grandchild count * review: describe the real OMP grandchild layout in the pattern comment --------- Co-authored-by: Dan Cieslak <dcieslak19973@users.noreply.github.com> Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com>
…o load (stablyai#12867) * fix(repo-icon): fall back to a lucide icon when an image icon fails to load Private-mode GitHub Enterprise avatars need a logged-in web session, so the stored avatar URL fails to load and the image branch rendered blank space. The lucide and missing-icon paths already fall back to Folder; the image branch had no equivalent. Track the failed src in state so a repo switched to a different icon still renders that icon instead of staying on the fallback. Fixes stablyai#11211 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(repo-icon): assert the specific fallback icon instead of any svg The fallback and unchanged-icon tests only checked that an svg rendered, so they passed even if the wrong icon came back. Assert the lucide class name, and cover an unknown lucide name falling back to Folder. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: prioritize filenames in new-tab file results * fix: preserve root separator in filename-first paths * refactor: use native file path tooltips * fix: position file path tooltips * fix: use the native OS tooltip for new-tab file paths Co-authored-by: Orca <help@stably.ai> * fix: show new-tab file paths in a system-style tooltip Co-authored-by: Orca <help@stably.ai> * fix: anchor new-tab path tooltip to the cursor Co-authored-by: Orca <help@stably.ai> * fix: tighten cursor tooltip to file rows and design tokens Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai>
* fix: show full paths in quick open results * refactor: use native file path tooltips * fix: position file path tooltips * refactor: share the cursor path tooltip with quick open Co-authored-by: Orca <help@stably.ai> * fix: let path tooltips run wider before wrapping Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai>
…blyai#12828) * perf: bound repeated watcher, vault, and terminal work * fix(terminal): preserve redraw recovery while bounding fit retries * fix(watcher): retain structural fallback after crash fuse * fix(ai-vault): preserve forced scan budget
* fix(mobile): choose host for new workspace * fix(mobile): close stale workspace host picker * fix(mobile): disambiguate workspace host choices * fix(mobile): keep host endpoint paths private * fix(mobile): redact invalid host endpoints * fix(mobile): handle opaque host endpoints * fix(mobile): announce host picker options * fix(mobile): harden workspace host picker * fix(mobile): preserve host through workspace creation
…tablyai#12816) Reverts the classification change and keeps behavior at base. cursor-agent's native OSC title is the bare literal "Cursor Agent" and never carries a status word, so it names the agent without proving one is present. The title tracker drops it live, so main records it only when the stale-working timer strips the spinner off the synthesized "⠋ Cursor Agent" — and that fires both when Cursor parks idle and when cursor-agent exited and the shell reclaimed the pane. The two states are observationally identical: same title, same null foreground read. Classifying it as an agent therefore removes a refusal rather than adding evidence. Guarded sends auto-submit Enter, so the false positive types into the user's shell. A null foreground is also not "unreadable" on the default local provider, which returns null when the pty is gone. hasPty, probePtyLiveness, hasChildProcesses and inspectProcess were each checked as corroborating signals; none separates alive-with-agent from alive-with-shell when the foreground read is unavailable. Tests pin every no-evidence branch fail-closed and document the mechanism, so both attempted fixes fail loudly if reintroduced. Real gap tracked in stablyai#12946.
* fix(terminal): use provider-native session titles * refactor(terminal): source session names from AI Vault * fix(tabs): harden AI Vault title sync
…evel Git base for on-prem Server (STA-3494) (stablyai#12832) * fix(azure-devops): retry with -preview api-version and keep project-level Git base for on-prem Server (STA-3494) Azure DevOps Server rejects api-version=7.1 with 400 VssInvalidPreviewVersionException unless the -preview suffix is supplied, so auth and every Git endpoint failed. Retry once with -preview on that rejection and remember the requirement per origin. Also stop letting a same-origin ORCA_AZURE_DEVOPS_API_BASE_URL (collection-level, needed only for the connectionData auth probe) override the project-level base derived from the remote for Git endpoints; cross-origin (proxy) overrides keep working. * fix(azure-devops): constrain preview retry and base override
…3505) (stablyai#12833) * fix(permissions): surface macOS silent Local Network denial with diagnostic and workaround (STA-3505) On macOS 27 beta, NECP silently denies Orca's whole process tree Local Network access: no prompt fires, the app never appears in System Settings, and terminal child processes fail with EHOSTUNREACH. The Settings trigger swallowed the probe's socket error and reported 'unknown' + a 'Permission request sent' toast, indistinguishable from success. Classify the mDNS probe outcome (EHOSTUNREACH/EHOSTDOWN -> denied, clean send -> granted, else unknown), remember the verdict for the status chip, and render an inline diagnostic with the documented NECP re-evaluation workaround when denial is detected. * fix(permissions): avoid false Local Network grants * fix(permissions): use standard Local Network request flow * feat(permissions): add local network connection test * fix(permissions): nest local network connection test * fix(permissions): collapse connection test by default * fix(permissions): emphasize connection test action * fix(permissions): restore outlined connection action
…ever lands (stablyai#12950) * fix(renderer): contain corrupt lazy chunks when the recovery reload never lands 9 react-error-boundary crash reports across v1.4.171-1.4.175 (macOS, Linux, Windows) all end the same way: a corrupt lazy chunk fails to import, recovery requests a reload, the reload never lands, and loadLazyWithRetry re-throws the raw SyntaxError/TypeError. RecoverableRenderErrorBoundary only suppresses LazyChunkLoadError, so the raw error files a user-facing crash report. LazyChunkLoadError was unreachable in production. Its precondition is a guard written by a *different* document ('reload-landed'), but the finally block clears that guard before the throw, so the only path that could construct it never ran. Confirmed by the shipped bundles: 16/16 lazy_chunk_reload_vetoed breadcrumbs carry outcome=never-landed, zero carry any other outcome, and no bundle contains a boundary-degraded breadcrumb. Route every exhausted-recovery path through exhaustedRecoveryFailure() so an attempted-and-failed recovery yields a LazyChunkLoadError the boundary can contain, and record a lazy_chunk_recovery_exhausted breadcrumb carrying the call site, the real chunk error, and the outcome. Deliberately unchanged: when recovery is never *attempted* (no window/SSR, blocked sessionStorage, guard write failure) the raw error is still thrown so normal crash reporting is unaffected. Only isKnownDynamicImportFailure matches are contained, so module logic bugs keep reporting. * perf(renderer): trim redundant work on the lazy-chunk failure path Hoist the dynamic-import message patterns to module scope so classification stops allocating seven RegExp objects per call, thread the already-computed classification into exhaustedRecoveryFailure so the guard-not-landed path does not re-run it, and bound recordedExhaustionKeys the way the breadcrumb and renderer-error key stores are bounded, since error.name is library-controlled. Failure path only; the success path is unchanged. * refactor(renderer): remove a transposition trap on the lazy-chunk failure path exhaustedRecoveryFailure ended in two adjacent booleans with opposite consequences: transposing them would have returned the raw SyntaxError and silently restored the crash this branch fixes, with no test able to catch it (the only call site passed true for both). The isChunkFailure parameter saved one regex scan on a path that only runs after a 10s reload wait, so drop it. Also evict recordedExhaustionKeys oldest-first instead of clearing wholesale, matching the breadcrumb and renderer-error key stores the comment cites, so an overflow cannot re-open the entire set to a repeat burst. * test(renderer): cover the exhaustion dedupe bound The bound had no coverage, unlike the crash-breadcrumb store it mirrors, so a refactor could drop it or invert the comparison with every test still green. Drive 200 distinct error names through the contained path and assert the set stays capped. Also move MAX_RECORDED_EXHAUSTION_KEYS above the comment that describes the set, not between them. * test(renderer): pin the exhaustion eviction policy, not just the cap The bound test asserted only the size cap, so it stayed green under the old wholesale clear(): after 200 distinct keys a clear-on-overflow leaves 72, which still satisfies the cap. Replay a key that oldest-first eviction retains and assert it emits no second breadcrumb — that fails under clear(), which would otherwise silently re-open the whole set to a repeat burst and flush the 30-entry ring the dedupe exists to protect. * refactor(renderer): cut the breadcrumb machinery down to the actual fix The lazy_chunk_recovery_exhausted breadcrumb was an optional addition that paid for itself in complexity and nothing else: it needed a dedupe set to avoid flushing the 30-entry ring, the set needed a bound because error.name is library-controlled, the bound needed an oldest-first eviction policy, and that needed two more tests plus a boolean parameter that review flagged as a transposition trap. On the dominant never-landed path it did not even fire, because lazy_chunk_reload_vetoed already records the same reloadKey, message and outcome. Drop it. Observability on every path returns to the main baseline, and the fix is what it always was: name an exhausted recovery so the boundary can contain it. Also revert the unrelated regex hoist -- its only caller is the failure path, so the saved allocations are noise. * Verify ordinary errors bypass lazy chunk containment Add test ensuring module evaluation bugs still surface despite never-landed reload attempts. Clarify containment scope: recovery only applies to known dynamic-import failures, not ordinary errors.
…tablyai#12945) * fix(terminal): per-pane WebGL attach latch and fit-anchored reattach The attach-failure latch was module-global: one pane's failed WebGL context creation stranded every other pane on the DOM renderer — whose cell metrics and rasterization differ visibly (bolder, ~5% wider text) — until the next recovery boundary. The latch is now per-pane. A successful fit additionally offers an event-anchored reattach: a pane that is WebGL-eligible but addon-less (late mount that missed the coalesced reveal repaint, stale fallback) regains WebGL the moment it proves measurable, so a user resize now heals a DOM-stuck pane instead of leaving it. Failed attaches still retry only at recovery boundaries. A webgl-fit-attach diagnostic records each late attach so the stuck state is finally visible in telemetry. Client-size fit helpers move to pane-fit-client-size.ts to stay under the pane-fit.ts line cap. * fix(terminal): refit onto WebGL cell metrics after a fit-anchored attach The fit that triggers the reattach measures DOM cell metrics; WebGL floors the device cell width, so healing a DOM-stuck pane left it on the DOM-derived column count — an unpainted right gutter and a PTY narrower than the pane. Refit on the next frame, mirroring the dispose-side refreshDimensions. Also cover the real wiring: the existing fit-anchored tests drive the signal module directly, so they stay green even if safeFit stops calling it. The new suite goes through safeFit, which is also what proves the import-time hook registration works. * test(terminal): gate the fit-anchored refit frame on a deferred rAF The existing suites stub requestAnimationFrame synchronously, so the window in which the refit handle is live never exists there — nothing covered the two properties that window has to hold. With a deferred stub: - disposing the pane cancels the refit, so it cannot fit (and forward a PTY resize for) an already-disposed terminal; - the deferred fit re-enters the hook exactly once and settles, so there is no fit -> attach -> fit cycle. Both fail against mutated production code (handle kept out of the cancellable slot; addon-less guard dropped).
innocarpe
force-pushed
the
fix/project-rows-fork-upstream-slug
branch
from
August 6, 2026 23:20
be539cd to
88bc31d
Compare
stablyai#12955) This reverts commit ae1ed5e.
Project cards often reference the public upstream repo while the open clone's origin is a personal fork. Map the parent slug to the same Repo so selected-repo filters no longer hide every board row. Preserves origin-based getRepoSlug identity for non-project callers. Fixes stablyai#12647
Resolve the referenced call to a nonexistent `resolveRepoUpstreamSlug` and match the persisted `repo.upstream` parent instead of issuing an extra `github.repoUpstream` RPC per repo on every index build — that lookup shells out to `gh repo view` for non-forks, so it would have gated the Projects tab on N network calls. `repo.upstream` is already resolved at repo-add time and backfilled at startup, so the fix costs no IPC. Origin matches take precedence over upstream ones so an open clone of the upstream repo itself is never made ambiguous by someone's fork of it. Also covers the two surfaces the origin-only match broke alongside the desktop table: mobile's project row matcher and the store-slice row-mutation routing.
Round-1 review fixes on top of the upstream-slug index: - Apply origin-over-upstream precedence among *selected* repos instead of globally. An open-but-unselected clone of the upstream repo was shadowing the selected fork, so stablyai#12647 still reproduced for anyone holding both — and repo selection collapses to one repo per project key, which is exactly that case. - Scope a fork's upstream identity key to the fork's own origin host. Persistence strips upstream.host, so GHES forks never matched their own rows and a GHES fork's parent could bind a same-named github.com row.
…olved Round-2 review fix. `githubHostFromIdentityKey` cannot tell "origin resolved to github.com" from "origin did not resolve" — both yield no host. A GHES fork whose slug resolution had failed (auth lapse, unreachable runtime) therefore landed in the github.com namespace, so an unrelated public Project row matched it and Start work opened the wrong clone on the wrong server. Require a resolved origin before indexing the upstream alias: it is the only host evidence there is, and a repo with an unresolved origin was already absent from the origin index, so nothing is lost that origin matching had.
`sanitizeRepoUpstream` kept only `{owner, repo}`, so a fork's parent lost the
server it lives on every time the record round-tripped through disk.
That forced the Project row matcher to re-infer the host from `origin`. The
inference is right for an API-resolved fork parent — `getRepoUpstream` stamps
`origin.host` there precisely because "a fork parent lives on the same server as
the fork". It is wrong for the other branch: a local `upstream` remote carries
its own host, so a github.com clone with a GHES `upstream` remote was indexed
into the github.com namespace, where an unrelated same-owner/name public repo
could claim it and Start work would open the wrong clone.
Keeping the host removes the guess. Absent stays absent, so records written
before this hydrate unchanged and the origin-derived fallback still covers them.
Also fixes the avatar for rehydrated GHES forks, which resolved against
github.com for the same reason.
Persistence now keeps non-empty upstream.host; originIdentityKey remains the host fallback for older records without one (CodeRabbit nit).
Move the failure-retry setTimeout into its own effect so cleanup always clears it. Scheduling from the async buildIndex then-handler failed the react-doctor effect-needs-cleanup gate in static analysis.
innocarpe
force-pushed
the
fix/project-rows-fork-upstream-slug
branch
from
August 6, 2026 23:42
88bc31d to
52298d8
Compare
…e twin comment Two follow-ups on 52298d8 and 2f89c20: - Cover the retry timer both ways: a failed resolution still re-resolves after the TTL and recovers the match, and the pending timer is gone after unmount. The second fails if the timer moves back into the async then-handler, so the property is guarded by more than the lint rule. - The mobile matcher's comment made the same stale "persistence strips upstream.host" claim that 2f89c20 fixed on the renderer side.
…after teardown CI shard `tests node 24 6/16` failed with 10 unhandled `ReferenceError: window is not defined` traced to this file. The tests mounted hooks without unmounting, so React scheduler work flushed after the DOM environment was disposed. All assertions passed; the shard failed on the unhandled errors alone. `cleanup()` after each test unmounts the trees. Does not reproduce locally in isolation — it needs CI's worker pooling and file ordering.
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
Description Index each open repo under both origin and upstream parent slugs so GitHub Project rows for upstream issues still match a local fork clone. ## Focused fix - In scope:
repo-slug-indexalso resolvesgithub.repoUpstream- Out of scope: changinggetRepoSlugorigNote
innocarpe/orcamainuntil the upstream PR is merged.