Cap the origin-repo and host-auth caches like ownerRepoCache; keep ports
from remote/link URLs so GHES on a non-default port is a distinct
identity; make positional github.com slugs explicit against GH_HOST;
compare work-item sources by host-aware identity key; bail cwd-less
branch lookups when no repository candidate resolved; thread host
through the renderer work-item slug lookup.
Co-authored-by: Orca <help@stably.ai>
diagnoseGhAuth accepts the host a surface needs credentials for, scopes
the account/scope diagnosis to that host, and reports whether gh has any
login there; GhAuthErrorHelp renders host-qualified login/refresh
commands so an unauthenticated GHES host stops masquerading as a
github.com scope problem. Also fixes the mobile paste-intent expectation
for host-carrying parsed links.
Co-authored-by: Orca <help@stably.ai>
Generalize the auth-gated enterprise resolver to any remote and build a
cached hosted-identity family (origin/issue/candidates/source) on top of
it, then migrate every github.com-only consumer: Tasks listing/counting,
branch-to-PR discovery, push targets, fork upstream, issue operations,
Projects, web links, avatars, and PR-link facts. Scope the rate-limit
breaker probe per runtime:host and classify WSL UNC cwds correctly.
Co-authored-by: Orca <help@stably.ai>
- Thread `host` through mobile PR RPC params, IPC work-item lookups, and
RPC schemas so GHES identity survives the renderer/mobile/main boundary
instead of silently falling back to a same-named github.com repo.
- Qualify `--repo`/`-R` args for github.com too (not just GHES), since
gh resolves bare shorthand against a process-level GH_HOST that can
redirect pinned github.com commands.
- Cache `getOriginGitHubApiRepository` to avoid a per-call uncached
`git remote get-url` round trip on connection-backed repos.
- Add a local-fork fallback in `getWorkItemDetails` so PRs living on a
base repo (not visible via the origin slug) still resolve via cwd.
- Centralize the github.com-vs-GHES host predicate in
`isDefaultGitHubHost` so cache keys, quota scoping, and identity
checks can't drift out of sync.
Move GHES/GH_HOST resolution from parsing --hostname/--repo out of gh argv to an explicit options.host passed through ghExecFileAsync, since SSH-backed repos spawn gh with no cwd and argv sniffing couldn't reliably detect the target host. The runner now injects --hostname and qualifies --repo/-R at spawn time from options.host, and rate-limit scoping/guards use the same explicit host instead of inferring it. Also adds a shared githubRepoIdentityKey helper to keep cache/store keys consistent with the new host-aware repository identity.
- Threads GitHub host identity (not just owner/repo) through the client,
work-item-details, issues, and RPC layers so gh commands target the
correct Enterprise server instead of silently falling back to github.com
- Adds a shared github-api-repository helper to resolve/host-qualify repo
identity consistently across REST, GraphQL, and CLI shorthand calls
- Scopes the gh rate-limit breaker and singleton rate-limit snapshot by
host/runtime so a github.com block or probe can't affect GHES or WSL
- Coalesces concurrent host-auth probes and paginates PR file fetching
beyond 100 results
- Propagates `host` through renderer PR caches, checks-panel keys, and
preload IPC types so Enterprise and github.com data never collide
Follow-up polish to #8730: key @keyframes on the resolved row/frames (so a same-row fallback like hover on a jumping-less pet doesn't restart idle), mirror the importer's 60s per-frame cap at render, and trim comments to the 1-2 line convention.
Imported .codex-pet bundles played every animation at a flat 8 fps (~9x too
fast). Mirror Codex's exact per-frame duration tables, render uneven holds as
step-end keyframes, upgrade legacy-persisted pets at render, and add the Codex
mascot's pointer interactions (hover, grab-and-hold on frame 0, horizontal-drag
running). Hardened over several adversarial review rounds: honor explicit fps,
start each row/pet from frame 0, scope drag to its pointer, and guard untrusted
persisted data.
Fixes#8729
Co-authored-by: nasagong <zinho2000@gachon.ac.kr>
#9085 made resolveNativeChatSessionOptionDefaults fall back to the catalog
default model (sonnet) and effort (high) whenever the user had not explicitly
picked one, injecting `--model sonnet --effort high` (and codex `-m`/`-c`
equivalents) onto every agent spawn: composer, worktree activation,
source-control agents, onboarding, work-item launch, floating terminal, and
new-tab launch. That silently overrode each user's own CLI-configured default
model.
Return undefined (spawn the agent exactly as its own CLI would) unless the user
has explicitly selected a model, and carry only explicitly stored option
values. An explicit model selection still applies that model and its catalog
option defaults via resolveAgentSessionOptionLaunch.
* feat(editor): bindable shortcut to add a markdown review note
Adds editor.addReviewNote (default Mod+Alt+N) to the shared keybinding
registry and wires it into all three markdown surfaces: the rich editor
key handler invokes the annotation popover opener, the Monaco editor
installs a keydown listener that opens the composer for the tracked
selection target, and the preview maps the DOM selection to its
annotation block. openAnnotationPopover now prefers the live selection
target over synced state so the shortcut works even before the sync
render lands.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XYtipbTz8N4woN1sxTK1ia
* fix(editor): cover list items and Monaco path for add-review-note shortcut
Tag the preview's list-item annotation blocks with data-annotation-block-key
so the shortcut resolves selections inside li blocks (review feedback), and
extend the e2e spec to drive the Monaco source-editor wiring.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XYtipbTz8N4woN1sxTK1ia
* docs(e2e): explain store-driven view-mode switch in add-review-note spec
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XYtipbTz8N4woN1sxTK1ia
* refactor(editor): extract add-review-note + selection-flush modules to satisfy max-lines after rebase
* refactor(editor): spread key-handler params and extract TOC hook to satisfy max-lines
* test(editor): move add-review-note installer test into its own describe
* fix(editor): pass add-review-note chord through when Monaco cannot act; cover preview surface e2e
* fix(editor): unify add-review-note chord consumption — consume only when a composer opens
* fix(editor): gate list-item annotation block key on composer availability
* fix(editor): require live selection for keyboard add-review-note
* chore: retrigger CI against current main (merge ref built during transient main breakage at 6e91ca6c0)
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* fix(agents): recognize OpenCode native OC | tab titles
OpenCode's native OSC titles use `OC | <task>` without an `opencode`
token, so title classifiers left tabs as Claude/unknown. Map the native
marker (optional mux prefix) to OpenCode identity in both title
classifiers, exclude it from isClaudeAgent, and cover lookalikes plus
stale Claude launch reclaim.
Builds on and supersedes #8590 (credit @gatsby74). Fixes#8478.
* fix(agents): drop renderer import from #8478 shared repro
tsconfig.node includes src/shared tests; importing agent-status pulled
renderer modules outside the node project and failed typecheck. Assert
opencode identity via shared title classifiers only (OpenCode TUI sets
"OpenCode" and `OC | ${title}`).
* fix(runtime): fill browser cert failure map in mobile snapshot fixtures
Main's #9104 reads browserCertificateFailuresByPageId in buildMobileBrowserTab
but left partial AppState test helpers without the field, breaking PR Checks
merge commits. Default the map in fixtures and use optional chaining so partial
state cannot throw.
* fix(settings): show "Up to date" for current skills in sidebar
Orchestration and Computer Use showed a green "Installed" badge in the
settings sidebar even when freshness tracking knows the copy is current.
Add an "up-to-date" nav status so a present, no-update skill reads
"Up to date" (pairing with the existing "Update available" state), while
Voice — which tracks model readiness, not skill freshness — keeps
"Installed".
* fix(settings): require freshness proof for current badge
Only label a skill Up to date when every discovered placement is current. Keep Installed for unloaded, blocked, inaccessible, or unrecognized inventories, and share the classifier with the existing freshness pill. Regenerate the current skill manifest so its app version matches the package release and the required verification gate passes.
* Add mobile image-diff previews via shared data-URI builder
- Extracts a `buildImageDataUri` helper (src/shared/image-data-uri.ts) shared by
the desktop ImageViewer and mobile, so both trim whitespace-wrapped base64 and
skip non-previewable mimes (e.g. application/pdf) the same way.
- Adds mobile-diff-image-preview.ts to render binary git.diff results (add/modify/
delete) as images instead of falling back to "Binary preview unavailable".
- Extracts resolveMobileFileTabDoc to consolidate the session file-tab loading
logic (diff/image/html/text) out of the route file for testability.
* Fix stale binary image fallback for empty modified diffs and relay reads
- mobileDiffImageDataUri now distinguishes a true deletion (modified
side absent) from a modify whose binary bytes arrived empty
(relay/size-cap cases), returning null instead of the stale
pre-change image
- readWorkingDiffFile passes the file path to bufferToBlob so relay
working-tree reads can detect previewable image extensions instead
of always reporting empty binary content
- add mobile-file-tab-doc.test.ts covering diff/image/binary/text
resolution paths
* Regenerate skill bundle manifest for 1.4.144-rc.2
Co-authored-by: Orca <help@stably.ai>
* fix(review): trim comments to AGENTS.md's one/two-line why-only rule
Comments in mobile-diff-image-preview.ts and mobile-file-tab-doc.ts ran
3-6 lines and narrated mechanism instead of stating only the non-obvious
reason, per AGENTS.md's "Code Comments: Document the Why, Briefly" rule.
Co-authored-by: Orca <help@stably.ai>
* Distinguish read failures from true deletions in binary diff results
- Working-tree stat/readFile errors and relay reads previously collapsed
onto the same empty-content signal as a genuine deletion, letting
previewers fall back to stale original bytes on a failed read.
- Add modifiedDeleted/missing flags through status.ts, git-handler-ops,
and git-working-file-read so only proven deletions trigger the
original-bytes fallback; failed reads now return null.
- Tighten buildImageDataUri to accept only image/* mimes instead of
special-casing application/pdf.
* fix(relay): expect missing:false on index blob maxBuffer overflow
readBlobAtIndex now returns a missing flag so staged deletions are
distinct from size-capped binary reads; update the overflow test.
* Allow opening deleted files to show pre-delete text or image diffs
Deleted files can now be opened to view their pre-delete content via
git.diff (including images via modifiedDeleted). Only unresolved conflicts
remain unopenable. Centralizes the canOpen rule in canOpenMobileGitStatusEntry()
to keep opener guards consistent across the mobile source control UI.
---------
Co-authored-by: Orca <help@stably.ai>
* fix(skills): hide split icon and stop update-terminal respawn on refocus
The "Update skills" modal embeds a one-off command terminal via
OnboardingInlineCommandTerminal. Two issues:
1. The pane header showed the regular terminal's split ("Split Terminal
Right") icon, which makes no sense for an ephemeral single-command
terminal. Add an `allowSplit` prop (default true) threaded
TerminalPane -> TerminalPaneHeaderOverlay, and have all onboarding
inline command terminals pass `allowSplit={false}` to omit it.
2. Alt-tabbing out of Orca and back respawned the terminal. On window
focus, useSkillFreshness republishes {inventory: null, loading: true}
to retract stale update authority; the dialog reacts by nulling the
draft command, then re-sets it after the scan, and the
`key={terminalCommand}` remount spawns a fresh PTY. Add a ref-counted
suspendSkillFreshnessFocusRescan(); the dialog holds focus rescans
while a live terminal is showing so refocus no longer tears it down.
Explicit Re-check and install-changed refreshes still run.
* fix(skills): preserve freshness checks without respawning terminal
* fix(skills): keep revalidated terminal safe and focused
* Add per-model session-option pickers (model/effort/fast-mode) to native
Introduces a shared agent session-option catalog (Claude/Codex/Gemini/Cursor)
with model-scoped options, launch-command composition, mid-session dispatch
via slash commands, and per-model persisted defaults. Wires the new
NativeChatSessionOptionPickers UI into the composer, threads sessionOptions
through every startup-plan builder (worktree creation, onboarding, source
control actions, folder workspace, direct work-item launches), and adds
localized strings and tests across the affected surfaces.
* Add per-model session-option pickers with a verified Claude model switch
Native chat's model/effort picker now dispatches option commands through a
body-then-verified-Enter write path and, for Claude, arms a PTY observer that
classifies the cached-history confirmation prompt as applied/rejected/needs
interaction before the picker returns — falling back to the terminal only
when Claude genuinely requires manual input, and clearing stale truth
otherwise. Also reorders the composer's model/effort pills, disambiguates
their tooltip labels, and switches disabled-reason strings to a closed enum
so producer and localized copy can't drift.
* Add live model/effort detection from Claude's TUI header
- Reads the mounted xterm's main-buffer snapshot (falling back to the
rendered screen when the alternate screen owns the buffer) to parse
Claude's header for the currently active model and effort, so the
native chat picker reflects reality instead of only dispatched state
- Extracts command-apply recording and reported-value application into
dedicated modules, and factors file-link click handling into a hook,
to keep native-chat-pty-session-options.ts focused
* fix(terminal): forward Windows Ctrl Alt chords
* fix(terminal): route rescued Windows Ctrl+Alt chords through xterm's own key encoders
Replace the hand-rolled Alt-prefix encoder in the custom key handler with a
narrow repair of xterm's third-level-shift classification. xterm's keyboard
service already computes the correct bytes for every input protocol (legacy
ESC-prefixed, kitty CSI-u, win32-input-mode) before _isThirdLevelShift
discards them on Windows Ctrl+Alt; rescuing only provably-genuine chords
(Chromium's layout-wide AltGraph simulation, crbug 762557) lets those
encoders deliver protocol-correct, layout-aware bytes with no duplicated
encoding knowledge in Orca.
Fixes vs the previous approach: kitty-mode TUIs now receive CSI-u instead of
legacy bytes, digits/punctuation no longer alias to plain Alt chords,
letters follow the logical layout (Dvorak/Colemak), Ctrl+Alt+Shift and
Ctrl+Alt+F-keys gain Linux parity, and handled keys get xterm's stock
preventDefault/stopPropagation. Firefox web clients keep stock behavior; a
real-Terminal contract test fails loudly if an xterm upgrade removes the
seam, degrading at runtime to the historical dropped-chord behavior.
Co-authored-by: Orca <help@stably.ai>
* Refactor Windows Ctrl+Alt chord test helpers and clarify AltGraph commen
- Extract a shared getCore() helper in the test file to dedupe repeated
`_core` casts across third-level-shift and keyboard-service lookups.
- Correct the AltGraph comment: Chromium simulates AltGraph per composing
keypress, not for the whole Ctrl+Alt press duration.
- Warn via console when xterm no longer exposes `_core._isThirdLevelShift`,
so a silent classification-repair failure is diagnosable in the wild.
* Clarify comment explaining why Windows Ctrl+Alt chords bypass AltGraph c
The comment previously implied Chromium always sets AltGraph=true for
composable chords; the revised wording states the actual mechanism
(Alt+Ctrl modifiers get replaced by AltGraph) so the inverse case is
unambiguous.
---------
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
OMP runs as a `shell → omp → pi` process tree and Orca recognizes both
`omp` and `pi` as distinct agents, so the foreground-process reader
alternates between reporting the two across command boundaries. In
`resolveTabAgentFromSignals` every identity signal is re-owned onto the
pane's durable owner within its title-identity group (pi and omp share
one) — except the process signal, which was consumed raw. Because it
ranks above launchAgent, each `pi` read repainted an OMP-owned tab's icon
to the Pi glyph and the next read flipped it back.
Re-own the process signal the same way the hook and title signals already
are, so a same-group read collapses onto the owner while a genuine
cross-group process (e.g. Codex) still stands and can reclaim a reused
pane. The tab title text was unaffected — its owner resolution already
excludes the process signal — so this is icon-only.
Adds regression coverage for the oscillation, the mirrored/restored pane
(launchAgent gone, durable hook record), and the cross-group scope guard.
* fix(terminal): restore clickable links after returning to a worktree
Clicking a terminal link (file path, URL, term_* handle, OSC-8) does nothing after switching to another worktree and back, until the terminal is scrolled a little.
xterm's linkifier only re-runs link providers on mousemove when the hovered buffer cell changes vs its cached `_lastBufferCell`. Hiding the terminal surface fires `mouseleave`, which clears the current link but leaves that cache. On reveal the pointer returns to the same cell, so the mousemove handler short-circuits and the link is never re-established; a scroll shifts the buffer position and re-linkifies, which is the "scroll to fix" symptom.
Reset the linkifier hover-cell cache when a terminal surface is revealed (resumeTerminalVisibility) so the next mousemove re-evaluates providers. Covers all link types, including file-path links whose geometry click fallback does not compensate after reveal.
Verified in real Electron via a new e2e that fails without the reset and passes with it, for both a file-path link and a URL.
* test(terminal): prove restored links activate after reveal
* Add safe skill freshness detection
* Accept observed copy-mode rail outcomes
* chore(skills): regenerate snapshot artifacts for the merged guide content
The rebase onto main picked up the reviewed guide fixes (#8624), so the
current manifest hashes and a new appended snapshot generation must
match those bytes; the registry keeps all prior snapshots so existing
installs classify as outdated rather than unrecognized.
* fix(skills): canonicalize snapshot file order and guard released history
Historical snapshots kept git ls-tree byte-order while the working-tree
walk and runtime observation use the sorted depth-first order, so any
future multi-file skill would misclassify older installs as unrecognized
and churn spurious registry revisions; all producers now share one
canonical order (no digest changes for today's single-file packages).
Also rejects executable files from shipped skills (Windows observation
cannot see execute bits, which would misclassify pristine Windows
installs) and adds an explicit append-only invariant for released
snapshots so a generation-logic change cannot rewrite them silently.
* fix(skills): throttle focus rescans and correct self-blocked placement copy
Every window focus re-read and re-hashed all installed packages, and the
nudge and panel each forced their own trailing rescan for one event; a
15s cooldown plus a shared invalidation latch keep one bounded scan per
event while install-change events stay immediate. Bundle artifacts are
now loaded once per run instead of re-parsed on every scan. A read-only
or otherwise unsupported outdated placement now explains that it blocks
itself instead of blaming a phantom sibling placement; the supported
topology set moved to shared so eligibility and copy cannot drift.
* feat(skills): move freshness surfacing to a lingering toast and update modal
The Skills page has been unreachable since its toolbox menu entry was
removed (#4535), so surfacing freshness there buried the feature behind
its own nudge. The nudge now lingers until acted on (ignoring it records
nothing; only the explicit close persists dismissal keys) and opens an
update modal hosting the pre-filled editable terminal, an honest
current/blocked summary, and the per-placement rows in a collapsed
Details section. A compact 'Check for skill updates' row in CLI settings
is the manual re-entry point. Skills page restored to main; design-doc
surfacing section records the venue decision.
* fix(skills): mount update dialog inside the link-routing provider and fold freshness into the setup rails
The dialog hosts a live terminal pane that requires the link-routing
preference context; mounted outside the provider it crashed the renderer
the moment an eligible update existed (caught by live QA — unit tests
mock the terminal). It now mounts inside the provider behind its own
recoverable boundary.
The separate 'Check for skill updates' settings button is gone: the
setup rails' own pill now carries freshness (Update available / Up to
date, falling back to Installed for blocked or unrecognized copies and
for non-local runtimes the local-only scan cannot vouch for), and
Re-check refreshes both installation detection and the freshness
inventory. Wired for the CLI, Orchestration, Computer Use, and
Per-Workspace Environments rails.
* fix(skills): use the sleek scrollbar style in the update dialog
* chore(skills): regenerate manifest for merged main (v1.4.142-rc.1)
Main advanced to 1.4.142-rc.1 with a v1.4.141 release, so the embedded
appVersion and release mapping were stale on the PR's merged tree. Only
appVersion and the new release entry change; no snapshot digests move
(released history preserved).
* fix(skills): bound and batch freshness work
* fix(skills): harden freshness integrity checks
* fix(skills): accept observed copy topology outcomes
* chore(skills): regenerate manifest for current main
* fix(skills): preserve update terminal lifecycle
* chore(skills): regenerate manifest for current main
* fix(skills): fail closed on stale freshness scans
* chore(skills): regenerate manifest for current main
* fix(skills): preserve freshness safety under focus churn
* feat(skills): group the update modal by skill with plain-language status
The Update skills modal now lists only skills that will update or that can't
(with why), grouped by skill with their install locations nested underneath —
no more one row per placement.
- Statuses collapse to "Update available" / "Can't update" at the skill level.
- A location's problem is a chip (Duplicate, Unrecognized, Inaccessible, Read
only, In a repo, External/Broken link, Plugin cache) with a hover tooltip
that explains what it means for the user and what to do.
- Up-to-date, unrecognized-only, and unreadable-only skills are hidden; a
current/unrecognized/etc. location only appears when it explains a shown skill.
- Copy is de-jargoned (drops "copy"/"placement"/"snapshot"/"official copy") and
names the mechanism as the npx skills update command, not "Orca's update".
- Rename the section to "Update details"; drop the unreachable newer-known state.
Renderer-only: derivation is a pure module (groupSkillFreshness) with unit tests;
no IPC or main-process change. Locales updated for all five languages.
* chore(skills): regenerate manifest for current main (v1.4.143-rc.0)
* feat(skills): don't let a duplicate block the update; clearer skipped copy
- Eligibility: a clean standalone duplicate no longer poisons the whole
name — the canonical copy still updates and the duplicate is flagged;
a duplicate-only skill stays unoffered.
- Update modal: "Can't update" -> "Skipped" with a reason-specific
sentence (edited/read-only/in-a-repo/plugin/link); chips describe only
the location state; footer "Check now" -> "Re-check".
- Settings sidebar nav pills go amber "Update available" when a skill is
updatable, matching the setup cards.
- Localized new strings across en/es/ja/ko/zh.
* chore(skills): regenerate manifest for merged main (v1.4.144-rc.1)
* feat(native-chat): add native chat view across mobile
* fix(native-chat): address review findings and CodeRabbit threads
Correctness:
- Restore an independent initial readSession seed and surface initial-drain
errors as snapshot frames so the chat view can never strand on 'loading'
- Pair mobile tool results to calls by ordinal FIFO (parallel calls no longer
misgraft results); clear a pending ask only when its own call resolves
- Show a new streaming reply immediately (same-turn suppression, not length)
- Delegate mobile noise filtering to the shared harness-injected classifier
- Admit soft-leaving mobile clients in beginMobileInputFloor (parity with
mobileTookFloor) so grace-window writes aren't dropped
- Self-heal a stale 'working' status once this turn's reply lands
- Catch RPC rejections in mobile file-open helpers; guard sanitizeToolInput
key collisions; settle web/runtime transports on unrecognized first frames
and forward snapshot errors
Perf:
- Throttle the mobile streaming bubble (50ms) so per-part status frames stop
re-parsing the whole accumulated markdown
- Short-circuit markdown path detection on dot-less or oversized runs
(quadratic backtracking guard)
UX/minor:
- Wire hold-mode dictation through the native chat composer
- Allow scoped-package (@) paths in file-path detection
- Move caret after mid-text autocomplete insertion; index-prefixed ask option
keys; single scroll-to-end effect; bounded wait + toast when image attach
races a resubscribe; count-based pending reconciliation; cache-hit search
cancels stale debounce; chat-tab toggle wins over in-flight preference load
- Share shouldStepNativeChatAskAnswer between desktop and mobile; import
block guards/source priority from shared instead of local copies
- Defensive non-positive transcript limits; test strengthening (TTL expiry,
post-unsubscribe stale frame, lease readiness, filtered console.error)
* refactor(native-chat): share desktop/mobile chat logic in src/shared
Extract the parity-mirrored native-chat modules into shared implementations
both surfaces re-export: ask parsing (registry, parseAskFromStatus,
extractPendingAsk, formatAskAnswer), answer stepping offsets/scheduler, diff
detection/parsing, harness-noise filtering, tool fold/pair/split, and tool
summaries. Removes the hand-synced copies and their stale Metro comments.
Divergence reconciliations take the safer side of each: diffs truncate at
120 lines/32KB everywhere (desktop previously unbounded), tool-run summaries
cap at 3 parts with bounded-depth previews, nameless tool calls are skipped,
and basenames split on both separators.
Also: settle and kill every sibling quick-open pass when one reaches
maxResults (main rg/git and relay git; relay rg already did) so a capped
search cannot leave a scan walking a huge tree; fold window-bounding into
the shared merger's applyAppend; localize the web 'Pair a host' snapshot
error.
* fix(native-chat): address CodeRabbit follow-ups on shared modules
- Attachment lease gate re-checks connection/target/tab after the bounded
wait, so a tab/host switch or disconnect mid-wait can't send into a stale
terminal; a moved-away target drops silently like the pre-wait guard and
only an unrecovered lease surfaces the toast. Adds hook tests.
- extractPendingAsk parses transcript tool-calls through the same
registered-parser + canonical-shape fallback as live status, so a custom
question tool that rendered live survives reconnect/replay.
- Direct unit tests for the shared ask parser (FIFO ordering, fallback,
malformed payloads) and tool-summary bounded preview (depth/collection
caps, circular refs, basename/command branches).
* fix(native-chat): treat initialLimit 0 as a valid empty window
Both engine guards used truthiness, so an explicit zero limit skipped the
bounded tail reader and fell back to an unbounded incremental read. Latent
only (every caller clamps positive), hardened for consistency with the
tail reader's non-positive-limit handling.
* fix(mobile): native-chat composer lock UX + send-failure feedback
- Distinguish input-lock reasons: transport 'disconnected' shows Reconnecting…
instead of mislabeling a reconnect as locked-by-another-client
- Guard the composer lock behind a 600ms hold so connState blips / lease
hand-offs don't flicker the placeholder; unlock stays instant
- Surface a rejected send inline above the composer (a bottom toast hides
behind the keyboard); auto-dismisses after 4s
- waiting-session hint invites the first message instead of implying the
agent is still starting
* test(mobile): sync answer-send pacing test to the 500ms advance buffer
Missed in merge 8fe3c391c, which carried main's NATIVE_CHAT_ADVANCE_BUFFER_MS
300->500 (#8568) into the shared stepping module that mobile derives from.
* fix(mobile): restore terminal stream after chat cold start
* fix(native-chat): harden retries, optimistic sends, and file scans
* fix(mobile): deliver AskUserQuestion answers by option number (STA-1860)
Port #8840's fix to the mobile native chat: the Ask card now tracks
per-question option INDICES (+ free text) and the answer-send hook drives
Claude's arrow-navigate selector with buildAskAnswerKeys keystroke groups —
option numbers, next-tab arrows, Enter — paced one selector step apart, instead
of pasting label text that the selector ignores (which silently committed the
default option). Non-Claude agents keep the pasted-label path via the
selection-based formatAskAnswer.
Backcompat: keystrokes are built client-side and written through the EXISTING
terminal.send passthrough with enter:false — the same contract the permission
card already uses — so an older desktop runtime (SSH/relay included) replays
them verbatim; no RPC/contract change in either update order. Free text is
newline-sanitized because terminal.send has no paste framing.
Drops the now-unused formatCompleteAskAnswer from the shared module.
* fix native chat send and runtime races
* fix mobile native chat formatting
* fix(native-chat): mobile empty state matches desktop copy
Mobile showed a single generic line ('Send a message to get started') where
desktop shows a titled two-line empty state naming the agent ('Start a chat with
Claude' + 'Ask Claude to inspect code, explain output, or make a change.'). Align
them from one source of truth so they can't drift again:
- Extract the agent-type label map + formatAgentTypeLabel to
src/shared/agent-type-label.ts (desktop re-exports; mobile imports).
- Add src/shared/native-chat-empty-state.ts with the canonical English copy;
desktop uses it as its i18n fallbacks (localization unchanged — en/es/ja/ko/zh
keys still win), mobile substitutes the agent label and renders it directly
(mobile ships English only).
- Mobile: render title + subtitle for waiting-session AND ready-but-empty (both
are 'start a chat'), error copy for errors; keep the loading spinner.
Live-verified on the iOS sim against a pn-dev of this branch. typecheck node/web
+ mobile tsc clean; 30 mobile + 428 desktop/shared native-chat tests green.
* style: oxfmt the empty-state parity test (line wrap)
---------
Co-authored-by: Brennan Benson <brennanbenson@Brennans-MacBook-Pro.local>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* Add native macOS menu bar status item with settings toggle
Extend the Windows-only system tray into a shared status-item module
so macOS gets a template menu bar icon (Open/Settings/Check for
Updates/Quit), a theme-aware attention dot, and a "Show Menu Bar
Icon" appearance setting. Also fixes a startup race where a tray
"Settings…" click could fire before the renderer's ui:openSettings
listener attached, by queuing a one-shot pending-open-settings intent
the renderer consumes on mount.
* Fix Retina blur, race conditions, and menu-label duplication in tray Set
- Rebuild the tray attention icon's @2x representation since toBitmap only
read 1x pixels, blurring the glyph on Retina displays
- Fix premultiplied-alpha math so light-glyph tinting uses per-pixel alpha
instead of a flat 0xff, keeping antialiased edges valid
- Always push ui:openSettings and leave a longer-lived pending flag, since
there was no reliable signal that a renderer's listener was attached
- Preserve tray attention state across macOS menu-bar hide/show toggles
instead of resetting it on tray destroy
- Route macOS tray creation through syncMacMenuBarIcon so startup and the
live toggle share one visibility policy
- Reuse app-menu translation keys for tray Settings/Check for Updates and
drop the now-duplicate tray-scoped locale strings
* Make menu bar icon default on and platform-independent in settings
Previously showMenuBarIcon was hardcoded to darwin-only in both the
default settings and the sanitize/load paths, so a profile written on
macOS lost its opt-out when touched from another OS. Store the raw
boolean everywhere and let darwin-specific consumers decide whether
to act on it, so the value round-trips unchanged across platforms.
* Fix Settings menu click being silently dropped after a slow cold rendere
Replace the 60s pendingOpenSettings TTL with an untimed intent, since a
cold renderer start can outrun any fixed timeout and cause the flag to
expire before the click is consumed. webContents-id scoping plus
consume-on-read still prevent the intent from leaking to an unrelated
renderer. Adds a test covering the queued-before-mount pull path.
* fix(ssh): reconnect persisted PTYs after shutdown races
* fix(ssh): harden shutdown reconnect-target derivation
- exclude runtime-owned (ephemeral-VM) targets: a renderer-driven startup
ssh.connect would dispose the runtime layer's live relay session
- exclude explicitly disconnected / auth-failed targets so startup never
dials a host the user chose to leave offline (sessions still restore
on tab focus via the deferred flow)
- avoid re-running buildTerminalSessionData via the default parameter
when the remote-session map is legitimately empty (null sentinel)
- recompute activeConnectionIdsAtShutdown alongside terminal-field
patches so a crash between patches cannot strand a stale target
- pin all of the above with tests, including the patch-writer path
Co-authored-by: Orca <help@stably.ai>
* fix(ssh): filter runtime-owned targets at startup reconnect consumption
Main's windowless-promotion path can persist runtime-owned ids into
activeConnectionIdsAtShutdown; renderer must never dial those.
Co-authored-by: Orca <help@stably.ai>
* test(ssh): pin runtime-owned exclusion independently of the status gate
Give the runtime-owned fixture a transient status so the status gate
alone cannot exclude it; verified by mutation (guard removal fails).
Co-authored-by: Orca <help@stably.ai>
* refactor(session): extract reconnect-target derivation to its own module
workspace-session.ts crossed the max-lines budget; the derivation is a
self-contained concern (session ids + live SSH state -> startup targets).
Parameter is now required (null = none) since every caller has the
terminal session data or must opt into the scan explicitly.
Co-authored-by: Orca <help@stably.ai>
* fix(ssh): exclude runtime-owned targets from the connected-states path too
A pane-level optimistic write can stamp a runtime-owned connection state;
the live 'connected' filter must apply the same exclusion as the
session-id union (CodeRabbit review finding). Pinned in the test on both
paths.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
Adds self-hosted Jira Server/Data Center support (personal access token or classic username + password) alongside Atlassian Cloud, fully addressing the older-instance ask in #6676. Takeover of #7724 (@wquintal's original PAT implementation), brought current with main and hardened via a multi-agent adversarial review.
Fixes#6676.
Co-authored-by: William Quintal <williamquintal95@gmail.com>
- Extracts a per-client localStorage persistence layer with normalization/fallback for malformed or catalog-stale stored values
- Adds a hook wrapping the previous local useState wiring so AiVaultPanel keeps the same setter API while reads/writes go through storage
- Scope is intentionally excluded from persistence since it depends on current workspace/project availability
* feat(linear): use Linear branch names for worktrees
* fix(linear): preserve branch overrides across composer resets
Normalize Linear branch metadata at the shared workspace-source boundary, restore it when repo changes preserve the issue, and clear it when another provider replaces or removes the link. Add regression coverage for each lifecycle transition.
---------
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Shift+Enter still submitted instead of inserting a newline when Droid ran in
Git Bash on Windows, even after the PowerShell fix (#7620/#8065/#8427).
On Windows, Orca sends Droid the kitty CSI-u byte (ESC [ 13;2u) for Shift+Enter
only when the active pane has fresh, trusted process evidence that Droid is the
foreground app; otherwise it falls back to the Codex-compatible ESC+CR, which
Droid decodes as a plain Enter and submits. That trusted evidence comes from a
confirming foreground read.
PowerShell triggers that read from its OSC 133 command-start event. Git Bash
(and cmd.exe) emit no OSC command boundary, and an Orca-launched agent delivers
its command programmatically (no typed-command inference), so a fresh Droid
spawn in those shells never earned routing trust -- leaving Shift+Enter on the
ESC+CR fallback forever.
Fix: on a fresh spawn into a no-OSC shell, seed the SAME command-start
confirmation the manually-typed launch path already uses
(paneForegroundAgentTracker.onCommandStarted). Its bounded retry ladder spans
Droid boot, and -- crucially -- a miss publishes shellForeground:false, so the
pane stays recoverable by later focus/reveal samples. (A visible-pty sample was
rejected here: on a pathologically slow boot its known-agent shell-confirm would
latch, clear launch identity, and permanently poison Shift+Enter for the rest of
the session.) A real OSC 133;C, if it arrives, simply supersedes this.
Verified on Windows 11 + Git Bash with the real Droid CLI and node-pty ConPTY:
- Droid's TUI treats ESC [ 13;2u as a newline (two-line composer) and ESC+CR as
a submit -- exactly the reported bug and the fix's mechanism.
- droid.exe is a descendant of bash inside the ConPTY console process list (so
the main-side scan resolves it) and becomes a live process ~0.8s after launch,
well within the retry ladder.
Tests: regression test drives the real connectPanePty spawn path and asserts
csi-u; a slow-boot test exercises the shell->droid ladder transition; a
recoverability test proves a total-miss does NOT poison the pane (it fails
against the visible-pty approach and passes with onCommandStarted).
* Route new-tab link clicks to Orca tabs instead of popup windows
- Adds an isolated-world click/auxclick listener that relabels
browser-native new-tab intents (target=_blank, cmd/ctrl-click,
middle-click) with a private frame name, so setWindowOpenHandler can
distinguish them from opener-dependent window.open() calls without
breaking OAuth popups.
- Wires matched clicks through to a new browser:open-link-in-orca-tab
IPC payload carrying foreground/background activation intent, so the
renderer opens a worktree tab instead of a native window.
- Adds unit and e2e coverage for modifier/middle-click routing,
cancellation/rewrite handling, and cleanup on guest teardown.
* Route new-tab link clicks to Orca tabs and stop background popups
Plain target=_blank clicks (main frame and iframes) now navigate the
current Orca tab in place instead of opening a new browsing context,
while explicit new-tab gestures (Cmd/Ctrl-click, middle-click,
Shift+modifier) route into Orca tabs via one-use private frame names,
including child frames. Drops the foreground/background frame-name
split and the activate flag now that all routed links always open
active — no more silent background popups from a plain link click.
* fix(mobile): background-mount never-mounted terminals on subscribe so they don't render blank (STA-1840)
A mobile terminal.subscribe to a tab the desktop never mounted this session —
a workspace the desktop isn't currently showing, a cold-activation-deferred tab
(#8597), or a cold-parked tab — has no attached PTY, so the runtime has no
headless emulator and the initial snapshot is empty. The mobile terminal then
renders blank (repro: on the phone, scroll to an old workspace whose terminals
the desktop unmounted; they stay blank).
When a mobile subscribe hits an empty snapshot (or resolves no PTY), the runtime
now asks the renderer to background-mount that tab so the PTY attaches and the
live data stream — already subscribed on that path — delivers its output.
A never-mounted workspace has no renderer-graph leaf, so its terminals are
surfaced to mobile via synthetic pty:<ptyId> handles that carry no real UI
tabId. The runtime passes the ptyId through the mount request and the renderer
resolves the owning tab from the persisted tab model (resolveTerminalTabIdForPtyId),
falling back to a whole-worktree background mount if it cannot. The request is
idempotent for already-mounted tabs and never unmounts a live pane, so the worst
case is the same blank as before — no regression.
Validated on an iOS simulator with a two-workspace desktop: opening the
workspace the desktop was not showing rendered a fully blank terminal without
the fix and the live shell prompt with it.
Tests: runtime mount-request gating (real-tab / pty-form / no-ptyId / unknown),
subscribe-handler empty-snapshot-vs-content, and the ptyId->tabId resolver; all
608 RPC method tests pass.
* test(mobile): mock onRequestTerminalTabMount in useIpcEvents tests (fixes CI for #8811)
* fix(mobile): harden blank terminal recovery mounts
* perf(mobile): avoid redundant terminal recovery mounts
* fix(mobile): ignore stale terminal mount requests
* fix(mobile): skip recovery mounts for closed streams
* fix(mobile): reject ambiguous terminal mount ownership
* fix(mobile): preserve terminal handle through recovery mount
* fix(mobile): recover legacy terminal history safely
---------
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
* feat(git-status): batch, cancel, and cache git status polling to cut idl
- Add a single duty-cycled refresh scheduler (activity debounce + 60s
safety timer) replacing multiple overlapping intervals, so status
polling no longer runs near-continuously on large repos (#7983).
- Let safety refreshes reuse cached numstat line counts instead of
re-running diff --numstat every cycle, invalidated by head change,
known mutations, and a bounded TTL.
- Thread AbortSignal/request-token cancellation through IPC, RPC, and
relay layers so a superseded or backgrounded git:status call is
killed instead of finishing wastefully.
- Fix automatic upstream/status apply ordering so a slow, older
refresh can no longer clobber a newer result, and so an earlier
refresh still applies when a later one fails.
* Fix aborted git status scans being mistaken for completed empty results
- An aborted scan/numstat pass now always rejects instead of silently
resolving, so a cancelled request can't look like a valid empty status.
- Stop clearing the line-stats cache key on abort, since an aborted pass
never wrote to it — clearing was evicting a concurrent scan's healthy
snapshot and forcing a redundant numstat recompute.
* Fix aborted git status scans resolving as completed results
Cancelled scans could still resolve with partial or stale data instead of
rejecting, letting callers treat an aborted refresh as a valid status. Also
stop counting aborted scan duration toward catch-up refresh pacing, which
was stretching the next refresh interval by the full length of a cancelled
(often slow) scan.
* Add cancellable, generation-aware git status polling to cut stale scans
- Route git.status through an abortable subscription per requestToken so
cancelStatus can actually abort the remote scan instead of being a
no-op, preventing wasted work and stale responses overwriting fresher
state.
- Bump the git status polling generation on push-target changes so an
in-flight refresh against the old remote/branch can't apply stale
upstream data to the new one.
- Guard the stale-conflict poller against writes after unmount.
- Retire pre-purge line-stat scans in the cache so an older in-flight
scan can't repopulate a key after a token-scoped purge.
* fix(issues): replace cursor-based pagination with page-number Search API
Problem
=======
Issue pagination (#8649) had two bugs:
1. Pages 6-16 were unreachable — clicking page 16 highlighted page 5;
clicking 6/7 did nothing. The old cursor-based approach
(updated:<CURSOR) broke with Search API's relevance sorting —
pages after the first few returned no items even though more
issues existed.
2. Issue numbers appeared out of order on loaded pages (e.g. #1082
between #1308 and #1499), because client-side sort used
updatedAt instead of issue number.
Root Cause
==========
The pagination used two separate GitHub API strategies:
- Initial page 0 load: REST endpoints (repos/:owner/:repo/issues,
repos/:owner/:repo/pulls) sorted by updatedAt
- Subsequent pages: Search API with cursor (updated:<DATE)
These two sources returned items in different orders, causing items
to go missing or appear on wrong pages across page boundaries.
Solution
========
1. Unified on GitHub Search API for all pages — initial load and
pagination both use search/issues?q=...&page=N, eliminating the
REST-vs-Search inconsistency.
2. Changed from cursor-based (update:<DATE) to page-number-based
pagination (page=N), which the Search API supports natively.
3. Switched client-side sort from updatedAt to issue number
(sortWorkItemsByNumber), matching GitHub's default Issues view.
4. Parallelized page fetches in handleLoadNextPage — clicking page
16 now fetches all intermediate pages concurrently (~2s) instead
of sequentially (~30s).
5. Cleaned up dead legacy gh issue list / gh pr list code path,
extracted quoteForSearch helper, shortened overlong comments.
Files changed: 11 files, +140/-127 lines
Closes#8649
* chore: remove unrelated merge formatting
---------
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Move toggle heading commands (toggle-h1..h4) into their own "Toggle headings"
group, separate from plain heading commands, so the slash menu distinguishes
static headings from collapsible toggle headings. Also shorten toggle heading
labels from "Toggle Heading N" to "Toggle HN" for consistency with the plain
heading labels.
* fix(sidebar): map header drags to the nearest boundary slot instead of a dead zone
Fixes#8879
* fix(sidebar): bound header edge drops to measured content
* fix: address pr-bug-scan validated finding from #6335
Replaced unguarded crypto.randomUUID() with the established createBrowserUuid() fallback in both markup shape-id call sites.
* fix(review): cover markup crypto fallback call sites with regression tests
PR #8804 replaced two unguarded crypto.randomUUID() calls with
createBrowserUuid(), matching the convention established in #5471, but
shipped without extending that PR's regression-test pattern to the new
call sites. Add hook-level tests for useMarkupPointerHandlers.onPointerDown
and useMarkupEditor.commitPendingText under a simulated non-secure-context
crypto global.
Co-authored-by: Orca <help@stably.ai>
* fix(review): consolidate annotation bridge token onto createBrowserUuid
BrowserPane had its own inline crypto.randomUUID guard with a
Date.now()+Math.random() fallback, duplicating the getRandomValues-based
fallback already centralized in createBrowserUuid() and used at every
other call site in the renderer.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: orca-bug-scan-bot <orca-bug-scan-bot@stably.ai>
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>