Skip to content

Sync upstream 01bcc57ff (conflicts) - #5

Open
WrittenByAI wants to merge 417 commits into
mainfrom
sync/upstream-01bcc57ff
Open

WrittenByAI wants to merge 417 commits into
mainfrom
sync/upstream-01bcc57ff

Conversation

@WrittenByAI

Copy link
Copy Markdown
Owner

Upstream 01bcc57ff merged into the fork with conflicts.

Unresolved markers remain in: package.json, pnpm-lock.yaml, src/renderer/src/i18n/locales/en.json, src/renderer/src/i18n/locales/es.json, src/renderer/src/i18n/locales/ja.json, src/renderer/src/i18n/locales/ko.json, src/renderer/src/i18n/locales/zh.json

Fork files upstream also touched: package.json, pnpm-lock.yaml, src/main/github/client.ts, src/main/ipc/github.test.ts, src/main/ipc/github.ts, src/main/runtime/orca-runtime.ts, src/main/runtime/rpc/methods/github.test.ts, src/main/runtime/rpc/methods/github.ts, src/main/runtime/runtime-rpc.ts, src/preload/api-types.ts, src/preload/index.ts, src/renderer/src/assets/rich-markdown-editor.css, src/renderer/src/components/editor/EditorContent.tsx, src/renderer/src/components/editor/EditorPanelShell.tsx, src/renderer/src/components/right-sidebar/SourceControl.tsx, src/renderer/src/i18n/locales/en.json, src/renderer/src/i18n/locales/es.json, src/renderer/src/i18n/locales/ja.json, src/renderer/src/i18n/locales/ko.json, src/renderer/src/i18n/locales/zh.json, src/renderer/src/lib/language-detect.ts, src/renderer/src/store/slices/diffComments.test.ts, src/renderer/src/store/slices/diffComments.ts, src/renderer/src/web/web-preload-api.test.ts, src/renderer/src/web/web-preload-api.ts, src/shared/types.ts


