* fix(cursor): write workspace trust where cursor-agent reads it
cursor-agent names ~/.cursor/projects/<dir> by replacing every
non-alphanumeric character with '-', collapsing repeats, and trimming
dashes. Orca only replaced Windows-illegal characters, so paths with
'_', '.', spaces or non-ASCII got a marker cursor-agent never reads and
the trust menu appeared on every fresh worktree.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(cursor): cover space, non-ASCII and Windows paths in the trust slug test
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
* fix(mobile): finish Android dictation on a call, alarm or leaving the app
A transient audio-focus loss (call, alarm, assistant) left the mic recording
silence, and pausing the activity closed the mic only to reopen it on resume,
splicing a silent gap into the dictation. Both now end the recording and emit
an interruption, so dictation finishes and inserts what was heard. The
dictation hook also finishes when the app goes to the background, which
covers iOS and shells without this native change.
* fix(mobile): keep a dictation that is waiting on the Android mic prompt
* fix(mobile): keep Android recording state consistent across the focus and JS threads
* refactor(mobile): name the Android pause path for what it does and stop retaking focus on resume
* fix(mobile): keep what the open mic heard when leaving during a pending host start
* fix(mobile): close the open mic at once when leaving during a pending host start
* fix(mobile): a second page capture end waits for the stop already in flight
* refactor(backend): use defining owners for orchestration and ACP startup
* test(orchestration): trim redundant observations and retain safety cases
* test(orchestration): seed restored sessions with one fixed operation clock
* fix(mobile): show the phone speech download size and drop a cancelled session's late text
* fix(mobile): show the Android language download size too
* fix(mobile): keep a recognizer error raised while its start resolves, and read the download size aloud
agy has no Full access session mode, so Full access chats always fell back to
Orca's managed runtime, which kept most people off the host's agy (and off
Intel Macs, where only the host can run it). agy 1.5.2+ `agy acp` accepts
--dangerously-skip-permissions, which approves tool requests but not plans.
Host chats now launch with that flag only in Full access, in agy's default
mode. Switching permission mid-chat already restarts the ACP process and
resumes the session, so the flag follows the chat's mode. The flag never
approves plans, and Orca still leaves agy's requests to the person, so plan
approvals keep surfacing.
The cached readiness check launches with the flag, so a build that rejects it
keeps chats on Orca's runtime without an extra spawn; such a binary is not
checked again until it changes. Because ask and Full access both run agy's
default mode, the reported mode is read per launch, so a Full access chat
does not show as Ask.
Refs #39006, #33188
App themes may set background.videoPath: a muted, looping MP4 or WebM (H.264
recommended, VP9 or AV1; one video track, no audio; <= 60 s, 8 MB, 2.3 MP)
played over the required static background.path. Main checks it from container
headers without decoding and serves it on the orca-plugin-image slot
theme-<i>-video; images stay static-only. The renderer reveals it on its first
frame, keeps the still on any failure or a decoded size past the limits, plays
it only while main reports the window focused, shown and not minimized and no
dialog is open, and omits it under prefers-reduced-motion.
Animated WebP was measured and declined: software-decoded every frame, it cost
11-23 points of CPU vs 3-10 for the same loop as video.
Implements #38363 proposed by @dohaidang.
* refactor(renderer): organize host and terminal implementation owners
* fix(refactor): align active adoption reliability references
* refactor(terminal): remove forwarding shells and stale mocks
* fix(renderer): consolidate direct owner imports
* fix(refactor): route incoming issue trust type to its owner
* test(runtime): give independent status viewers separate pairing tickets
* style(refactor): normalize rebased terminal mock formatting
Store absolute deadlines instead of two last-seen timestamps, and let
scroll events with no wake pending return before reading the clock.
Behaviour is unchanged.
* fix(mobile): report a slow Android speech service as busy, not unsupported
* fix(mobile): replace only the speech recognizer the service disconnected
* fix(plugins): don't count time asleep as worker idle time
The idle reaper measured idleness with the wall clock, so after a sleep longer than the reap window its first sweep reaped every worker before their own timers could fire. A sweep gap well past the 60s interval now starts a fresh idle window.
* fix(plugins): count a sleep before the first idle sweep as asleep, not idle
Start the reaper's sleep-detection clock when the first worker starts, so the
first sweep after waking can see the gap even if no sweep ran before the lid
closed.
* fix(plugins): keep worker manager under max-lines and restore real timers after sleep tests
Move the idle-candidate filter into PluginWorkerIdleClock so the rebased manager
stays within the 300-line lint cap, and reset fake timers after the idle-reap
tests so the sleep-before-first-sweep case does not leak them.
---------
Co-authored-by: petersindex <287557875+petersindex@users.noreply.github.com>
Co-authored-by: Neil <neil@stably.ai>
The managed-runtime install message now names the observed cause after the
existing headline: a timeout, an exit code with a redacted stderr excerpt, or
the version the server answered as. The host agy check that shares the
initialize helper is unchanged.
Takes over #34167 by @Jinwoo-H (away), ported onto main after #39006.
Co-authored-by: Jinwoo-H <73622457+Jinwoo-H@users.noreply.github.com>
Live foreground output with background colors or in-place rewrites
requested a synchronous xterm row repair after every write, on top of
the dirty-row frame xterm had already queued while parsing. Busy
OpenCode frames all carry background SGR, so DOM panes painted each
frame twice.
The synchronous repair exists for Windows ConPTY DOM rendering. Keep it
there and let other platforms merge the repair into xterm's queued
frame. Replay and snapshot refresh policy is unchanged.
Drop tests that only asserted the private synchronous refresh call.
Fixes#22458
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(plugins): surface reserved-identity install errors for local forks
Official template forks keep stablyai/orca-* identities that correctly
cannot install from a local path, but the UI swallowed the reason as a
generic failure. Keep the security block and show actionable rename
guidance in the install dialog (#12598).
* test(plugins): mock i18n for reserved-identity error presentation
* fix(plugins): match reserved-identity errors before git heuristic
Production trust strings mention github.com; the broad /(git|...)/ check
matched the "git" substring inside "github" and mis-surfaced reserved
fork installs as fetch failures. Prefer reserved-identity matching and
lock presentation tests to the full pluginInstallTrustError strings.
* fix(plugins): map the current reserved-name install refusal to rename guidance
Main now refuses reserved plugin keys from every non-official source with
"<key> uses a name reserved for official Orca plugins; ...", so the
author's matcher on the old "reserved plugin identity" string never fired
and forks of official templates still saw the generic install failure.
Match the current string (before the Git heuristic), use one message that
tells the user to rename id/publisher, and register the English key.
Refs #12598
---------
Co-authored-by: Neil <neil@stably.ai>
* fix(plugins): resolve the active workspace context on remote hosts
* fix(plugins): scope the remote workspace context to its host
* docs(plugins): document the remote context resolver
* fix(plugins): keep remote context ids raw and never fall back for local rows
The stored-row fallback returned host-qualified ids (ssh:host|repo::path), which no
terminal selector or agent-status row matches, so agents.list stayed empty for the
remote workspace. It also answered for a removed local worktree, turning a stale
focus that used to read null into a context for a workspace that no longer exists.
* fix(plugins): resolve a focused paired-server workspace from the desktop's server rows
A paired orcad server's workspaces live in that server's store, so neither the local
catalog nor the desktop's stored rows can name one and workspace.readContext,
workspace.changed and agents.list saw no context. The runtime now asks the relay host
catalog, which answers from the worktree.ps rows the desktop already caches. Only a
miss on a reachable server fetches, bounded at 1.5 s and at most once per 10 s per
missing workspace; an unreachable or hung server answers null.
* fix(plugins): keep a stale local focus stamp resolving as before, and cover #38653 end to end
A focus stamped 'local' whose repo row now routes elsewhere resolved through the
unscoped selector before #38653; the host-scoped lookup turned it into null. A
local stamp now retries unscoped, so local and unstamped focus answers exactly
what main did. Remote stamps keep the scoped lookup, so a paired-server focus
never borrows a local twin's context.
Tests: real-git parity of local, folder and SSH contexts against the pre-change
selector answer for every session shape the renderer writes; panel polling
against a hung or failing server (one fetch, one bounded wait per 10 s window);
an e2e that pairs a desktop with a headless Orca server, clicks a local and a
server row, reads workspace.readContext from a plugin panel, and keeps polling
fast after the server goes away.
---------
Co-authored-by: Neil <neil@stably.ai>
* feat(themes): public plugin theme tokens for chat text and selection
Themes can now set chat bold text, muted text, inline code, code blocks and
the chat selection. Omitted tokens paint exactly what chat painted before,
except the selection, which now keeps selected text 4.5:1 by default.
* docs(themes): note the bubble and code-block checks for chat text tokens
* fix(themes): never lower the chat selection under its visible floor
React Compiler skips any function containing `??=`, `||=` or `&&=`. Rewrite
the statement-position ones whose target re-reads without side effects
(identifiers and plain member chains) as the equivalent `if`:
a ??= b -> if (a == null) { a = b }
a ||= b -> if (!a) { a = b }
a &&= b -> if (a) { a = b }
21 files; 25 fewer bailouts and 12 more compiled functions in the
compiler-compat baseline.
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* fix(plugins): isolate development watcher retries by root
* test(plugins): keep coverage for late binding checks after housekeeping disposal
The per-root rewrite replaced the old housekeeping unit tests; restore the
disposal/coalescing case so a slow check that resolves after dispose cannot
restart a watcher or refresh, and per-root checks do not stack.
* fix(plugins): skip the full plugin rediscovery while a dev folder retry keeps failing
A maintenance retry that subscribes already refreshes itself; one that keeps failing (an
unreachable WSL share) rediscovered every plugin each tick. Only a changed root binding needs
discovery now.
Also covers what the per-root rewrite must keep: adding/removing a folder leaves the others'
watchers and frame revisions alone and releases its subscriptions, a rejecting registration
retries once per tick, Linux sibling folders keep separate parent watches, and an Electron e2e
where deleting/recreating/removing one dev folder never reloads another folder's open panel.
---------
Co-authored-by: be0hhh <182023099+be0hhh@users.noreply.github.com>
Co-authored-by: Neil <neil@stably.ai>
A focused plugin frame resolved Mod+B/Mod+L to the sidebar toggles and
Mod+=/-/0 to zoom, but the guest forwarder had no sidebar branch and
dropped zoom for non-page guests, so the plugin kept the keys.
* fix(plugins): keep a workspace-scoped panel edit made just before a workspace switch
The guest's persist queue holds atoms, not values, and the renderer stored each
ui.persist under whichever workspace was active when it arrived. An edit made
less than 0.4 s before a switch was flushed after the new workspace's hydrate,
so workspace B's value was written under B and A's edit was lost.
The host now names each ui.hydrate; the guest flushes pending writes before
applying the next hydrate and tags ui.persist with the hydration its values came
from, and the renderer stores the write under that hydration's workspace.
(STA-13316)
* test(plugins): cover workspace-persist hydration routing edge cases and add switch e2e
Adds unit coverage for A->B->A routing, legacy (no hydration) persists, the eight-hydration window, plugin-scope entries, and guest flush/tagging; adds an Electron e2e that types in workspace A, switches inside the persist debounce, and checks both workspaces after switching back and after the panel's guest is recreated. Regenerates orca-testing.mjs after rebasing on main.
* fix(plugins): stop a late replacing command from repeating the built-in action
The renderer ran Orca's own action after 2 s but the still-running replacing
command could then call commands.run for the same action, doing it twice.
Main now owns the 2 s cutoff: it rejects at the deadline (the renderer falls
back on that answer) and from then refuses the late handler's commands.run of
the replaced action; a handler that already ran the action is not followed by a
fallback. The deadline now starts when the renderer asked, not after plugins
load. The renderer's own timer is only a safety net. (STA-13530)
* test(plugins): cover replacement races and verify the sidebar toggles once end to end
A handler's commands.run now counts for the oldest in-flight press only, so a second
concurrent press whose handler fails still gets Orca's fallback. Adds unit cases for a
throwing/crashed handler, two presses racing, and a press after a late handler returns,
plus an Electron e2e spec driving fast, late (2.1 s), throwing and no-op replacements of
sidebar.left.toggle.
* fix(plugins): stop terminal inventories for agent status events nobody hears
Each agent.status.changed event for plugins lists the workspace's terminals,
which refreshes every PTY provider and host. Interest was only checked when an
event was admitted, so after the last panel closed the queued backlog kept
running full inventories with no one to deliver to.
Recheck interest before each lookup and drop the backlog once nobody listens,
and let events queued behind a slow lookup share one listing per workspace
while keeping arrival order. (STA-13319)
* fix(plugins): keep the agent status queue moving past a throwing delivery
A synchronous throw from one emitEvent dropped the rest of its batch and left
later queued events waiting for the next arrival. Catch per delivery, and cover
a failed listing, a 1000-event storm, a panel reopening mid-lookup, and several
panels on the real bridge; the panel e2e now checks a burst arrives in order.
(STA-13319)
* fix(ssh): keep old relay channels off the host's own SSH connection
Each old relay still running a shell held one exec session on the target's
connection. At sshd's MaxSessions (default 10) every later exec failed, so the
host stuck at Connecting with 'Could not detect the remote platform: (SSH)
Channel open failure: open failed'.
* test: say why the session-limit repro needs a longer timeout
* test: note why the relay channel fakes are cast
* test: move the many-relays repro into the existing legacy relay bridge suite
React Compiler drops bare `void x;` reads, narrows callback deps to the fields
read, and oxc 0.152 emits `a[q.head++]` as a prefix update. Rewrite those
sites so they behave the same with the compiler on, and identically to today
with it off:
- `void tick` memo invalidators -> `recomputeOn([tick], () => ...)`
- `void el.offsetWidth` transition resets -> `forceLayoutReflow(el)`
- pty termination queue reads, then advances, the head index
- host agent preference subscription takes the whole subscription object
Prunes the compiler-compat hazard baseline from 40 to 2 entries.
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
A keychain item for agy does not prove agy can sign in silently: agy acp then
answers every session request with "Authentication required", and Orca never
authenticates the host (oauth-personal opens a browser). The host check now
also lists sessions after initialize (which writes no conversation, unlike
session/new), so a signed-out agy keeps new chats on Orca's runtime. Any
answer except an incompatible binary is rechecked after 5 minutes so a lapsed
or restored sign-in is noticed. agy settings that name a modelProvider select
API-key auth, whose key host launches drop, so they keep chats off the host.
Refs #39006
* feat(pairing): show which devices still sign in with their original pairing code
Settings lists paired phones and access links that signed in with a pairing
code but never exchanged it (apps from before single-use pairing) with
"Uses an older pairing method. Update ...". Display only; rows from older
builds are never flagged.
* fix(pairing): no update hint for links used by plain-http browsers or still pairing
A link that served a session-only browser key is marked, so the hint stays
off after a host restart drops those keys. Codes still inside the pairing
window are not flagged either: an updated client exchanges within moments.
* feat(plugins): let plugins contribute web fonts to the font pickers
Plugins declare font families under contributes.fonts (WOFF/WOFF2 only).
Install checks each file's signature against its extension and caps file
size, family count and plugin total; main never parses a font. Files are
served over the plugin image scheme only while the plugin is enabled,
typed and nosniff. The renderer registers each family under a name that
carries the plugin key, so a plugin font cannot shadow an installed or
built-in font, and it applies only once the user picks it. Disabling or
uninstalling removes the faces; uninstalling also resets font settings
that named them.
Refs #38607
* fix(plugins): harden plugin fonts and re-measure editors when they load
- Refuse WOFF/WOFF2-wrapped font collections by checking the flavor bytes.
- Check dev folders' font files at discovery, not only at install.
- Re-check family, weight and style from the plugin list in the renderer.
- Track loaded families (case-insensitively) from document font loads, so
the code editor re-measures and terminals, previews, the SSH terminal and
the dashboard popout re-apply only when their own font changes.
- Register plugin fonts in the dashboard popout window.
- Give dev-folder font URLs their change count so edits reload.
- Keep monospace fallbacks unless the chosen family is exactly that font.
* fix(terminal): keep every fallback only for plugin font families
System font choices keep their existing fallback list unchanged.
* fix(plugins): leave the dashboard popout's plugin list alone; add the font row string
Registering fonts there started the plugin auto-update watcher in a window with no toaster, which could consume update notices. Adds the new contribution-row string to the English catalog and the fonts list to the RPC test manifest.
* Use Cmd+K for search on new Mac profiles
Preserve existing search and terminal clear shortcuts during migration
on desktop and web.
* Preserve legacy Mac shortcuts during web profile migration
* Preserve keybinding resets across profiles and platforms
* Preserve legacy shortcuts and markdown link bindings
* Separate profile shortcut defaults from shared overrides
* Check shortcut conflicts when restoring defaults
* Fix tab shortcuts targeting floating workspace panels
* perf: stop host-status pings rebuilding the title catalog and rereading server config
Host-status updates walked every saved tab for title-refresh support before checking whether any host's support changed, and remote RPC/tunnel/worker checks reread, rehardened and reparsed the saved-server registry files on every call. Gate the title walk on an actual per-host capability flip, and reuse the merged registry read while a stat signature of its three files is unchanged.
* perf: invalidate registry read cache on in-process writes
Coarse-timestamp filesystems can reuse an inode within one tick, so a same-size rewrite could slip past the stat signature; writers in this process now bump a generation that is part of the signature.
* perf: key the registry read cache on file bytes, not stat metadata
A stat signature can miss a same-size rewrite on coarse-timestamp filesystems or a reused inode. Comparing the files' exact bytes (plus mode/ctime and the directory mode, which the uncached read re-hardens) means a hit can never serve data that differs from disk, from any writer.
* perf: only reuse a registry read once hardening holds and the path is unchanged
Reuse now requires the files' permissions to already be restricted (POSIX), so a transient chmod failure is retried on the next read as before; Windows hits keep the ACL verify-and-retry. Snapshots also record each path entry's identity, so a symlink re-pointed and restored mid-read cannot pin the value read through the other target.
* perf: build the cached registry value from the exact bytes it is keyed on
The value was computed by a second, independent read, so a directory or symlink swapped and restored around it could pin data the key's bytes never held. Each reader is split into read + parse-text, and on a miss the cache hardens, snapshots once, and parses that snapshot. Reuse also now requires the exact 0600/0700 modes the readers set, so an incomplete owner mode is repaired as before.
* perf: require the full hardened mode, including special bits, before reusing a registry read
* perf: keep Windows on the plain registry reader
Measured on a Windows host: a cache hit still has to re-verify ACLs, so it cost as much as the read it skipped (0.37 ms vs 0.33 ms). Windows now always uses the original reader, keeping its behavior byte-for-byte unchanged.
* perf(daemon): cache Linux CLK_TCK instead of forking getconf per start-time read
CLK_TCK is fixed for the kernel's lifetime, so the daemon identity check
no longer runs a synchronous getconf process on every read.
* fix(daemon): retry invalid Linux clock tick reads
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* feat(mobile): transcribe dictation on device with SpeechTranscriber on iOS 26
* fix(mobile): fall back to the dictation model, reserve locales and stop a finishing session on cancel
* fix(mobile): resolve on-device speech locales deterministically and report the simulator unsupported
* refactor(mobile): simplify the on-device speech engine and skip conversion when formats match
* feat(pairing): Add server and orca environment add exchange the link for their own token
After verifying the host, Add server (and repair) swap the access link's
token for one generated on this computer when the host advertises
pairing.ticket-exchange.v1, and save that instead. orca environment add does
the same with a short best-effort probe. Any failure saves the link as today;
the host keeps accepting it until the new token is used.
* test(pairing): Add server and CLI leave links unchanged for a released host
* fix(pairing): Add server and orca environment add sign in once after exchanging, and the CLI rejects a refused code
After exchanging a link, sign in once with the new token so the host
retires the link right away instead of at the first later use; a host
downgraded in between would not know the new token.
orca environment add no longer saves a pairing code the host refuses
(for example one another device already used); it asks for a new code.
* feat(mobile): define the on-device speech contract for the native audio module and page bridge
* feat(mobile): dictate on the phone first and replay to the computer when the phone fails
* refactor(mobile): type the on-device speech module without a cast
* fix(mobile): point dictation's finish ref at the committed callback
* test(mobile): wait on the takeover request instead of a fixed sleep
* fix(mobile): release each dictation request's own share of a replayed backlog
* fix(mobile): let the computer's text replace the phone's after a fallback