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
Fix native Windows CJK terminal repaint and ConPTY wrap metadata handling.\n\nNormalize filesystem realpath results before authorization so WSL UNC roots compare consistently in CI.
Fix native-chat tool-run summaries so Windows-style file paths display their basename instead of the full backslash path.
Also reuse the renderer basename helper so trailing Windows paths keep a useful summary hint.
* Apply #6826: reclaim xterm focus after window blur (verbatim)
Co-authored-by: Orca <help@stably.ai>
* Improve #6826 focus reclaim: pane-scoped helper + cross-platform focus-steal guard
- Reclaim the exact split helper that owned focus at blur, not the first
helper in the container (a single TerminalPane hosts all splits as siblings).
- Defer the reclaim refocus to the next frame on every platform and only
take focus if nothing newer grabbed it, matching the existing macOS guard,
so a click into the sidebar/dialog during reactivation isn't yanked back.
- Skip reclaim if the released helper was detached before refocus.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* Harden merged #6830 daemon degraded-mode handling
- shutdownFallbackSessions is now best-effort: a single un-killable local PTY
no longer throws and aborts the daemon restart (which would leave the user's
recovery path unusable, recreating the original lockup). Logs and continues.
- checkPtySpawnHealth retries once (timeout raised 2s->4s) so a transient stall
on a busy machine right after an upgrade does not mis-classify a healthy
daemon as unable to spawn PTYs and silently drop new terminals to the local
provider without daemon persistence.
- Surface degraded mode: DegradedDaemonPtyProvider exposes isDegraded, and
pty:management:listSessions returns { degraded } so the session UI can warn
instead of it being a silent console.warn. Clearer actionable warn message.
Refs #6814.
Co-authored-by: Orca <help@stably.ai>
* Add #6814 daemon failure-mode classification test
Drives the real DaemonServer + checkDaemonHealth over a real socket to lock in
the healthy / degraded(pty-spawn-unhealthy) / wedged(unreachable) / unreachable
classification. Documents the load-bearing boundary that the degraded-daemon
fallback rescues a degraded daemon but NOT a fully wedged one.
Co-authored-by: Orca <help@stably.ai>
* Add degraded flag to web preload listSessions stub (typecheck parity)
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
- Update the PR state badge icon to dynamically match merged, closed, or draft states.
- Simplify PR merge direction layout using branch tags and a directional arrow.
- Add author avatars to the PR metadata row.
- Prevent scrolled task rows from bleeding through sticky elements by adjusting header z-indexes and applying opaque hover/surface backgrounds.
- Refine padding, text size, and spacing across the PR and task views.
A synchronous throw inside any terminal link provider's provideLinks
escapes to window.onerror and gets the renderer killed (reason=killed,
exit 1). The reported crash (F0BDKBHDAUE) is xterm web-links'
LinkComputer._getWindowedLineStrings raising 'RangeError: Invalid array
length' while scanning a pathological wrapped line during agent CLI
output (opencode).
Patch terminal.registerLinkProvider at construction so every provider
registered afterward — the web-links addon's internal provider plus
Orca's file-path and terminal-handle providers — has its provideLinks
wrapped in a try/catch that records a crash breadcrumb and degrades to
'no links this hover' instead of throwing.
Covered by terminal-link-provider-guard.test.ts (reproduces the
RangeError and asserts it no longer escapes).
Co-authored-by: Neil <neil@stably.ai>
Migrate fileURLToPath(import.meta.url) / dirname(...) boilerplate to the
native import.meta.dirname / import.meta.filename, then enable the rule
at error so new code stays on the native form.
The oxlint autofix rewrites the expression but leaves the now-unused
node:url / node:path imports behind (which the already-enabled
no-unused-vars=error would then flag), so this commit also removes those
34 orphaned imports — trimming the named import where other names are
still used, deleting the line where it was the sole import.
Scope is build scripts + Node-env tests only (config/scripts, tools/
benchmarks, *.test.{ts,mjs}, vitest configs); zero shipped runtime code.
The native properties are exact equivalents (Node >= 20.11; repo is on
24), so behavior is unchanged.
Verified: oxlint 0 errors tree-wide (root + mobile), oxfmt clean,
typecheck (node+cli+web) + mobile tsc pass, root vitest 22825 passed /
0 failed, mobile vitest 1018 passed. Exercised the rewritten scripts
directly: build:relay (6 targets), ensure-native-runtime,
verify-macos-entitlements all run correctly with import.meta.dirname.
* 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>
A worktree created from a PR whose branch ships an .envrc with a failing
direnv command bounced the user straight back to the Landing screen. The
worktree's only terminal spawns in the worktree cwd, direnv runs on shell
startup, fails, and the login shell exits non-zero immediately. The
single-pane PTY-exit branch routed that through onPtyExitRef ->
handlePtyExit -> closeTerminalTab, which deactivated the just-created
worktree (setActiveWorktree(null)) and rendered Landing.
The existing newborn-death guard only protected freshly-split panes, never
the first/sole terminal. Track the freshly-spawned ptyId (onPtySpawn fires
only for fresh spawns, never reattach/coldRestore) and, in the sole-pane
branch, keep the dead pane mounted when a never-interacted fresh spawn
exits. A typed 'exit' or a reattached-dead session still tears down as
before. Not gated on hasReceivedPtyOutput on purpose: a failing direnv
prints its error first, so output is received.
Co-authored-by: Orca <help@stably.ai>
* fix(terminal): scope skill-install terminals to the floating runtime selector (#6789)
Inline setup/onboarding terminals (skill installers, feature tips) create a
PTY under a synthetic per-panel worktree id. On a remote runtime the remote
PTY transport sent that id as `id:<panel>` to `terminal.create`, which the
runtime cannot resolve, so installing a skill via Settings failed with
selector_not_found. Locally it worked because the IPC spawn uses cwd directly
and never resolves the selector.
These terminals are ephemeral floating terminals with no backing worktree, so
brand their id and resolve it to the floating-terminal selector
(global-floating-terminal), which every runtime already maps to the home dir.
Local tab isolation keeps the distinct per-panel id; only the runtime terminal
selector changes. The server is unchanged.
* docs(terminal): add JSDoc for functions touched by the skill-install terminal fix
Document the new ephemeral setup-terminal id helpers, the runtime terminal
selector, and the onboarding/remote-transport entry points so the changed
functions carry contract-level docstrings.
---------
Co-authored-by: vladmesh <vladmesh@gmail.com>
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.
Restyle shared Sonner toasts (wrapping, bottom-chrome clearance with mobileOffset, custom delete-failure footer) and add a dev-only Dev Tools settings pane gated behind import.meta.env.DEV. Removes dead devToolsSearch locale keys, aligns toast description opacity/width with sibling toasts, and uses a distinct nav icon for Dev Tools.
* Show coding agent icons on mobile terminal tabs
Move agent title decoration and terminal title parsing utilities
from the desktop renderer to shared code for reuse on mobile.
* Extract agent title stripping and terminal agent resolution to shared
* Implement mobile agent identity resolution and title-cleaning helpers
* Render agent icons on mobile terminal tabs when an agent is active
* Strip leading status glyphs from tab titles when showing an icon
* Suppress PTY resize on resume for mobile-driven terminals
Avoid reasserting the PTY size on resume if desktop resizing is
suppressed. This prevents overriding the intentional drift from
desktop dimensions for parked or mobile-driven terminals.
GitHub rejects enabling auto-merge on a PR that is already mergeable
with a "Pull request is in clean status" error.
* Suppress "Enable auto-merge" option in the UI when direct merge
is available, while retaining "Disable auto-merge".
* Translate the GitHub "clean status" GraphQL error into an actionable
message recommending direct merge.
* Recommend Tailscale when the remote Orca runtime is unreachable
When a remote-runtime connection fails (RemoteRuntimeClientError "Could not
connect to the remote Orca runtime."), append an actionable Tailscale hint to
the user-facing error, branched on whether the endpoint is already on a tailnet:
- Non-Tailscale endpoint: recommend connecting both devices over Tailscale and
pairing with its Tailscale address, with a download link.
- Tailscale endpoint (*.ts.net or 100.64.0.0/10): point at the real causes —
server offline on the tailnet, or Funnel reverted to tailnet-only — and note
that already-paired devices reconnect without re-pairing.
Applied at the desktop transport chokepoint (status probe, in-use calls, and
subscriptions — connection failures reject, so the hint is applied to the thrown
error, not just ok:false responses) and at the web client's connect/timeout
sites. New pure shared helper mirrors withMacTailscaleDnsHint.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Address review: scope CGNAT hint to IPv4 literals, track re-paired endpoint
- isTailscaleEndpoint: gate the 100.64.0.0/10 check on a full IPv4 literal so
DNS names like 100.64.0.1.example.com no longer get tailnet-specific advice.
- callRuntimeEnvironment: capture the endpoint the queued closure actually used,
so a re-pair between enqueue and dispatch can't append the wrong hint.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Recognize Tailscale IPv6 endpoints and trailing-dot FQDNs in hint
The remote-runtime Tailscale hint classified IPv6 Tailscale nodes
(fd7a:115c:a1e0::/48) and trailing-dot FQDNs as non-Tailscale, so a
user already reaching their server over Tailscale by IPv6 literal was
wrongly told to 'connect both devices to Tailscale'. Pairing endpoints
can carry bracketed IPv6 literals (resolvePairingEndpoint), so this is
a reachable path. Normalize the extracted host (strip brackets and the
trailing FQDN dot) and add an IPv6 ULA-range check.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: s546126 <268420947+s546126@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
Register Helium (imput, bundle id net.imput.helium) so it is auto-detected
and importable from the picker on macOS like Chrome/Edge/Arc/Brave/Comet,
instead of only via the "From File..." JSON path. Reuses the existing macOS
Chromium decryption path unchanged.
Helium quirks (verified on a real install): its Keychain entry is service
"Helium Storage Key" / account "Helium" (not the usual "<Browser> Safe
Storage"), it stores data under its bundle id, and it keeps cookies at the
legacy <Profile>/Cookies path. Mac-only for now; Windows/Linux data dirs are
unverified so winRoot/linuxRoot are omitted and the source defaults off there.
Claude-Session: https://claude.ai/code/session_01SZzX6ktZaaCUg5PWnzniuC
* Add worktree labels for localhost ports
* Fix localhost label test fixtures
* Extend localhost worktree labels to agents
* Address localhost label review feedback
* Remove localhost label page injection; stream responses untouched
Drop the title/favicon HTML injection from the localhost label proxy.
The proxy now only relabels the hostname and pipes responses straight
through, so app CSP/cookies/bodies are preserved and large/streamed
responses are no longer buffered in memory.
- Normalize wildcard bind hosts (0.0.0.0 -> 127.0.0.1, :: -> ::1) before
using them as a proxy connect target.
- Delete the favicon SVG generator and dead repoIcon/badgeColor plumbing.
- Remove orphaned LocalhostLabelMock i18n keys.
Co-authored-by: Orca <help@stably.ai>
* Harden localhost label proxy from review
- IPC register: restrict proxy target to loopback or a matching scanned
workspace port (close open-proxy/SSRF surface from untrusted renderer).
- Proxy: guard against ERR_HTTP_HEADERS_SENT on mid-stream upstream error;
add error/cleanup listeners on client request/response and upgrade socket.
- labelLocalhostUrl: fall back to the raw URL when the proxy rejects a
target (e.g. https) instead of throwing.
- Port label route: include worktreePath so button-open and terminal/CLI
paths produce the same label.
- Terminal OSC link hover: discard stale async tooltip results via a hover
token, matching the WebLinks path.
Co-authored-by: Orca <help@stably.ai>
* Match default-port advertised hosts in localhost label target check
Co-authored-by: Orca <help@stably.ai>
* Consolidate duplicated localhost label helpers
- Move the loopback host set, loopback-URL parser, and wildcard
connect-host normalizer into shared/localhost-worktree-labels.ts; proxy,
runtime, terminal link routing, and the IPC guard now share one copy.
- Extract the port -> repo -> worktree -> project label-route lookup into
workspace-port-localhost-label-selector.ts (a hook plus an imperative
resolver), replacing the block triplicated across the ports surfaces.
Co-authored-by: Orca <help@stably.ai>
* Extract command-code prompt-status seed to its own module
Keeps launch-agent-in-new-tab.ts under the max-lines limit after the
localhost-hint additions, without a lint disable.
Co-authored-by: Orca <help@stably.ai>
* Remove agent-facing localhost mechanism
Orca does not mutate user prompts or inject prompt snippets, so drop the
localhost-open agent hint entirely:
- Remove appendLocalhostOpeningHint / includeLocalhostOpeningHint and the
hint constant from tui-agent-startup; agent prompts are no longer rewritten.
- Remove the ORCA_LOCALHOST_OPEN env var from local agent terminals (it was
only discoverable via the now-removed hint).
- Remove the orca localhost label|open CLI commands, their workspacePorts RPC
methods, and the runtime labelLocalhostUrl/openLocalhostUrl methods.
The feature is now purely structural: the loopback label proxy plus the
ports-panel and terminal-link 'Open in Browser' paths, which surface a
clickable labeled URL without touching agent prompts.
Co-authored-by: Orca <help@stably.ai>
* Make localhost worktree labels opt-in (default off)
Serving a dev app under a different host than localhost:<port> can break
apps that bind cookies/sessions to localhost, so the feature should not
change existing users' Open-in-Browser behavior on upgrade.
- Default localhostWorktreeLabelsEnabled to false.
- Flip the gates to enable only when explicitly true (=== true / !== true)
instead of treating undefined as enabled.
- Update the setting switch to checked only when explicitly enabled.
- Drop the now-unused runtime store settings field.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
* fix(terminal): converge post-spawn PTY size reconcile to fix split-mount column desync
Follow-up to #6644/#6649. Those added a post-spawn PTY reconcile but capped
it at a FIXED 12 requestAnimationFrames whose counter advanced even on frames
where the pane was unmeasurable or the split layout had not yet equalized. When
a tab MOUNTS with a split layout already present (a new worktree opened with the
side split panel on), the real narrow split width settles AFTER frame 12, so the
reconcile gave up while xterm had reflowed narrow and the PTY stayed pinned at
the wide spawn width. The corrective xterm onResize is dropped during the hidden
mount window (isRendererPtyResizeAuthoritative() is false), so process.stdout.columns
stayed wide and interactive TUIs (Claude Code) rendered garbled until a manual resize.
Extract the reconcile into pty-size-reconcile.ts with an authoritative-gated
convergence loop instead of a fixed frame budget:
- While the pane is hidden (onResize dropped), the reconcile is the SOLE corrector:
it keeps polling and forwarding every grid change (its transport.resize bypasses
the visibility gate). Hidden frames never advance the settle counter.
- Once visible AND stable for SETTLE_FRAMES, it hands off to the live
onResize/ResizeObserver path, which reliably catches any later reflow.
- Hard cap (MAX_FRAMES) guarantees termination; mobile-fit/locked frames are
skipped; the reconcile handle is cancelled on dispose.
Harness: pty-size-reconcile.test.ts (14 tests) drives the loop with a deterministic
frame scheduler; the desync-repro tests fail against the old 12-frame logic and
pass on the fix. Adds an e2e "MOUNTS with a split layout present" test.
Caveat: headless Electron does not reproduce this layout-settle-after-rAF race
(the existing golden e2e passes even against the old buggy logic), which is why
#6644/#6649 merged with green e2e yet the bug persisted. The unit test is the
real regression harness; the e2e tests are integration smoke.
Made with [Orca](https://github.com/stablyai/orca)
Co-authored-by: Orca <help@stably.ai>
* fix(terminal): re-assert PTY size on visibility resume to heal stubborn column desync
Follow-up within the same fix: the user reported "sometimes even resizing
doesn't fix it." Root cause beyond the mount-timing race — the renderer forwards
resizes fire-and-forget and dedupes on the size it THINKS it sent, but never
learns the PTY's actual size. A resize dropped main-side (the pane was hidden,
a mobile take-back resize-suppression window, or a provider no-op) leaves xterm
and the PTY silently diverged; a later same-cols layout fires no onResize, so it
never self-corrects.
Expose the PTY's last-APPLIED size to the renderer and re-assert on show:
- New read-only IPC pty:getSize -> ptySizes.get(id) (the size written only when
a resize actually lands or at spawn — the authoritative "what the PTY believes
it is"). Wired through preload (window.api.pty.getSize) + api-types.
- On visibility resume (noteVisibilityResume), the pane re-fits, reads the PTY's
real size, and re-asserts via forwardPtyResize ONLY on genuine drift — so no
spurious SIGWINCH on an already-synced resume (which would jar alt-screen TUIs).
Routed through forwardPtyResize so the authoritative/mobile gates are
re-checked at send time; remote-runtime PTYs (separate viewport channel) are
skipped; overlapping resumes coalesce to one query.
Also register pty:getSize in the registerPtyHandlers removeHandler cleanup block
so re-registration (macOS re-activate / new window) doesn't throw on a duplicate
ipcMain.handle, and make the pty IPC test mock throw on duplicate channels like
real Electron so this class of omission is caught going forward.
Tests: 7 resume-reassert cases (drift / match / null / remote-skip /
mobile-fit-skip / hide-during-hop / overlap-coalesce), all non-vacuous. Full
terminal-pane + pty IPC suites green (1494 tests); typecheck (web+node) + oxlint
clean; e2e desync specs pass against a fresh build.
Made with [Orca](https://github.com/stablyai/orca)
Co-authored-by: Orca <help@stably.ai>
* Stub PTY getSize API and skip redundant Wayland GPU sandbox tests
- Implement PTY `getSize` stub in `web-preload-api.ts` to satisfy API
requirements for the web-preload environment.
- Skip the unfixed Wayland GPU sandbox negative control test if the
target base branch already contains the sandbox workaround.
---------
Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>