* feat(plugins): dev emit also reaches the plugin's open panels
orca plugin dev emit now publishes through the panel bridge to that dev plugin's open panels
that subscribed with orca.on, under the same approval and capability rules as the worker, and
reports delivered.panels. The bridge's publishEvent takes an optional pluginKey filter and
returns how many panels heard it. A panel-only plugin no longer needs a worker; an event
nothing listens for is refused.
* docs(skills): trim the plugin authoring guide; details move to references
The main guide drops from 299 to 121 lines: the decision table, build-less rules, core loop
(create → dev watch --check --shot → read the PNG → test → check → release-entry), approval
and top gotchas. Test/check and orca/testing (panels and workers), dev watch events, shot,
open and dev emit move to a hand-written references/workflow.md; the panel/worker/data and
agent status recipes to references/recipes.md, each with a read-when row. The guide test now
also scans hand-written references for commands and caps the main guide at 150 lines.
* plugins: components catalog 2 (forms, tabs, table, tree, markdown, code, images)
Adds 18 components to TSX components panels, drawn with Orca's own
primitives and gated by ui.catalog 2 / ui.has() (Orca 1.4.247):
Textarea, Checkbox, RadioGroup, Slider, Field, Tabs/Tab, Collapsible,
Toolbar, Grid, a virtualized Table with sort, selection and pull-based
paging, a keyboard Tree with lazy children, Markdown (sanitized, links
through the checked open path), CodeView (Orca's highlighter), Image
(header-checked plugin files read by main), KeyValue, Callout and
Skeleton, plus a tooltip prop on Button and IconButton.
Value controls share the edit protocol; handlers get plain JSON event
args, including small objects ({ keys }, { key, expanded }). The test kit
gains matching actions and hides unselected tabs and closed collapsibles;
a components-gallery example and e2e cover the catalog end to end.
* plugins: worker test harness, host refusals in tests and check
- orca/testing gains mountWorker(main, { host, events, commands }): it calls
the default-exported activate(orca) with the orca object Orca itself builds
(createPluginWorkerOrcaApi, moved to shared so the kit reuses it), backed by
mockHost. emit() checks payloads against Orca's event schemas and only
delivers subscribed events; command(), request() (the worker's
orca.panels.handle, as a panel's plugin.request), posts, logs, refusals and
deactivate(). Storage and settings share the mock host's in-memory store.
- orca plugin test loads a worker main's module graph as Orca's worker host
does (.ts/.mts transform, Node resolution, no runtime orca import).
- mockHost records refusals (capability_denied, not_permitted, ...) even
when plugin code catches them; panel.refusals exposes them and
orca plugin check reports them as warnings. Check dedupes repeated issues.
- Typings, docs and a generated references/testing.md skill reference; the
reference generator reads the split orca typings.
* feat(plugins): orca plugin open and shot; dev watch --shot
- orca plugin open <key> [--panel|--editor] shows a panel in the right sidebar or a file in the
plugin's custom editor, by changing renderer state only (no focus or window raise).
- orca plugin shot <key> [--panel] [--theme] [--width] [--out] [--json] waits for the panel's
current code to draw, then crops webContents.capturePage to the panel (html guests included),
with background throttling off while hidden. --width and --theme are temporary in-memory
overrides; the PNG returns base64 (5 MiB cap), so --environment works.
- dev watch --shot <dir> writes a PNG per panel after the load and each good reload.
- The authoring skill's loop is now dev watch --check --shot → read the PNG → plugin test.
* fix(plugins): shots never disturb a visible Orca
- shot --theme is skipped while the Orca window is on screen (themeApplied: false with a
themeReason); the override applies only to a hidden or minimized window.
- dev watch --shot opens the first panel (or --shot-panel <id>) once, then shoots it with
ifShown: if the user moved the sidebar away it emits one shot_skipped instead of switching.
* fix(plugins): dev loop dogfood fixes; orca plugin dev emit
- dev watch: approval re-run command drops watch-only flags and old --approve; a folder added
by a refused load is owned by the next approved load (Ctrl-C unloads it); check events
carry the plugin's status (no green while pending/errored); reloaded prints before that
reload's logs; --json errors are one NDJSON line.
- Errors: log, banner and check share source-mapped positions (a line-1 prelude hands sucrase
mappings to the components runtime); check dedupes issues and locates syntax errors;
Orca runtime frames and the error module's bogus location are dropped from logs.
- .orca/types tsconfig sets preserveSymlinks (tsc 7 loaded typings twice under /tmp).
- Templates' READMEs use dev watch.
- orca plugin dev emit <dir|key> <event> [--data] sends a test event to a dev plugin's worker
through the real delivery rules (plugins.devEmit).
* test(plugins): fixed timestamp in the dev emit test
* fix(plugins): shot captures an html panel's guest itself
On Linux the main window's capturePage leaves a <webview> guest blank. The renderer now reports
the guest's rect, and main captures the guest's own webContents and draws it over the window
capture at that rect (raw bitmaps, nativeImage only). Components panels are drawn by the host,
so the window capture alone stays right for them.
* refactor(plugins): keep plugin-service and main-process-plugins under max-lines
After rebasing onto #37013 both files crossed 300 lines. The guest log sink moves into the
desktop view binding (bindPluginDesktopViews), and plugin-service tightens emitEvent and its
event-delivery imports.
* feat(plugins): panels talk to their own worker, read storage, settings and events
- plugin.request (host API 1.2): a panel or editor asks its plugin's worker, which answers with
orca.panels.handle(name); the worker pushes with orca.panels.post. Same plugin only, JSON
checked, 1 MiB answers, 256 KiB/200-per-10s posts, a deadline and the panel's lifetime.
- Panels and editors get storage.*, settings.* and secrets.* behind the same capabilities, and
storage/settings onDidChange on every surface; events.subscribe delivers workspace and agent
events to panels that hold the read capability.
- contributes.settings: typed settings (a JSON Schema subset) with defaults and value checks.
- Frames get orca.plugin/storage/settings/secrets; components panels import plugin, storage,
settings, secrets from 'orca', and atom(key, init, { persist: 'storage' }) shares a value with
the worker. Workers get orca.panels/storage/settings/secrets sugar.
- One typed contract: declare module 'orca' { interface PluginRequests / PluginPanelEvents }.
- Desktop push channel per open panel session (plugins:openPanelPush).
* feat(plugins): Orca Settings shows and edits a plugin's contributes.settings
The plugin page lists declared settings with Orca's own controls (switch, picker, text and
number fields, Reset to default). Edits are checked against the declaration in main, written to
the plugin's settings, and reach open panels and the running worker through onDidChange.
* docs(plugins): hello-tsx talks to its worker; authoring guide covers panels, workers and data
The example now uses one of each: plugin.request answered by orca.panels.handle, a worker post
heard with plugin.on, a persist: 'storage' atom the worker watches, two contributes.settings and
agent.status.changed in the panel, all typed by one src/protocol.ts. The build-less TSX e2e
drives the whole loop.
* fix(plugins): typed bridge overloads, panel call budget, settings IPC and catalog keys
- PluginBridgeApi/PanelsApi use overloads: declared names are typed (and complete), any other
name carries JSON; no never-unions for the type-aware lint.
- The per-plugin panel call budget is 120 per 10 s, since a panel loading storage atoms,
settings and its first requests makes a burst; the hostile fixture floods past it.
- Settings IPC builds its host per call; the settings section's strings are in en.json.
* test(plugins): the example's declared settings show under the plugin with the person's value
* refactor(plugins): keep hello-tsx the minimal starter; the bridge demo is hello-worker-panel
hello-tsx goes back to a sandboxed-ui panel with no worker or capabilities. The worker, protocol,
storage atom, settings and events move to examples/plugins/hello-worker-panel, with their own
e2e (plugin-worker-panel) and PR route.
* feat(plugins): test kit covers the panel bridge and plugin data
mockHost keeps the plugin's storage, settings and secrets in memory (seeded with storage,
settings, secrets; read with host.data()) and fakes the worker with requests, so plugin.request,
storage/settings/secrets and persist: 'storage' atoms work under mount(), writes reach
onDidChange, and panel.post(event, payload) is what plugin.on hears. Typed by PluginRequests and
PluginPanelEvents.
* test(plugins): focusInGuest waits for the guest and retries focusing as one step
Under load the custom-editor guest could still be attaching, so executeJavaScript threw
'dom-ready' before the focus poll began. Wait for waitForGuestReady, then retry focusing the
webview and the element together, tolerating a guest that is still attaching or remounts.
* docs(plugins): the authoring skill covers panels, workers and data; persistence leaves the renderer
- skill-guides/orca-plugin-authoring.md gains Panels, workers and data (plugin.request/on,
orca.panels.handle/post, persist: 'storage', contributes.settings, panel events, test kit), and
its generated references read orca-services.d.ts for shell, files, on and the bridge types.
- After the dev-loop rebase, persisted-atom wiring (queue plus storage sync) moves to
createDeclarativePersistence so the renderer stays under max-lines.
* feat(plugins): panels show agents: agents.list, terminal ids on agent events, workspace.changed
- agents.list (agent:read, every surface): the focused workspace's agents with terminalId,
title, agent kind, state and stateStartedAt, from the hook server's store.
- agent.status.changed carries terminalId and title: paneKey mapped through the runtime's
terminal list (tabId:leafId) or the status's terminal handle; looked up only while a plugin
listens, in arrival order.
- workspace.readContext terminals carry title; workspace.changed (workspace:read) fires after
workspace session writes when the focused workspace or its terminals change.
- Event subscriptions are kept per panel session, so one made before the push channel opens
still counts.
- shell.openExternal never prompts on a hidden or minimized window: it settles
{ opened: false, reason: 'consent-unavailable' } and says so in the plugin log, which also
notes 'waiting for the person to confirm' while a prompt is up; the rate budget is spent only
when Orca acts. e2e launches never open a real browser or mail client.
- A worker may export activate as default or by name; docs, typings and the authoring skill
document the entry, the agent events and a live agent status recipe.
* fix(plugins): the plugin page names the workspace.changed subscription
* fix(plugins): workers activate only by default export, and say so when it is named
Orca calls the worker module's default export. A module with a named `activate` and no default
now fails to start with an error naming the fix (`export default function activate(orca)`),
instead of being called by a fallback; docs, typings and the skill say default export.
hello-worker-panel gains a picker setting (suggestionOrder) the worker reads.
* feat(plugins): build-less TypeScript workers
A worker's main may be .ts/.mts. The plugin host installs module.registerHooks scoped to the
plugin folder: .ts files are transpiled with the frames' sucrase options (lines kept, inline
source map, source maps on), relative imports fall back to the frames' candidate files, and a
value import of the type-only 'orca/worker' fails with a clear error. Needs engines.orca
>=1.4.246.
* feat(plugins): components panels hot reload with their state
- In dev folders a save swaps only the hidden guest: keyed memory atoms are handed to the new
guest (ui.retain → ui.hydrate memory), dropped when an atom's version changes.
- A save that fails to load keeps the last good render and shows an inline file:line:col
error banner; the next good save clears it. A subtle Reloaded mark follows a reload.
- Transpile and import errors carry line and column; dev worker restarts keep storage.
* feat(plugins): orca plugin dev watch
One command for the inner loop: refreshes .orca/types, dev-loads the folder, follows its frame
and worker logs (plugins.getLogs gains an optional cursor), prints reloads (optional
devChangeCount on list entries) and status changes, and unloads on Ctrl-C. --json streams
NDJSON events for agents.
* feat(skills): orca-plugin-authoring bundled skill guide
A guide for agents to build Orca plugins end to end (decision table, build-less rules, the
dev watch loop, test/check, validate, release-entry, approvals, gotchas) with references
generated from the component catalog, host API table, event schemas, manifest schema and CLI
specs, checked for drift. Fixes the components template's Reset to set(count, 0).
* feat(plugins): orca plugin dev watch --check; test kit details in the authoring skill
- --check runs orca plugin check after the load and after each reload (one at a time; saves
made meanwhile fold into one rerun) and prints each panel's render or errors inline, as a
`check` event with --json. Local folders only, since it runs the plugin's code here.
- The orca-plugin-authoring guide's Test and check section now documents orca plugin test and
check from #36969 exactly (JSON results, exit codes, test placement, the orca/testing kit and
its common mistakes), and the loop uses dev watch --check.
* refactor(plugins): move the components renderer's timing and helper constants to their own module
A user act admits any gated work for its distro that runs inside it,
including work it merely kicks off. Three starters could lend that
allowance to background work: repo username enrichment's shared pass,
the PR refresh queue's drain timer, and gate-note listeners, which the
act's own wake note runs. Each now starts outside the act, so its Git
or WSL access is checked against the running list like any background
read. The act's own direct work is unchanged.
* feat(plugins): orca/testing kit, `orca plugin test` and `orca plugin check`
- `orca/testing` mounts a components panel in plain Node (node:test, vitest, bun) with the real
runtime and Orca's own tree validator: find by text/role/label/testId, press/type/submit/toggle/
select that resolve once the panel settles, tree() snapshots, errors (with stacks) and warnings,
persisted state (with stored() for migrations), emit/setVisible/rerender, and unmount running
setup cleanup. It re-exports node:test and strict assert so tests type-check without @types/node.
- mockHost refuses what Orca refuses with the same codes (surface, capability from the manifest,
params, a gesture method without a press), checks configured results against the host API, and
records calls.
- `orca plugin test [dir]` discovers *.test.ts(x) and runs them with node:test through Node module
hooks that resolve `orca` like the guest's import map and transpile/resolve sources exactly as
Orca's build-less loader, or with the plugin's own vitest via a generated config.
- `orca plugin check [dir]` validates, then renders each components panel once and reports what it
threw (file:line), layouts Orca would refuse, and props it would drop.
- The kit, hooks and register entry ship minified beside orca-runtime.mjs and are unpacked from asar;
typings land in .orca/types/orca-testing. The components template and hello-tsx get sample tests.
- ui.error carries an optional stack; a new runtime starts every atom from its initial value; the
hydration fallback timer is cleared once hydrated.
* plugins: run plugin test/check on a system Node or Orca's own, and warn they are unsandboxed
* test(plugins): run the shipped example plugin tests from the unit suite
* fix(mobile): save iPhone library photos as JPEG named by their bytes
The iOS photo picker handed back the asset's HEIC bytes as-is, and the host
named every pasted image .png, so agents read a HEIC file as a PNG and failed.
Ask the picker for the compatible representation (JPEG), and name pasted
images by their signature so the extension always matches the bytes.
* fix(mobile): keep unknown pasted bytes on the .png name
A .heic name falls outside the shared image MIME table, so chat previews
and agent prompts would drop the image. The picker now sends JPEG, so
leave HEIC detection out.
* refactor(mobile): keep the pasted image extension list module-private
* fix(wsl): an automation wake no longer boots a host for a run it already ran
The wake trusted a due time saved while the host was connected. A run that
came due while the host was still up, which orcad ran itself, still read
as due at the next launch, and the launch booted the stopped distro to run
it again. Each saved host now records when this desktop last saw it ready
(on every learn, which runs on ready and while connected), and only a run
due after that wakes the distro, once. A file from before the field counts
its stored run as already run, so it never boots for a time that may have
run, and still wakes for a later run that came due since.
* fix(wsl): save an automation wake's next run before booting; keep a fresher ready stamp
A quit during the boot no longer repeats the wake at the next launch, and a
learn that ran during an earlier host's wake keeps its newer ready stamp.
The launch test uses a fixed clock instead of the wall clock.
* fix(runtime): record terminal activity on a headless host so Recent sorts by it
* test(runtime): justify the store fixture cast
* fix(runtime): skip paired-window creates, coalesce notices, keep exits after inventory disconnects
* fix(runtime): don't count a pane's recovery or a replayed stop exit as activity
* docs(runtime): say which hosts stamp terminal activity
* fix(runtime): don't count a saved session's resume as activity on any route
* refactor(runtime): let the exit lifecycle say when a terminal ended on its own
* test(runtime): expect reopensPane on pane recovery and paired-window creates
Flips PLUGIN_ISOLATED_FRAMES_DEFAULT. ORCA_PLUGIN_ISOLATED_FRAMES=0 or the
experimentalPluginIsolatedFrames: false setting turns it off for one machine; reverting
this commit turns it off for everyone.
* fix(native-chat): stop async question Submit from queuing duplicate answers
When the agent is mid-turn, answering a Codex async question queues the
answer as an ordinary message. The card stayed up until a transcript row
retired it, which a queued message never posts until the turn ends, so
every Submit queued another copy. Hide the occurrence once its answer is
queued, on desktop and phone.
* fix(native-chat): steer async question answers into the running turn
An answer to the agent's own mid-turn question belongs to that turn.
Queuing it behind the follow-up setting held it until the turn ended,
after the agent had already proceeded without it. Send it with the
steer intent whenever the follow-up resolver would queue a plain send,
on desktop and phone. Stop and a waiting command still queue, and the
queued fallback still hides the card.
* fix(native-chat): hold async questions while a queued answer waits
Replace the mount-scoped hide on a queued answer with one derived from
the queue: a question stays hidden while a queued card quotes it, across
remounts, and returns if that card is deleted or edited away. Lock
Submit until the send settles, and keep a returned answer out of the
composer draft.
* fix(native-chat): keep structured session under the line cap
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* feat(window): working background opacity and blur on macOS, Windows and Linux
One window material model (opaque / translucent / blurred): macOS vibrancy without transparent, Windows 11 22H2+ acrylic, Linux transparent windows. Terminals paint their translucent background once, the transparent WebGL frame clears before drawing (#6491), and Reduce Visual Effects drops modal/backdrop blur and honors prefers-reduced-transparency.
* perf(window): drop hidden macOS vibrancy while a theme image covers the window
* refactor(settings): move Reduce Visual Effects into its own setting and search files
* feat(plugins): build-less TS/TSX plugins, typings and authoring tools
- Plugin frames transpile .ts/.tsx/.mts/.jsx on load (sucrase on a shared worker thread with a
2 s budget, cached in main), resolve relative imports to exact files, and refuse bare
package imports by name. Components and html panel/editor entries may be .ts/.tsx.
- orca plugin types writes typings for the components orca module, window.orca, worker
activate(orca), host.call and events into .orca/types, from one host-typings generator.
- orca plugin create --template components; templates omit publisher and target 1.4.246.
- orca plugin release-entry, dev inspect and restart; validate:plugin checks named files.
- Guest console errors reach orca plugin logs; dev workers restart on edits; the dev watcher
ignores .git, .orca and node_modules; Git and folder installs cannot replace catalog installs.
- examples/plugins/hello-tsx, docs, and the plugin-buildless-tsx e2e spec.
* ci: run the Windows parity probes with one tsgo checker
tsconfig.node peaks near 22 GB with tsgo's default four checkers (each holds its own types)
and about 16 GB with one; the 16 GB Windows runner paged the native probe past its 900 s
timeout once this branch's package.json change triggered the job. One checker gives the
same diagnostics (verified locally with --parity-only); the timing benchmark is unchanged.
git.history gains optional branchOnly and skip params. A branch-only page
lists only commits the base lacks, with per-commit stats and whether the
upstream has each one, plus ahead/behind/unpushed counts. Older clients
send neither param and get the same reply as before; older hosts ignore
them, so clients detect support from the echoed branch and skip fields.
Mobile may now call git.remoteCommitUrl to open a commit on its host.
* Show child activity counts beside the existing children toggle
* Keep child activity counts inside the children control
* Reuse seeded settings in the child activity preview
* Reapply "Size tabs to titles and preserve floating panel drag space (#34025)" (#34506)
This reverts commit 4b4bcc2e60.
* Hold tab widths through agent renames and gate title-fit tabs as experimental
Title-sized tabs resized whenever an agent, shell, CLI, or page changed a
tab's live title, shifting neighbors. Tabs now size from a settled title
that only updates when the strip changes, the workspace is shown again, or
the user renames; the live title overlays it. Title fitting is opt-in via
Settings > Experimental > Fit tabs to titles; fixed widths remain default.
* Settle title-fit tab widths after titles go quiet and never under the pointer
Holding widths until the strip changed left agent-renamed tabs truncated
beside empty space. Tabs now follow their titles once no title in the strip
has changed for 3s, all at once, and never while the pointer is over the strip.
* Stabilize tab widths when agent activity icons change
* Stabilize tab widths during title changes
* Stabilize fit-to-title tab widths during live updates
- Refit only on workspace return or confirmed inline rename
- Preserve tab geometry through title, status, and control changes
* Use stable fixed widths for tab strips
* Distribute tab widths across available strip space
* Keep tab chrome consistent when only one tab is open
* Update tab strip test to expect full available width
* Fix editor tab hover card to show the file display name
* fix(ui): let toast actions work above a modal instead of looking clickable but dead
Radix modals dismiss/refocus on outside pointerdown, so the Toaster already swallowed
pointerdown inside toasts; it additionally swallowed click for every control except the
close button, making actions like 'Update skill' look live while doing nothing. Keep the
pointerdown shield (modal stays open, focus/input intact) and let the click run.
* test(e2e): keep a toast mounted and close Settings after the over-modal action
* fix(ui): default-deny toast actions above a modal, visibly disabled, with a modal-safe opt-in
Restores #32065's lock (close always works; actions that navigate or change settings
don't bypass the modal) and fixes the clickable-but-dead look: blocked controls get
aria-disabled, a 'Close the dialog to use this' title, and disabled styling, updated
live as modals open and close. Background/toast-local actions opt in via
modalSafeToastActionLabel or data-toast-modal-safe.
* fix(ui): return focus to the modal draft after a toast-opened dialog closes
A modal-safe action that opens a trigger-less dialog (Update skill) left focus on body
after close, minimize or Escape. captureDialogReturnFocus records the focused element
before opening and restores it via onCloseAutoFocus.
Also harden Dialog Escape: Radix arms a just-opened sibling dialog's Escape a render
late, so an early Escape closed the dialog beneath and dropped its draft. A dialog now
ignores Escape while a later-opened modal is open.
* fix(ui): hide toast actions above a modal instead of leaving them clickable-looking but dead
Replaces the modal-safe opt-in design. Main's close-only capture gate stays as is; a
single CSS rule hides every toast control except close while a modal overlay is open,
so the message and dismissal remain and actions return once the modal closes.
* fix(ui): type the sheet fixture's effect cleanup
* test(e2e): dismiss an older star-nag toast over the dialog instead of re-raising one id
* fix(ui): toasts sit behind a modal's backdrop until pressed or newly raised
Older toasts drop behind an open Dialog/Sheet backdrop, dimmed and inert, so nothing looks
clickable that isn't. A press on one, or a toast raised while the modal is open, brings the
stack forward; close and actions then work above the modal with its focus and draft intact.
Actions run that way are an escape hatch and emit toast_action_over_modal.
* test(e2e): read recorded telemetry without a type assertion
* test(e2e): read recorded telemetry through a typed global
* test(e2e): check the raised Later action once by mouse; keep #32065's per-pointer Dismiss as on main
* Optimize agent picker menu updates and pointer highlighting
- Keep catalog hydration re-renders scoped to open menu content
- Preserve unchanged picker rows and fix menu highlights after scrolling or submenu flicks
* Improve menu pointer handoffs and picker update performance
- Preserve focus during scrolling and portaled setup interactions
- Open rested submenu targets without an extra pointer move
- Avoid unnecessary picker row updates while keeping translations current
* Fix inventory subscriber assertions to identify agents
* Defer launch picker inventory updates to passive effects
* Add menu pointer highlight component fixtures and tests
* Fix checkbox menu item test accessibility state
* Reset retained menu pointer tracking on reopen
* Keep menu highlights in sync while scrolling submenus
* Remove synthetic submenu pointer handoff
- Preserve Radix hover and keyboard navigation without triggering clicks
- Keep wheel-scroll highlight synchronization responsive to current cursor position
* Preserve submenu pointer grace after wheel scrolling
* Preserve prevented menu pointer movement during scrolling
* Fix menu pointer tracking observer cleanup
* Recognize component fixture specs as Playwright tests
* feat(sidebar): collapse sub-agent lists by default and group idle sub-agents
Child agent lists no longer auto-expand when a parent spawns sub-agents, so
cards don't grow and shift the sidebar. A collapsed parent shows +N plus a
state dot when hidden children are doing live work. Two or more idle children
fold behind an expandable 'N idle' row.
* test(sidebar): build typed agent rows instead of casting
* fix(sidebar): use a data attribute instead of an unknown class for the idle group hook
* fix(sidebar): keep hidden sub-agents reachable and visible
- Send-target picker opens every lineage level and idle group so child agents stay targetable
- Full display mode shows the live-state dot beside a collapsed +N, like compact
- Collapsed signal considers all descendants, not just direct children
- A collapsed parent highlights when it hides the focused agent
- Idle group row joins the agent tree as a treeitem
* fix(sidebar): sync idle-group strings; no toggles or folding while picking a send target
Right-clicking a File Explorer row now offers an "Open in" submenu
listing the apps from Settings > Open In, matching Source Control.
The submenu moves out of the Source Control entry menu into a shared
OpenInContextSubmenu used by both menus, keeping the existing
getOpenInEntryAvailability gating for SSH, remote runtime, and
unresolved owners.
Open failures for a file row are worded as a path rather than the
workspace (new pathInvalid/pathNotFound strings; remoteLaunchFailed
renamed to pathLaunchFailed since it now covers local paths too).
Fixes#9742
Co-authored-by: Neil <neil@stably.ai>
* feat(themes): make Match Terminal a real app theme painted at :root
Match Terminal used to be an inline override on three left-column
elements, so the tab bar, right sidebar, status bar, titlebar, menus and
editors kept the built-in palette, and its fixed color mixes became
unreadable on low-contrast palettes.
It is now a light/dark app theme slot value synthesized from the active
terminal palette (read through resolveTerminalPalette, so each mode's
color overrides apply) and painted through the plugin theme pipeline
(derivation, contrast gate, startup cache, Monaco theme). The document
mode follows the terminal background, including onboarding's restore.
The legacy leftSidebarAppearanceMode='match-terminal' value fills the
empty slots on load and on writes (desktop and web), and it is kept
mirrored while a slot matches the terminal so older Orca still matches
its sidebar. The pickers mark the option with a muted "(dynamic)" label.
* fix(onboarding): sync the painted-theme ref in an effect, not during render
* feat(themes): use a lightning icon for Match Terminal's dynamic label
* perf(themes): reuse the Match Terminal theme when its content is unchanged
* fix(wsl): host-row Start follows the same precedence as the other surfaces; Start's reload doesn't wake
The sidebar host menu offered Start whenever the distro was stopped and the
WSL setting was on. It now uses the same rule as the editor, Explorer and
Source Control: stopped, and the host can run. Missing server files keep
their own notice, and a setting-off user whose saved host still serves gets
Start. After Start, the editor reloads the file without asking to wake the
distro again; the click already did.
* i18n(wsl): translate the host offer's project-not-found message
* test(wsl): run the stopped-host hook tests through the real unavailable-reason path
* fix(agents): open a shell, not a chat, in empty workspaces when the default agent is Auto
* fix(agents): use the host launch preferences type for the empty-workspace default
A switch over runtime RPC (the phone's Accounts screen) changed the
selection but never reached the window's running-session handling, which
only the status bar and Settings called after their own IPC switch. The
RPC path now reports the lane and outgoing/incoming account to the main
window, which runs the same handler. The web client handles its own
switches and opts out.
* fix(wsl): turning the WSL hosts setting off keeps saved hosts serving; it stops only new hosts and moves
* chore(wsl): drop the stale run-refusal doc comment
* feat(skills): auto-update unedited Orca skills; ask only for edited copies
Planner already hashes each installed SKILL.md against every shipped release
digest (legacy installs included). When a plan's only writes replace such
hash-verified copies, the freshness nudge now applies it silently. Edited
copies and repairs still go through the consent toast; edited copies now raise
it too instead of only showing attention in settings.
* feat(skills): decide silent skill updates per skill and per location
Add skills.autoUpdateBundledOrca (capability-gated): the host plans the named
installed skills under their locks and applies only hash-verified released-copy
updates, re-checking each location before writing. The nudge auto-updates each
unedited skill and names only edited, unknown or repair-needing skills; the
try-once guard is keyed per skill and observed state, so one failure falls back
to the toast for that skill alone.
* fix(skills): surface preserved edits, share run ownership, re-show retracted nudges
- A silent update that moved a racing edit to a backup now shows a warning
naming the skill and backup path instead of passing as success.
- Silent updates run under skill-update-run-store's per-target ownership, so
manual and automatic runs never interleave and the dialog shows busy.
- Programmatic toast retraction releases its session marker; only a user
dismissal suppresses the nudge.
* fix(skills): report stranded edits, isolate silent runs, bound attempt memory
- An old copy whose late edits cannot be backed up now fails the op with
strandedPath naming where the edits remain; the silent caller warns with that
path, and the next plan's backup cleanup is nudged, never deleted.
- Silent runs never start over a running or unacknowledged foreground result
and never surface in the status bar.
- Auto-update attempts are keyed per target and pruned to the states the
current scan observes; unchanged states stay suppressed.
* fix(skills): warn when a silent update's result is lost
Any rejected or timed-out auto-update request may follow a committed swap
whose racing edit went to a backup. Show a persistent warning naming the
target and its skill backup directory, and settle the attempt so the same
state is handed to the nudge instead of retried.
* test(skills): guard version skippers; ledger also walks release tags
Audit of every v* tag and HEAD history found no SKILL.md bytes missing from
released-digests.json. The generator now also walks release tags (falling back
to HEAD) so a release cut off-branch can never ship unrecognized bytes. Add a
real-disk test that installs the oldest shipped orchestration (v1.3.24-rc.2) in
the npx-skills and agent-folder layouts and auto-updates it silently.
* test(skills): type the nudge mock's error without an assertion
* chore(i18n): add the silent skill update warnings to the English catalog
* fix(plugins): refuse main-window permission requests from plugin frames
Plugin srcdoc frames share the main window's session, so a plugin could ask for
camera, microphone, fullscreen or pointer lock with the app's trust. Requests from a
frame registered as a plugin frame at creation, or nested in one, are now refused.
Chat visuals and other host frames keep their permissions.
* fix(plugins): type host frame identity instead of object
* perf(diff): stop combined diff sections re-rendering on unrelated settings writes
Combined diff viewers and EditorPanel subscribed to the whole settings object,
and DiffSectionBody built a fresh Monaco options object each render, so any
settings write re-rendered every mounted section and called updateOptions on
each. Select only the settings each component reads, let DiffSectionItem read
its diff display settings directly, and memoize the DiffEditor options.
* test(diff): drop type assertion in options identity test
* perf(editor): select resolved typography so inactive fallback inputs do not re-render editors
* fix(editor): resolve typography when no cached entry exists
* feat(telemetry): A/B holdout for the Chat UI launch tip
Freeze a 50/50 chat_ui_launch_tip_v1 cohort for profiles still due the tip on
official builds with telemetry not disabled; control never sees the tip. Emit
chat_ui_feature_tip_exposure in both arms and agent_default_surface_changed
from the tip and from Settings -> Agents.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(telemetry): hold the Chat UI launch tip until the telemetry banner resolves
Enroll only when consent is enabled. While the banner is pending, the tip is
skipped for that launch without being marked seen, so its exposure is not
spent unmeasured and the arm cannot influence the consent choice.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(telemetry): document best-effort Chat UI tip exposure on rejected sends
Co-Authored-By: Claude <noreply@anthropic.com>
* test(telemetry): pass the exposure test through a hoisted mock instead of in-test importActual
Co-Authored-By: Claude <noreply@anthropic.com>
* test(telemetry): justify the fake Store assertion in the telemetry harness
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat(editor): choose a code editor theme independently of the app theme
* fix(editor): paint the diff overview ruler with the editor theme
* chore(i18n): add editor theme strings to the English catalog
* fix(editor): editor-theme settings search and Orca line tints in a separate editor theme
Searching 'editor theme' now opens the Interface section that holds the rows.
Diff comment-range bands and the conflict base-section tint use the editor
theme's line-number color, so they stay visible when the app's tokens don't
contrast with a differently based editor background.
* fix(editor): an editor-only theme paints the diff colors it would paint as the app theme
Main's diff tokens reach Monaco only where a theme painted them, and omitted tints
come from main.css fallbacks. An editor-only theme now reads its tokens through the
same fallbacks, so its diff colors match the ones it paints as the app theme.
* fix(mobile): run the repo's archive hook when deleting a workspace from the phone
Mobile's delete sent worktree.rm without runHooks, so the host skipped the
orca.yaml archive hook that desktop runs. The phone now does what desktop's
ensureHooksConfirmed('archive') does: it asks to trust an unapproved shared
archive script, sends runHooks with desktop's reply timeout, and when the hook
fails it shows the host's error with Delete Anyway, which resends with
allowFailedArchiveHook.
repo.hooks now reports archiveTrust (hash and content of the shared orca.yaml
archive script, or null when none needs trust). Older hosts omit it, and the
phone then keeps skipping the hook rather than run a script it cannot check.
* fix(mobile): share one Modal for delete drawers and pin each prompt to its host
Delete's confirm, archive trust prompt and hook failure now share one
BottomDrawerModalHost so iOS never presents over a closing drawer. A pending
prompt or failure keeps the clients it started on, the row hides as soon as
Delete is tapped, repo-wide trust counts server-owned repos, and folder repos
report no archive trust since their removal runs no hook.
* fix(mobile): recheck archive trust before Delete Anyway and count server repos once
Delete Anyway goes back through the trust decision, so a script changed since
approval is asked about again. Repo-wide trust counts each server repo once
rather than once per workspace, and restoring a row never duplicates it.
* fix(mobile): honour repo-wide archive trust only with the desktop catalog loaded
Until the catalog loads, a same-id repo on another host cannot be ruled out,
so only an approval of that exact script counts. The trust drawer's actions
take the pending request synchronously so a double tap cannot both run and
skip the hook.
* fix(mobile): drop archive prompts and failures from another desktop
A trust prompt or hook failure belongs to the desktop it started on. After a
host switch it is no longer shown or acted on, so a retry can never pair one
desktop's trust with another desktop's repo catalog.
* refactor(mobile): one archive trust check and an optional drawer busy flag
* fix(mobile): file delete-failure strings under the host screen overlays
Also pins the host's archive hash as a literal digest so the node test no
longer imports renderer code.
* fix(mobile): carry delete haptics and automations onto the renamed hook trust drawer