* Keep inactive tabs clickable when active dock crowds the strip
* Fix narrow tab dock state after drag cancellation
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
* Recover brief Linear read transport failures
* Preserve safe diagnostics across runtime Linear reads
* Match existing attachment metadata in read contract
* Use a checked runtime in Linear CLI integration tests
* Keep Linear CLI integration coverage in the backend test graph
* Use integration test ownership for Linear CLI recovery coverage
* Observe empty Linear recovery traces before the first sink write
* Keep removed chat rows out of coalesced updates
* test: bound destructive history burst work
* test: verify removed history through transcript projection
* test: count retained row reads with typed getters
* feat(editor): built-in syntax highlighting for .env files
.env, .env.* and *.env files now open as a dedicated dotenv language with
comment, key, export, value, quoted-string and ${VAR} colouring instead of
INI. .envrc opens as shell, since it is sourced as a shell script.
* fix(mobile): map dotenv to the ini grammar for mobile highlighting
* fix(editor): keep quotes after the first dotenv value character literal
* fix(mobile): stop bundle work after caller cancellation
* test(mobile): use a type import for the fflate mock
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* plugins: download Git sources without checkout, within a protocol allowlist and size cap
Plugin and marketplace sources were a real clone/checkout, so user hooks,
smudge filters and core.hooksPath ran, insteadOf rules could reach any
transport, and nothing bounded the download. Now a --no-checkout depth-1
clone (or exact-commit fetch) runs with an empty hooks path, only the https
and ssh transports, and Orca writes the commit's exact blobs from ls-tree +
cat-file --batch. Symlinks, submodules, .git aliases, colliding portable
paths and SHA-256 repositories are refused. A size watcher stops the whole
Git process tree once the pack outgrows an approximate cap.
The exact argv lives in shared code and joins the real-Git compatibility
matrix, so the Git 2.25 lane runs it. Tests reach fixtures over an SSH
shim, since file transports are now refused.
* plugins: manifest v2 declares an execution profile
data < instructional < sandboxed-ui < trusted-worker. A v2 manifest is
strict and fails when it carries contributions beyond its profile; v1
manifests keep loading with the least profile inferred from what they
declare.
* plugins: pin exact catalog releases in orca-marketplace.v2.json
A v2 catalog entry carries the release it vouches for: commit, content
hash, version, profile, minimum Orca and contribution types. Preview and
install fetch that commit directly and refuse bytes that differ. The v1
file stays strict and frozen for shipped clients; v2 entries strip unknown
keys, and entries this client cannot install are dropped at parse time.
Catalog fetches read only the index blobs under their own download cap,
so a catalog repository is not held to one plugin's limits. Release-bearing
snapshots are cached under a new key, so an older build never reads them
and simply refetches.
readMarketplaceJson and parseMarketplaceFile from #30612 are replaced by
parsePluginMarketplaceIndex: catalogs are now read as Git blobs, so the
on-disk JSON read has no caller left.
* test(plugins): serve marketplace e2e fixtures over SSH
Plugin downloads now refuse the file transport, even through an insteadOf
rewrite, so the fresh-profile marketplace journey reaches its fixture
repositories through the GIT_SSH_COMMAND shim the unit tests use.
* test(plugins): give the Git compatibility plugin source its own identity
* test(plugins): record the plugin fixture's executable bit in the Git index on every platform
The hook relay's bridge-terminal presence borrowed the Codex-home helper; both now call a
neutral isWslTerminalSpawn, so a Codex-only change can't alter relay presence.
Co-authored-by: Claude <noreply@anthropic.com>
* fix(sidebar): prefetch the pressed workspace instead of switching to it
Rows are dragged to reorder them, so a press must not switch. Press activation
from #34594 is removed; a plain mouse press now starts the hover warm mount
immediately, and the click switches as before.
* fix(sidebar): start the press prewarm a task later and skip modified presses
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
* fix(browser): expire abandoned extension menu snapshots
* Keep menu snapshot expiry cases in a focused host suite
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* fix(preflight): skip probing agents disabled in Settings
Background worktree-startup detection now passes settings.disabledTuiAgents
(which it already held for agent selection) into detectInstalledAgents, so
disabled agents are neither probed nor reported. The preflight:detectAgents
and preflight:refreshAgents handlers keep probing every agent so Settings →
Agents still reports real install status.
Fixes#34635
* test(preflight): cover disabled agent detection forwarding
* fix(preflight): skip disabled agents in background inventory
* fix(preflight): restore native inventory after settings changes
---------
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
* perf(chat): typeset each unchanged formula once instead of on every streamed update
A streaming paragraph or list re-parses on each revealed prefix, and every
complete formula in it went through KaTeX again, as did every formula on a
virtual-row remount. Keep a bounded cache of typeset results keyed by what
the typesetter reads (tag, math classes, formula text), hand each tree its
own copy, and remember failures so invalid formulas still show their source.
* test(chat): import the typeset module type at the top of the math test
* fix(chat): evict the oldest cached formula without a type assertion
* fix(chat): type the readable math fallback without an assertion
* Cache immutable math results with bounded retention
* fix(watcher): prune nested ignored dirs on macOS and bound the flush fan-out
macOS was the only platform still watching nested node_modules/.git/dist/build
churn: the darwin branch passed the first 8 ignore dirs as plain @parcel/watcher
`ignore` entries, which the daemon resolves to `<root>/<dir>` only, and built
`ignoreGlobs` from the *remainder* of the list. Nested copies were therefore
pruned by neither layer, while Linux/Windows already got the full
component-anchored regex. Build the glob from the full list; the 8 plain paths
stay as-is because FSEventStreamSetExclusionPaths silently drops every exclusion
past its cap.
The resulting storms then hit an unbounded amplifier in the main process:
flushBatch stat'd every coalesced path with Promise.all (up to 5,000 in flight
against a 4-thread libuv pool), and scheduleBatchFlush could start another flush
for the same root while the previous one was still awaiting stats. Bound the
fan-out to the same limit the watcher child already uses and serialize per root,
keeping one flush running plus at most one queued so bursts coalesce instead of
multiplying. A queued flush is delayed, never dropped.
Fixes#11218
Co-authored-by: Orca <help@stably.ai>
* fix(watcher): share one stat budget process-wide and fence flushes on teardown
The per-batch cap multiplied by watched root, so N roots ran 8N stats; extract
the child's limiter and have both consume it. Teardown now fences and drains a
root's flush machinery so a queued flush cannot start, or emit, after close.
Also stop the queued flush from dropping a newer trailing timer's handle.
Co-authored-by: Orca <help@stably.ai>
* fix(filesystem): abort queued metadata when WSL watcher stops
* test(filesystem): satisfy watcher stat casting quality checks
---------
Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
A mic tap opened capture, and with it the OS permission prompt, before
anything asked the host whether it could transcribe; the setup sheet only
appeared after the host refused dictation.start. The session screen now
gates the start on the setup read it already does, opens the setup sheet
when setup is missing, and hands the next tap back to the host and re-reads
when the sheet closes. A host that never had a model selected now offers
the mic. The sheet's switch uses MobileSwitch.
Fixes#35203
* fix(agents): preserve turn verdicts and monitoring in secondary views
* fix(agents): publish accepted native status changes to dashboard
* fix(agents): suppress equal rebuilt status notifications
* test(agents): explain proxy read forwarding in perf fixture
* test(agents): type production status fixtures completely
* test(agents): stabilize status fixture formatting
* Align web session test mocks with status transactions
* Offer plain folder opening before Git discovery
* Preserve nested repository marker short circuit
* Verify folder handoff parity and ancestor scan bounds
* Format folder handoff verification test
* Use complete settings fixture for folder handoff test
* fix(browser): free the page queue when an eval's caller disconnects
A local orca eval whose caller gave up (CLI timeout, Ctrl-C) kept the
page's serial command queue until its script settled, so a never-settling
script blocked every later browser command on that page. The unix socket
now forwards its disconnect signal to browser.eval, and the eval stops
waiting on abort; the abandoned script keeps running.
* test(runtime): pin local disconnect forwarding for browser.eval
* refactor(runtime): name the methods that get the local disconnect signal
* perf(quick-open): preserve scans across worktree metadata updates
* test(quick-open): collapse duplicate metadata-update case
* fix(quick-open): key exclude request by state instead of render-time ref
React Doctor rejects mutating a ref during render. Hold the nested-worktree
exclude request in state and replace it only when its key changes, so
metadata-only worktree updates keep the same object identity.
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* Reduce stuttering while long chat replies stream
* Keep streaming markdown pipeline within lint limits
* Type the streaming parser meter without a cast
* Type the streamed markdown test reveal state
* Preserve markdown render domains when configured syntax cannot reuse discovery
* perf(files): ignore generated churn before Windows refreshes
* test(files): trim Windows watch filter tests to behavior-bearing cases
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
A long Codex command in a multi-agent chat could write gigabytes to the
agent-session journal and freeze Orca on the next launch while it replayed
them. One 16 MiB `rg` was saved 661 times in 163 s, 8.3 GB for one tool call.
Two things compounded:
- Every event from any Codex thread force-saved every other thread's
in-flight stream, bypassing its x1.125 checkpoint cadence. The command's
own thread wrote nothing else during that span; other sub-agents' events
drove all 661 saves.
- Since #29982 a running Codex command saved its whole output on each
revision, though the journal's contract (and ACP/Pi) keep a running call
to a 16 KiB preview and save it whole only on the closing revision.
Forced flushes now re-save only the event's own thread. flushAll still
writes every other stream's first row first, so nothing is journaled ahead
of the text it follows. The journal enforces the running-preview contract
where item rows are built, so it holds for every provider; completed,
failed and interrupted rows stay whole.
Replaying the incident's shape (an 8 MiB command while another thread is
active) through the real translator and row builder: 354 command rows and
1,370 MiB before, 51 rows and 1.8 MiB after (1 MiB of it the completed
output), with translator time down from 1,043 ms to 47 ms.
* perf(quick-open): rescore only the previous matches while a file query is typed
Each keystroke in a native chat @file mention or Quick Open ranked the whole
workspace listing (up to 20,001 paths) on the UI thread and rebuilt a set of
every path. A path that matches a longer term always matches its prefix, so
when the new query only extends the old terms, score just the old matches;
cache the listing's membership set per listing. Results are identical.
* refactor(quick-open): keep separator-variant matching beside the other separator matching
* test: cover mention ranking reuse and inventory replacement
* test: type mention query props for rerenders
* perf(files): share Windows runtime watches by root
* test(files): trim duplicate Windows watch pool cases
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* fix(mobile): retry unavailable host profiles without discarding cache
* fix(mobile): cancel startup work on terminal shell failures via flow advance
Merging main pushed use-mobile-web-shell-session.ts over max-lines; move
the host-profile cancellation into the reducer so the flow-change cancel
path from #34780 handles it.
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* fix(computer-use): keep Linux element indexes unique after browser tab compaction
Dropping inactive tabs shrank the records list mid-walk, so later nodes took len(records) and reused a retained tab's index; elementParam then acted on the tab.
* fix(computer-use): keep Windows element indexes unique after browser tab compaction
* fix(ssh): offer Remove from Orca for SSH workspaces this app cannot delete
Refs #34677
* fix(ssh): retire delete-remote copy for unserved SSH workspaces
Refs #34677
* fix(ssh): retire delete-remote copy in the German catalog
Refs #34677
* fix(ssh): drop a stale host-removal comment
Refs #34677
* test(ssh): pin that an unserved SSH twin is only forgotten
Refs #34677
* test(ssh): build the unserved-twin store without a cast
Refs #34677
* test(ssh): type the unserved-twin store state
Refs #34677
* fix(mobile): suspend gestures in hidden and background terminals
Adapt the retained-pane interaction lifecycle from community PR #30324.
Restore policy across readiness/recovery and keep output/query replies flowing.
Release cancelled gesture frames, discard touches interrupted by suspension,
and restore the resting pinch transform on every gesture cancellation.
Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
Co-authored-by: Neil <nwparker@users.noreply.github.com>
* test(mobile): stub route focus where the web mount renders terminal panes
* test(mobile): send production-shaped file taps and the clipboard prop in activity tests
* fix(mobile): keep inactive and interrupted terminal moves away from xterm
---------
Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
Co-authored-by: Neil <nwparker@users.noreply.github.com>
* fix(mobile): keep the reading position when resize snapshots overlap
A snapshot that overtook a hidden replay took its scroll anchor from the
hidden terminal, which sits at the bottom, so the reader jumped down. The
newer replay now keeps the anchor the overtaken one would have restored,
unless live output, a clear or an alternate screen made the hidden buffer
the source. A superseded replay also hands the live terminal back before
theme painting reads it.
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: ogarciarevett <ogarciarevett@gmail.com>
* fix(mobile): keep the overtaken anchor when live output arrives during a replay
* test(mobile): use T[] for the replay harness completions
---------
Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
Co-authored-by: ogarciarevett <ogarciarevett@gmail.com>
* feat(native-chat): one queue header that becomes the pause row; no composer Resume or send dialog
The queued cards now sit under one header row on desktop and phone: "Queued"
and the count normally, and "Paused" with the count, Clear all and Resume
while the host holds the queue after a Stop. A chevron folds only the cards;
the choice is local to the view and resets when the queue empties.
The composer's Send no longer turns into a second Resume, and a message sent
into a paused queue no longer asks "Send message?": it sends, its accepted
turn lifts the pause, and the cards follow it. Clear all deletes every card
through the existing per-card delete. No wire or host change.
* test(mobile): find the background-tasks strip's toggle inside the strip, not the queue header's
* Make queued Clear all atomic across clients
* Show queued message failures in collapsed headers
* Use per-card delete for paused queue Clear all
* Join repeated paused queue Clear actions
* fix(mobile): grey the queue's other controls while Clear all or Resume is on its way
On the phone, Resume and each card's Steer and Delete stayed tappable while
Clear all was still deleting the cards, so Resume could start sending cards
Clear was deleting, and a card's Steer could race its own delete. Clear all
and Resume now share one in-flight key, so each greys during the other, and
Clear all holds every card it deletes, leaving out a card whose own action
is already running. Same mechanism as the desktop fix.
* fix(mobile): wait for pending cards before clearing the queue
* style(mobile): format queue control contract test
* fix(mobile): Clear all and Resume share the queue's pending key, as on desktop
Clear all holds every card it deletes and greys Resume until it settles, and
waits for any card's own action instead of starting over it.
* test(mobile): bound queued message conflict work
* fix(mobile): preserve Korean 10-Key live-input syllables
Hold unsent conjoining jamo until committed so grapheme-deleting editors do not erase preceding syllables. Based on community PR #24821; verified real current iOS shell keyboard taps and actual Claude PTY replay.
Co-authored-by: Hojin <hojinkim971597@gmail.com>
* test(mobile): cover held Korean jamo editing boundaries
* test(mobile): keep held-jamo commit tests that add coverage
Boundary cases (Tab, accessory Enter, external send, tab switch, disconnect) already run through the existing held-text tests; the jamo hold reaches them the same way.
---------
Co-authored-by: Hojin <hojinkim971597@gmail.com>
Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
* fix(mobile): validate pairing routes and isolate invite lifetimes
Repeated code parameters crash parsing, and replacing an invite retains old attempts and callbacks. Validate route input and key confirmation state by invite; allow confirmation content to scroll on short screens.
* fix(mobile): retire pairing immediately when Back is pressed
* fix(mobile): keep the pair confirmation layout unchanged
The ScrollView wrapped ConnectionLog's own vertical scroller without nested
scrolling on Android and was not needed for the invite-lifetime fix. The
repeated-code case is covered by the route-state test.
---------
Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
* fix(runtime): resolve workspaces, not only Git worktrees, wherever a folder id can arrive
The Git-only selector resolver never matches project-group folder
workspaces (no repo row), so worktree show/sleep, terminal list by
workspace key, the active-terminal fallback, scrollback reads, Kitty file
reads, Linear --current, browser commands, plugin context and the session
tab fallbacks failed with selector_not_found for them. Route those
callers through resolveWorkspaceSelector and rename the Git-only resolver
to resolveGitWorktreeSelector so it reads as the repo-backed catalog only.
Refs STA-12562
* test(runtime): follow the workspace resolver rename in host fakes
Refs STA-12562
* test(runtime): keep partial browser host fakes and mail-routing stubs on the renamed resolver
Refs STA-12562
* test(runtime): drop an accidental copy of the folder selector suite
Refs STA-12562
* fix(runtime): keep folder workspaces off the skill worktree fence and the Linear meta backfill
Skill installs fence a worktreeId by repo rows, so a folder id there must
come through folderWorkspaceId, where its own host fence applies. Linear
--current answers a folder's legacy link without writing a worktreeMeta
row keyed by the folder id.
Refs STA-12562
A start that failed on the host while Orca resolved or prepared the launch
(workspace or launch folder, command or account home, ACP prepare or temp
folder) reached the chat only as "Claude couldn't start" with no Details.
The adapters now mark that failure where it happens as the typed reason
launchPreparationFailed, and the host error is shown behind Details. A
reason already named there is kept, and a start withdrawn by Stop or close
keeps today's words. Older clients drop the unknown reason and kind.
Refs #34795
* fix(terminal): don't count a PTY reattach as workspace activity
A remount (hidden prewarm, background mount) reattaching the tab's own live PTY bumped
lastActivityAt and sortEpoch, re-sorting the sidebar on hover. Also keep store map
identity on that no-op path so subscribers skip re-rendering.
* perf(terminal): prewarm hovered and keyboard-neighbor workspaces before the switch
A row hovered for 120 ms, and the previous/next workspaces in sidebar order (local hosts,
500 ms after a switch settles), take a prewarm lease: hover holds one, neighbors two.
A lease mounts only each group's visible snapshot-restorable terminal plus live tabs,
never wakes sleeping agents, and lays out and warms WebGL in separate idle tasks.
Hover stays laid out until it ends; a neighbor hides again once warm. Held leases are
exempt from ordinary cold parking (the retention budget still applies), individually
parked sibling tabs stay unmounted, a narrowed live mount is handed back on release,
and prewarm is off when hidden-view parking is disabled.
* perf(sidebar): coalesce held workspace-step keys
Auto-repeated Cmd+Shift+Up/Down and list arrows move only the sidebar highlight (and the
listbox's active descendant); the workspace under it switches once the repeat stops, on
keyup or a missed repeat since macOS withholds keyup while Cmd is held. Single presses
still switch synchronously. Keyboard navigation also publishes its sidebar neighbors to
the prewarm lease.
* perf(palette): close Cmd+J without the exit fade after a selection
A selection already moved the user to the new surface; the 150 ms overlay fade only
dimmed it. Escape and outside dismissals keep the fade.
* fix(terminal): undo a prewarm narrowing only while the lease still owns it
Releasing a lease after its workspace was activated (or after another mount replaced the
restriction) stripped the deferred tab ids that activation now owns, so over-cap tabs
stayed on the restoring placeholder and lost parked watchers. The undo now runs only
when the lease's restriction is still installed and the workspace was not activated.
Simulator workspaces also get no restriction on any unrestricted mount, parked included,
so the emulator overlay stays mounted.
* fix(terminal): prewarm never reattaches a session the daemon has not confirmed live
After a daemon loss a parked shell tab keeps its PTY id and snapshot capability, and its
reattach spawns a replacement process. A lease now asks main (pty:hasPty, authoritative
true only) for the revealable tabs it would reattach and admits only confirmed sessions;
unknown or remote-runtime answers decline. Workspaces with a simulator tab never take a
lease, since any restriction unmounts the emulator overlay and its local state.
* fix(terminal): confirm every split leaf and keep parked siblings out after a lease ends
A split tab reconnects each leaf on mount, so prewarm now confirms every resolved leaf
session (re-resolved after the answer; any change fails) instead of only the tab's
first PTY. Releasing a narrowed lease ends its measurement, which starts tab cold
parking's 30 s cool-down with an empty park set; the narrowing now outlasts that
cool-down so individually parked siblings stay unmounted after an abandoned hover.
Prewarm target selection moves to its own file.
* fix(terminal): hold a prewarm narrowing through any newer measurement cool-down
A mount that measures the worktree after the lease ended starts its own tab cold-park
cool-down; lifting the narrowing on the original deadline would remount parked
siblings while parking is still off. The undo now re-waits while the worktree is
measuring or inside its park cool-down. Teardown cancels pending undos after the final
releases, which would otherwise schedule timers past the unmount.
---------
Co-authored-by: m4air <m4air@Mac.localdomain>