* fix(wsl-mover): add-host copy says bridge projects move automatically, in every locale
* fix(wsl-mover): add-host copy scopes the move to this distro and keeps Windows-drive projects here
* test(wsl-mover): sweep every locale's old 'until you move them' wording
* perf(explorer): reuse watcher child indexes by listing identity
* test(explorer): trim child index reuse tests to reuse and invalidation
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* plugins: bind worker and panel approval to exact package bytes
Consent for any plugin that runs code (a v2 sandboxed-ui or trusted-worker
profile, or a v1 manifest with a worker or panel) now names its installed
content hash, so an update cannot swap code under an old approval. Shipped
v1 installs re-prompt once. Data packs keep their fingerprints, and dev
trees stay unbound so a save doesn't re-prompt. The re-review notice is one
short line.
* plugins: integrity-check reviewed bytes before every worker spawn
The content verifier cached its first check, so bytes swapped between a
crash and its restart still ran. The controller now hands the manager one
uncached verifyBeforeSpawn hook that runs before every spawn, including
crash restarts, and replaces the cached first-start check, so nothing is
hashed twice and a running worker is reused without hashing. It is an
integrity check, not a sandbox: Node still reads files after it. Approval
is re-asserted at the same points, so a revoked plugin stops restarting
instead of counting as a crash, and cancellation does not wait on a hash.
* plugins: restart a running worker on its updated code
A refresh that changes a running worker's spec used to stop it and wait for
the next command or event. Reconcile now restarts it once on the new spec
after every stop, through the same approval and integrity path, and skips
the restart if a newer reconcile or dispose has taken over. Idle workers
stay lazy.
* test(plugins): re-review a dev copy that takes over an approved install
Approval of worker and panel plugins now names their exact bytes, so
switching the demo plugin from its installed copy to the dev folder asks
for review again; the journey approves the dev copy before using it.
* fix(plugins): admit worker restarts against current reviewed spec
* test(plugins): say why the worker refresh tests read the clock and wait out a backoff
* fix(plugins): refuse custom Git remote helpers in plugin downloads and test smudge filters for real
* fix(plugins): keep crash restarts through refresh pauses, stop workers before awaiting refresh on shutdown, and narrow inherited Git protocol allowlists
* test(plugins): shrink the flaky-test baseline for the worker controller test
Picks up the GHSA-5jr9-983r-c8x4 nesting-regex ReDoS fix (26.4.4), the
placeholder-injection fix for interpolated values (26.5.4), and
react-i18next's useTranslation resubscribe/stale resolvedLanguage fixes.
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* perf(orcad): spawn the terminal daemon as soon as the login env lands
orcad started its terminal daemon only after the secret store, profile and hook server were up.
The daemon (and every PTY) inherits process.env, which startup changes only through the
login-shell env adoption, so it now spawns as soon as that resolves, in parallel with the stores
and the hook server, and startup awaits it where it did before. A cleanup waits for an in-flight
spawn to settle before disconnecting. The login-env adoption and its logging move into
adoptWslHostLoginEnvironmentLogged.
* fix(orcad): observe the daemon startup promise where it is created
An early abort may never reach the await, so a future change that makes the spawn reject must
not surface as an unhandled rejection.
* fix(wsl-host): a terminal on a stopped distro offers Start, and New Terminal starts it
* fix(wsl-host): a Start whose reconnect fails toasts and returns false
* fix(wsl-host): hide a pane's error toast only where the Start card replaces it
* test(wsl-host): give the terminal Start e2e's timeout and no-boot window their reasons
* test(wsl-host): await the pre-stop New Terminal so its user-act boot can't land in the no-boot window
CI saw 2 extra fake connects right after the first stop. Instrumented runs traced
every boot after a stop to the Start flow's two calls (wslHosts:connect, then the
runtimeEnvironments connect, logged as a pair) and the renderer stack of the
second pair to openNewTerminalTabInActiveWorkspace. In CI the spec clicked New
Terminal, then waited only for the first .xterm. That was the auto-created
terminal, so it wrote the stop while its own New Terminal was still in flight.
That terminal then started the distro as the user act it is. No background path
booted anything.
The spec now runs the tab bar's New Terminal action and awaits it before the
stop. The no-boot assertion is unchanged.
* test(wsl-host): narrow the tab group id before opening the pre-stop terminal
A windowed host answered OSC 10/11 for every terminal with its own window's
theme, so a Codex started from a dark client on a light-appearance host baked
a light input area. On such a host the launching client's colours and palette
now travel to the PTY owner as launcherColors and launcherAttributes. That
owner becomes the terminal's only theme responder: it answers OSC 10/11/12,
OSC 4 and ?996n with the launcher's theme (plus anything the app set) and
strips them, so neither the host's hidden-pane responder nor a viewer answers
with another theme. The startup query scanner gains these queries for pinned
terminals only; without launcherColors they pass through as before.
Terminals no client launched keep following the host-wide theme, and a
windowless host still follows its clients' later pushes. Older owners drop the
optional fields and behave as before.
terminal.setViewerColors also accepts an optional full appearance, so a
windowless host's hidden panes answer OSC 4/12 and ?996n like the client. A
colours-only update that contradicts the kept palette drops it rather than
mixing two clients' themes.
Fixes#34340
* test(wsl): copy whole orcad startup profiles out of the lane distro
A guest command's output is capped at 1 MiB, which truncated orcad's own CPU profile. The lane
now reads each profile in 512 KiB base64 chunks.
* test(wsl): refuse a lane file read that did not come back whole
execCommand keeps only the tail past its output cap; the chunked read now throws unless it got
exactly the file's size.
A failed agentSession.currentRequests read marked its version as tried for good, and the live read only reopened the subscription once per version. Two slow or dropped reads in a row left a pending approval or question without controls on remote desktop, web and phone clients until the decision changed.
The reader now re-reads a version whose read failed in transit, with the stream's reconnect backoff, and stops only when the answer breaks the contract or the host refuses for good. The transcript subscription is no longer reopened for a decision-read failure.
* 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