Upstream merge conflict report — 01bcc57ff

  • Merge: upstream origin/main @ 01bcc57ff ("perf(mobile): gate dictation
    setup progress polling on foreground + single-flight (perf(mobile): gate dictation setup progress polling on foreground + single-flight stablyai/orca#9892)") into fork
    HEAD @ 0e6048b34.
  • Conflicted files (7): package.json, pnpm-lock.yaml, and the five locale
    files src/renderer/src/i18n/locales/{en,es,ja,ko,zh}.json.
  • Outcome: all 7 files resolved; all conflict markers removed. No
    git add/commit/merge run. git diff --check is clean; all five locale
    files and package.json parse as valid JSON (jq empty).

package.json

  • Region: the pnpm.overrides map.
  • Base: { "monaco-editor>dompurify": "3.4.11" }.
  • Upstream changed: added "@modelcontextprotocol/sdk>@hono/node-server": "2.0.10"
    and bumped "monaco-editor>dompurify" 3.4.11 → 3.4.12.
  • Fork changed: added "@tiptap/extension-highlight": "3.22.5" — a fork-only
    pin required by the tldraw canvas feature (tldraw's rich-text path drags in
    tiptap; see the "Грабли форка" note in CLAUDE.md).
  • Why they collided: both sides edited the same (small) overrides object.
  • Resolution: union of all three keys, taking upstream's newer dompurify
    (3.4.12) and keeping the fork's tiptap pin:
    "@modelcontextprotocol/sdk>@hono/node-server": "2.0.10",
    "monaco-editor>dompurify": "3.4.12",
    "@tiptap/extension-highlight": "3.22.5"
    
  • Human must decide: nothing. Confident.

pnpm-lock.yaml (5 conflict regions)

Only 5 regions conflicted; everything else auto-merged. Notably, upstream's
dompurify@3.4.12 and @hono/node-server@2.0.10 package and snapshot entries
auto-merged in cleanly (verified present at lines 4023/10151 and 1009/7295), so
the combined overrides above are backed by real resolutions.

1. overrides: block (top of file)

Mirror of the package.json conflict. Resolved to the same union, keys/values
matching package.json exactly (pnpm compares the lock's overrides against
pnpm.overrides):

'@modelcontextprotocol/sdk>@hono/node-server': 2.0.10
monaco-editor>dompurify: 3.4.12
'@tiptap/extension-highlight': 3.22.5

2 & 4. idb / ieee754 (packages + snapshots sections)

  • Fork side: entries for idb@7.1.1 and ieee754@1.2.1.
  • Upstream side: neither (upstream has no tldraw and dropped buffer@5.7.1).
  • Investigation:
    • idb@7.1.1 is a real dependency of the fork's @tldraw/editor@5.2.4
      (referenced at the auto-merged snapshot line ~8988) and of tldraw@5.2.4
      itself → kept.
    • ieee754@1.2.1 was, in the fork, referenced only by buffer@5.7.1
      (optional: true). Upstream removed buffer@5.7.1 entirely, and that
      removal auto-merged in — so in the merged tree nothing references ieee754
      anymore (no buffer@5.7.1, no ieee754: 1.2.1 reference remain). Keeping it
      would leave a dangling/orphan lock entry → dropped (follows upstream).
  • Resolution: keep idb@7.1.1 in both sections, drop ieee754@1.2.1 from both.

3 & 5. tldraw@5.2.4 / tldts-core (packages + snapshots sections)

  • Fork side: the full tldraw@5.2.4 package + snapshot (the fork's core
    feature) plus tldts-core@7.0.30.
  • Upstream side: tldts-core@7.4.9 (upstream bumped the unrelated tldts
    URL-parsing lib 7.0.30 → 7.4.9).
  • Investigation: tldts@7.4.9 (common, auto-merged) depends on
    tldts-core: 7.4.9; nothing references 7.0.30 outside the conflict → the
    fork's 7.0.30 was stale.
  • Resolution: keep tldraw@5.2.4 (package + snapshot, unchanged), take
    upstream's tldts-core@7.4.9 in both sections, drop 7.0.30.

Post-resolution structural checks (all pass): no leftover 7.0.30,
dompurify 3.4.11, or buffer@5.7.1; idb@7.1.1 present in both sections;
ieee754 fully absent; tldts-core unified at 7.4.9; overrides match
package.json.

  • Human/CI must decide: the lockfile was hand-merged, not regenerated.
    The analysis above shows an internally consistent graph, but I could not run
    pnpm install in this sandbox to get a green check. CI's
    pnpm install --frozen-lockfile is the final authority — if it reports the
    lock as out of date, re-run pnpm install --lockfile-only on the merged
    package.json and commit the regenerated lock.

Locale files — en.json, es.json, ja.json, ko.json, zh.json

All five files have the same two conflict regions with identical structure
(only the translated string values differ). Both are clean add/add conflicts:
distinct sibling keys added to the same JSON object by each side.

Region A (~line 9772–9778): a source-control string bucket

  • Fork added: "5c951f3e68": "Canvas", "897d7c622d": "Canvas" (canvas
    labels for the tldraw feature; "Canvas" is intentionally untranslated in every
    locale).
  • Upstream added: "97e7124eac" ("Could not refresh Source Control…") and
    "a4e93c21d7" ("Current branch: {{value0}}"), each translated per locale.
  • Resolution: kept all four keys, fork's two first then upstream's two,
    comma-joined; no trailing comma before the closing brace.

Region B (~line 12344–12404): tldraw blocks vs. editor.save.failure

  • Fork added: four sibling objects — TldrawIssueDialog,
    TldrawSelectionOverlay, TldrawViewer, and a nested tldraw.issue.image
    bucket (all fork-feature strings, carried as English fallback in every locale).

  • Upstream added: a nested editor.save.failure.notice."8c59ce5075"
    ("Failed to save the file. Please try again.") — also English in every locale
    in this diff.

  • Why they collided: both sides appended keys at the same position, and the
    fork's final key was deeply nested (tldraw → issue → image → …) while
    upstream's was deeply nested (editor → save → failure → notice → …), so the
    shared closing braces after the marker had to be re-associated.

  • Resolution: kept the fork's tldraw blocks, closed the tldraw.issue.image
    nesting (}}},), then appended upstream's full editor block. Verified brace
    balance and JSON validity for all five files.

  • Human must decide: nothing on structure. Optional follow-up: the fork's
    tldraw strings and upstream's editor.save string are English placeholders in
    the non-English locales — real translations can be added later via the normal
    localization pass (and the zh value-override script), but that is unrelated to
    this merge.


