* Sweep all persisted carriers of a removed SSH target id on re-adoption
reassignSshTargetId re-pointed repos and worktree metas but left the old
target id embedded in persisted session pty ids (ssh:<id>@@pty-N in tabs,
layouts, remoteSessionIdsByTabId), the startup reconnect list
(activeConnectionIdsAtShutdown, replayed via ssh.connect at boot — the
exact 'SSH target not found' in STA-1468), sleeping-agent resume records,
provisioned project host setups, sidebar host-scope arrays, and relay pty
leases. Any survivor resurfaces later as a failing connect or reattach.
New ssh-target-id-migration module re-points every carrier in one pass,
wired into reassignSshTargetId with per-carrier unit and store-level
round-trip tests.
Co-authored-by: Orca <help@stably.ai>
* Bridge SSH connection state to paired remote clients
The SSH surface was desktop-only: ssh:state-changed went to the host's
own BrowserWindow and the web client's ssh API was a no-op stub, so a
paired client's reconnect overlay never learned the host connected and
its target labels stayed empty (STA-1468 — overlay stuck on 'please
connect' over a live terminal).
- New sshStateChanged runtime client event, emitted from broadcastSshState
through OrcaRuntimeService onto the existing clientEvents stream.
- New ssh.listTargets / ssh.listRemovedTargetLabels RPC methods next to
the previously unused ssh.getState / ssh.connect.
- Web preload now routes listTargets / listRemovedTargetLabels / getState
/ connect to the paired host's runtime RPC instead of stubbing them.
- useIpcEvents applies sshStateChanged on paired web clients through the
same guarded path as desktop ssh.onStateChanged; desktop clients ignore
the event since a foreign runtime's targets would pollute their local
SSH store.
Co-authored-by: Orca <help@stably.ai>
* Harden the SSH reconnect overlay against stale or unknown target state
- Only present the destructive 'SSH host removed' state on positive
evidence (a removal tombstone label, or a hydrated non-empty target
list lacking the id). A client whose SSH state never hydrated has an
empty labels map for every id and must not offer workspace removal.
- After a failed Connect, resync target metadata so a stale overlay
converges to the ghost/re-adopted state instead of offering the same
failing Connect forever (the repeated 'SSH target not found' toast
loop in STA-1468).
Co-authored-by: Orca <help@stably.ai>
* Address CodeRabbit review on #7767
- Re-key workspaceSessionsByHostId partitions stored under a removed SSH
host id during re-adoption (no writer keys partitions by ssh host today,
but the schema tolerates it — re-key instead of stranding; live partition
wins when both keys exist).
- Track SSH target-list hydration explicitly (sshTargetsHydrated) instead
of inferring it from a non-empty label map, so a legitimately empty
target list still counts as removal evidence and a never-hydrated client
still never offers destructive removal.
- Apply the refreshed target list before the best-effort removed-labels
fetch in the overlay resync, so a labels failure can't discard it.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* fix(runtime): keep same-path imports host-qualified (#7018)
* review: harden runtime repo host match against SSH-repo hijack
An unstamped repo with a connectionId is an SSH repo (resolves to
ssh:<id>), so a same-path runtime import must not adopt it into a
runtime/local host. Match/adopt an unstamped repo only when it has no
connectionId, mirroring the existing local-IPC dedup guard
(src/main/ipc/repos.ts). Adds a regression test that fails without the
guard (SSH repo hijacked into runtime host).
Co-authored-by: Orca <help@stably.ai>
* review: only runtime hosts backfill an unstamped repo
A legacy unstamped repo is indistinguishable from a genuine local repo
(both have null executionHostId and connectionId). Restrict the adoption
branch to runtime incoming hosts so a local/ssh import at a colliding
path can never re-attribute a real local project to the wrong host.
Runtime is the only host that lost its identity to the pre-#7018
path-only import and needs the backfill. Adds a regression test.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
110 files carried an eslint/oxlint-disable max-lines directive but are
already under the default max-lines budget (300 .ts / 400 .tsx / 600 .mjs
/ 800 test), so the suppression is dead. Removing it restores real
max-lines coverage on these files with zero behavior change.
Each removed directive had max-lines as its only rule; verified via a
full oxlint run (0 max-lines violations, 0 new errors). Diff is pure
deletions (200 lines, 0 additions) — no code touched.
Co-authored-by: Orca <help@stably.ai>
Ensure agent CLI startup and draft launch commands use the correct quoting
format based on the user's configured local Windows shell (e.g., cmd.exe).
This avoids using host settings for remote/SSH targets where local shell
preferences do not apply.
Enable three unicorn rules — one correctness, two performance — and fix every
existing violation repo-wide so the rules pass as errors.
prefer-number-properties (76 sites)
- parseInt/parseFloat/NaN -> Number.* : safe aliases (autofixed).
- isNaN -> Number.isNaN (12 sites, hand-converted): global isNaN coerces its
argument, Number.isNaN does not. Verified every call site already passes a
number (Number.parseInt results, number-typed fields, Date.getTime()), so the
conversion is behavior-preserving today and guards against a future non-numeric
argument silently coercing.
prefer-array-find (26 sites)
- .filter(pred)[0] -> .find(pred); .filter(pred).at(-1) / .pop() -> .findLast(pred).
Drops the intermediate array and short-circuits.
prefer-array-index-of (5 sites)
- .findIndex(x => x === v) -> .indexOf(v).
Verified: typecheck (node/cli/web) clean, 53 affected suites pass (1679 tests),
oxlint clean repo-wide. mobile/ uses findLast safely (already ships ES2023
.toReversed()); config scripts and e2e helpers run on Node 24.
* perf(runtime): memoize onPtyData tail wait scan to halve per-chunk work
onPtyData runs per raw PTY chunk (hundreds/sec during verbose builds and
agent token streaming). For any terminal past the 2000-line / 256KB tail
cap it built the full wait text (a map/trim/filter/join over the entire
retained tail) and lower-cased + scanned it twice per chunk — once for the
pre-append tail and once for the post-append tail — producing hundreds of
KB of transient string allocation per chunk and steady main-process GC/CPU
pressure under load.
Cache the post-append wait state (text + lower-cased blocked-signal scan)
on the pty/leaf record and reuse it as the next chunk's pre-append state.
The prior chunk's post-append tail *is* this chunk's pre-append tail, so
the cached scan is exact; reuse is gated on fromTail so the empty-tail
preview fallback (which depends on a value updated after append) is never
reused stale. This drops per-chunk full-tail scans from 2N to N+1.
Adds an equivalence test proving the memoized stamping is byte-for-byte
identical to the recompute-both-sides reference across split prompts,
partial lines, ready-after-blocked demotion, and tail eviction, plus a
count assertion (memoized N+1 vs reference 2N scans).
Co-authored-by: Orca <help@stably.ai>
* fix(runtime): clear memoized wait cache when a disconnected transcript is pruned
pruneDisconnectedPtyTranscript empties a disconnected PTY record's retained
tail but left the new tailWaitState memo untouched. If such a record resumed
output (adoption/reattach while a leaf keeps it alive), onPtyData would reuse
the stale pre-prune wait state (fromTail=true) as the next chunk's previous
state and could miss or mis-time the waitBlockedAt stamp on that first chunk.
Clear tailWaitState in the prune reset so the resumed chunk recomputes from the
emptied tail.
Adds a runtime guard (prune clears the cache) and a sim equivalence test
covering prune-then-resume stamping.
Co-authored-by: Orca <help@stably.ai>
* docs(runtime): reword wait-scan comment that named a removed function
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
- Instruct workers to stop and idle or exit immediately after sending
`worker_done`, rather than running a 10-minute polling loop.
- Distinguish instructions based on worker kind: prompt-returning
agents should remain idle for re-engagement, while bare-shell
workers should exit.
- Prevent infinite polling overhead since the coordinator re-engages
workers via fresh terminal input instead of inbox polling.
Every terminal.subscribe / terminal.multiplex slot registers a runtime
exit-waiter via waitForTerminal(condition:'exit'). With no AbortSignal that
waiter sits in waitersByHandle until the PTY actually exits — but agent
terminals routinely never exit for the life of a session. It is only ever
cleared by real exit or a desktop renderer graph reload (markRendererReloading
/ markGraphUnavailable), neither of which a remote/mobile WebSocket reconnect
triggers. So on long SSH/mobile sessions every reconnect and tab-switch
re-subscribe leaked a waiter, and each captured its closed-connection handler
context (including the dead ws), growing host-process memory monotonically.
- multiplex: give each stream an AbortController and abort it in detachStream
(the single teardown point, reached on slot unsubscribe, re-subscribe
pre-detach, and connection close via closeMultiplex). Passing its signal to
waitForTerminal removes the waiter at detach. The existing .catch no-ops
because the stream is already deleted (streams.get(streamId) !== stream).
- legacy json/binary subscribe: pass the per-connection dispatch signal so the
waiter is removed on socket close/error.
Regression test proves a signalled exit-waiter is released when its signal
aborts (and 25 reconnect churns leave zero waiters), while an unsignalled one
accumulates — the pre-fix behavior.
Co-authored-by: Orca <help@stably.ai>
* feat(native-chat): add runtime owner selector for panes (U1)
Co-authored-by: Orca <help@stably.ai>
* feat(native-chat): add session transport with runtime adapter (U2)
Co-authored-by: Orca <help@stably.ai>
* Add runtimeEnvironmentId to UseNativeChatLiveSessionArgs
Allows the native chat live hook to route read and subscribe requests
to a remote runtime host when a runtime environment ID is specified,
otherwise falling back to the local IPC path.
* feat(native-chat): route live-session hook through owner transport (U3)
Co-authored-by: Orca <help@stably.ai>
* feat(native-chat): select runtime owner in caller view (U4)
Co-authored-by: Orca <help@stably.ai>
* Improve native chat transport and add local Docker SSH VM scripts
- Clamp high pagination limits instead of rejecting to keep paging unstuck.
- Re-subscribe after mid-stream drops with a short backoff.
- Drop explicit unsubscribe RPCs as connection closure reaps watchers.
- Discard stale load-earlier resolves after transport or owner flips.
- Add Docker-SSH scripts and Dockerfile for local Orca VM workspaces.
* rm unrelated files
* Handle errors when loading older native chat history
- Catch and swallow rejected promises from IPC-backed history reads to
prevent unhandled rejections when a "load more" action fails.
- Document focus-containment check in the window-scoped paste bridge.
---------
Co-authored-by: Orca <help@stably.ai>
* Add MiniMax rate-limit tracking and secure cookie storage
* Securely store MiniMax session cookies using an encrypted envelope format and local file hardening.
* Fetch rate limits in an isolated session partition and clear the cookie jar before and after requests to prevent leakage.
* Add a default-on "minimax" status bar item to display subscription usage.
* Expose minimax configuration settings (group ID and models) in settings panes and sync them via the runtime client.
* Isolate MiniMax config resolver and decryption failures from affecting other rate-limit providers.
* Redact MiniMax secrets with whitespace around colons
Update redactMiniMaxSecret to allow and match optional whitespace
surrounding the colon when redacting quoted cookie values. This matches
the spacing tolerance used during parsing.
* Address PR review: harden cookie read, validate IPC, add tests
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* fix(worktrees): fall back from stale default base refs (#7312)
* docs(worktrees): explain base fallback policy
* fix(worktrees): harden stale base fallback edge cases
---------
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Bare repo-backed terminal creates now reuse Settings agent defaults for exact agent commands while preserving raw commands, disabled agents, and already-managed launches.
Validation:
- PR Checks / verify passed
- Local runtime tests, typecheck, lint, CLI build, and Electron CLI/runtime smoke passed
Resolve terminal CWD and worktree paths to their canonical form using
realpath before validating containment. This blocks symlink escape
routes for local terminal spawns, returning the default workspace
directory if a requested path attempts to escape the worktree.
- Local terminal paths are canonicalized using native realpath logic
- WSL UNC paths and SSH/remote terminals skip canonicalization
- Floating terminal startup directories bypass containment checks
- Missing or unresolvable workspace directories default safely
Creating a workspace hard-failed with "Could not refresh base ref …"
whenever the pre-create git fetch of a remote-tracking base (e.g.
origin/main) failed — offline, transient, or (common on Linux GUI) the
Electron process lacking SSH_AUTH_SOCK so `ssh -o BatchMode=yes` cannot
auth. This blocked creation even when a perfectly usable local
origin/main already existed and `git worktree add` from it would succeed.
Regression introduced by #2310 (5c93579e40), which replaced the prior
best-effort fetch + post-create reconcile with a hard network gate.
Fix: at all three create sites (local runtime, local IPC, SSH), only
throw when the refresh failed AND no usable local base ref exists;
otherwise create from the local ref (a possibly stale but valid base).
Also: move the SSH local-ref probe after session.registerRoot so relays
that gate generic git.exec don't false-negative and defeat the fallback.
Co-authored-by: Orca <help@stably.ai>
* fix(gitlab): port-aware self-hosted host recognition
Use the URL host (including a non-default web/API port) as the GitLab
host identity instead of the port-less hostname, and match known hosts
port-aware:
- A known-host entry without a port matches any port of the same
hostname (preserves legacy bare-host and gitlab.com recognition).
- A known-host entry WITH a port matches only that exact host:port, so
two services sharing a hostname on different ports (e.g. a GitLab and
a Gitea) are no longer conflated.
- For ssh/git remotes the port is a transport port (e.g. ssh :2222) and
is dropped; for http(s) remotes the port is the endpoint and kept.
- Also capture an optional :port in parseGlabAuthStatusHosts so a
self-hosted GitLab on a non-default port is discovered correctly.
* fix(gitlab): per-connection known-hosts cache + port-aware auth-status parsing
getGlabKnownHosts() was connection-blind and cached process-globally,
and on any failure it cached [gitlab.com] forever — so a repo on an SSH
connection never discovered its self-hosted host once a probe failed
before the tunnel was ready.
- getGlabKnownHosts(connectionId?) now caches per connection so a
connected repo's authenticated hosts don't leak into the local
context (or vice versa).
- The failure fallback (canonical default) is no longer cached, so a
later probe can re-discover the real host once auth/tunnel is ready.
- parseGlabAuthStatusHosts captures an optional :port on both the
'Logged in to <host>' and header-style lines, keeping two services on
the same hostname distinct by port.
* fix(gitlab): isolate unresolvable projects instead of cwd-fallback that hits exit 128
listIssues/getIssue fell back to an unscoped 'glab issue list' / 'glab
issue view' that infers the project from cwd. For a repo on an SSH
connection cwd is not the repo dir, so glab runs git resolution in a
non-repo dir and fails with 'git: exit status 128'. In an 'All projects'
aggregate one such failure could sink the whole issues panel.
When a projectRef cannot be resolved, return a structured, isolated
per-project result (listIssues: { items: [], error: not_found };
getIssue: null) and spawn no glab subprocess. Behavior is unchanged when
a projectRef IS resolved (the scoped '-R' / 'api projects/...' path).
* fix(gitlab): recognize modern /-/work_items/<iid> issue URLs
Modern GitLab emits issue URLs as /-/work_items/<iid> in addition to the
legacy /-/issues/<iid>. The URL classifiers only matched /-/issues/, so
work-item-form issue links went unrecognized.
Extend the gitlab-links parsers (parseGitLabIssueOrMRNumber /
parseGitLabIssueOrMRLink, which also backs isWorkItemLookupText) and
isGitLabIssueUrl to accept /-/work_items/<iid>, mapping it to an issue
work item with the same project-path + iid extraction.
* fix(gitlab): thread connectionId into getGlabKnownHosts call sites
Follow the existing connectionId-threading pattern: pass the repo's
connectionId into every getGlabKnownHosts() call (client.ts,
work-item-details.ts, orca-runtime.ts) so the per-connection known-hosts
cache is keyed correctly and self-hosted hosts are discovered against
the right glab context.
* docs(gitlab): use generic example hosts in comments
* fix(gitlab): pass self-hosted host:port via GITLAB_HOST (glab --hostname rejects ports)
* polish: satisfy oxlint curly + oxfmt on merged gitlab port-recognition code
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Ptah-CT <auctor@xinfty.space>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
* fix(browser): keep isolated profile storage on its partition
* fix(browser): keep cloned isolated tabs on their resolved partition
* fix(browser): thread resolved partition through UI profile switches
Keep the isolated-storage invariant universal: UI-initiated profile
switch/create now persist the resolved partition alongside the profile
id, matching the runtime path, so a tab stays on its partition even if
the renderer profile mirror is later stale (issue #6923).
Co-authored-by: Orca <help@stably.ai>
* fix(browser): persist resolved partition through session restore
Add sessionPartition to the workspace-session zod schema so the resolved
partition survives persist->load; without it zod strips the field and a
restored isolated tab whose profile mirror is stale at startup falls back
to the shared default partition, reopening the storage leak (#6923).
Fold the webview teardown branches (parent drift or partition mismatch)
into one and re-resolve the viewport container once, matching the
pre-refactor null-guard behavior.
Split the sleeping-agent parse tests into their own file to keep both
under the 800-line cap.
Co-authored-by: Orca <help@stably.ai>
* fix(browser): keep CLI-created tabs inheriting the default profile
browserTabCreate without an explicit profile was sending sessionProfileId:
null, which the renderer store treats as 'no inheritance' (its guard is
!== undefined). That forced CLI-created tabs onto the shared default
partition even when the user had configured a default browser session
profile, silently changing behavior a #6923 fix should not touch.
Leave sessionProfileId/sessionPartition undefined when no profile is named
so the renderer applies default-profile inheritance; thread the resolved
partition only when a profile is explicitly chosen.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
* Recover exported terminal handles from live PTYs
* Guard terminal-handle adoption: first-wins, no collisions
Discovery adoption of ORCA_TERMINAL_HANDLE is now skipped when the pty
already has a handle bound this session (re-keying would strand waiters
registered under the old handle) or when the reported handle is already
bound to a different pty (provider-reported values are not trusted to be
collision-free). Also adds the relay why-comment and PtyProcessSummary
type alias from review feedback.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* Cut fseventsd load: fix macOS exclusion cap, drop wide always-on FSEvents streams
- @parcel/watcher maps plain-path ignores to FSEventStreamSetExclusionPaths,
but macOS caps exclusions at 8 per stream and fails closed. Orca passed 9,
silently disabling ALL daemon-side exclusions: fseventsd delivered every
node_modules/.git event to each per-worktree explorer stream (measured 16x
client CPU). Cap plain paths at 8 (extras demoted to userspace globs) via a
shared filesystem-watcher-ignore module, also unifying the runtime
file-watcher host list so identical roots share one native stream.
- worktree-base-directory-watcher subscribed recursive FSEvents streams over
every repo's ENTIRE workspace root (all worktrees) and whole common .git
(objects included) with glob-only ignores (zero daemon-side exclusion),
just to observe a few shallow paths. Replace with a 2s readdir/stat poller
(zero fseventsd clients, measured 70x less delivery CPU); SSH targets keep
provider.watch unchanged.
- serve-sim state watcher held an app-lifetime fs.watch on $TMPDIR (the
noisiest dir on the system); its existing 250ms existence poll makes the
parent watch redundant, so drop it.
Co-authored-by: Orca <help@stably.ai>
* Eliminate base-poller idle cost: mtime-gated ticks + narrow macOS worktree-metadata stream
- base targets: tick is now one stat of each gate dir (root + nested
containers); the readdir + marker fan-out runs only when a gate dir
changed, with a 30s ungated backstop and a bounded pending-marker
recheck list. Idle tick 600us -> 1.1us at 146 worktrees.
- git-common targets on macOS: replace the 2s entry sweep with one narrow
native stream on <common>/.git/worktrees (tiny, rare-churn tree): zero
idle syscalls and external worktree add/remove/HEAD detection back to
sub-second. Re-arms via existence poll across git worktree prune
deleting/recreating the dir. Win/Linux keep the listing poll (no
fseventsd there; a Windows dir handle could block prune).
- split git-common logic into worktree-git-common-watch.ts (max-lines).
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* fix: reordered default columns in kanban board
* Introduce dedicated flag to repair reversed default workspace statuses
- Add `_workspaceStatusesReorderedDefaultRepaired` to decouple the
one-shot repair from the initial status order migration.
- Ensure the repair runs for users who saved the reversed default
payload (with "Done" on the left) during a short-lived broken build.
- Support both "Completed" and "Done" labels when identifying default
status shapes to migrate or repair.
---------
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
* Add keyboard shortcut to toggle the Quick Commands menu
- New `tab.openQuickCommandsMenu` keybinding action (no default binding)
- TabBarQuickCommandsMenu listens for the binding and toggles open/closed
- Scoped to the active tab group naturally since the component only mounts when its group is focused
* Show keyboard shortcut in Quick Commands menu trigger tooltip
* Add tests
* expand tests
* Expand keyboard toggle to call handleOpenChange and skip repeated keys
- Replace `setMenuOpen` toggle with `handleOpenChange(!menuOpen)` so closing
via keyboard runs the same reset logic (query, focus frame, value override)
- Guard against key-repeat events to prevent rapid toggling on held key
- Wrap `handleOpenChange` in `useCallback` so it's stable enough to include
in the `useEffect` dependency array without causing spurious re-registrations
- Update tests to reflect that re-running the effect between presses is
required for the close path, and add a repeat-event test
* Add docstring to withShortcutHint func
* review: harden quick commands menu shortcut
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
Keep the synthetic floating workspace local while a remote runtime is active, including terminal/browser creation, activation, close, and remote snapshot handling.
Maintainer follow-ups:
- require worktreeId for runtime-session terminal create payloads
- add renderer-backed terminal create reply sender regression coverage
- merge current main and keep the WSL readDir breadcrumb test aligned with main's Windows-only handler coverage
* chore(lint): upgrade oxlint to 1.71 and enable 7 new rules
Upgrade oxlint 1.67.0 -> 1.71.0 (1.72 was blocked by the repo's 3-day
minimum-release-age supply-chain guard; nothing here needs it). The
bump is a no-op on the existing config.
Enable 3 error rules (backlog autofixed to zero in this commit) and
4 warn rules (surface signal without gating CI):
error (autofixed, behavior-preserving):
- unicorn/prefer-node-protocol (~1531 sites: bare builtin -> node:)
- typescript/no-import-type-side-effects (~36: all-inline-type -> import type)
- unicorn/no-array-reverse (19: copy-then-reverse -> toReversed)
warn (real signal, current fires are test-only/correct):
- unicorn/no-array-fill-with-reference-type (aliasing footgun guard)
- typescript/no-unsafe-function-type (bans bare Function type)
- unicorn/prefer-array-flat-map (map().flat() -> flatMap())
- unicorn/prefer-regexp-test (.match() in bool ctx -> .test())
mobile/.oxlintrc.json extends root, so it inherits all 7; the autofix
ran from root and covered mobile/ too.
Verification (all green): oxlint 0 errors (root+mobile+aux configs),
oxfmt clean, typecheck (node+cli+web), vitest 22795 passed / 0 failed,
builds (electron-vite + web + cli) succeed. node: rewrites confirmed to
skip embedded SSH/CLI string payloads (AST-only); all toReversed sites
verified to operate on fresh copies or write-once locals.
* chore(lint): bump mobile oxlint to 1.71 so inherited rules parse
mobile/ is a standalone pnpm project pinning its own oxlint@1.67, which
lacks unicorn/no-array-fill-with-reference-type (needs >=1.70). Since
mobile/.oxlintrc.json extends the root config, mobile CI's 'cd mobile &&
oxlint' failed to parse the new rule. Bump mobile to match root (1.71).
Verified in mobile/: oxlint 0 errors, oxfmt --check clean, tsc --noEmit
pass, vitest 978 passed / 0 failed.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
Fixes#5508.
Rejects mobile-scope phone-QR pairing in the full web client, keeps runtime browser access links on the instant startup path via advisory pairing scope metadata, and surfaces forbidden runtime scope errors instead of rendering empty workspaces or retry-looping setup checks.
Security note: pairing offer scope is UI metadata only. Runtime RPC authorization remains based on the server-side device token registry and mobile allowlist.
* Support respawn-pane in Claude Agent Teams tmux shim
Claude Code's pane backend spawns each teammate in two steps: it
`split-window`s a holding pane running `cat`, then `respawn-pane -k`s
that pane with the real teammate command. Orca's tmux dispatcher had no
`respawn-pane` case, so every teammate spawn failed with
`tmux: unsupported command: respawn-pane` and the real command was
dropped — agent teams were completely broken in Orca on current Claude
Code (2.1.x).
Honor respawn-pane by closing the placeholder terminal and re-splitting
from the pane's original origin/direction with the real command, keeping
the fake pane id stable so later send-keys/kill-pane/list-panes still
resolve. Panes now remember how they were split for faithful recreation.
Pure pane geometry/format helpers move to a sibling module to keep the
dispatcher within the 300-line cap.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* respawn-pane: create replacement before closing placeholder
Address review: closing the placeholder before the replacement split
succeeded could leave the fake pane id registered against a dead handle.
Split first; on cleanup failure, discard the new split and keep the
placeholder registered so send-keys/kill-pane/capture-pane still resolve.
Adds a regression test covering a failed respawn split.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>