Extract provider-authored turn boundaries (completion, interruption) directly
from Claude/Codex transcripts so the chat view knows when work ends without
guessing from message presence. Reconciles live hook state with transcript
lifecycle: when a terminal boundary lands, it settles a dropped Stop hook
instead of letting prose mislead the UI into showing 'working' after done.
* fix(terminal): tear down runtime-owned headless PTYs on whole-tab close
#8958 placed the closeTerminalTab renderer relay ahead of the authoritative
headless teardown in closeMobileSessionTab. Production always wires
closeTerminalTab, so the teardown became dead code for whole-parent closes
whenever a window is attached. The relay resolves close targets only from
renderer state (resolveTerminalCloseTarget), which never contains a
runtime-owned headless tab, so it acks success without killing the PTY —
leaking SSH-durable/serve PTYs and letting syncMobileSessionTabs republish
the "closed" tab (the exact case the removed comment warned about).
Run the isRuntimeOwnedHeadlessMobileTab teardown before the relay so headless
tabs are killed and pruned authoritatively even with a renderer attached;
normal adopted tabs still take the durable closeTerminalTab path. Add a
regression test that wires both notifiers (prior tests covered only the
no-notifier and in-graph cases).
* fix(terminal): preserve adopted tab close guards
* fix(runtime): preserve headless close durability on notifier failure
When `orca serve --port <P>` is set, bind the pinned port before any
persisted mobile-ws-fallback-port.json entry so clients dialing <P>
are not stranded. Default auto/desktop keep STA-1511 fallback-first.
Stat the resolved local/SSH path in openMobileFile before creating an
editor tab so `orca file open` returns ok:false for ENOENT instead of a
ghost tab. Fixes#8844.
* 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>
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>
* 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>
* fix(runtime): retain watcher and PTY teardown ownership
* fix(runtime): restore watchers after interrupted cleanup
* fix(runtime): prevent stale watcher revival
* test(runtime): cover watcher shutdown ownership
* test(daemon): model physical PTY exit
* fix(daemon): keep shutdown terminating when disposal cannot prove exit
A rejecting host.dispose() (unreapable child past its exit deadline) left
the shutdown RPC without its process.nextTick(shutdown) and skipped socket
cleanup in shutdown(), stranding the daemon as an unreachable orphan after
the stale-daemon replacement flow unlinks its socket. Log and continue:
daemon exit reparents the child to init instead of blocking on it.
* fix(runtime): keep local watching alive after an idle-kill deadline miss
An idle child that outlived the exit deadline set shutdownRequested on the
shared desktop supervisor, which has no retire-and-replace path — every
later subscribe rejected supervisor_disposed and the roots were cached
unwatchable, silently ending local file watching for the session. The idle
path owns zero records, so there is no double-watch hazard; the zombie
keeps its capacity reservation until physical exit and the next subscribe
gets a fresh child.
* fix(renderer): resync replayed paired-web file watches
Transparent replay removed the implicit resync the old close-and-rebuild
path provided: a replayed files.watch only reports changes from its own
native setup, so changes during the reconnect gap were silently lost.
Deliver a conservative overflow to consumers once the replayed watch is
ready, matching the overflow-after-interruption contract everywhere else.
* fix(runtime): address teardown review findings
* fix(runtime): retry watches after teardown deadlines
* Fix PTY descendant leaks on forced teardown
* Fix jitter-sensitive terminal lifecycle test
* fix(terminal): kill agent descendant processes on session teardown (STA-1800)
Agent CLIs spawn tool children in detached process groups that PTY
SIGHUP can never reach. Killing an agent session (tab close, retire,
sleep) left those children running as orphans — eight orphaned git
processes burned ~8 cores for up to 11.5h under the agents-running
keep-awake and drained a battery to 8%.
New pty-descendant-termination module: snapshot the ppid tree BEFORE
signalling (a dead root's descendants reparent to pid 1 and become
unfindable), SIGTERM the root group and every descendant, then after a
2s grace SIGKILL survivors gated on a pid+start-time identity re-check
so a recycled pid is never signalled. Snapshot is bounded and never
rejects; failures degrade to today's shell-only kill.
Wired for agent sessions only (plain terminals keep nohup semantics) at
all three POSIX kill sites: local provider shutdown, daemon
TerminalHost immediate kill (the pty:kill path — force-kill bypassed
Session.kill entirely), and daemon Session graceful kill.
Verified live in the built app: an agent pane with a detached-pgid
child; the child survived on the unwired build (three control runs) and
dies within ~5s with the fix. Windows ConPTY and SSH-hosted PTYs keep
the previous foreground-tree contract (documented follow-ups).
* fix(terminal): harden descendant teardown
* fix(terminal): require fresh process snapshots
* fix(terminal): close descendant teardown races
* refactor(terminal): preserve teardown line budget
* fix(terminal): keep descendant teardown fresh and identity-safe
* docs(reliability): record integrated descendant E2E
* fix(terminal): bound descendant teardown work
* fix(terminal): share descendant snapshot indexes
* docs(reliability): record descendant review evidence
* revert: remove speculative descendant hardening
* fix(mobile): detect and repair overriding Windows Firewall Block rules for pairing
Firewall inspection now reports an overriding inbound Block rule as
blocked instead of false success, the UAC repair removes only
conflicting rules for the current Orca executable, TCP pairing port,
and Private profile before recreating the scoped allow rule, and the
notice re-inspects Windows policy after repair instead of optimistically
reporting success. Stale focus-triggered inspections can no longer
overwrite a newer result during UAC elevation.
Fixes#8371
* fix(mobile): inspect the ActiveStore so GPO firewall rules are visible
Without -PolicyStore ActiveStore the NetSecurity queries read only the
local persistent store, so a GPO-applied Block rule was invisible and
the post-repair re-inspection could report a false success on managed
hosts.
* Suppress Git Credential Manager OAuth popup on git clone (fixes#7652)
Orca's git runner disables the interactive credential prompt on every git
call that goes through gitExecFileAsync/gitStreamStdout, but the two raw
'git clone' spawns (desktop repos:clone and the runtime clone path) passed
no env, so they inherited process.env with no guard. On Windows a clone
that needs GitHub auth then makes Git Credential Manager pop its
'Connect to GitHub' OAuth window, and in a network-restricted intranet the
browser/device flow never completes while git's credential retry re-pops it.
Apply nonInteractiveGitEnv() to both clone spawns so the prompt is
suppressed (GCM_INTERACTIVE=never, credential.interactive=false,
GIT_TERMINAL_PROMPT=0). The credential *helper* is kept, so cached-token
clones for private repos still work; only the interactive fallback popup is
disabled and the clone fails fast with a clear error instead.
* Suppress GCM OAuth popup in agent terminals and setup hooks too (#7652)
The clone-spawn fix stopped Orca's own managed git from popping Git
Credential Manager, but git run in terminals and setup scripts inherited
process.env with no guard. That is the more likely source of the reported
loop: agents are told to run 'git pull --rebase'/'git fetch'/retry 'git
push' (preamble + conflict/push-failure prompts), and each retry re-pops
GCM's 'Connect to GitHub' window in a network-restricted intranet.
Apply the credential-prompt guard to:
- setup/archive/hook scripts (hooks.ts non-WSL exec env), which run
unattended on worktree create/archive.
- the shared PTY host env (buildPtyHostEnv), via a small
applyTerminalGitCredentialPromptGuard helper. Agent terminals are
guarded unconditionally (they cannot dismiss a GUI popup); user
terminals are guarded by default via the new
terminalSuppressGitCredentialPrompt setting so power users can opt out.
The credential helper is kept, so cached gh auth still works; only the
interactive fallback prompt is disabled. Verified end-to-end in a real
Orca terminal (GIT_TERMINAL_PROMPT=0 + GCM_INTERACTIVE=never by default;
absent when the opt-out is set).
* Scope user-terminal credential guard to Windows, add settings toggle, forward guard into WSL (#7652)
* Retrigger PR checks (Actions dropped the synchronize dispatch for 57e7ce249)
* Keep shell locale out of the terminal/hook credential guard (#7652 review fix)
* Fix Fable review findings: guard WSL hook branch, wire settings search, catalog keyword keys, sparse-env askpass, one-shot agent classification (#7652)
* fix(terminal): harden Git credential popup guard
* test(pty): cover SSH credential guard setting
* fix(git): guard remote clones and setup runners
* fix(git): scope credential guards to unattended work
---------
Co-authored-by: Brennan Benson <brennanbenson@Brennans-MacBook-Pro.local>
Take-over of #8605 (issue #8591). Ships #8498 (worktree resync + pull-to-refresh + cache write-through) and #8129 (idempotent notification replay on reconnect). Fixes the original PR's field mismatch (seq vs notificationSeq) and adds the missing notifications.getMissedSince mobile RPC allowlist entry. #6784 and #4500 held back to avoid conflicting with the relay work (#8536). Co-authored-by: Brandon Bennett (@branben).
* Fix stacked-worktree PR creation targeting a local-only parent branch
- Resolve the eligibility default base to a remote-tracking ref instead
of blindly trusting the submitted parent branch, since a stacked
worktree's base is often a local-only branch the remote can't resolve
- Add a create-time hard block (base_not_on_remote) so a stale or
unpushed submitted base fails with actionable copy instead of the
provider's opaque error
- Update the dialog's default-base resolution and blocked-action/
dropdown copy to match the new remote-validated default
* Split hosted-review-creation.test.ts to fix max-lines lint error
Moved getHostedReviewCreationEligibility tests to a separate file (hosted-review-creation-eligibility.test.ts) to reduce the original file size from 880 to 579 lines, satisfying the max-lines lint constraint.
Co-authored-by: Orca <help@stably.ai>
* Fix Create PR intent flow to use remote-validated eligibility default fo
Prefer eligibilityDefaultBaseRef over the raw compare base when resolving
the review base for the one-click Create PR intent flow, since eligibility
is recomputed from the same compare base right before creation and already
corrects a local-only stacked parent to the repo default. Falls back to
the compare base only when eligibility supplies no default.
* Simplify base-ref remote existence check into a single for-each-ref call
Combine the wildcard and exact-tracking-ref lookups into one for-each-ref
invocation with multiple patterns instead of two sequential git calls,
removing the redundant rev-parse fallback path.
---------
Co-authored-by: Orca <help@stably.ai>
* fix(runtime): preserve terminals during headless desktop activation
* rm design doc
* Fix desktop activation launch ordering and blocked-window status resolut
- Check desktopWindowStatus before spawning the Orca app so a blocked
runtime no longer launches a doomed second instance.
- Reuse resolveDesktopWindowStatus for remote runtime status so it
honors the same authoritativeWindowId fallback as local status.
- Re-check the authoritative window at spawn time instead of trusting
a possibly-stale snapshot, since it can be destroyed mid-await.
- Harden the e2e activation spec against silent spawn failures.
---------
Co-authored-by: bbingz <zzb@gxsmjx.com>
* fix(mobile): validate Windows firewall remote scope
* fix(review): simplify string-guard ternary to boolean AND
The ternary returned only boolean literals, so cond ? f() : false is
equivalent to cond && f() (addressScopeIsSufficient returns boolean).
Co-authored-by: Orca <help@stably.ai>
* fix(mobile): accept dotted-netmask firewall scopes and lock in fail-safe edges
- Parse dotted-netmask CIDR (192.168.0.0/255.255.255.0) via a contiguous-mask
check, failing closed on holey masks.
- Factor subnetFromParsed so CIDR parsing no longer re-parses the address.
- Document why single-host (/32, /128) subnets and family-specific keywords with
an unknown interface family fail closed, and add regression tests covering
those deliberate false-deny edges plus policy-defined keywords (Intranet, DNS).
Co-authored-by: Orca <help@stably.ai>
* Add explanatory comment on why firewall scope check isn't unioned
Documents the fail-safe rationale behind checking coverage per rule
instead of merging rule scopes, so future edits don't "fix" this
into a less conservative union check.
---------
Co-authored-by: Orca <help@stably.ai>
* feat(mobile): start a workspace from a branch, issue/PR, or Linear ticket
Unify mobile workspace creation with desktop. The "+" Create Workspace
modal now has a primary "Start from" field that opens a tabbed search
drawer (Branch · GitHub · GitLab · Linear), letting a user start a
workspace from an existing/new git branch, a GitHub issue/PR, a GitLab
issue/MR, or a Linear ticket — in addition to the default blank workspace.
No new backend is required: the search RPCs (github.listWorkItems,
gitlab.listWorkItems, linear.searchIssues/listIssues, repo.searchRefs) and
the worktree.create linked-item params were already used by the mobile
Tasks screen. This surfaces them in the create flow, reusing the existing
pure modules (buildTaskWorkspaceCreateParams, shouldResolveHostedReviewStartPoint,
filterAvailableTaskProviders).
Details:
- New pure modules: workspace-source-selection, use-workspace-source-search,
source-workspace-create, worktree-create-retry, blank-workspace-create
(the blank/retry path extracted from the modal for reuse + line budget).
- New UI: WorkspaceSourcePickerDrawer (+ row) and SetupHookTrustDrawer
(extracted from the modal).
- Older paired desktops (missing the mobile.tasks.v1 capability) degrade to
Branch + Blank only; GitLab/Linear tabs appear only when available.
- GitHub/GitLab sources pin their repo; switching repos resets the source.
PR/MR sources resolve their base branch at create time; SSH repos gate
search until connected (Linear search is repo/SSH-independent).
* fix(mobile): hydrate settings/trust before availability probes settle
Review fixes for #7985: setTrustedOrcaHooks/setRuntimeSettings no longer
wait on status.get/preflight.check/linear.status (a first-open
preflight.check can take seconds, widening the spurious setup-trust
re-prompt window). Also adds param-parity tests for createBlankWorkspace
and a GitLab MR base-resolve test.
* feat(mobile): match desktop's Smart source picker exactly
Rework the mobile create-workspace source picker to be a faithful port of
desktop's Smart picker instead of the earlier divergent "Start from" drawer.
The mobile field is now the workspace-name input AND the source search, with the
exact desktop tabs — Smart · GitHub · Linear · GitLab · Branch · Name. "Smart"
fans out across GitHub + GitLab + Linear + branches, prepends a "Use '<name>'"
row, and resolves pasted URLs / #123 / STA-42 to exact items (with a cross-repo
switch prompt). Selecting a source shows a pill and moves the editable name into
Advanced. The invented "Blank workspace" concept is removed — the neutral state
is just a typed/empty name (blank submit still yields a creature name).
DRY: the pure desktop logic (smart-workspace-source-results, -command-value,
github-links, gitlab-links, work-item-link-query-bounds, github-work-item-identity)
moves to src/shared/new-workspace/ with re-export shims at the old renderer paths,
so both renderer and mobile share one implementation. composer-branch-selection
and workspace-name were already shared and are reused directly.
Two read-only lookup RPCs are allowlisted for mobile so pasted GitLab URLs and
cross-repo GitHub URLs resolve to exact items (github.workItemByOwnerRepo,
gitlab.workItemByPath).
New mobile modules are split for max-lines: use-mobile-composer-source (selection
state + desktop-parity handlers, PR/MR base resolve), use-smart-workspace-source
+ smart-source-fan-out/-search-requests/-paste-intent (RPC orchestration),
composer-linked-work-item / work-item-lookup-text / mobile-smart-source-modes
(pure logic), and SmartWorkspaceSourceField/Drawer/Row + SmartWorkspaceAdvancedFields.
Replaces WorkspaceSourcePickerDrawer/Row, workspace-source-selection,
use-workspace-source-search, and MobileWorkspaceNameInput.
Reviewed by three adversarial agents + re-reviewed after fixes: GitHub search now
returns issues AND PRs (not issues-only), Linear defaults to assigned, create-branch
preserves slashy names, cross-repo PR base resolves against the item's own repo,
displayName is suppressed for user-edited names, and the smart-mode GitHub fan-out
respects availability. tsc/oxlint/max-lines-ratchet clean; 1328 mobile tests pass.
* fix(mobile): keep smart source drawer fully visible
* refactor: share workspace creation behavior across clients
* fix: address workspace creation review findings
---------
Co-authored-by: Brennan Benson <brennanbenson@Brennans-MacBook-Pro.local>
* Keep running-agent workspaces visible under "Hide sleeping" (#7197)
The "Hide sleeping" sidebar filter judged a workspace active only when it
had a live PTY (or a browser tab), so a workspace with a running agent
whose live-PTY entry was momentarily absent — an SSH reconnect grace
window, an unmounted pane, a remote surface not yet `ready`, or an
orchestration worker reporting before its tab is mirrored — was
classified as "sleeping" and hidden while its session was still open.
The smart sort already treats a fresh `agentStatusByPaneKey` entry as
"working" independent of live-PTY, so the filter and sort disagreed. Add
`getWorktreeIdsWithLiveAgent`, which derives the worktrees with an open
agent session from the live agent-status map (sleep/teardown drop those
entries via dropAgentStatusByWorktree, so slept/hibernated workspaces
still hide), and consult it in `hasActiveWorkspaceActivity`. Wire it
through the sidebar list, Cmd+J jump palette, and kanban board.
* fix(agent-status): align live workspace attribution
* fix(mobile): preserve live-agent workspace activity
* fix(mobile): prefer newest agent status source
* chore: restore unrelated benchmark formatting
* fix(mobile): resolve projected agent worktree ids
* perf(mobile): index projected worktree summaries
* fix(mobile): preserve projected activity under limits
* fix(mobile): preserve POSIX path identity
* perf(mobile): cache projected summary fallbacks
* fix(mobile): preserve remote path and priority contracts
* perf(mobile): index projected paths by host flavor
* test(mobile): prove projected path index keys
* perf(mobile): bound projected path fallback
* perf(mobile): reuse projected repo platforms
* test(mobile): enforce projected lookup bounds
* chore(runtime): remove review instrumentation
* perf(mobile): skip unresolved repo platform scans
* perf(mobile): batch represented project runtimes
* perf(mobile): batch cold project runtime scans
* fix(mobile): couple worktree platform snapshots
* fix(sidebar): prioritize attributed headless agents
* fix(sidebar): activate smart sort for headless agents
* fix(sidebar): prefer mirrored agent ownership
* fix(mobile): follow mirrored agent ownership
* fix(sidebar): resolve mirrored unstamped agents
---------
Co-authored-by: Brennan Benson <brennanbenson@Brennans-MacBook-Pro.local>
* fix(orchestration): bind lifecycle sends to dispatched pane
* fix(orchestration): bind injected worker messaging
* Fix worker_done pane identity gating and rename SSH linear CLI files
- Lifecycle reconciliation now returns an explicit `rejected` action
(instead of `ignored`) for persisted sender_not_assignee markers, and
only treats a payload's `_orcaLifecycleRejection` as trusted state
when it exactly matches that reserved shape, preventing caller-
supplied markers from spoofing a rejected send as success.
- SSH legacy fallback CLI now fails closed with
`no_active_sender_terminal` when a worker_done/heartbeat send has no
resolvable sender identity, rejects mixed raw/structured payload
flags, and reconstructs structured lifecycle payload fields
(task/dispatch id, files-modified, report-path, phase) instead of
dropping them — extracted into ssh-remote-orchestration-send.ts.
- Renamed ssh-remote-linear-argument-error.ts to
ssh-remote-cli-argument-error.ts since RemoteCliArgumentError is now
shared beyond the Linear CLI.
* Add re-read logging for already-converted lifecycle rejections
Send-path reconcile converts worker_done/heartbeat rejections with a
no-op logger, so the coordinator's later re-read is the only chance
to surface the rejection message. Log it there instead of dropping it.
* Fix crash-isolated file watcher process pool for orca-serve SIGSEGV afte
Replace the worker-thread runtime file watcher with a forked, crash-isolated
@parcel/watcher child process pool so a native FSEvents fault can no longer
take down the main/serve process, and add bounded event batching, delivery
backpressure, and quarantine-based recovery for faulty watch roots.
* Fix crash-isolated file watcher teardown and shutdown leaks
- Fault harness could throw before mkdtemp/realpath completed, skipping
cleanup; now tracks each temp path independently and races an async
watcher-callback error so it can't escape the try/finally unhandled.
- In-process fallback swallowed unsubscribe failures via a bare rejection
handler that could still throw; use .catch() instead.
- Watcher process entry's cancel-subscribe handler now reuses the async
unsubscribe path when a crawl already finished, releasing the native
handle instead of leaking it (blocks worktree unlock on Windows).
- Runtime watcher process pool exposed no real dispose(); shutdown now
kills pooled children so they don't outlive the main process.
* Fix disposeSlot double-iteration bug in file watcher pool teardown
Remove the unnecessary array snapshot in dispose(): disposeSlot mutates
allSlots by deleting the slot being visited, and deleting the
in-progress element during Set iteration is well-defined, so the spread
copy was dead weight left over from prior debugging.
* Fix pending file watcher installs not aborting on unsubscribe
- Local/WSL watcher installs and SSH fs.watch setup now honor the
in-flight AbortSignal, so the last unwatch cancels a slow native
subscribe or remote setup instead of waiting for it to finish.
- Thread signal through IFilesystemProvider.watch and SSH-backed
file explorer watches for the same early-cancel behavior.
* Fix crash-resubscribe hangs and SSH watch teardown races in file watcher
- Add a bounded deadline for post-crash resubscription crawls so one
stuck root quarantines instead of pinning its whole shard forever.
- Report FSEvents overflow as recoverable so delivery continues after
a dropped-events error instead of surfacing as terminal.
- Make WSL watcher abort errors real DOMException instances so
AbortSignal-based cancellation checks recognize them.
- Rework SSH watch registration so ownership of the shared setup
request (not just the first caller) decides teardown, preventing
one caller's abort from cancelling another's shared watch and
guaranteeing exactly one fs.unwatch per registration.
- Reformat reliability-gates.jsonc arrays and refresh WSL/SSH coverage
entries and evidence runs to match the above.
* Add CI gate to run the file-watcher SIGSEGV fault harness under Electron
- The reliability gate and release workflows (mac, Linux) previously only
exercised the crash-isolation harness under vanilla Node, which doesn't
catch runtime differences in the actual Electron binary that ships to
users.
- Adds an `ELECTRON_RUN_AS_NODE=1 pnpm exec electron ...` run of the same
harness alongside the existing Node run, so #8212's SIGSEGV-survival
contract is proven against both runtimes before packaging.
* Add CI gate blocking Linux/macOS release packaging on watcher fault reco
Adds a contract test asserting release-cut.yml and release-mac-build.yml
run the runtime-file-watcher-fault-harness after building and before
publishing artifacts, so a regression in watcher process fault recovery
fails release packaging instead of shipping silently.
* Fix use-after-clear crash in failAllWatcherSubscriptions
Snapshot the records map before iterating, since onTerminalError
hooks can dispose the supervisor and clear `records` mid-loop,
causing a crash. Also update the matching test to assert against
the shared buildParcelWatcherIgnoreOptions helper instead of a
loose arrayContaining match.
* Fix use-after-clear crash in failAllWatcherSubscriptions
Snapshot watcher records with Array.from instead of spread, since
spread syntax over an iterator that's mutated mid-loop by
onTerminalError hooks can produce inconsistent results.
#7598 pushed client-ui.ts to 301 lines, one over the max-lines limit, which
breaks repo lint for every PR that merges current main (verify does not run
on main pushes). Follow the existing browser-schemas.ts/computer-schemas.ts
pattern: zod schemas move to a sibling module, the method registry stays.
Co-authored-by: Brennan Benson <>
* fix(pr-comments): let users mark comment authors as bots for the Humans/Bots filter
Some review bots post from regular user accounts that defeat both provider
bot metadata and login heuristics, so their comments were misclassified as
human. Adds a persisted prBotAuthorOverrides setting with a "Mark author as
bot" comment action, applied consistently across desktop and mobile.
Fixes#7597
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* fix(pr-comments): address review feedback on bot-author overrides
- Cap sanitized prBotAuthorOverrides at 500 entries so malformed payloads
can't bloat GlobalSettings or slow comment classification
- Reuse the shared normalizePRCommentAuthorLogin in isBotPRComment on
desktop and mobile instead of duplicating the normalization inline
- Pass botAuthorOverrides from CommentRow to CommentMoreMenu instead of
re-subscribing per menu instance
- Re-fetch mobile bot-author overrides alongside each PR refetch so they
don't stay a stale one-shot snapshot for the whole session
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* fix(pr-comments): harden bot author override sync
* fix(pr-comments): bound and recover override updates
* fix(pr-comments): merge overrides from canonical settings
* fix(pr-comments): make bot override updates atomic
* fix(pr-comments): surface rejected bot overrides
* fix(i18n): translate bot override warning
* fix(i18n): translate bot author actions
---------
Co-authored-by: Dzmitry Bachko <dbachko@users.noreply.github.com>
Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Brennan Benson <brennanbenson@Brennans-MacBook-Pro.local>
* fix(orchestration): complete worker tasks and improve coordinator UX
* Fix orchestration lifecycle sender resolution and peek/check compat hand
- Lifecycle sends (worker_done/heartbeat) now use ORCA_TERMINAL_HANDLE
verbatim, skipping the liveness probe and pane remint that could
block delivery during restarts or mismatch stale-runtime assignee
handles.
- --peek now round-trips as {peek:true, unread:false} so older runtimes
that strip unknown params degrade to non-destructive "all" instead of
mark-read, with client-side filtering to restore peek semantics and a
clear error when --peek --wait can't be honored.
- Reject combined read-mode flags (--unread/--peek/--all) before calling
the runtime.
- Distinguish suppressed (already-consumed) lifecycle messages from
ignored ones so send doesn't wake --wait waiters for stale heartbeats.
- Fix task summary truncation to avoid splitting UTF-16 surrogate pairs
and to not misreport whitespace normalization as truncation.
* Add shared helper to abbreviate orchestration task specs for brief listi
- Normalizes whitespace and caps spec length at 160 chars, flagging
truncation separately from whitespace-only changes
- Truncates on UTF-16 code point boundaries to avoid splitting
surrogate pairs and emitting malformed strings
* Add pane-key identity to worker_done/heartbeat reconciliation and server
- Records the sender's pane key on messages and dispatch contexts so
worker_done/heartbeat ownership can be verified by the remint-stable
pane leaf instead of the terminal handle, which is reissued across
restarts.
- Rejects lifecycle messages from a genuinely foreign pane while still
tolerating handle remints, tab break-outs, and older CLIs that lack
pane identity.
- Moves task-spec abbreviation server-side (orchestration.taskList
--brief) so full specs no longer cross SSH/relay transports, with a
client-side fallback for older runtimes; consolidates the shared
abbreviation helper under src/shared.
- Adds a stderr warning when a pre-peek runtime's --peek response hits
the 100-row cap, since older unread messages may be missing.
* Isolate ORCA_PANE_KEY in CLI test beforeEach to fix leaked senderPaneKey
Co-authored-by: Orca <help@stably.ai>
* Fix pane-key remint bypassing dispatch mutual-exclusion lock
- Dispatch locking only matched on assignee_handle, so a reminted
terminal handle (tab break-out) could open a second concurrent
dispatch on the same pane.
- Add leaf-UUID-based pane key comparison (parsePaneKey) as a
secondary lock, falling back to exact handle match for legacy
rows without pane keys.
* Update orchestration skill docs for lifecycle authority and CLI flag add
- Clarify that dispatch lifecycle is tied to taskId+dispatchId verified against
the dispatched pane, not the terminal handle, since handles can be reminted
after restart
- Document new `check --peek`/`--all` and `task-list --brief` flags, with
fallback guidance for older CLIs that reject them
- Note that a valid worker_done auto-completes the task/dispatch, so workers
shouldn't also call task-update manually
---------
Co-authored-by: Orca <help@stably.ai>
* feat(ssh): support file transfers over system ssh
* fix(ssh): harden system file transfers
* test(runtime): stub getRepo in headless mobile tab cwd test
The mobile-session selector validator (getValidatedExplicitWorktreeIdSelector,
from main) calls this.store?.getRepo to reject repo ids passed as worktree ids.
The store stub only implemented getWorkspaceSession, so the guarded call threw
'getRepo is not a function' once main merged into this branch. Add a getRepo
that returns null (wt-1 is a worktree, not a repo).
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
* fix(runtime): explain full worktree id selectors (#7432)
* Fix full worktree id selectors for bare repo ids and doc guidance
- Reject bare repo-id selectors up front via a shared validator instead
of relying on worktree-list scanning, so RPC callers no longer trigger
an unnecessary rescan just to detect the mistake
- Propagate the structured worktree_id_requires_full_path code through
RPC error mapping so callers get a typed error, not just a message
- Update orca-cli, orca-emulator, and orchestration skill docs to show
the full `<repo-id>::<path>` id shape and stop implying a bare repo
id is a valid worktree selector
---------
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
* docs: design Grok orchestration group
* docs: plan Grok orchestration group implementation
* fix: add Grok orchestration group
* test(orchestration): accept Windows skill newlines
* Fix @grok orchestration group matching and remove stale planning docs
- Reuse the shared buildAgentNameRe matcher in groups.ts instead of a
divergent local regex, so orchestration groups honor the same
Windows launcher-suffix rule (grok.exe/.cmd/.bat/.ps1) as the rest
of Orca's agent-title detection.
- Add test coverage for real Grok OSC title shapes (spinner-collapsed,
session titles) and Windows launcher-suffix titles.
- Delete the now-completed design and implementation-plan docs for
the Grok orchestration group work.
---------
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
* feat(status-bar): notify upgraded users usage meters show % used
Show a one-time status-bar callout when upgraded profiles still use the
new percent-used default. Brand-new profiles and users who already chose
remaining stay quiet; dismissing or changing the setting is permanent.
* Add settings deep-link to expand Appearance's Window accordion for one-s
- Replaces the searchQuery-based redirect (fragile, flashed filter UI) with a
dedicated appearanceAccordionDeepLink store field that force-opens the
correct accordion and scrolls to the target row
- Rebuilds the status-bar change notice as a plain elevated card instead of
a Popover, since PopoverContent's glass/backdrop-filter defaults fought
the opaque callout styling and needed heavy overrides
- Simplifies the light/dark card CSS tokens accordingly
* Reposition status-bar usage-change notice via fixed-position portal
Portal the one-shot callout to document.body with fixed positioning
anchored via getBoundingClientRect, since the status-bar's overflow-hidden
flex ancestors clipped or mispositioned the previous in-tree absolute
layout.
* Refine status-bar usage notice styling and test coverage
- Replace hand-tuned light/dark card colors with existing design
tokens (--popover, --border) and the documented floating elevation,
so the callout stays in sync with the design system instead of
duplicating its own palette
- Add tests covering dismiss via X button, "Got it", and Escape
- Scope the foreground-process confirm assertion to the pane's ptyId
so an unrelated pane's delayed confirm can't cause a false failure
* fix: authorize guarded sends to active ConPTY agents
* Fix stale-handle rejection using controller-less status checks and contr
- Distinguish `terminal_handle_stale` (PTY rebind mid-check) from `terminal_exited`
so callers get an accurate reason instead of a generic `terminal_not_writable`.
- Move the PTY binding assertion to always run after the fresh foreground read,
even when no controller is present, closing a gap where a rebind during a
controller-less check went undetected.
- Call `confirmForegroundProcess` through its owning controller instance so the
method keeps its `this` receiver instead of being invoked unbound.
- Update reliability gates and design doc to reflect the new test coverage and
corrected error semantics.
* rm design doc
Add a persisted Appearance setting that switches provider usage labels between percent used and percent remaining while keeping meter fill consumption-based.
Cover desktop and web persistence, current providers, settings search, localized copy, and regression tests.
Co-authored-by: gatsby74 <166927047+gatsby74@users.noreply.github.com>
* fix(terminal): let remote desktop viewers own the shared PTY width
A remote (relay/shared-control) desktop viewer resizes the host source PTY to
its own width, but was never registered as a width owner. So the host's own fit
cascade (window resize, split drag, tab reveal, "+"-new-tab re-render) freely
resized the viewed PTY back to the host-local width with no signal to the
viewer. The viewer kept its narrower grid while the host streamed wider
alt-screen frames -> cell-layout garble ("porridge") until a manual resize.
The mobile "presence lock" already solves this shape for phones by suppressing
the host's pty:resize while the phone drives. This does the same for remote
desktop viewers WITHOUT joining the mobile driver state machine (a viewer needs
only resize suppression, not input lock / phone-fit / driver banners), and it
routes every PTY geometry change through the existing enqueueLayout/applyLayout
serialization path rather than resizing the PTY ad hoc.
Design:
- Registry `remoteDesktopViewers: Map<ptyId, Map<subscriptionKey, viewport>>`,
keyed per SUBSCRIPTION so duplicate streams of one client cannot release each
other. isPtyResizeDrivenRemotely() = mobile driver OR any viewer present;
pty:resize (host fit cascade) bails on it. INPUT is never locked (shared
control: host and viewers can both type), unlike mobile.
- New internal layout target { kind: 'remote-desktop' } in applyLayout: clears
terminalFitOverrides like 'desktop', resizes only when dims changed, emits no
fit-override notifications, and does not call onExternalPtyResize (so a
viewer's width never pollutes desktop restore state). enqueueLayout remains
the sole serialized writer of PTY dimensions.
- Smallest client wins: applyRemoteDesktopLayout() sizes the PTY to the SMALLEST
attached viewer, so viewers with different screen sizes never overflow the
narrowest grid.
- No snapshot/replay race: a viewport that arrives while the initial scrollback
snapshot is being serialized is buffered and applied as ONE serialized resize
before serialization, so a mid-repaint alt screen is never baked into the
snapshot.
- Reclaim on detach: when the last viewer leaves, applyRemoteDesktopLayout
resizes the PTY back to the host's own width via enqueueLayout({kind:'desktop'}),
so the host reflows to full width without a manual window jiggle. The host
width is captured at the pty:resize suppression point (the host's own resize
attempts) — a source a remote viewport never pollutes.
- Mobile coexistence: a phone outranks a viewer; when the phone leaves, the
mobile-release paths call applyRemoteDesktopLayout so a surviving viewer keeps
the PTY, else the host reclaims.
Verified live (two local desktop instances): correct render on connect, no
garble on viewer-window resize (synchronous), and host reclaims full width on
disconnect. Unit tests cover the registry lifecycle, smallest-client-wins,
reclaim-via-layout, and mobile coexistence.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Codex <noreply@openai.com>
* fix(terminal): address remote-desktop PTY width review findings
Resolves four CodeRabbit findings on the remote-desktop viewer width
ownership change, plus a round-trip race surfaced while fixing the leak:
- Leak: the one-shot terminal.updateViewport RPC has no disconnect hook,
so it must never create a width floor (nothing releases it, pinning the
host at a stale width). Add refreshRemoteDesktopViewer, which only
refreshes floors the client already owns via its stream subscription
(matched by clientId), mirroring the mobile updateMobileViewport
no-op-without-subscription invariant. The one-shot handler now refreshes
instead of registering.
- Round-trip replay: with the one-shot fallback now refresh-only, a resize
landing during the subscribe round-trip (connected, stream not yet
current) was dropped. The transport now replays the latest viewport over
the stream once it becomes current.
- Reclaim: keep the host reclaim target unless the reclaim resize actually
landed (result.ok), so a failed reclaim can retry against true host
geometry instead of a stale remote width.
- Snapshot drain: drain pendingRemoteDesktopViewport in the
sendRequestedSnapshot finally block; a viewer resize parked during a
SnapshotRequest buffering window was otherwise dropped until the next
resize.
- Key scope: scope the multiplex width-floor key by connectionId so two
connections reusing the same client-local streamId can't overwrite or
release each other's floor.
Tests: refresh-never-creates-floor, reclaim-target-retained-on-failed-resize,
SnapshotRequest parked-resize drain, connection-scoped key assertions, and a
round-trip replay race test (verified to fail without the flush).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(terminal): arbitrate active desktop viewport ownership
Co-authored-by: Orca <help@stably.ai>
* test(terminal): expect viewport claim capability
Co-authored-by: Orca <help@stably.ai>
* fix(terminal): preserve input across ownership teardown
Co-authored-by: Orca <help@stably.ai>
* test(terminal): keep subscribe buffer coverage within lint limit
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: shady <shady2k@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Orca <help@stably.ai>
* Fix mobile terminal query reply authority
* fix(terminal): harden mobile query reply handoffs
* fix(terminal): exclude passive mobile query responders
* fix(terminal): gate mobile query replies on host capability
Older hosts strip terminal.send's inputKind (zod drops unknown keys), so a
forwarded xterm reply would land as ordinary floor-taking shell input. Hosts
now advertise terminal.query-reply-input.v1 via status.get and mobile drops
replies unless the host advertises it (pre-fix behavior). Also documents the
bounded desktop-to-mobile handoff double-reply residual.
Co-authored-by: Orca <help@stably.ai>
* fix(terminal): advance snapshot seq across recovery snapshots
The pending-overflow recovery loop trims buffered output against
recovery.seq while query replay and boundary strips kept using the
initial snapshot seq. Unreachable under today's control flow (no await
separates the initial-overflow consume from the loop), but the stale
seq would silently drop covered query replies if that ordering ever
changes. Track the seq that actually covered the buffered chunks.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>