Commit Graph
7603 Commits
Author SHA1 Message Date
OrcaWinandOrcaWin ab665a3ce7 fix(remote): preserve terminal recovery across control refresh (#11513)
* fix(remote): recover stalled terminal streams

* fix(i18n): localize manual disconnect error

* fix(remote): park paired terminals with host snapshots

* test(remote): mock authoritative resync snapshots

* fix(terminal): defer startup mounts until hydration

* fix(remote): raise paired terminal stream capacity

* fix(remote): harden terminal recovery lifecycle

* fix(remote): preserve calls across control refresh

* test(remote): harden paired recovery oracle

* test(workspace): seed Jira source context

* test(remote): assert raw host terminal identities

* test(terminal): keep restore sentinels atomic

* test(terminal): keep restore sentinel on one row

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-07-30 03:05:10 -07:00
Jinjing 9eede0084d fix(relay): refuse silent fallback when pairing invite fails (#11528)
* fix(relay): refuse silent fallback when pairing invite fails

When Orca Relay pairing fails, don't silently degrade to a LAN-only QR under the Relay label. Instead, surface structured failure information so the UI can clearly inform the user and offer recovery options.

* fix issues
2026-07-30 02:13:47 -07:00
Neil 0fe1278244 fix(sidebar): stop background workspace creation from scrolling the sidebar (#11530)
* fix(sidebar): stop background workspace creation from scrolling the sidebar

Creating a workspace in the background still spawns its terminals, and the
renderer treated "no presentation stated" as "point the user at this
terminal" -- revealing (scrolling to) the owning workspace.

Split adoption from surfacing with an explicit surfaceOwner flag: background
worktree creates and worker dispatch adopt their tabs silently, while
`orca terminal create` keeps its discoverability reveal.

* fix(sidebar): keep split-mode setup panes silent, tighten surfaceOwner

Review catch: with setupScriptLaunchMode split-vertical/horizontal the Setup
terminal goes through splitTerminal, whose reveal payload had no surfaceOwner,
so a background create still scrolled the sidebar in that configuration.

Also narrow surfaceOwner to `false` so "surface it" can only be expressed by
omitting the key, and fold the repeated conditional spreads into ownerSurfacing.
2026-07-30 02:08:01 -07:00
NeilandOrca 5f642841fd fix(worktrees): stop terminals after external deletion (#11237)
* fix(worktrees): stop terminals after external deletion

* fix(worktrees): request teardown per caller and revalidate uncached

Two defects let the original fix silently strand PTYs:

- teardown rode the scan's coalescing promise, so any caller that joined an
  in-flight scan purged its renderer state without ever asking for a sweep;
  it now runs per caller against its own known-id snapshot, deduped on the
  request it actually produces so fan-out still shares one host sweep.
- the runtime's authoritative recheck was served from the 30s worktree-scan
  cache, which can still list a directory git already dropped. The renderer
  purges either way, so a stale miss leaked those processes permanently.

Co-authored-by: Orca <help@stably.ai>

* perf(worktrees): enumerate the host once per teardown sweep

An agent cleaning up N workspaces made killAllProcessesForWorktree issue one
full provider enumeration per missing worktree: O(N) relay round-trips carrying
O(N^2) rows. At 30 worktrees over an 80ms-RTT SSH link that is 30 scans and
~1.3s of stalled teardown; it scales linearly from there.

Share one point-in-time process list across the sweep — every worktree in it is
already known-missing, so a single snapshot answers all of them. A failed scan
is never shared: it falls back to a per-caller scan so one transient relay error
cannot suppress the sweep for the whole batch. Pinned requirePhysicalStop:false
since that path re-lists after shutdown and must not read a pre-shutdown snapshot.

Co-authored-by: Orca <help@stably.ai>

* test(worktrees): pin the disconnected-SSH no-teardown invariant

main's new directSshAuthority gate bails before any refresh when an SSH target
is not connected. That is exactly the #10562 safety rule — "host unreachable"
must never be read as "worktree deleted" — so pin it: a disconnected target
issues no teardown RPC and keeps its renderer state.

Co-authored-by: Orca <help@stably.ai>

* fix(worktrees): keep selector grammar intact when scoping by connection

resolveRepoSelectorForConnection matched the selector as a bare repo id, so an
explicit connection identity silently changed the grammar: `path:` and `name:`
selectors resolved to repo_not_found on that path alone, losing the whole sweep.
A connection identity should only *narrow* the candidate set.

Extract the selector matching both paths now share, and stop re-resolving an
already-resolved repo: teardown rescanned via `id:<repo.id>`, which throws
selector_ambiguous when an id is duplicated across hosts even though the
caller's own selector was unambiguous.

Reported as a P2 by Greptile (as redundant work); it is load-bearing.

Co-authored-by: Orca <help@stably.ai>

* fix(worktrees): keep the shared snapshot out of provider internals

The snapshot proxy passed itself as the Reflect.get receiver, so prototype
methods invoked through it ran with `this` bound to the proxy. A provider whose
own shutdown() re-read state via `this.listProcesses()` would then silently get
this sweep's cached snapshot instead of the live host — batching leaking past
the calls it was built for.

Bind non-listProcesses members to the target so only the sweep's own calls share
the snapshot. No shipped provider does this today; the point is that adding one
must not quietly change teardown semantics.

Raised by Greptile as an undocumented implicit constraint; closed structurally
rather than by comment.

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Orca <help@stably.ai>
2026-07-30 02:05:13 -07:00
Jinjing d0d86958ed feat(settings): clarify Cloud VM setup (#11527) 2026-07-30 01:32:25 -07:00
NeilandOrca ab2b517cf9 perf(terminal): serialize checkpoints with one payload walk (#11422)
* perf(terminal): serialize checkpoints with one payload walk

Co-authored-by: Orca <help@stably.ai>

* fix(terminal): bound checkpoint serialization

Co-authored-by: Orca <help@stably.ai>

* test(terminal): correct bounded serialization proof

Co-authored-by: Orca <help@stably.ai>

* test(terminal): cover over-limit multibyte checkpoints

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Orca <help@stably.ai>
2026-07-30 01:14:12 -07:00
NeilandOrca 37af457752 fix(daemon): split router subscription fanout (#11490)
Co-authored-by: Orca <help@stably.ai>
2026-07-30 00:49:40 -07:00
64a1269409 perf(orchestration): bound mutation ledger and run pages (#11432)
* perf(orchestration): bound mutation ledger and run pages

Co-authored-by: Orca <help@stably.ai>

* fix(orchestration): close retention pagination gaps

* fix(orchestration): preserve unpaginated run listing

Co-authored-by: Orca <help@stably.ai>

* fix(orchestration): reject malformed run cursors

---------

Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
2026-07-30 00:49:23 -07:00
Neil 3bc9355edd fix(ui): right-align Project detail in new workspace combobox (#11521)
Match Run on field layout so short provider details like stablyai/orca
sit on the far right of the committed Project field instead of next to the name.
2026-07-30 00:37:43 -07:00
Jinjing a60aa85592 fix: make remote server pairing failures actionable (#11510)
* fix: make remote server pairing failures actionable

* refactor: extract daemon router event types

* fix: address remote pairing review findings

* fix: address final remote pairing review feedback
2026-07-30 00:31:34 -07:00
NeilandOrca 191fdf2ae6 fix(runtime): skip unreadable Windows drives (#11421)
Co-authored-by: Orca <help@stably.ai>
2026-07-30 00:23:24 -07:00
561e2d32cd fix(floating-workspace): persist Markdown tab renames (#11398)
* fix(floating-workspace): route markdown renames locally

* test(floating-workspace): strengthen rename regression

* test(floating-workspace): verify rename restart persistence

* fix(filesystem): serialize local rename destinations

* fix(filesystem): serialize Unicode rename aliases

* fix(filesystem): align rename locks with native aliases

* fix(filesystem): canonicalize rename parent locks

---------

Co-authored-by: Dzmitry Bachko <dbachko@users.noreply.github.com>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-07-29 21:43:45 -07:00
Rod BoevandBrennan Benson 38e9581758 fix(editor): save rich-markdown preview edits on blur, switch, and quit before the serialize debounce (#9730) (#9823)
* fix(editor): flush markdown preview saves before teardown (#9730)

* fix(editor): keep rich markdown blur saves policy-safe

---------

Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
2026-07-29 20:46:26 -07:00
Brennan Benson f37b9f63d3 fix(terminal): log pane recovery at warn, not error (#10796)
* fix(terminal): log pane recovery at warn, not error

STA-2373 made this path routine: every daemon death now remounts each live
pane, so error level floods logs and crash telemetry with a message that
reports recovery *succeeding*. The breadcrumb right below is what
diagnostics actually consume.

* fix(terminal): correct recovery log comment and test console spy

The comment claimed error level floods telemetry; nothing forwards renderer
console into telemetry, and the breadcrumb below is untouched, so this change
alters telemetry volume by zero. The test's console.error spy silenced the
old call site and now stubs nothing, leaking 26 stderr lines under verbose.
2026-07-29 20:39:54 -07:00
Brennan Benson f908ba38bc Fix stale agent icons in terminal tabs (#11484)
* fix(tabs): prefer retained agent identity for icons

* test(tab-bar): include retained agent store state
2026-07-29 20:27:47 -07:00
NeilandOrca 0c861d79b5 fix(daemon): hand off slept v29 history to v30 (#11423)
Co-authored-by: Orca <help@stably.ai>
2026-07-29 20:20:11 -07:00
Brennan Benson f8b553b7d5 fix(agent-hooks): skip unavailable agent homes (#11442)
* fix(agent-hooks): skip unavailable agent homes

* refactor(agent-hooks): separate Pi and OMP home fix

* test(agent-hooks): update merged protocol harnesses

* fix(agent-hooks): avoid redundant reconciliation

* fix(agent-hooks): harden reconciliation and detection

* test(agent-hooks): cover settings reconciliation

* fix(agent-hooks): hydrate PATH for paired clients
2026-07-29 20:19:18 -07:00
Brennan Benson f0eca5fe32 fix(sidebar): isolate runtime reconnect refreshes (#11472) 2026-07-29 20:10:44 -07:00
OrcaWin bf894ef150 fix(remote): recover and safely park paired terminals (#11416) 2026-07-29 20:04:55 -07:00
Neil 8ad9448905 revert: restore pre-worker process boundaries (#11481) 2026-07-29 20:01:31 -07:00
Brennan Benson fa2f5de7da feat(feedback): attach images to feedback submissions (#10465)
* feat(feedback): attach images to feedback submissions

Users pasting screenshots into the feedback dialog were silently dropped:
the textarea had no paste handler, the IPC payload had no image field, and
the endpoint had nowhere to put one. Reports arrived saying "images
attached" with nothing attached, which is why feedback-sourced tickets
never have a screenshot to work from.

Adds paste, drag-drop, and a file picker with thumbnail previews (up to 4
images, 8 MB each, png/jpeg/webp/gif). Rejected files raise a toast rather
than disappearing — silent loss is the bug being fixed.

Images ride the existing multipart lane, which previously activated only
for crash diagnostic bundles. Crash submissions still drop images; that
lane already carries bundles and the server rejects them there.

When the server reports imagesDelivered: false the dialog says the
feedback sent but the images did not, instead of a blanket success. A 2xx
without the field counts as delivered so this keeps working against a
server that predates the field.

Requires the marketing-site half to deploy first.

* copy(feedback): shorten attachment hint to 'Attach up to 4 screenshots'

* fix(feedback): make dropped screenshots actually attach

Three defects that discarded a user's image without telling them — the exact
failure this feature exists to fix.

Drag-and-drop never worked. `DataTransfer.files` is empty until the drop
lands, so the dragenter guard always saw zero files and the highlight never
armed. Worse, preload consumes native file drops on document capture with
`stopPropagation()` and routes the paths to the editor, so React's `onDrop`
never ran at all: dropping a screenshot on the dialog opened it in an editor
behind the modal. The drop is now claimed one phase earlier on window capture
and scoped to the dialog element, and the highlight keys off the drag types
the OS advertises — matching useComposerFileDragOver and useSidebarProjectDrop.

`crypto.randomUUID()` is undefined in non-secure browser contexts (the LAN web
client over plain HTTP), so building draft ids with it rejected the read and
dropped every image in the batch with no message and an unhandled rejection.
Use createBrowserUuid, the repo's fallback for exactly this.

`readFeedbackImageFiles` had no rejection handler, so any read failure (file
removed after picking, permission error) silently lost the whole batch.

Also: capacity was checked against a ref mirroring committed state, so two
pastes landing during an in-flight read both saw room for four and the main
process then rejected the entire submission; in-flight batches now count
against capacity. And the non-en catalogs still carried the pre-amendment
English copy for the attachment hint.

* fix(feedback): close the prototype-chain hole in the image allow-list

`contentType in FEEDBACK_IMAGE_EXTENSIONS` walks the prototype chain, so
"constructor", "__proto__", "toString", "valueOf" and "hasOwnProperty" all
cleared the allow-list. feedbackImageFilename then indexed the same object and
named the upload after the inherited value — "feedback-image-1.function
Object() { [native code] }" — and the part went out with that content type.

Only reachable by invoking feedback:submit directly (the renderer screens
types with Array.includes), which is exactly the threat model this function's
own doc comment claims to cover. Object.hasOwn matches the 54 other uses in
the repo and is identical for the four real types.

The inherited values carry no quotes or CRLF, so this was a bypassed allow-list
and a malformed upload, not multipart header injection.

Adds unit coverage for the module, which had none, plus an IPC-level case; all
six new assertions fail against `in`.

* fix(feedback): accept the drag on dragover so the drop can fire

The window-capture drop interception only fires if something first
preventDefaults `dragover`. In Electron that comes free from preload's
document-capture handler, but the same renderer is served to browsers as
web-index.html, where `installWebPreloadApi` builds `window.api` in JS and
installs no drag listeners at all. Nothing else in the renderer
preventDefaults dragover for a native file drag.

So on the web client the dialog is not a valid drop target: `drop` never
fires and the browser falls back to its default action for a file dropped
on a page — it navigates the tab to the file, taking the user's typed
feedback with it. The new types-based dragenter guard makes this worse
than before, because the highlight now arms and invites the drop that the
old `files`-based guard could never light up.

Mirrors useSidebarProjectDrop.onDragOver, which the drop rework already
claimed to match. In Electron it is a harmless duplicate of the
preventDefault preload already applied.

* fix(feedback): revoke batch previews when a read rejects partway

readFeedbackImageFiles creates the object URL for each accepted file as it
goes. If a later file in the same batch fails `arrayBuffer()` — the
removed-after-picking case the new rejection handler was added for — the
whole promise rejects and the already-built drafts are never returned, so
nothing ever revokes their previews.

Each leaked URL pins its blob for the life of the renderer, up to three at
8 MB. Release them before rethrowing; the caller's rejection handler is
unaffected.

* fix(feedback): cancel non-image drops the dialog already accepted

dragover advertises copy for every native file drag over the dialog, but
drop only cancelled for images. On the web client an uncancelled drop
navigates the tab to the file, taking the typed feedback with it.

* fix(feedback): stop image validation from aborting crash reports

buildSubmitBody drops images on the crash lane, but validation ran
unconditionally, so a crash submission carrying an invalid image would
have failed outright over attachments that were never going to be sent —
losing a crash report the user needs delivered. Gate validation the same
way body construction is gated.

Not reachable today (the IPC handler forces submissionType 'feedback' and
internal crash callers pass no images), but the two gates disagreeing is a
trap for the next caller. Raised by CodeRabbit.

Also documents why the image lane deliberately skips the 5xx retry the
text lane performs: replaying up to 32 MiB on a flaky link costs more than
it saves, and the dialog preserves the draft and thumbnails on failure.

* fix(feedback): stop mutating the image-count ref during render

React Doctor fails CI on "Ref mutated during render": the count was
assigned in the component body, where React can discard or replay work
that never commits.

Read the committed count from the callback closure instead of a ref.
Syncing the ref in an effect (the suggested fix) would reintroduce the
race a previous commit removed — right after an add, the ref is stale-low
until the effect flushes, so a paste in that window over-accepts and the
main process rejects the whole submission. The closure value is always the
committed count, and pendingImageReadsRef still covers in-flight reads.

Costs a re-registration of the drop listeners per attach, which is the
same teardown the hook already does when the dialog opens or closes.

* fix(feedback): stop an unsupported pasted image from eating co-pasted text

The paste handler consumed the event whenever the clipboard held any
image/* file, but only the four allow-listed types can actually attach.
Pasting text alongside an SVG or BMP therefore lost the text and attached
nothing — a silent loss of the user's own input, in the dialog where they
are mid-sentence.

Consume the paste only when something is attachable. Unsupported types
still route through readFeedbackImageFiles for their rejection toast, so
nothing is dropped silently; the difference is that the default paste is
left alone when we have nothing to offer in exchange.

Extraction deliberately stays broad. Narrowing it there (as suggested by
review) would skip handleAddFiles entirely, and a file paste into a
textarea does nothing visible — the image would vanish with no feedback.

The drop path is untouched: it must keep cancelling every native file drop
or the browser navigates the tab to the file.

* fix(feedback): stop the dialog accepting more than the endpoint will take

The endpoint rejects reports over 5000 characters with a 400, which the
dialog surfaces as a generic "Failed to submit feedback. Please try again."
Nothing said length was the problem, so retrying could not help — the draft
survived but the user had no way to know what to change.

Cap the textarea at the same 5000 and show a counter once 500 characters
remain, so the limit is visible before it bites rather than after. The
counter stays hidden until then; an always-on count reads as a word limit
to hit.

Extracted rather than inlined: the dialog is already past the 300-line mark
React Doctor warns on.

* fix(feedback): prevent silent attachment loss

* fix(feedback): improve attachment failure feedback

* fix(feedback): bound attachment response parsing

* fix(feedback): surface response body timeouts

* fix(feedback): harden image delivery

* fix(feedback): bound image preview resources

* fix(feedback): honor atomic image delivery response

Production’s single-message feedback endpoint uploads text and images atomically, then returns 202 {"ok":true} without an imagesDelivered field. Treating that omission as false warned users that every successful production attachment had failed.

Treat a settled successful JSON response with ok: true and no image field as delivered. Explicit imagesDelivered: false still surfaces partial delivery, while malformed, oversized, aborted, and stalled bodies remain unconfirmed or fail through the existing response bound and timeout path.
2026-07-29 19:58:10 -07:00
Brennan Benson c67791e4c1 fix(setup-prompt): isolate state by execution host (#11447)
Prevent setup prompt inspection, caching, dismissal, saves, telemetry, and settings navigation from leaking across local, direct SSH, and runtime-relayed hosts.
2026-07-29 19:56:19 -07:00
Jinjing 74563b6498 feat(jira): link Jira issues from the workspace create dialog (#11296)
* Link Jira issues from workspace create dialog

Add Jira issue linking to workspace creation, matching existing GitHub and Linear workflows. Users can paste Jira issue URLs in the smart name field to auto-populate workspace names and link the issue to the created workspace/worktree.

Linked Jira issues appear on workspace cards via the new 'jira-issue' card property. Implements cancellable searches and summary reads to prevent stalled requests from blocking the shared Jira pool. Persists paired issue + source context metadata with validation of provider/site identity.

Fixes git-username rate-limit handling to reject malformed JSON responses so garbage never becomes branch prefixes.

* feat(jira): link issues during workspace creation

- Display linked Jira issues on worktree cards
- Fetch issue summaries and timestamps via Jira API
- Gate Jira linking behind runtime capability check
- Preserve user-typed names during async lookups

* Enforce git check-ref-format rules in login validation

Extend isBranchSafeHostedLogin to reject usernames that git rejects as
invalid branch components: trailing dots, consecutive dots, and .lock
suffix. Prevents invalid branch names from login usernames.

* Enforce filesystem filename cap for branch-safe logins

Loose refs store logins as single filenames, so the real constraint is the
255-byte filesystem cap, not git check-ref-format rules. This allows longer
provider-agnostic logins while staying platform-safe.
2026-07-29 19:50:18 -07:00
Neil 1f2f809a11 fix(computer): bind macOS helper to supervised peer pid (#11475) 2026-07-29 19:49:36 -07:00
jmdallandOrcaWin 80c42d38c7 fix(runtime): avoid immediate WebSocket heartbeat sweep (#11300)
* fix(runtime): avoid immediate WebSocket heartbeat sweep

Defer the first heartbeat sweep until the interval tick.

The immediate sweep can close a newly accepted WebSocket before the E2EE handshake completes on Linux ARM64.

* test(runtime): update heartbeat expectations for deferred sweep

* docs(runtime): update heartbeat initialization comment

Clarified comment regarding socket pinging during heartbeat.

* fix(runtime): arm heartbeat after socket listeners

* test(runtime): pin shared heartbeat cadence

* chore(runtime): preserve reliability gate formatting

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-07-29 19:31:29 -07:00
ye4241andOrcaWin 6b1139f29e fix(mobile): keep a proxied wss host on :443 when editing (#11383)
* fix(mobile): keep a proxied wss host on :443 when editing

A host paired through a reverse proxy is stored as `wss://desk.example.com`
with no explicit port. Editing it — even to only change the display name —
rewrote the endpoint to `wss://desk.example.com:6768` and stranded the host,
with no warning.

`endpointPort` intentionally reports only explicitly written ports, so it
returns undefined for that endpoint. The edit screen passed that undefined
straight through as `fallbackPort`, where `resolveFallbackPort` substituted
the LAN `DEFAULT_PORT`.

Add `endpointPortOrSchemeDefault`, which falls back to the scheme's implicit
port for wss and leaves bare ws alone so LAN pairings keep landing on
DEFAULT_PORT, and use it for the edit screen's fallback. `normalizeHostEndpoint`
is untouched — filling a missing port from `fallbackPort` is its documented
contract and stays covered by its existing tests.

* review(mobile): preserve untouched host endpoints

* fix(mobile): preserve routed endpoint edits

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-07-29 19:28:01 -07:00
BingZandOrcaWin bd9653c26d fix(tabs): trust native OpenCode titles without hook signals (#11382)
* fix(tabs): trust native OpenCode titles

* test(tabs): cover native OpenCode identity authority

* fix(tabs): preserve sleeping provider identity

* fix(tabs): preserve completed hook authority

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-07-29 19:25:22 -07:00
Neil ef90f6099c fix(computer): supervise Linux and Windows desktop providers from main (#11468)
* fix(computer): supervise desktop providers from main

* fix(computer): remove unreachable provider timeout mapping

* test(computer): flush stale supervisor response
2026-07-29 19:14:10 -07:00
Yunqian Fanandfanyunqian.1 791577861b fix(project-host-setup): carry identity across hosts (#9413)
Allow setup when the selected project exists only on another host by carrying its validated provider identity with the request instead of reverse-parsing project IDs. Preserve host-qualified provider identity and reject mismatched payloads before linking.

Make linking atomic for local and runtime imports, including clone setup: roll back only newly registered repos and invalidate the same caches as canonical removal. Cover local, runtime, host-qualified identity, mismatch, clone rollback, and renderer routing paths.

Co-authored-by: fanyunqian.1 <fanyunqian.1@bytedance.com>
2026-07-29 18:57:50 -07:00
Brennan Benson 3eddc467cf test(skills): pin both sides of the nested-skill prune boundary (#11462)
The payload prune had only its miss side covered, so the bound could be raised
or lowered by a refactor without anything failing. Both directions are now
pinned: a skill is found through 2 intermediate directories below a package and
missed at 3.

Raising the bound spends the entry budget on vendor payload — the cost that made
ordinary caches collapse and pin every skill amber (#10865). Missing a deeper
copy costs only a Details row, since a plugin-cache placement is not convergeable
by any update command. Recording the tradeoff on the constant so the next person
to touch it knows which direction is the safe one.

No behavior change.

Closes #11454
2026-07-29 18:44:01 -07:00
Brennan Benson 0fe7759c64 fix(sidebar): float setup script prompt (#11439) 2026-07-29 18:43:04 -07:00
Brennan Benson 64aa726301 fix(quick-open): support projects past 10k files (#11440) 2026-07-29 18:32:21 -07:00
Neil d0f341ad69 fix(computer-use): make modifier clicks interruption-safe (#11451)
* fix(computer-use): make modifier clicks interruption-safe

* fix(computer-use): pace modified Windows multiclicks

* fix(computer-use): address modifier safety review
2026-07-29 18:29:10 -07:00
Brennan Benson 5517bfcbd2 fix(native-chat): make the launch-draft mirror reachable (#11222)
* fix(native-chat): make the launch-draft mirror reachable

Seed the chat-composer copy of unsent launch context on every originating
draft path, then let those launches open in chat by default.

Three paths delivered a draft to the TUI without mirroring it into chat:
folder-workspace create, the local argv-prefill branch of launchAgentInNewTab,
and the web-host equivalent. The first was invisible; the other two were hidden
only because draft launches were forced into terminal view.

The view-mode decision now gates on the same predicate as seeding
(canMirrorLaunchDraftToNativeChat), so a draft can never open in chat with a
composer chat would refuse to fill.

* fix(native-chat): gate draft view mode on argv-prefill launches too

The draft view-mode gate read `startup.draftPrompt`, which only the
post-ready-paste delivery sets. An argv-prefill launch carries its draft
inside `launchCommand`, so the gate never saw one and the tab opened in
chat unconditionally — a multi-line draft was correctly not seeded yet
still opened chat, leaving an empty composer beside a filled TUI input.

Adds `launchDraftText` to the activation startup payload as a view-mode-only
field, deliberately distinct from `draftPrompt` so it cannot double-deliver
the draft through pty-connection's bracketed paste, and sets it at all four
originating producers.

* fix(native-chat): reconcile backend draft launch tabs
2026-07-29 18:28:17 -07:00
Sebastian 8c5b02547e fix(main): prevent claude login hang on Windows due to inherited handles (#11407) 2026-07-29 18:24:00 -07:00
Neil eb58e00c19 fix(terminal): activate fresh OSC links on first click (#11453) 2026-07-29 18:22:52 -07:00
Jinjing cbe8635f46 fix(worktrees): prevent deletion from blocking Orca (#11233)
* fix(worktrees): prevent deletion from blocking Orca

* test(worktrees): loosen async history-delete event-loop bound for CI

The main-thread safety check failed on a loaded runner when a single
timer gap hit ~48ms under the prior 30ms threshold. Keep the bound well
below a recursive sync-rm stall without treating CI jitter as a block.

* test(worktrees): measure history-delete critical path, not timer gaps

setInterval gaps during async rm of thousands of files still flake under
CI scheduling. deleteWorktreeHistoryDir is sync and must only rename, so
assert that critical-path wall time stays well below a recursive walk.

* fix(worktrees): prevent deletion from blocking Orca

Add timeout-based draining of watcher closes so SSH round-trip delays don't
indefinitely block the worktree removal path. Also: order durable temp-file
sweeps ahead of writes to reclaim orphans before accumulation, skip own-process
temps to avoid deleting live writes, swallow persistence errors so disk failures
don't cascade to query callers, and measure history-deletion progress by loop
turns rather than timer gaps to detect blocking on CI runners.

* fix(worktrees): prevent deletion from blocking Orca

Worktree deletion can now proceed even if filesystem watchers or history cleanup operations hang, preventing Orca from freezing. Changes:

- Fence install slots with tokens instead of counters so removals can abandon wedged installs without corrupting later removals
- Timeout-bound watcher unsubscribe operations with a shared drain budget
- Move JSON serialization of large usage caches from queue-time to write-time to avoid blocking main thread
- Async tombstone + schedule history tree deletion instead of blocking recursive rmSync during GC, preventing main-thread stalls ~10s after startup

* Extract usage cache writer into reusable durable snapshot class

Consolidates serialized durable-write and generation-veto logic from
three usage stores into UsageCacheSnapshotWriter. Eliminates duplication,
centralizes multi-MB JSON serialization on the main thread via write-queue
serialization, and vetoes superseded snapshots to avoid wasted rewrites.

* fix(worktrees): prevent deletion from blocking Orca

Worktree deletion used to recursively delete large session trees (hundreds
of MB) on the critical path, stalling the event loop. Instead, rename trees
into a `.pending-delete` tombstone queue and reclaim them asynchronously
off the removal's critical path.

Extracted host tree removal into a reusable helper (`removeHostTree`) that
centralizes Windows retry logic. Added usage-cache flush on quit to prevent
data loss when scans complete right before shutdown. Improved watcher
removal deadline management with reserved tail slices for the final
unsubscribe, and added retry logic for tombstone removals that fail once
under transient Windows locking.

* fix(history): retry failed session tree removals

Tombstoned session trees whose removal fails transiently (e.g., EBUSY
under Windows AV) are now re-queued in-process with bounded exponential
backoff instead of sitting until the next HistoryManager construction.
Prevents a single stuck tree from blocking the entire Orca process.
2026-07-29 18:21:26 -07:00
JinjingandOrca 4e99602ac8 Add search to kanban view (#11244)
* feat: add search to workspace kanban board

Search filters workspace cards by display name, branch, repo, and comment. Lanes show match counts (e.g., "2 / 5") when filtered and reset to full counts when cleared. Drag-drop indices are mapped from rendered cards to the full lane so manual-order math is correct even when hidden. Query clears when the board closes to prevent stale filters on reopen. Includes keyboard shortcuts (Escape to clear), live region announcements for matches, and i18n support.

* feat: add search to workspace kanban board

Adds a search field to filter the kanban board by workspace name. Range selections now index rendered cards only, preventing silent selection of hidden items when filtering. Selection badges count only the visible cards that drag/context-menu actions will move. Lane totals distinguish between empty-by-definition and filtered-away cards. Drop operations commit against the full lane while displaying filtered indices. Whitespace-only queries don't show match counts, since they don't narrow the board.

* fix(kanban-search): let the board search field own Escape

The board's Escape handler is a capture-phase listener on document, so it
runs before React's handlers and the search field's stopPropagation could
never reach it — pressing Escape to clear a query dismissed the whole board
instead, and the reopen reset then discarded the query too.

useWorkspaceBoardPanel now defers Escape to editable targets inside the
board sheet, and the field handles both outcomes itself: clear when it has
text, close the board when it does not.

Also: keep focus in the field when the clear button unmounts itself,
reserve counter width from the rendered text so three-digit counts cannot
overlap typed text, and align the icon centering, X size, and placeholder
with the sibling search fields.

Co-authored-by: Orca <help@stably.ai>

* perf(kanban-search): defer the filter and stabilize its derived identities

Clearing a query re-mounts every hidden card, so it costs roughly what
opening the board costs. The input stays controlled and undebounced, but
the filter now reads a deferred query so React can interrupt that work and
the caret stays responsive.

The match set also keeps its identity when the matched ids are unchanged.
Board worktree identities churn on agent-status ticks, and a fresh Set on
every tick cascaded new identities through the lane views, the rendered
selection, and every memoized card.

Also harden the lane full-id channel: the identity guard in
resolveFullLaneDropIndex compares membership rather than length, so a stale
lane of equal size no longer skips translation; serialization declines ids
containing the newline delimiter instead of inventing phantom lane members;
the sidebar drop path scans lane cards once instead of twice; and the
unfiltered full-id fallback is no longer offsetParent-filtered, restoring
the pre-branch notion of lane membership.

Adds coverage for the stale-equal-length lane, the full-id round trip,
regex metacharacters and non-ASCII queries, and the over-bound query at the
drawer level.

Co-authored-by: Orca <help@stably.ai>

* fix(kanban-search): leave mid-composition Escape to the IME

Escape during an IME composition cancels the in-progress reading. The
search field was clearing the query behind it instead, matching the
isComposing guard other keyboard handlers in the app already use.

Co-authored-by: Orca <help@stably.ai>

* fix(kanban-search): stop a hidden anchor from collapsing a shift-click

A query can hide the selection anchor while leaving the rest of the
selection on screen. updateWorktreeSelection reads an anchor missing from
visibleIds as "no anchor" and replaces the selection with the clicked card,
so shift-clicking dropped the still-visible cards too. Re-anchor onto the
first still-rendered selected card, and carry hidden selections through a
range so the query cannot silently discard them. A plain click still clears
everything.

Also, in the drop-index translation:
- a lane filtered down to nothing now appends rather than always prepending
  (an empty rendered lane reports index 0 for every pointer position, so the
  old branch could only prepend, disagreeing with the document-drop path)
- an unresolvable rendered id falls back toward the end of the lane its
  branch was aiming at, instead of sending every head drop to the bottom
- the full-id channel uses NUL, the one character no path can contain, so
  serialization can no longer be defeated by a newline in a repo path.
  Dropping the channel was the wrong fallback: under a query the reader
  would scan the DOM and see only the matched cards.

Tests now build the channel through its own serializer rather than
hardcoding the delimiter.

Co-authored-by: Orca <help@stably.ai>

* fix(kanban-search): explain a query discarded for length

Past the palette byte bound the query is dropped and the board stays
unfiltered, which looks identical to a query that matched everything — full
field, untouched board, no counter. The field now marks itself invalid,
shows a "Too long" badge carrying the full reason, and announces it.

Whitespace-only text stays silent: it is also non-filtering, but self-
evidently so.

Co-authored-by: Orca <help@stably.ai>

* fix(kanban-search): derive the too-long badge from the deferred query

The badge describes the board, so reading the live query made it flip a
frame before the filter it is describing.

Co-authored-by: Orca <help@stably.ai>

* fix(kanban-search): let a range replace a hidden selection like every other gesture

Carrying hidden cards through a shift-click made it the only replace-shaped
gesture that did so — a plain click and a non-additive marquee both drop
them. It also left the user unable to narrow a selection: shift-clicking the
two visible matches silently re-added the six hidden ones, and the badge
counts only rendered cards, so nothing disclosed it. Re-anchoring onto the
first still-rendered selected card, which is what actually fixed the
collapse, is kept.

Also state the Escape contract where a reader will look: SheetContent now
declines Radix's dismiss explicitly instead of depending on
handleSheetOpenChange quietly dropping the request, and the overlay reserve
is capped so a wide counter in a narrow drawer cannot squeeze the typed text
to nothing. The reserve is exported and tested directly — happy-dom cannot
parse min(), so it could not be read back off a style.

Co-authored-by: Orca <help@stably.ai>

* fix(kanban-search): stop mutating match-set ref during render

React Doctor blocks ref writes during render; keep match-set identity
stable with setState-during-render so discarded renders cannot leak it.

---------

Co-authored-by: Orca <help@stably.ai>
2026-07-29 18:12:27 -07:00
Neil 48184b9e21 fix(computer): supervise macOS helper from main (#11441)
Move native macOS helper process ownership into Electron main while preserving the sidecar as the authenticated socket peer. Add fixed lifecycle IPC, bounded claim and release handling, confirmed-exit tracking, sidecar and helper force-kill escalation, cleanup across failure paths, and focused lifecycle coverage.
2026-07-29 18:08:55 -07:00
hanjoonchoeandBrennan Benson f4e46383df feat(mobile): add session.tabs.list handler to mock server (#9293)
* feat(mobile): add session.tabs.list handler to mock server

The mock WebSocket server had no handler for session.tabs.list, so the
session screen of a paired dev client hung on 'Loading tabs' forever —
the terminal pane, live input, and command input could never be
exercised against the mock. Respond with a single ready terminal tab
wired to the existing term-1 fixture so the whole session surface works
offline.

* fix(mobile): complete the session.tabs.list mock contract

The new mock response omitted four non-optional fields of
RuntimeMobileSessionTabsResult: publicationEpoch and activeGroupId on the
result, and parentTabId and leafId on the terminal tab. Nothing caught it —
the object literal had no type annotation, and MobileSessionTabsStreamHealth
is generic over both result and tab. A shape-incomplete mock yields
untrustworthy repros for exactly the bugs it gets used for (session tabs,
split panes, pane-to-tab attribution).

Fill the fields with host-realistic values: a per-process publisher epoch, a
layout UUID leaf id, and the `${parentTabId}::${leafId}` surface id
mobileTerminalSurfaceId actually emits. Pin the shape with an explicit return
type so a future required field fails typecheck instead of silently drifting.

Move the fixture into its own module: inlining it pushed
mock-server-rpc-handlers.ts to 317 lines against a 300-line max-lines cap,
which broke `pnpm lint` on the parent commit. It registers through the file's
existing delegation chain, after the native-chat scenario so MOCK_NATIVE_CHAT=1
keeps ownership of the method.

Co-authored-by: Hanjoon Choe <hanjoonchoe@gmail.com>

* test(mobile): pin session tabs mock fidelity

Normalize the selector-backed worktree ID like the real runtime and cover the complete terminal surface response so future contract drift fails the mobile suite.

* fix(mobile): share terminal.list worktree resolution with session tabs

Main added `terminalListWorktreeId`, which the rebased session-tabs fixture
duplicated with a different no-selector fallback — `terminal.list` resolved to
the active fake worktree while `session.tabs.list` returned a literal 'mock',
so a session repro saw two different worktree ids for one screen.

* test(mobile): cover the bare session-tabs worktree selector

Answers the review note that only the `id:`-prefixed path was exercised.

* fix(mobile): make the mock publication epoch unique per process

Date.now() can repeat across a sub-millisecond restart, so the epoch did not
actually guarantee the fresh-publisher identity its comment claimed.

---------

Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
2026-07-29 18:01:38 -07:00
Neil 270c5ad3fa Set selected create-worktree agent as default (#11443)
* feat(new-workspace): set selected agent as default

* fix(agent-picker): guard empty default action
2026-07-29 17:46:39 -07:00
Neil 78b8a37aed fix(cli): keep automated worktree creation in background (#11445) 2026-07-29 17:45:57 -07:00
Brennan Benson 5e00a30e4e Decouple feature copy from locale parity (#8512)
* Decouple feature copy from locale parity

* Fix undeclared dynamic localization key check

* Fix localization code owner
2026-07-29 17:44:41 -07:00
Brennan Benson 32926bc831 fix(dashboard): remove per-worktree status dot from agent cards (#11437) 2026-07-29 17:28:43 -07:00
Brennan Benson b339fe0346 Fix Node 26 test gate and happy-dom storage (#11434)
* ci: test PR shards on Node 26

* test: isolate happy-dom storage from Node globals
2026-07-29 17:11:16 -07:00
Jinjing 8d4e975ff7 fix(new-workspace): stop UI flashing when typing ahead of search (#11436)
* fix(new-workspace): stop UI flashing when typing ahead of search

Hold branch results while queries settle, show the spinner only on
initial load, use stable cmdk values, and guard selections against
stale rows. This prevents the highlight from jumping around when
typing faster than the debounced search settles.

* fix(new-workspace): keep dropdown visible while typing within settled qu

Hold the last search results while the user extends or trims the query,
only hiding them when the query diverges completely. This prevents the
dropdown from flashing empty between debounced keystrokes and removes the
guard that made provider rows unselectable during typing.

* fix(new-workspace): align held provider results with live typing

Cap prefix hold by length delta, hide GitHub/GitLab/Linear rows when the
field is cleared ahead of debounce, and re-sync the cmdk arm when search
settles so the highlight cannot lag the resolved selection.
2026-07-29 17:10:58 -07:00
Brennan Benson 93dfe68d73 fix(settings): reject malformed navigation targets (#11433)
* fix(settings): reject malformed navigation targets

* fix(settings): allow setup guide navigation
2026-07-29 17:08:40 -07:00
Jinjing 4c65b42ee2 fix(sidebar): move project header grab cursor to title surface only (#11435)
* fix(sidebar): move project header grab cursor to title surface only

Prevent grab cursor appearing over action buttons (…, +, chevron) which
should show cursor-pointer, not the reorder hand.

- Grab cursor scoped to icon + label surface only
- Row retains data-repo-header-drag-handle for indent/padding drag targets
- Actions excluded via [data-repo-header-actions] selector
- Add lockstep test to keep action selectors synchronized

* fix(sidebar): share project header action selector across drag contracts

Address Greptile feedback: drop the format-sensitive regex lockstep parse and
import one shared REPO_HEADER_ACTION_SELECTOR for repo and group headers.
2026-07-29 17:03:58 -07:00
JinjingandOrcaWin 5f7807497e feat(ssh): bound relay PTY output end to end (#11005)
* docs: design SSH relay PTY backpressure

* fix(ssh): bound relay frame decoding

* fix(relay): bound PTY output publication

* fix(ssh): bound PTY model admission

* fix(ssh): settle closed model admissions

* feat(ssh): negotiate bounded PTY consumer sessions

* fix(ssh): fence exit on renderer settlement

* feat(ssh): track PTY source credit end to end

* fix(ssh): recover bounded PTY output across reconnect

* feat(ssh): complete relay PTY output backpressure

* fix(ssh): close final PTY source credit races

* docs(ssh): record final backpressure validation

* feat(ssh): complete relay PTY source-credit lifecycle

* test(ssh): complete provider notification fixture

* fix(ssh): preserve terminal source credit across rotation

* fix(ssh): fail closed on recovery cancellation

* fix(ssh): prioritize mux control writes after drain

* fix(ssh): retire canceled relay restore deliveries

* fix(ssh): order exit cancellation cleanup

* fix(ssh): gate provisional source activation

* test(ssh): register mux drain-priority coverage

* fix(ssh): type stale owner recovery mismatches

* fix(ssh): close projection replacement races

* fix(relay): contain streaming edge failures

* fix(ssh): secure relay endpoint credentials

* docs(ssh): reconcile final backpressure lifecycle

* fix(ssh): bound main IPC output lifecycle

* fix(ssh): close recovery ownership gaps

* docs(ssh): record exact artifact validation

* fix(ssh): reject reclaimed snapshot replacements

* fix(ssh): fence model admission across reconnect

* fix(ssh): contain migration failure per PTY

* docs(ssh): record final exact-head validation

* test(ssh): align deploy fixtures with credential publication

* feat(ssh): add per-target bounded output setting

* fix(ssh): close source recovery review gaps

* fix(ssh): latch source credit environment override

* feat(ssh): make PTY source credit the default

* docs(ssh): record always-on relay validation

* docs(ssh): bind validation to current main

* test(ssh): grant source credit in IPC fixture

* test(ssh): grant source credit in fake relay

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-07-29 17:03:15 -07:00
Jinjing c676b6aa3b docs: update mobile APK link to 0.0.36 (#11438) 2026-07-29 17:02:13 -07:00