Verdict

All 7 conflicted files resolved confidently. The only item warranting a human/CI
touch is the hand-merged pnpm-lock.yaml, which should be confirmed by
pnpm install --frozen-lockfile in CI (standard for any lockfile merge).

Jinwoo-H and others added 30 commits July 17, 2026 20:09
Co-authored-by: Orca <help@stably.ai>
…inals (stablyai#7742 sub-issue B) (stablyai#7836)

* fix(pty): stop daemon reattach/kill race from orphaning restored terminals

Two cold-start defects let a PTY kill silently orphan a live daemon session
after a renderer/app restart — the PTY-orphaning-on-restart half of stablyai#7742:

- DaemonPtyAdapter.shutdown() was the only session op that skipped
  ensureConnected(). The daemon client connects lazily, so a kill arriving
  before the first spawn (startup sleep/hibernation sweeps, closing a
  restored tab) threw "DaemonProtocolError: Not connected" against a
  HEALTHY daemon; the renderer swallows the rejection and the live session
  survives detached. Fix: connect first, like every other session op.

- pty:kill had no startup barrier while pty:spawn did. A kill issued before
  initDaemonPtyProvider swapped the provider resolved against the pre-daemon
  LocalPtyProvider, whose shutdown() silently no-ops on daemon ids; the
  handler then reported success and sent a synthetic pty:exit, permanently
  clearing the persisted tab->ptyId binding so the session never reattaches.
  Fix: pty:kill awaits the same getLocalPtyStartupPromise gate as spawn.

Both reproduced red-first (real DaemonServer socket rig for the adapter;
deferred-barrier IPC rig for the handler) and pass after the fix.

Scope: this restores the reattach/teardown invariant. It does NOT address
the renderer crash trigger or the daemon-process-death path that produces
the Windows PowerShell FailFast symptom (separate follow-ups).

* fix(pty): gate runtime shutdown on daemon startup

* fix(pty): keep shutdown independent of hook startup
…pretty-print) (stablyai#9290)

Co-authored-by: Orca <help@stably.ai>
…chunk 64KB concat) (stablyai#9291)

Co-authored-by: Orca <help@stably.ai>
… (crash 99657ab1) (stablyai#9315)

* fix(sidebar): guard every worktree sort against undefined displayName (crash 99657ab1)

Worktree.displayName is typed non-optional but arrives undefined at runtime for
persisted/discovered worktrees (crash 99657ab1). PR stablyai#8683 fixed the kanban board;
this extends the same guard to the five other sort sites that share those worktree
objects — including the Cmd+J switcher (order-empty-query-worktrees) — via one
shared compareWorktreeDisplayName helper in lib/. Behavior-neutral when names are
present; a missing name now sorts as '' instead of throwing.

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

* docs(agents): add over-commenting anti-pattern example to the comment rule

Anchors the existing 'document the why, briefly' rule with a concrete
before/after (the worktree-displayName guard) so agents stop dumping the
crash id, mechanism, file location, and every call site into a comment.

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

---------

Co-authored-by: Orca <help@stably.ai>
* fix(terminal): bypass rejected macOS login sessions

* fix(terminal): bound macOS login preflight

* test(terminal): isolate macOS login preflight

* fix(terminal): avoid blocking macOS login preflight

* fix(terminal): cancel pending PTY preparation

* fix(terminal): cancel preparation on daemon shutdown
Groups 1 and 2 are full; point readers at the new third-group QR in EN/ZH README.
Scratch investigation doc is no longer needed on main.
…irst hover (stablyai#9320)

Invalidate xterm's linkifier hover cache (throttled) when output streams into a visible pane, so a URL printed under a stationary pointer underlines and becomes Cmd/Ctrl+clickable on the next pointer move instead of after several clicks. Skips while a link is hovered (no flicker) and re-arms so the reset is never dropped mid-hover. Reuses the visibility-resume primitive (stablyai#9061).
…stablyai#9384)

On a fork checkout (origin=fork, upstream=parent) PR details failed to load because getOwnerRepo() resolved only the origin remote while GitHub PRs live on the upstream parent. Make getOwnerRepo() prefer upstream (mirroring getIssueOwnerRepo), and pin the call sites that genuinely need the checkout's own origin identity (getRepoSlug, getRepoUpstream, createGitHubPullRequest, resolvePrWorkItemSource) to the origin-only primitive.

Lands the fix community-identified in stablyai#7332 and hardened in stablyai#7513.

Closes stablyai#7331

Co-authored-by: fsdwen <1214772+fsdwen@users.noreply.github.com>
Co-authored-by: brennanb2025 <79079362+brennanb2025@users.noreply.github.com>
… bar (stablyai#5399)

Derive the collapsed session limit label from resetsAt and tick it live via a shared boundary-scheduled countdown clock (no polling). Combines stablyai#6252/stablyai#6585.

Fixes stablyai#5399.

Co-authored-by: gatsby74 <166927047+gatsby74@users.noreply.github.com>
…re/which spawn) (stablyai#9297)

Resolve agent CLIs against PATH with fs instead of spawning one where/which subprocess per candidate (~34 at startup). Same found/not-found result (same env/PATH, faithful which/where semantics); subprocess spawns 34->0.

Fixes stablyai#9297.
Co-authored-by: Orca <help@stably.ai>
* fix(chat): preserve drafts through reconnects

* fix(terminal): bound stale handle reconnect polling

* refactor(chat): extract native view props

* fix(chat): harden reconnect lifecycle

* fix(terminal): replace stale reconnect identities atomically

* fix(terminal): recover mirrors after reconnect polling

* docs(reliability): record merged reconnect coverage

* fix(terminal): preserve reconnect lifecycle state

* fix(terminal): keep reconnect polling truly bounded
* Guard add-review-note chord to prevent remount (product B)

- Scoped guards consume chord at composer level to prevent remount and leakage
- Handle OS key-repeat: ignore when no draft, consume when one is mounted
- Clear stale block keys in markdown preview when content renumbers
- Flush pending selection before reading targets to fix timing races

* fix(editor): repair add-review-note guard tests and close remaining chord gaps

- Update the product-B guard tests to the Mod+Shift+A default binding
  (stablyai#9257 retired Mod+Alt+N as AltGr-unsafe); they asserted the old chord,
  so seven landed red and the rich-editor repeat test passed vacuously.
- Monaco: recompute the annotation target from the live selection at
  keydown instead of the render-lagged ref, so a chord right after a drag
  cannot open on the previous selection or miss a fresh one.
- Preview: key the stale-block-key cleanup on renderedContent (the DOM
  the block keys live in), which can lag content during external-edit
  section preservation.

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

* fix(editor): mirror shortcut-guard refs in effects instead of render body

CodeRabbit on stablyai#9412: render-body ref writes can leak from a render pass
React replays and discards. Move the state->ref mirrors for
commentPopoverRef / shouldShowMarkdownAnnotationsRef (MonacoEditor) and
activeAnnotationBlockKeyRef (MarkdownPreview) into effects; same-tick
keydown paths keep their eager event-handler writes.

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

---------

Co-authored-by: Orca <help@stably.ai>
…tablyai#9420)

Raise the floating terminal panel from z-30 to z-45 so it sits above
notification cards (z-40) but below the modal layer (z-50), preventing
notifications from burying the terminal. Raise the toggle button to z-46
to keep it clickable where it overlaps the panel.
* feat(ssh): download folders from remote explorer

* fix(ssh): harden remote folder downloads

* Add missing getRepo stub to worktree cwd test mock

Restoring headless mobile tabs looks up the repo for the active
worktree id; the mock lacked getRepo, so the test only passed
incidentally. Add it explicitly and return undefined since wt-1
is a worktree id, not a registered repo.

* Enable SSH folder downloads, gated for system-SSH connections

- Folder downloads require SFTP, unavailable on system SSH (which
  offers only raw file operations). Add supportsFolderDownload flag
  to gate the feature in the UI layer.
- Reject symlinks at directory-entry level, preventing tree escapes
  and eliminating unnecessary stat calls.
- Check abort signal before opening dialog for better responsiveness
  when renderer closes.
- Log cleanup errors without re-throwing to preserve underlying
  transfer failures.

* Gate SSH folder downloads to SFTP-capable connections

Enforce fail-closed gating and add Windows path traversal validation to
ensure downloads are only available when explicitly supported and safe.

* Gate SSH folder downloads to SFTP-capable connections

Enforce fail-closed gating and add Windows path traversal validation to
ensure downloads are only available when explicitly supported and safe.

* fix(ssh): keep provider types under max-lines after main merge

Move FolderDownloadOptions next to the SFTP download implementation and
narrow IFilesystemProvider.downloadFolder options to AbortSignal only so
types.ts stays within the 300-line oxlint budget when merged with main.

---------

Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
…#9070)

Only offer 'Proceed Anyway (Unsafe)' if the connected remote runtime
advertises browser.certificate-trust.v1 support. Older runtimes cannot
honor the request and would fail silently, creating a false affordance.

Centralize certificate error normalization to prevent divergence
between main and renderer certificate matching, and unify URL redaction
for Kagi session token stripping across load-error paths.
…lyai#5785)

* Allow hiding Orca Mobile sidebar button once a device is paired

Introduce a shared external store (`paired-mobile-devices.ts`) to cache
and synchronize the paired mobile device list across the sidebar,
Settings pane, and Mobile page. This prevents redundant backend IPC
requests when multiple surfaces mount simultaneously.

Using this shared pairing status, display an inline "Hide from sidebar"
control next to "Orca Mobile" in the navigation sidebar once a device
has been paired. This allows users to easily declutter their sidebar
after completing setup.

* Handle device load errors with recovery on focus/reconnect

- Add error flag to distinguish failed device loads from empty device lists
- Recover from transient startup IPC failures by retrying on window focus or online
- Validate revoke device success before routing or showing success toast
- Only show hide control after first device is actually paired
- Use snapshot getter to avoid manual device ref tracking in MobilePane

* Handle device load errors with recovery on focus/reconnect

- Shared module-level listeners for failed load recovery reduce IPC spam
  compared to per-consumer event listeners on every focus/online event.
- Optimistically remove revoked devices if post-revoke reload fails,
  keeping success toast and intro routing correct.
- Prevent repo/branch label overflow with CSS text truncation.
- Fix HTML entity rendering: use plain spaces instead of &nbsp; in
  translations so React text nodes render them correctly.
- Add comprehensive tests for the paired devices hook covering load,
  navigation, revoke, and error recovery paths.
…9437)

Resolve Microsoft Store PowerShell App Execution Aliases to their real package executable before ConPTY spawn, while preserving explicit PowerShell choices and the existing fail-safe fallback chain.
…tablyai#9398)

* fix(composer): show full worktree path on hover in Run on dropdown

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

* refactor(composer): use native title tooltip for Run on paths

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

* fix(composer): reliable native-style tooltip for Run on paths

The native title attribute won't re-trigger between adjacent rows in
Chromium, so the tooltip flickered/dropped as you moved down the list.
Use the shared (controlled) Tooltip instead, styled compact + arrowless
to read like an OS tooltip; the app-level provider's skip-delay makes
row-to-row hovers reliable.

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

* fix(composer): stop Run on path tooltip wedging shut

Radix's uncontrolled hover state could flash the tooltip open then
wedge it closed inside the cmdk list (shared pointer-transit state
getting stuck), so re-hovering never reopened it. Drive open state
with a controlled hover-intent handler (open after a delay, close
after a short grace) so re-entry always reopens and transient
enter/leave thrash debounces to a stable state.

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

* fix(composer): rebuild Run on path tooltip as a portal (no loop/wedge)

Both the native title and Radix Tooltip (controlled or not) misbehaved
inside the cmdk list: flashing, wedging shut, or opening/closing in a
loop. Replace with a self-contained tooltip that renders a fixed,
pointer-events-none portal on document.body and only changes state on
pointer enter/leave/down (never pointermove). Opening it cannot reflow
the list or become the hover target, so it structurally cannot create
the enter/leave feedback loop.

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

* fix(composer): trigger Run on path tooltip on the path line, always below

- Scope the hover trigger to the truncated path line itself (not the
  whole row), so only hovering the path reveals it.
- Always position the tooltip below the hovered line for predictable
  placement (was flipping above near the viewport bottom).

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

* fix(composer): anchor Run on path tooltip under the path line

The old placement anchored to the window's right half when the trigger
sat past viewport-center, which shoved the tooltip to the far right edge
of the window, disconnected from the dropdown. Always left-align it
directly under the hovered path line and cap max-width to the remaining
viewport width so long paths wrap instead of overflowing.

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

---------

Co-authored-by: Orca <help@stably.ai>
brennanb2025 and others added 30 commits July 22, 2026 13:12
…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>
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>
…tablyai#10000)

The workspace-options filter row used the Workflow icon while the
Automations nav item and page use CalendarClock. Match them so the
filter clearly maps to automation-created workspaces.
… a remote runtime is active (stablyai#6628)

* fix(worktrees): refresh local worktrees in the sidebar while a remote runtime is active

When a remote runtime is active, a local `worktrees:changed` event for an
unbound repo was dropped by the renderer guard in useIpcEvents. Worktrees
created outside Orca for that repo (e.g. `orca worktree create` from a CLI or
automation flow) therefore stayed invisible in the sidebar until an app
restart, even though their sessions were already running.

The guard existed because an unbound repo's list fetch routes to the active
runtime (settingsForKnownRepoOwner's unbound fall-through), so refreshing with
local worktree ids could query — and purge against — the remote host.

Instead of dropping the event, pin the refresh to the local host
(forceLocalOwner): fetch the worktree list against the local owner and merge
additively. The merge is host-scoped and the deletion-purge is skipped on this
path, so it only ever adds local-host worktrees and never overwrites the active
runtime's worktree state. A genuinely-removed local worktree is reclaimed by
the next unguarded full refresh.

* test(e2e): regression — CLI-created worktree visible while a remote runtime is active

Drives the real `orca worktree create` path: the CLI RuntimeClient calls
`worktree.create` over the app's socket, registering a managed worktree and
firing the `worktrees:changed` IPC the renderer listens for. Stages a remote
runtime as active by injecting `activeRuntimeEnvironmentId` into the renderer
store, so no real remote host is needed. Fails on the prior behavior (the
worktree never appears while a runtime is active) and passes with this fix.

* fix(worktrees): pin local lineage refresh during runtime activity

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

* review: trim comments to house style, normalize queue coalescing to booleans

* review: sweep rename-grace expiry before early returns in worktrees:changed handler

* review: document accepted workspace-space gap, drop imprecise 'additive' wording

* fix(worktrees): route duplicate local repo events locally

* fix(worktrees): tag local worktree events at origin, gate purge skip on runtime overlap

* test: pin origin-based forceLocalOwner with a no-runtime local event assertion

---------

Co-authored-by: brennanb2025 <brennankbenson@gmail.com>
Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
…tablyai#9996)

* feat(agent-status): show question glyph for needs-you state everywhere

Replace the amber attention dot with the dashboard's MessageCircleQuestion
chat glyph for the waiting/permission "needs you" state across all
surfaces: the agent dashboard cards, in-app dashboard rows, the sidebar's
Agent Dashboard quick-indicator counts, the worktree-level status dot, and
the shared agent-row/terminal-tab indicators.

On the dashboard card the header glyph is suppressed when a question
summary pill is present so "needs you" reads once, not twice.

* test(agent-status): assert amber question glyph, not amber dot

The needs-you unification replaced the amber dot with the amber
MessageCircleQuestion glyph, so update the remaining state assertions in
DashboardAgentRow, WorktreeCardStatusSlot, and TerminalTabLeadingIcon to
match (lucide-message-circle-question + text-amber-500).

* test(agent-status): cover needs-you glyph surfaces
…ablyai#9883)

* perf(web): pause runtime heartbeat while hidden

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

* fix(web): preserve inbound-liveness baseline across a hidden heartbeat re-arm

The visible re-arm rebaselined lastInboundFrameAt=now, so a socket that went
silent while the window was hidden looked freshly-heard-from and its death was
masked for another full idle window (~25s). Move the fresh-connect baseline into
startHeartbeat (the real 'we just connected' moment) and have the visible re-arm
only reset the tick clock + clear an in-flight probe, preserving lastInboundFrameAt
so the next visible tick probes a stale connection promptly and closes if unanswered.

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

---------

Co-authored-by: Orca <help@stably.ai>
…nected (stablyai#9885)

* perf(runtime): idle websocket heartbeat without clients

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

* fix(runtime): probe immediately when the WS heartbeat arms

Arming the heartbeat on the first accepted connection started a fresh interval,
so the first liveness ping was a full interval (~15s) out — a socket that died
right after connecting went unprobed for that window. Run one sweep synchronously
in start() so the first ping goes out at arm time; the seeded socket is pinged
(never reaped on the arm sweep) and reaped on the next tick only if it never pongs.
Tests updated for the earlier first probe.

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

---------

Co-authored-by: Orca <help@stably.ai>
…blyai#9888)

* perf(mobile): coalesce overlapping home requests

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

* fix(mobile): queue a trailing follow-up for triggers during an in-flight read

Single-flight returned the in-flight promise to any trigger that arrived mid-read,
so a distinct refresh requested while a slow read was on the wire was silently
answered by the older response and never re-read the latest state (UI could stay
one refresh cycle stale). Coalesce mid-flight triggers into exactly one trailing
follow-up (latest params win) whose fresh result is delivered to those callers.

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

---------

Co-authored-by: Orca <help@stably.ai>
…ingle-flight (stablyai#9892)

* fix(mobile): gate dictation setup polling

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

* fix(mobile): fence a stale dictation refresh against a newer setPolling intent

An in-flight setup read resolving 'keep polling' after an explicit setPolling(false)
wrote polling=true and rescheduled, resurrecting a poll the caller had just stopped.
Snapshot a pollingRevision when each read starts and only apply its result if no
explicit setPolling superseded it mid-flight — so a late true can't restart a stopped
poll (nor a late false cancel a restart).

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

---------

Co-authored-by: Orca <help@stably.ai>
Resolved by fork-sync agent; see report.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.