Move toggle heading commands (toggle-h1..h4) into their own "Toggle headings"
group, separate from plain heading commands, so the slash menu distinguishes
static headings from collapsible toggle headings. Also shorten toggle heading
labels from "Toggle Heading N" to "Toggle HN" for consistency with the plain
heading labels.
* fix(sidebar): map header drags to the nearest boundary slot instead of a dead zone
Fixes#8879
* fix(sidebar): bound header edge drops to measured content
The native-smoke job ran bare `pnpm vitest run <files>` with no --config.
There is no root-level vitest/vite config, so vitest fell back to its
built-in defaults: a 5000ms testTimeout and no Windows worker cap. The
real csc.exe launcher-compile tests (build-windows-cli-launcher and
ssh-remote-cli-launcher) cold-start in 1.4-7.4s on hosted Windows and
tipped over the 5s default, failing the PR check.
Pass --config config/vitest.config.ts like every other vitest invocation
in the repo (pnpm test, pr.yml) so the job gets the shared 30s timeout and
Windows maxWorkers:4 cap (less CPU contention -> lower compile variance).
Positional file args still filter to just the listed files.
* feat(mobile): edit saved host endpoints
* fix(mobile): reject ambiguous numeric host addresses
* fix(mobile): label edit host inputs
* fix(mobile): make host edit save atomic and remove superseded mutators
Two independent review rounds found the same class of foot-gun: a
superseded mutator (updateHostEndpoint, then renameHost) left in
host-store.ts after the atomic updateHostNameAndEndpoint refactor, with
zero remaining callers. Either could be reintroduced by a future caller
and silently regress the non-atomic name/endpoint race the atomic
function was written to close, so both are removed.
Also covers reconnect-rejection and endpoint-only save paths that were
missing test coverage, and merges origin/main (#8789) so this lands
without reverting the mobile terminal restore fix.
Co-authored-by: Orca <help@stably.ai>
* Simplify save-race comment and reword host-removed error message
- Trims the redundant comment explaining the savingRef race guard down
to one line.
- Changes the "no longer saved" load-error copy to "was removed" for
clearer phrasing, updating the matching test expectation.
---------
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
* fix: address pr-bug-scan validated finding from #6335
Replaced unguarded crypto.randomUUID() with the established createBrowserUuid() fallback in both markup shape-id call sites.
* fix(review): cover markup crypto fallback call sites with regression tests
PR #8804 replaced two unguarded crypto.randomUUID() calls with
createBrowserUuid(), matching the convention established in #5471, but
shipped without extending that PR's regression-test pattern to the new
call sites. Add hook-level tests for useMarkupPointerHandlers.onPointerDown
and useMarkupEditor.commitPendingText under a simulated non-secure-context
crypto global.
Co-authored-by: Orca <help@stably.ai>
* fix(review): consolidate annotation bridge token onto createBrowserUuid
BrowserPane had its own inline crypto.randomUUID guard with a
Date.now()+Math.random() fallback, duplicating the getRandomValues-based
fallback already centralized in createBrowserUuid() and used at every
other call site in the renderer.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: orca-bug-scan-bot <orca-bug-scan-bot@stably.ai>
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
* docs(readme): fix Korean translation issues
* docs(readme): align agent list with English README across translations
Replace OpenClaude with Pi in Spanish, Japanese, and Chinese READMEs
to match the English README's agent list (Codex, ClaudeCode, OpenCode, Pi).
Portuguese already had the correct list.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
* Reopen closed tabs with Cmd+Shift+T, including terminals
Cmd/Ctrl+Shift+T now reopens the most recently closed tab of any kind
(terminal, browser, or editor) in true recency order, walking back
through close history on repeated presses — Chrome/Ghostty semantics.
Terminal tabs previously had no reopen at all, and the shortcut picked
browser-vs-editor by focused surface instead of recency.
Terminal closes capture a snapshot (startupCwd, shellOverride,
customTitle, color) on explicit user closes only; PTY exits, launch
cleanup, onboarding, and floating-panel closes are excluded. Reopen
spawns a fresh shell through the same createTab path as the "+" button.
A per-worktree cross-type kind list (new recently-closed-tabs slice)
orders the three existing per-type stacks; both new maps are purged on
worktree removal and re-keyed on rename with startupCwd prefix remap.
createNewTerminalTab moved to terminal-tab-create.ts to keep
terminal-tab-actions.ts under the max-lines budget.
Requested via Discord feedback; STA-1287.
Co-authored-by: Orca <help@stably.ai>
* Update close-tab assertions for captureRecentlyClosed flag
Floating-panel, launch-failure, and stale-agent-tab closes now thread
{ captureRecentlyClosed: false } so system-initiated closes don't pollute
the reopen stack. Update the affected test expectations to match the new
call shape.
Co-authored-by: Orca <help@stably.ai>
* fix(reopen): skip local terminal reopen on remote-runtime worktrees
reopenClosedTerminalTab recreated terminals via a raw local createTab
without the isWebRuntimeSessionActive guard every sibling terminal-create
path uses, so Cmd+Shift+T on a remote-runtime-owned worktree (paired-web
or desktop-attached headless-serve) spawned an unbacked local phantom tab
not tied to the host session. Guard at reopen time via the cycle-free
getRuntimeEnvironmentIdForWorktree — importing web-runtime-session into a
store slice creates a store-index init cycle. Remote terminal reopen stays
deferred; the cross-type dispatcher falls through to browser/editor.
Adds a regression test and corrects an inaccurate removal-path comment.
* fix(test): seed runtime host id for remote-reopen guard test
The prior test set settings.activeRuntimeEnvironmentId by spreading the
store's settings, which widened workspaceDir to string|undefined and broke
`pnpm typecheck` (GlobalSettings requires string). Seed the worktree with a
`runtime:` hostId instead — getRuntimeEnvironmentIdForWorktree reads it
directly, so no settings spread is needed.
* Remove unrelated formatter changes
---------
Co-authored-by: Orca <help@stably.ai>
* Fix PR checks sticking to a stale linked PR after a terminal branch switch
A worktree's linked PR is a branch-scoped hint, but two refresh paths race
when a terminal switches branches: the git-status identity path clears
branch-scoped review links, while the worktree-listing path rehydrates the
new branch together with the stale persisted link and clears nothing. When
the listing lands first (the common case — worktree listing is much faster
than git status), the identity path sees no branch change and the stale
link survives. Every subsequent refresh then re-fetches the linked PR by
exact number, which ignores the branch, so Checks stays pinned to the old
branch's PR and the Refresh button cannot recover.
Two-part fix:
- Prevention: listing refreshes now route observed branch switches through
updateWorktreeGitIdentity before merging, so the existing link clear and
tombstone machinery runs no matter which refresh path wins. Gated on the
entry still carrying branch-scoped review context so a stale listing row
cannot roll back a newer branch identity.
- Recovery: PRInfo now carries headRefName, and a fetch that returns the
linked OPEN PR whose head branch matches neither the current branch, the
worktree push target, nor the worktree HEAD clears the durable link and
re-resolves by branch. Wired into both fetchPRForBranch and the
background refresh coordinator, mirroring the merged-PR divergence clear.
This also heals wedged workspaces persisted by earlier builds.
* Harden stale PR recovery across refresh races
* Avoid duplicate PR recovery refresh work
* Index linked PR refresh aliases once
* fix(worktrees): stop surfacing prunable git worktrees as live workspaces
A worktree still registered in git but whose directory was deleted
(git's `prunable` state) was enumerated as a normal workspace,
producing repeated pty:spawn DaemonProtocolError / fs:readDir ENOENT
loops and a blank pane.
- Parse the `prunable` porcelain field (Git >= 2.36) in both the main
and relay worktree-list parsers.
- For Git < 2.36 (no `prunable` field), probe each linked worktree
path for existence on the fallback line-block path, skipping locked
registrations to mirror git's own prunable rules.
- Omit prunable worktrees from the detected-workspace enumeration only;
removal/cleanup flows keep seeing them.
- Extend the real-binary compatibility contract with the 2.36
`prunable` boundary.
Fixes#8389
Claude-Session: https://claude.ai/code/session_018Rg1Bpq4GGwmz613hq6RSD
* fix(worktrees): pin the prunable/locked porcelain annotations to their real Git 2.31 boundary
The prunable and locked annotations landed in Git 2.31, five releases
before `worktree list -z` (2.36); only -z defines the capability
fallback boundary. Correct the compatibility contract so a future
matrix entry in the 2.31-2.35 range passes, and reword the fallback
comments: on 2.31-2.35 the annotations still parse and the existence
probe is a backstop; only Git <2.31 relies on it outright.
* fix(worktrees): omit prunable registrations from the Space scan
A prunable registration has no directory to size or reclaim, so Space
rendered it as a dead "Missing" row whose checkbox stayed disabled with
no prune/remove affordance (reported on macOS after a reboot cleared
/private/tmp under 16 registrations). Skip prunable entries in the scan,
matching the workspace enumeration; removal flows list worktrees
separately and still see them.
---------
Co-authored-by: kaynan <kaynan.camargo@terceiro-sky.com.br>
Co-authored-by: Brennan Benson <79079362+brennanb2025@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(crash-reporting): coalesce repeated renderer error breadcrumbs
The crash-breadcrumb ring holds only 30 entries, and renderer breadcrumbs
arrived via the plain uncoalesced path. A repeating renderer error — like the
ResizeObserver and SSH-rejection storms in #8260 (50+ identical events) —
flushes the entire ring in seconds, erasing the pre-crash trail exactly when a
crash report is about to snapshot it.
Route renderer_error and renderer_unhandled_rejection breadcrumbs through the
existing recordCoalescedCrashBreadcrumb, keyed by name plus message prefix
with a 30s window. Repeats collapse into one entry carrying
suppressedSinceLast, so a storm shows up as "error X fired N times" alongside
29 slots of surrounding context instead of 30 identical copies.
Other breadcrumb names keep the plain path; telemetry trace spans are
unchanged. Renderer breadcrumb routing tests move to a focused
crash-reporting-renderer-breadcrumbs.test.ts (the main suite is at the
max-lines ceiling).
* fix(crash-reporting): coalesce durable renderer traces
* fix: harden renderer crash guards
* fix(crash-reporting): preserve distinct error sources
* fix(ssh): AND lsof selectors so relay reset can't kill unrelated processes
`forceStopRelayForTarget` resolved PIDs with `lsof -t -U "$sock"`.
lsof ORs its selectors by default, so this selects every process holding
ANY unix socket in addition to the socket's owner. On hosts where lsof
cannot match AF_UNIX sockets by path, the path term matches nothing and
the sweep degrades to all unix-socket holders — including systemd --user
— which the TERM+KILL loop then takes down (#8762).
Add `-a` to AND the unix-socket and path selectors: where lsof supports
path matching the result is exactly the socket's holders (verified: 1
PID with -a vs 97 without on macOS); where it doesn't, the result is
empty and the existing pgrep fallback — scoped to command lines that
reference the relay's per-instance socket name — takes over.
Fixes#8762
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* review: add relay reset process-safety coverage
Exercise the generated POSIX reset script with controlled lsof and pgrep commands plus a mocked kill, and keep the selector rationale concise.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
* fix(native-chat): deliver AskUserQuestion answer by option number, not label
Claude Code's AskUserQuestion is an arrow-navigate selector: a bare Enter
commits the HIGHLIGHTED default (the first option), and pasted label text
does not move the highlight. Native chat delivered the answer as the option
LABEL text + Enter, so every non-first pick was silently committed as the
first option (STA-1860 Bug A).
Deliver the answer by each option's stable 1-based number instead (the marker
the selector commits on), paced so each keystroke lands after the selector
step renders. Covers single/multi-select, multi-question, and free-text
('Type something') answers. Verified live against Claude Code 2.1.210.
- native-chat-interactive-prompt.ts: buildAskAnswerKeys / AskAnswerSelection /
AskAnswerKeyGroup / hasAskAnswer; formatAskAnswer kept (index-based) for the
non-Claude label path
- native-chat-runtime-send.ts: sendNativeChatAskAnswer (paced keystroke groups)
replaces the old label-line sender
- card + interactive hook thread structured { indices, other } selections
STA-1860
* fix(native-chat): harden AskUserQuestion answer lifecycle
* fix(native-chat): stop stale paced answer writes
* fix(native-chat): cancel paced answers during commit
Cancel stale AskUserQuestion timers before replacement UI can commit, and localize the multi-step sending state in every shipped locale.
* fix(gitea): share and cache the /pulls scan so card refreshes can't hammer a self-hosted forge
Each worktree card resolved its branch by paginating the repo's full
/pulls listing (state=all, up to 5 pages) independently, with a 5s
timeout. Gitea/Forgejo have no head-branch filter, self-hosted Forgejo
takes ~5s per page (it loads reviewer data per PR), and a push event
refreshes every card at once — so one repo turned into hundreds of
near-simultaneous requests whose responses were aborted right as they
completed, and the burst OOM-killed a 512Mi Forgejo pod (#8807).
- Share one in-flight /pulls scan per repo across concurrent branch
lookups and cache the result for 30s, so a burst costs a single page
walk instead of one per worktree.
- Raise the list-scan timeout to 15s so slow-but-successful pages are
used instead of discarded and retried.
- Invalidate the cached scan after Orca itself creates a PR (and before
the post-create fallback lookup) so the new PR is seen immediately.
Fixes#8807
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(gitea): don't let an in-flight scan re-cache a listing from before an invalidation
Review feedback: if a PR is created while a card-refresh scan is in
flight, the invalidation cleared the cache but the scan then finished
and re-cached the pre-create listing, hiding the new PR for a full TTL.
Track a per-repo generation: invalidation bumps it (and drops the
in-flight promise), and a scan only writes the cache when its generation
is still current.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(gitea): bound PR scan cache and retry failures
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
* Preserve original markdown style when editing in the rich editor
The rich markdown editor re-serialized the whole document into a canonical
style on the first edit, so a one-character change rewrote every non-canonical
construct (`_x_`->`*x*`, `__x__`->`**x**`, `* item`->`- item`, dropped trailing
newline) and produced a large, unexpected diff.
Reconcile the canonical getMarkdown() output back toward the original source
bytes at every disk-bound serialize site (debounced autosave, flush, Cmd/Ctrl+S)
so untouched regions keep their markup and only the edited region changes. A
divergent-base fuzzy patch (diff-match-patch) carries the user's edit onto the
original style; a safety re-parse requires the result to render-equal what the
editor shows, else it falls back to today's canonical output — so content
semantics can never change. The rich editor stays the default for markdown.
Fixes#6080
* Bound markdown reconciliation diff work
* 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: close Add Project host selector when an SSH credential prompt appears
The SSH password/passphrase dialog (z-50) renders below popovers (z-60),
so a host selector left open during a connect attempt covered the
credential prompt and its input field. Close the selector as soon as a
credential request is queued so the modal prompt is visible and usable.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: raise SSH credential prompt above popovers instead of closing the host selector
The credential prompt is a preemptive global modal, but DialogContent's
default z-50 sits below popovers (z-60) and menus (z-70), so any surface
left open while a prompt fires covers the focused password input — the
Add Project host selector was one reproducible case (#8490).
Raise the prompt's overlay/content to the frontmost tier (!z-[140] /
!z-[150], matching link-routing-preference-dialog) so it renders above
every popover/menu, and drop the host-selector close effect: the picker
can stay open and is available again after cancel for an immediate retry.
---------
Co-authored-by: jonghoonlee <jonghoonlee@inocras.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* fix(agent-status): reap finished Claude named agents/teammates from the sidebar roster
#8522 stopped one-shot subagents from squatting as idle rows, but named
background agents (Workflow/orchestration/ultracode lanes and agent-teams
teammates) still piled up permanent "Idle - <type>" child rows for the rest
of the session — the reported regression (11 idle rows under an
"Orchestration Messages" pane, all idle 3-6h after finishing).
Root cause, confirmed against live hook captures (claude 2.1.210): named
agents get teammate-shaped ids (a<name>-<hex>) AND now appear in Stop's
`background_tasks` as `type: "teammate"` entries whose status stays "running"
forever — even after the agent finished. The old code read that shape as a
"resumable teammate", so SubagentStop only marked it idle and the fold never
reaped it (a present teammate task kept hasTeammateTypedTask true). The rows
never left.
Fix: the roster now tracks only WORKING children.
- SubagentStop removes the child outright (teammate-shaped or not) — it is
the reliable finish signal; the teammate task's "running" status is not.
- TeammateIdle removes by name as the fallback when a SubagentStop is lost.
- A lead Stop's background_tasks still reaps unlisted children: hyphen-free
one-shots always, and teammate-shaped rows once a complete inventory shows
no teammate-typed task at all. A live named agent whose id never appears is
kept only while a teammate-typed task is still present (the done-gate).
- Hydration drops persisted idle snapshots so a restart can't re-pile them.
Verified live in a dev Electron instance driving a real Claude TUI that spawns
four named background agents: pre-fix the pane resolves to done with four
persistent "Idle - <probe>" rows; post-fix each row disappears the instant its
agent finishes and the roster drains to empty (done, zero child rows).
Tests: roster + row-lifecycle + hook-listener suites rewritten to the
working-only semantics, grounded in the captured 2.1.210 hook stream
(126 passing). Typecheck + oxlint clean.
* fix(agent-status): prune persisted idle Claude children
* fix(agent-status): persist Claude idle-row hydration cleanup
* fix(agent-status): avoid ambiguous teammate idle cleanup
* fix(agent-status): reconcile replacement children at roster cap
Reconstruct remote POSIX commands with bounded printf arguments so non-POSIX SSH login shells can forward them without requiring remote base64. Preserve relay and system-SSH stdin, and centralize login-shell flag selection for csh/tcsh compatibility.\n\nValidated against real csh and tcsh OpenSSH targets with built-in and system SSH, including cold relay deployment, stdin upload, PTY I/O, file mutation, and reconnect.
Re-pairing a desktop that was already paired created a duplicate host card (STA-1840). Pairing now resolves the durable host identity by the desktop's pinned publicKeyB64 and reuses the existing id/name, collapses any already-stored duplicates for that key, clears stale relay overlays on a direct-only re-pair, fails closed on unreadable storage, and closes the host's client on pairing success so a reused id reconnects on the newly-paired endpoint. Mobile only. Full mobile suite (1,719 tests) + typecheck/lint/format pass.
* feat(ssh): support Kerberos/GSSAPI hosts via the system OpenSSH transport
ssh2 has no gssapi-with-mic support, and adding it would mean forking its
protocol layer plus packaging the kerberos native module for three
platforms. Instead, route GSSAPI hosts through the existing system-OpenSSH
transport, which delegates Kerberos (tickets, SSPI on Windows) to the
platform ssh binary.
Two tiers, because RHEL-family distros enable GSSAPIAuthentication
globally in /etc/ssh/ssh_config and ssh -G therefore reports it for every
host:
- Targets whose ~/.ssh/config Host block explicitly sets
GSSAPIAuthentication yes (imported as target.gssapiAuthentication) try
system ssh first, falling through to ssh2 so key auth and credential
prompts still work when no ticket is available.
- When ssh2 exhausts key/agent auth and the ssh -G-resolved config
enables GSSAPI, retry over system ssh before prompting for credentials,
so Kerberos-only hosts on distro-default configs connect without a
password prompt. Hosts where keys work never leave the ssh2 path.
Manual targets flagged for GSSAPI pass -o GSSAPIAuthentication=yes
explicitly since they bypass ssh_config. Both tiers work headless (no
credential callbacks required).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(ssh): harden GSSAPI transport selection (review fixes for PR #7507)
Review fixes on top of the Kerberos/GSSAPI feature branch (s546126/kerberos-ssh):
- HIGH: reset useSystemSshTransport on the ssh2 fall-through. doSystemSshProbe
sets the flag before spawnSystemSshCommand, which throws synchronously when no
system ssh binary is on PATH (outside the probe try/catch). The proactive
fall-through previously reset only 2 of 3 transport fields, so exec/sftp kept
routing through the failed transport - breaking GSSAPI on Windows-with-Git-ssh
and headless Linux.
- MEDIUM: throw a cancellation error (not the stale ssh2 authError) when a
disconnect supersedes the reactive probe mid-flight, and guard connect()'s
catch on disposed, so a deliberate disconnect is not overwritten with
auth-failed.
- MEDIUM: skip the encrypted-key passphrase prompt when the GSSAPI fallback
applies, so a Kerberos ticket is tried before prompting; the general prompt
still fires if the probe fails.
Adds 3 mutation-verified regression tests and hardens two existing tests to
assert the probe actually ran. Not connected to any PR remote.
Co-authored-by: Orca <help@stably.ai>
* fix(ssh): isolate GSSAPI system transport
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: s546126 <268420947+s546126@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
* 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.
- Replace the force-open Tooltip discovery hint with a Popover (title,
body, Try it/Got it actions) that can be dismissed via outside
click, Escape, or window blur — Radix's tooltip couldn't handle
Electron webview clicks, which never reach the renderer document.
- Gate the hint on a new `surfaceActive` prop so it only opens for the
visible pane, preventing a portaled layer from anchoring against a
hidden/zero-size trigger at the viewport origin.
- Add locale strings for the new badge/try/dismiss copy across all
supported languages and cover the hook's eligibility/dismiss
behavior with unit tests.
Two renderer paths let Promise rejections escape unhandled, where the
crash-breadcrumb system retains them and grows the heap until the renderer
white-screens (#8260):
- The terminal file-link provider fires a `void Promise.all(...)` whose async
mappers probe path existence over SSH. On disconnect these reject with
"Remote connection dropped/reconnecting"; add a `.catch` that resolves the
callback with no links so the rejection is handled.
- "ResizeObserver loop completed" is a benign Chromium quirk; suppress it in
crash-diagnostics so it stops filling the breadcrumb buffer and inflating
the renderer error count.
Scope note: the original fix for #8260 also proposed Windows pty:kill signal
handling (killPtyProcess / isPtyAlreadyGoneError) and a ResizeObserver
double-rAF. Those landed on main independently (Windows kill handling by
v1.4.135; the pane-title-overlay rect-equality guard from #2756 already breaks
the loop at the source), so this change carries only the two guards still
missing upstream.
Co-authored-by: Yixian HU <yixian@YixiandeMacBook-Air.local>
Co-authored-by: Claude <noreply@anthropic.com>
* fix(speech): resume interrupted voice model downloads and surface real errors
Voice model downloads that died late in the transfer (flaky networks,
scanning proxies) failed at the UI's 90% progress cap and restarted from
byte zero on every retry, so affected users could never complete any
model download (#8620 / STA-1775).
- Retry transient download failures (net::ERR_* transfer cuts, resets,
timeouts, HTTP 5xx/429) with an HTTP Range resume from the bytes
already on disk, restarting from the canonical URL each attempt since
signed CDN redirect URLs expire; bounded backoff, gives up after
repeated zero-progress attempts with a diagnosable error.
- Correct catalog sizeBytes to exact upstream asset sizes (parakeet
v2/v3 are 482/487 MB, not 170/180; paraformer is 1,047 MB, not 115) —
also the progress denominator when content-length is missing.
- Show the underlying failure cause in the download-error toast and log
it in main; it was previously invisible everywhere.
All 7 pinned catalog SHA-256 hashes verified against current upstream
assets. Live-verified end to end: a transfer killed mid-body resumes
with a 206 and completes through checksum, extraction, and validation.
Fixes#8620
* fix(speech): harden resumed model downloads
* fix(speech): validate resumed download completion
* fix(speech): keep segmented download resumes progressing
* fix(speech): don't abandon a download that keeps making progress
The retry loop gave up after 8 total failures regardless of progress, so
a large model (e.g. the ~1GB Paraformer) over a network that resets every
few hundred MB was abandoned mid-download even though every attempt was
resuming and advancing — the exact 'stuck then fails' symptom, just later.
Replace the all-time failure budget and the separate advancing-segment
cap with a single rule: give up only on a genuine stall (repeated
attempts with no forward progress) or an absolute request ceiling that
bounds a pathological tiny-segment server. Any download that keeps
advancing now runs to completion.
Verified live under Electron 43: a transfer reset mid-stream on every
attempt but advancing each time previously gave up at 67% after 8
attempts; it now completes. All prior resume/cancel/timeout scenarios
still pass.
* Add in-place screenshot markup for the browser pane
Adds a Draw button to the browser toolbar (local and remote/SSH panes) that
freezes the visible viewport into a still image and overlays a drawing canvas.
Users can mark it up with pen, highlighter, arrow, rectangle, ellipse, and text
(color, thickness, undo/redo), then copy the composited PNG to the clipboard to
paste into their agent — reusing the existing clipboard screenshot path, so it
works for local and remote agents alike.
The drawing layer is a renderer-side canvas, so the base image is the only
environment-specific piece: local panes use webview.capturePage(), remote panes
snapshot the already-displayed screencast frame. Drawing model, compositing, and
shape rendering are isolated, unit-tested modules.
* Fix browser markup clipboard, transparency, text input, and toggle
- Copy now always produces a PNG (the clipboard handler accepts PNG only;
a JPEG fallback silently produced an empty clipboard) and targets the
clipboard size limit so realistic viewports stay full-resolution.
- Flatten the captured base image onto opaque white so transparent page
backgrounds no longer ghost the live view through the frozen backdrop.
- Keep markup text-input keystrokes local (focus the field explicitly and
stop propagation) so the browser pane's global handlers can't swallow them.
- Make the Draw toolbar button toggle markup mode off on a second click.
* Add markup text sizing, object re-editing, and text-input fixes
- Add a font-size control (text tool) to the markup toolbar.
- Add a Select tool to re-edit committed shapes: click to select, drag to
move, change color/width/font-size of the selection, Delete to remove, and
double-click text to re-edit its content. Undo/redo now snapshots the whole
shape list so every edit (not just adding) is reversible.
- Fix the text input: transparent ink-colored field (no dark theme box over the
screenshot) and ignore Enter during IME composition so Japanese conversion no
longer commits the annotation early.
- Split the grown overlay logic into focused modules (editor hook, pointer +
keyboard hooks, canvas renderer, document edits, hit-testing) to stay within
the file-size budget.
* Improve markup text commit and move toolbar to the bottom
- Clicking elsewhere while a text box is open now commits the text and
switches to the Select tool, instead of discarding it (the input
unmounted before its blur fired) and opening a new box at the click.
- Move the drawing toolbar to the bottom (stacked above the actions bar)
and open the color/size popover upward.
* Fix markup text re-editing alignment, doubling, and selection bounds
- Hide the text shape being re-edited from the canvas so the live input
isn't doubled by its committed render.
- Drop the input's padding/border and use the same font as the canvas so
the typing preview sits exactly where the text renders (no shift);
field-sizing keeps the box hugging the text.
- Estimate text width with full-width (CJK) awareness so the selection box
and hit bounds track Japanese text instead of clipping it.
- Move the font-size control out of the color popover into its own toolbar
button showing the current size.
* Unify markup text selection and edit framing
Draw the selection box on the canvas even while a text shape is being
re-edited (the shape stays hidden so it isn't doubled), and strip the box
off the text input so it renders only the editable glyphs. The frame is now
identical whether selecting or editing, and the text overlays the same
position with the same font, so there's no shift between the two.
* Align markup text-edit baseline with the committed render
Force the text input's line-height to 1 and zero its height/padding so its
top edge matches the canvas textBaseline:'top' render — the editing text no
longer sits slightly lower than the committed/selected text.
* Address review: markup hit-testing, clear reset, and async guards
- Hit-test highlights against their rendered (4x) thickness so the select
tool doesn't miss large highlight strokes.
- Reset transient editor state (pending text, selection, in-progress drag) on
Clear all so a pending input blur can't re-add text and no stale selection
lingers over the emptied canvas.
- Guard markup completion with the capture token so a stale onDeliver can't
reset or error a session the user already cancelled or restarted.
- Skip the grab-element keyboard shortcut while the markup overlay is open,
matching the already-disabled grab toolbar buttons.
- Localize the markup error messages and surface them via a toast (the
controller's error was otherwise never shown).
* Trim browser markup to a draw-only tool and fix two delivery bugs
Reshape the screenshot-markup overlay to match its actual use — a throwaway
scribble the user copies once into their agent — and drop the re-editable
vector-editor machinery that made it an outlier in scope.
- Remove the select/move/restyle/re-edit subsystem: hit-testing, per-shape
document edits, drag preview, selection frame, double-click text re-edit,
Delete-to-remove, and the CJK text-width heuristic / multi-line text plumbing
that only fed selection bounds. Keep pen, highlighter, arrow, rect, ellipse,
and text with color/thickness/font-size and undo/redo/clear.
- Fix silent data loss on HiDPI: the compositor budgeted only bytes, so a large
Retina viewport could produce a PNG under the byte limit but over the
clipboard handler's pixel ceiling, which the handler drops silently while the
UI still reported success. Composite now scales down to fit maxPixels too.
- Fix a stranded UI after a capture failure: the controller stayed "active" with
no overlay and an inert Escape. It now returns to idle (capture failure) or
back to the drawing surface (compose failure) after the error toast.
Net ~660 fewer lines and 3 fewer modules; delivery (freeze → composite PNG →
existing clipboard-image paste) is unchanged. Unit tests updated.
* Add a one-time discovery hint for the browser Draw button
Highlight the new screenshot-markup Draw button the first time it's usable so
users — including existing ones — notice the tool. A localStorage-gated,
show-once tooltip + highlight ring, independent of the browser contextual tour
(which is capped at three steps and only shows to users who haven't seen it).
- New use-markup-draw-hint hook: opens once per install when the button is
enabled and idle, auto-dismisses, and dismisses on click; skips cleanly when
storage is unavailable.
- MarkupDrawButton forces its tooltip open with the discovery copy and rings the
button while the hint shows.
- Localize the hint (drawHint) across en/es/ja/ko/zh; normalize the markup key
order alphabetically.
* Align the markup copy button label with "Copy Screenshot"
The browser pane's two image-copy actions read inconsistently in English —
"Copy Screenshot" (the grab flow) vs "Copy markup" (the overlay). Match the new
markup button to the existing one: "Copy Markup". English only; other locales
already use their own sentence-case convention consistently, so they're
unchanged.
* Drop the markup Draw hint auto-timeout; dismiss on act only
The one-time Draw-button hint no longer auto-dismisses on a timer the user might
miss. It now stays open until the user clicks Draw or the button stops being
usable (grab started, markup open, or a blank tab), which also fixes a case
where losing eligibility mid-hint left the forced-open tooltip stuck over a
disabled button.
* Optimize markup rendering and harden compose delivery
Performance:
- Rasterize committed shapes once into an offscreen layer; per-frame paint blits
that layer and draws only the in-progress shape, so a fast pointermove stream
never re-strokes the whole scene.
- Encode the composite PNG off the main thread via canvas.toBlob (a large
synchronous toDataURL froze the renderer), with a rAF yield so the "composing"
UI paints first.
Correctness / review fixes:
- Don't overwrite the clipboard after cancel: re-check the capture token between
the (now async) compose and onDeliver, closing the window where Escape during
composing still delivered the markup.
- Keep the offscreen layer and visible canvas in lockstep by threading one
measured devicePixelRatio through both paints, and re-measure on window resize
so a monitor move (dpr change without a CSS-box change) repaints at the new
scale. Blit stretched to the canvas box so a transient mismatch never clips.
- Drop the stale "or JPEG fallback" note; compose is PNG-only.
* Fix test fixtures for markup pixel-budget clamping to use a truly oversi
The old 3840×2160×dpr2 fixture (33.2M px) was already under the 32×1024×1024
(~33.55M) ceiling, so the "never over budget" assertions weren't exercising
the clamp path. Switch to 4000×3000×dpr2 (48M px), which genuinely exceeds
the ceiling.
---------
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>