Commit Graph
1206 Commits
Author SHA1 Message Date
buf0-bot[bot] 97f0dd7a04 Update README downloads badge (#39561)
Co-authored-by: buf0-bot[bot] <252831055+buf0-bot[bot]@users.noreply.github.com>
2026-11-07 18:20:34 +00:00
Neil c0e8e4e6eb docs(plugins): theme background video ships in 1.4.254 (#39478)
#39450 merged after the 1.4.253 cut, so the videoPath since-version and the
theme reference name 1.4.254.
2026-11-07 03:49:08 -08:00
Neil b0dd73e7aa feat(plugins): play a looping video behind app themes (#39450)
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.
2026-11-07 03:32:14 -08:00
Neil 35975c0580 feat(themes): theme tokens for chat text styling and a readable chat selection (#39432)
* 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
2026-11-07 03:02:23 -08:00
Neil 51f3c956ed feat(plugins): let plugins ship web fonts for the terminal, editor and interface (#39416)
* 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.
2026-11-07 02:08:35 -08:00
buf0-bot[bot] 3d8a285132 Update README downloads badge (#39291)
Co-authored-by: buf0-bot[bot] <252831055+buf0-bot[bot]@users.noreply.github.com>
2026-11-07 04:51:25 +00:00
Neil 3c866a31db feat(serve): refuse public source addresses on orca serve unless allowed (#39207)
* feat(serve): refuse public source addresses on orca serve unless allowed

`orca serve` listens on every interface. It now accepts connections from
loopback, private LAN (10/8, 172.16/12, 192.168/16, 169.254/16, fc00::/7,
fe80::/10) and Tailscale (100.64/10, fd7a:115c:a1e0::/48) sources, and drops
public-source TCP connections before HTTP parsing or the WebSocket upgrade.

Public sources stay accepted when:
- --allow-public is passed (orca serve, orcad, serve service install);
- a paired device has authenticated from a public address (persisted flag);
- --pairing-address is a hostname or a public IP literal;
- any device paired before this server first ran the check is still paired.

The startup log states which rule applies; the first refused public address
each hour is logged with the flag name. Desktop listeners and Orca Relay are
unaffected.

* feat(serve): accept on-link public addresses and add --refuse-public

A public source inside a prefix of one of this host's own interfaces (os.networkInterfaces, re-read at most every 5 s) is a same-network peer, such as a LAN client using global IPv6, and is now admitted. Prefixes wider than /16 (IPv4) or /48 (IPv6) are ignored.

--refuse-public (orca serve, orcad, serve service install) refuses public addresses regardless of grandfathered pairings, a remembered public connection or the pairing address; it cannot be combined with --allow-public.
2026-11-06 17:02:15 -08:00
047e113783 feat(cli): pair clients with a running Orca via orca pair (#36469)
* feat(cli): pair clients with a running Orca via `orca pair`

A headless Orca could only print a pairing offer at startup, so pairing a
phone with `orca serve service install` meant stopping the service. `orca
pair` asks the running runtime for an offer over the owner's local socket
through a new `pairing.createOffer` method (pairing-admin, never granted to
paired clients or phones). It reuses the desktop's offer logic, now shared
by the Settings IPC handlers: mobile offers stay LAN-only unless --relay,
and the first network pairing widens a loopback bind as the desktop does.

Also honor an explicit ORCA_SERVE_RUNTIME=orcad on packaged macOS. Electron
serve needs a desktop login and aborts in an SSH session with nobody logged
in; orcad is plain Node and serves there, at the cost of no in-app updater.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(cli): move the serve-restore and pair handler groups out of the manifest

handler-group-manifest.ts was already at the 300-line limit on main, so
adding the pair group failed lint. Group the two serve/pairing entries in
their own file, as jira, linear and reference already are.

* test(mobile): pin the desktop pairing IPC handlers' replies and runtime calls

Characterizes mobile:getPairingQR and mobile:getRuntimePairingUrl across
every branch (explicit and default address, LAN-only with no address,
Relay without an address, relay failures, this-computer reach, widening
and its failure, unavailable offers). The same file passes unchanged on
main and on this branch, so moving the offer logic into
pairing-offer-requests.ts did not change the desktop path.

* test(mobile): type the QR stub instead of casting it

---------

Co-authored-by: Office exitnote <h@Offices-MBP.lan>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Neil <neil@stably.ai>
2026-11-06 17:01:32 -08:00
Kelvin Amoaba a3a0e68380 fix(packaging): require macOS 13.4, the minimum the on-device speech engine runs on (#39135)
* fix(packaging): require macOS 13.4, which the bundled speech engine needs

* fix(updater): keep the macOS requirement title clear of the close button

* fix(packaging): check out the Darwin floor table for adhoc builds and raise the cask floor

* refactor(updater): reuse the parsed Darwin floor for the macOS requirement

* fix(updater): use a margin to keep the macOS requirement title clear of the close button

* fix(updater): say the macOS requirement comes from Electron and the speech engine
2026-11-07 00:18:50 +00:00
0b5a293fa2 feat(serve): add bind address option (#35095)
* feat(serve): add bind address option

* fix(serve): validate service bind address

* fix(serve): validate bind flag values

* fix(serve): reject scoped bind addresses

* docs(serve): document bind option paths

* docs(serve): note that --mobile-pairing needs an all-interfaces bind

* fix(serve): move bind address parsing to shared so the packaged CLI can load it

The packaged CLI ships out/cli and out/shared but not out/main/orcad, so
cli/runtime/serve-bind-flag.js failed the packaged runtime-import check
(and would fail to load) when it imported main/orcad/orcad-bind-address.
Move the module, unchanged, to src/shared and point orcad, Electron serve
and the CLI at it.

* test(serve): pin down the default listener and the --bind startup paths

- Extract the Electron serve listener pin into resolveServePinnedBindHost
  and test that serve without --bind stays unpinned (all interfaces, as
  before), an explicit --bind pins it from either argv form, and the orcad
  browser sidecar stays on loopback.
- Golden the orcad and Electron serve argv without --bind against the
  pre---bind output.
- Test that the CLI passes bind only when given and refuses --bind with
  --mobile-pairing before launching.
- Guard that serve-bind-flag and the shared serve validation load only CLI
  and shared modules, which is what the packaged CLI ships.

---------

Co-authored-by: lw <13579959+bertramlongwuyan@user.noreply.gitee.com>
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: xuanchangfeng <210147232+xuanchangfeng@users.noreply.github.com>
2026-11-06 15:47:42 -08:00
Neil de80cd74c0 fix(agents): list agents found through a custom command path (#39178) 2026-11-06 14:52:08 -08:00
Neil be3b87f3ac cli: add orca plugin install, update, uninstall and catalog list/add/rm (#36647)
* cli: add orca plugin install, update, uninstall and catalog list/add/rm

* plugins: escape the invisible-character regex so the orcad bundle stays ASCII

* plugins: split approval re-run helpers and plugin RPC binding to stay under max-lines
2026-11-06 14:20:27 -08:00
Neil f0bfce8fce fix(automations): accept Antigravity model and effort extra arguments (#39161)
Antigravity automations refused --extra-agent-args, so unattended agy jobs
could only use the model and effort from agy's global settings.json. Allow
--model and --effort through the existing allowlist; permission, mode, and
other flags stay refused.

Part of #35234 (STA-12678).
2026-11-06 12:55:54 -08:00
Neil a20e577dc9 feat(antigravity): manage accounts in the selected WSL distro (#39007)
Antigravity Accounts rejected a WSL account location outright. agy inside
WSL keeps its login in its file store (~/.gemini/antigravity-cli/
antigravity-oauth-token; it detects WSL the same way
isAntigravityFileStorageHost does), so save/select/remove now work there:

- A WSL credential backend reads and writes that file through runWslProcess
  in the selected distro (default resolved via resolveWslDefaultDistro). It
  checks the distro is running before every guest command and never starts a
  stopped one. Writes are a compare-and-swap inside the guest (sha256 of the
  expected file), go through a private temp file and rename, and pass the
  credential on stdin rather than the command line.
- runWslProcess accepts stdin input for argv-delivered commands.
- Each distro gets its own encrypted vault, so accounts never cross distros
  or the Windows host.
- Accounts "Refresh usage" is enabled for WSL now that usage reads the
  selected runtime (#38985), and rejects a reading tagged with another
  runtime's provenance.

Fixes #25847
Linear: STA-9624
2026-11-06 12:54:20 -08:00
buf0-bot[bot] 76e4769cf7 Update README downloads badge (#39125)
Co-authored-by: buf0-bot[bot] <252831055+buf0-bot[bot]@users.noreply.github.com>
2026-11-06 18:23:29 +00:00
NeilandFlorin Timbuc 239e17f84f feat(cli): add orca host rm, and clean up after every SSH target removal in main (#38774)
* feat(cli): add orca host rm for saved SSH targets

orca host rm --host ssh:<target-id> calls the runtime RPC ssh.removeTarget,
the same removeSshTarget the Settings UI reaches, so the tombstone keeps the
passive ~/.ssh/config import from adding the alias back.

Also document how the import treats Match blocks and Include files.

* fix(ssh): clear a removed target's live state and rename in main for every removal path

Removing an SSH target through ssh.removeTarget (orca host rm, paired clients)
left open tabs with stale reconnect state and kept the host's rename, because
only the Settings dialogs cleaned up after their own IPC call. The shared
removal mutation now drops the display-label override and tells the renderer,
which clears the target's state once for all callers.

Fixes #37949

* fix(ssh): clean up after a removed target in the teardown every removal ends in

A recipe VM's cleanup removes its SSH target through removeRegisteredSshTarget
directly, so the window kept that target's live state. The broadcast and the
rename drop now run there, after the target is gone.

Fixes #37949

* test(cli): keep host rm's retarget-flag refusal next to its other cases

Its old file was removed on main.

Fixes #37949

* refactor(cli): give the host, managed-server and VM handler groups their own module

Keeps the handler manifest under the file-length limit.

Fixes #37949

---------

Co-authored-by: Florin Timbuc <florin@sowild.design>
2026-11-06 06:23:46 -08:00
Neil 26741256dc fix(sidebar): keep the selected card's host pill outline visible in dim themes (#39054)
* fix(sidebar): keep the selected card's host pill outline visible in dim themes

#38841 tinted the host pill outline 40% of the sidebar text into the
sidebar. A theme whose sidebar text is only 4.5:1 leaves that outline at
about 1.6:1 against the selected wash, which matches the pill's fill, so
the pill loses its shape.

When an app theme is painted, its colors are now concrete, so compute
once per theme the smallest mix (from 40%) that gives the outline 2:1
against the selected wash, the same floor focus rings use, and paint it
as --worktree-host-pill-outline-mix. main.css falls back to 40%, so
Orca's built-in themes and any theme bright enough keep today's look.
The tinted sidebar resets it, like the row wash. The wash's CSS fallback
joins the fallback table so the check (and the token reference) read
what main.css paints.

Refs #38641

* fix(sidebar): floor the host pill outline in the tinted left sidebar too

The tinted sidebar paints the app's text on its tint mixed into the app
background, so the theme's measured outline mix doesn't apply there and
it fell back to 40%: a dim theme (#919191 on #2a2a2a) at the default 8%
tint left the outline at 1.6:1 on the selected wash. Measure the tinted
colors once whenever the app theme or tint settings change, paint the
result as a root variable, and have the tinted sidebar read it. Share the
measurement (hostPillOutlineMixOver) with the theme path, and make its
comment name the actual invariant instead of the pill label's contrast.

Refs #38641
2026-11-06 06:06:50 -08:00
buf0-bot[bot] f3cba2e772 Update README downloads badge (#39026)
Co-authored-by: buf0-bot[bot] <252831055+buf0-bot[bot]@users.noreply.github.com>
2026-11-06 12:26:07 +00:00
Neil 95c6a04133 fix(antigravity): switch agy accounts through the Linux Secret Service keyring (#38996)
* fix(antigravity): switch agy accounts through the Linux Secret Service keyring

agy stores its Linux login with go-keyring's Secret Service provider
(service=gemini, username=antigravity, login collection or default alias)
and falls back to its credential file without a session bus, in containers,
SSH and WSL. Add a secret-tool/gdbus adapter that mirrors that collection
choice, refuses locked keyrings with a clear error, and keep the file store
for headless hosts.

Fixes #32818 (STA-11911)

* test(antigravity): avoid casts in Linux keyring adapter test
2026-11-06 03:31:58 -08:00
352e69f09c feat(cli): select and remove any saved agent account from orca account (#38944)
* feat(cli): select and remove any saved agent account from `orca account`

`orca account select` and `orca account rm` accepted only OpenCode and
Devin. They now cover every provider with saved accounts — Claude, Codex,
OpenCode, Devin and Antigravity — through the runtime methods the
status-bar switcher and Settings already use (accounts.selectClaude,
selectCodex/selectCodexForTarget, selectData, antigravitySelect and the
matching remove methods). No runtime or wire change.

`--account` takes an id, an email/label (case-insensitive) or `system`.
A name shared by several accounts is refused with their ids rather than
guessing which subscription to bill. `system` is refused for Antigravity
(no system default), for Claude from the WSL bridge (the RPC has no
target and would reset the Windows host), and always for `rm`.

`orca account list` now prints each account's id and a `system` row,
accepts `--agent` for every provider, and adds the Antigravity block when
the host can read it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cli): assert the host refusal for Antigravity accounts from WSL

The host rejects every client-selected WSL target for Antigravity account
management, reads included, so the test now mirrors that refusal and checks
that no select call is made instead of asserting an impossible success path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cli): keep only the account-selection tests that guard this change

Moves the guards into account.test.ts (the handler layer that owns them) and
drops layer-duplicating, copy-pinning, Cartesian and mock-only cases per
AGENTS.md test rules; un-exports resolveAccountId (test-only export).

---------

Co-authored-by: Maxime AUBURTIN <m@hellomax.io>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 03:03:51 -08:00
Neil b2015a2d89 docs: clarify multi-account setup and concurrency for Codex and Antigravity (#38880) 2026-11-06 01:27:55 -08:00
Kelvin Amoaba 345c326a91 feat(cli): rename a chat with orca chat rename (#38719)
Fixes #38556
2026-11-06 03:44:40 +00:00
Jinwoo Hong a9ba59c944 Nest workspaces under parents on other machines (#38740)
* feat(lineage): store cross-store parent links beside same-store lineage (inert)

A workspace's parent can live in another Orca store. Save that link in the
child's store under a new top-level key, crossStoreLineageByChildKey, so older
builds' same-store cleanup never reads it. Nothing writes records yet.

- Types, load normalization (malformed entries dropped one by one), section label.
- Store get/set/remove by child; a child keeps at most one parent record.
- Every maintenance path acts by the child only: deletion and missing-worktree
  pruning, repo removal and deregistered-repo residue, identity migration,
  profile project transfer, WSL move projection and UNC drive conversion, and
  the orcad migration dormant payload (export, validation, import, subtraction).

* fix(lineage): gate cross-store links on host capability; one parent per child

- Migration stage/commit carrying crossStoreLineage requires the new
  orcad.migration-cross-store-lineage.v1 capability; older hosts are refused
  before staging instead of silently dropping the links. Empty arrays are omitted.
- Payload validation and destination prepare reject a child that has both a
  cross-store and a same-store parent, through one shared conflict check.
- Saved entries stay on disk as written; the map is Record<string, unknown> and
  readers go through validCrossStoreLineage. Re-keying moves unreadable entries unchanged.
- Tests: literal expectations; drop touched mock-call and work-count assertions.

* test(lineage): drop clone and save work-count assertions from project group removal test

* feat(lineage): resolve cross-store parents into the sidebar parent map (inert)

- One resolver: a saved record nests its child under the single visible
  row whose store reports the parent's installationId and whose instance
  matches; copied installs, unknown stores and stale instances stay standalone.
- getSidebarLineageParentsByIdentity merges resolved cross-store parents with
  same-store edges before cycle breaking; a valid same-store edge wins.
- Workspace delete collects descendants only from the parent's own store.
- Store identity: paired stores from their verified status hostDescriptor;
  this store from the published descriptor, else host-installation.json.
- Nothing passes a view or writes records yet, so the sidebar is unchanged.

* fix(lineage): import WorkspaceKey from workspace-scope after main move

* fix(lineage): resolve sidebar parents once over complete rows; keep unowned rows' delete rule

- resolveSidebarLineageParentIdentities resolves same-store edges and cross-store
  records once over every visible store's unfiltered rows, then drops cycles.
  Ancestor injection and nesting reuse that map, so a filtered-out twin still
  makes a parent ambiguous.
- Removed, retired or refusing paired servers leave the view: their records stay
  saved and their children standalone. Offline servers keep their last-known id.
- A row with no owner is unknown, not ours: it never resolves, and delete only
  excludes descendants whose store is known and differs from the parent's.

* fix(lineage): refuse UNC rekey collisions and transfers giving a child two parents

The UNC drive conversion rekeys cross-store child keys through the shared
collision merge, so a UNC key and its drive key with different records throw
instead of one silently overwriting the other. Profile project transfer runs the
shared child-key conflict check before writing the target.

* feat(lineage): write, carry and nest cross-machine parents

- Capability worktree.lineage.crossMachine on the child's store; the CLI
  refuses before any request when the server lacks it.
- worktree.set / worktree.create accept crossStoreParent
  { installationId, workspaceKey, instanceId } and write only
  crossStoreLineageByChildKey (refused when it names this store, or the
  child has no identity). Remove Parent clears both.
- worktree.crossStoreParentRef: the parent's own store names it with its
  installationId; reparent refuses a parent whose chain already reaches
  another store. Same-store Set Parent refuses the same.
- worktree.ps rows and lineage lists carry the records; same-store lineage
  fields stay null so older readers show the child standalone.
- The desktop sidebar resolves parents over its own and paired stores'
  records (PR 2's resolver) at the visible-worktree and row-plan call sites.
- CLI: active/current or an inferred terminal parent against a server is
  sent as a cross-store parent.

* fix(lineage): read worktree rows through the record selector; mark the test runtime cast

* fix(lineage): hydrate every store, fence refreshes, honest CLI fallback

- Each host's catalog hydration fetches its lineage snapshot, so this
  desktop's identity and records load whichever store is focused.
- Cross-store records join the per-child request-start fence: a stale
  lineage list can't restore a removed record or override a newer one.
- A failed inferred cross-store parent sends noParent instead of
  forwarding the selector a server could match to a same-store twin.
- Inferred cross-store creates record env-workspace/inferred capture.
- The writer states its trust contract; the chain rule runs at the mint.

* feat(lineage): cross-machine parents on the phone, pickers, chat resume and labels

- Phone: the list nests a child under a parent on another machine, from the
  records rows already carry and the store identities its host verified
  (status.get descriptor; relayed servers' installationId on the host list).
- Set Parent popover, context menu and the composer's parent picker offer
  parents in other visible stores. The parent's own store names it
  (worktree.crossStoreParentRef); the window checks it against that store's
  verified installationId and the child store's capability before any write.
  Remove Parent and Open parent cover cross-machine links.
- Native chat resume ancestors follow resolved cross-machine parents.
- Host sections keep a nested child under its parent and keep its own
  machine label.
- Main sidebar feed resolves cross-store parents (rows carry records; desktop
  facts and paired-host facts carry identities); paired web windows read
  their host's installationId; reveal without an occurrence plan resolves
  them; `worktree create --agent` names a parent on another machine.

* test(lineage): type the mint test's parent row through unknown

* test(lineage): give the cross-machine tests' partial-row casts their SAFETY reasons

* fix(cli): worktree set names a parent on another machine

* feat(lineage): name the machine of parent candidates on another machine

* fix(lineage): Set Parent and Remove Parent act on the clicked row's own host

* fix(lineage): phone resolves parents over every row before the repo filter

* fix(lineage): chat resume nests by host-qualified identity across machines

* fix(lineage): local create refuses a same-install parent before checkout and shares the RPC recorder

* fix(lineage): name parent machines without a focus read; keep create's probes synchronous

* fix(lineage): chat resume keys offers by their own machine; only cross-machine parents leave a section

* fix(lineage): name an older parent server before asking it for a cross-machine ref

* fix(lineage): an app-picked parent on another machine records as manual

* fix(lineage): chat resume keeps base grouping; only resolved cross-store parents move a chat

* revert(lineage): drop chat resume from cross-machine parents; it groups by machine as before

* refactor(lineage): CLI builds cross-machine parents; explicit only

- Drop worktree.crossStoreParentRef and its reparent chain walk: the CLI
  builds the ref from this desktop's installationId (worktree.lineageList
  now reports it) and the parent row it reads. Cross-machine chains are
  allowed; same-store cycle validation and same-install refusal stay.
- No inferred cross-machine parent: remove crossStoreParentInferred and
  the fallback states. Against a server, this machine's terminal, cwd and
  env context is never sent; the CLI prints how to nest explicitly.

* refactor(lineage): build cross-machine parent refs from verified ids; pickers hide only loops; one desktop install id source

* refactor(lineage): refuse moving a linked workspace instead of carrying the link

Orcad migration and profile project transfer now refuse, before any staging or
write, when a workspace in the move has a cross-store parent record (raw entries,
readable or not). Remove the carry machinery: the dormant crossStoreLineage
field, its runtime capability and destination gate, import/subtract/claim code,
transfer carry and the two-parent conflict checker. Also drop the unused
removeCrossStoreWorkspaceLineage.

* docs(lineage): describe cross-store parent refs without the removed mint RPC

* test(lineage): drop the RPC call-list assertion from the cross-store ref test

* fix(lineage): refuse linked moves before any write and for shared-repo children

- Migration refusal passes the child's own host stamp to the scope matcher and
  refuses an unstamped key whose shared repo id the move includes, so such a
  child can't move while its link stays behind.
- Profile transfer checks the read-only source snapshot before pending-move
  recovery or storage migration; the payload check stays as the final guard.

* fix(lineage): refuse a linked profile move before the profile lookup can restore the index

* fix(lineage): startup hydration of other stores loads only cross-machine links

* fix(lineage): list the child's own machine first in the parent picker

* fix(lineage): keep profile argument errors ahead of the linked-move preflight

* fix(worktrees): let the first listing replace a restored server workspace stub

A restored active server workspace starts as a session stub with no head.
If git status fills in its branch before the first listing lands, the
listing merge took that as a concurrent branch switch and kept the stub,
dropping the catalog row (instance id, lineage, provenance). The workspace
then showed standalone until the next listing.

* fix(worktrees): accept a headless row's listing only when it confirms git status

A stale listing could otherwise roll back a branch switch git status saw on
an empty-head row (e.g. an SSH fallback row) during the first connected listing.

* feat(lineage): CLI nests a create on another machine under the caller workspace

- No parent flag and no --no-parent against a server: the caller's workspace
  on this machine (env, else cwd) is sent as an inferred cross-store parent,
  the way --parent-worktree active builds it. The host records it as
  inferred from environment or cwd.
- A server without worktree.lineage.crossMachine, or a caller workspace that
  can't be linked, still creates unnested and prints one note.
- An inferred parent falls away for folder targets, as same-machine folder
  creates record none.

* fix(lineage): CLI cross-machine inference reads the caller terminal and suppresses fallbacks

- The caller terminal's workspace joins env and cwd as an inferred parent,
  with same-machine precedence: env and terminal must agree, cwd only alone.
- A fallback (old server, unlinkable or unresolved caller, ambiguous cwd)
  prints one note and sends noParent, so the server's own create-time
  inference can't nest what the CLI reported as not nested.

* feat(sidebar): drag a workspace onto one on another machine to nest it

Dropping a workspace on a workspace from another machine now sets the
parent through the same cross-machine path as Set Parent: same eligibility
(loops, older servers), same refusal messages. Drop targets carry host
identity so the child's own copy is the one written.

* refactor(lineage): this machine's Orca resolves the caller parent for a create on another machine

- New read-only worktree.resolveCallerParent runs the same-machine create
  resolver over the caller's env, terminal and cwd context, so stale
  contexts are skipped and only validated parents can conflict, and returns
  the cross-store ref or a reason.
- The CLI's own precedence and terminal lookup are removed; it keeps the
  capability gate, one note with noParent on any fallback, and silence with
  no Orca context.

* feat(orchestration): remote worker-start --worktree new-child nests under the coordinator's workspace

worker-start --on <server> --worktree new-child creates the server's worktree
under the coordinator's workspace on this machine, using the same verified
cross-machine parent ref as the CLI's caller auto-nest. A server without
cross-machine parents still starts the worker, top-level, with a warning in
the receipt. Guide, CLI help and docs describe it.

* fix(lineage): resolveCallerParent reads this store's identity without creating it

- Uses the published host descriptor's installationId, else the existing
  host-installation.json; never creates or repairs it. Missing identity
  returns a reason, so the CLI creates unnested with its note.
- Drops the read-only wording: parent resolution does the same catalog
  upkeep a same-machine create does.

* fix(sidebar): validate every cross-machine parent before a multi-select drop writes

A drop of several workspaces builds every cross-machine parent ref first, so an
older server's refusal leaves the whole selection unchanged. Drops the touched
preview-timing test cases.

* refactor(orchestration): build the remote new-child parent ref with the shared caller resolver

Orchestration still picks the caller workspace; resolveCallerParentForAnotherStore
verifies it and builds the ref, and both read the installationId the same way.
Drops the copy and mock-call tests.

* fix(lineage): narrow the inferred source with a guard instead of casts

* fix(lineage): a composer parent picked on another machine nests normally once the destination is that machine

The retained host-qualified pick is classified against the current destination:
same host gives the ordinary parent fields, another store the cross-machine ref.
Also corrects two stale comments.

* perf(lineage): read only the requested child's cross-store entry

* perf(lineage): request each store's lineage list once at startup

* perf(lineage): cap optional caller-parent inference at 3 s and skip a timed-out local Orca

* perf(lineage): return cached sidebar parents when selector inputs are unchanged

* fix(lineage): snapshot selector inputs without a type assertion

* Route startup lineage and the sidebar lineage memo through their owners
2026-11-05 19:42:19 -05:00
buf0-bot[bot] ad17492205 Update README downloads badge (#38752)
Co-authored-by: buf0-bot[bot] <252831055+buf0-bot[bot]@users.noreply.github.com>
2026-11-06 00:39:37 +00:00
Jinwoo Hong 4c82aab266 README: refresh hero and feature animations (#38726)
New hero still: a coordinator running two workers with the mobile app replying. The Features table is rebuilt from the website's product animations (13 rows led by orchestration, chat or terminal, run anywhere and the mobile app), Also in the box lists the rest of the feature wall, the tagline matches the website, and the contributors image shows up to 500 people.
2026-11-05 19:13:29 -05:00
Kelvin Amoaba 4fbb1ccd97 feat(native-chat): color chat links with the theme's link color (#38676)
Chat links used the body text color, so a link only showed as a faint
underline and a theme's textLink.foreground had no effect. They now
paint --chat-link: the theme's link color, else the built-in note blue
for the chat's own scheme, mixed toward the chat foreground like the
other chat hues. Code and bold link labels follow the link color.

When the chat paints a terminal palette the app theme doesn't match,
the app theme's link color is cleared, as its bubble already is. The
theme contrast check now measures the chat link on the theme's user
bubble and drops only the bubble when it fails.
2026-11-05 21:57:27 +00:00
buf0-bot[bot] 9deff9dffd Update README downloads badge (#31749)
Co-authored-by: buf0-bot[bot] <252831055+buf0-bot[bot]@users.noreply.github.com>
2026-11-05 15:48:38 -05:00
NeilandMatthew Wilkinson f660d8c47d feat(plugins): add permissioned desktop appearance publication (#38524)
* feat(plugins): add permissioned desktop appearance publication

* docs(plugins): add one-line API comments for appearance provider

* chore(plugins): regenerate declarative testing kit for appearance API

---------

Co-authored-by: Matthew Wilkinson <wilkinson.m@outlook.com>
2026-11-05 11:31:23 +00:00
Neil 6bb2e13c35 refactor(tooling): organize remaining scripts by domain owners (#38407)
* refactor(tooling): group remaining scripts by concrete domain owners

* fix(tooling): preserve relocated launcher resource roots
2026-11-05 00:29:16 -08:00
Neil 9bd6272fdf refactor(tooling): organize build, CI, and test owners (#38130)
* refactor(tooling): organize test runtime and maintenance owners

* test(tooling): preserve relocated runtime fixture dependency edges

* refactor(tooling): preserve xterm routing owner checks

* ci(tooling): include routing owner in sparse checkout

* test(tooling): preserve copied workflow dependency closures

* fix(tooling): normalize hashed JSON assets in release parity

* fix(tooling): distinguish asset aliases from literal references

* fix(tooling): frame normalized asset reference payloads

* fix(tooling): normalize decoded JSON asset references

* refactor(tooling): organize build, CI and release owners

* fix(tooling): align moved generator and workflow path contracts

* fix(tooling): update generated plugin bundle owner banners

* test(tooling): create relocated mobile verifier fixture directories

* test(tooling): copy headless detector dependency closure

* test(tooling): align native batch policy fixtures with owner path

* fix(tooling): link moved reliability command to historical evidence

* fix(tooling): preserve nested sandbox owners and canonical guide source

* fix(tooling): target release version owners in checkout fixture
2026-11-04 21:08:19 -08:00
Jinwoo Hong 9658ec48ad fix(automations): accept Pi model and thinking extra arguments (#38020)
* fix(automations): accept Pi model and thinking extra arguments

* fix(automations): keep Pi provider pins and refuse unpinned inline keys

* fix(automations): match Pi value options to its argument parser

* fix(automations): refuse Pi model overrides beside unscoped inline keys

* test(automations): cover Pi's last-provider-wins key pin

* fix(automations): refuse any Pi custom Command text with --api-key

* fix(automations): drop the Pi inline-key pin guard

Pi already sends an unpinned inline key to the chosen model's provider and warns;
the user typed both the key and the model, so Orca does not refuse it.
2026-11-04 02:36:51 -05:00
Jinjing ee6a248ec9 Remove the New divider from Chat UI (#38044) 2026-11-03 21:49:09 -08:00
NeilandCopilot 542a2ec78b docs(agents): default to no new tests and tighten the anti-pattern list (#37993)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-04 02:05:26 +00:00
Neil 9c7fe9793e fix(deps): backport minimatch, glob, and deepmerge security fixes (#37981)
Backport upstream matching and abort fixes plus safe deepmerge property handling across the independent workspaces. Includes installed-package regression tests and audit-tool fixture repair. Agent-reviewed at 83ca381d519e6247d7c1b3f0a52ce2907a9f9a19; CI aggregate passed. Version-based scanner alerts remain open pending documented disposition or compatible upstream releases.
2026-11-03 17:13:11 -08:00
Neil d5f2a17cd2 refactor: group agent launch contracts under concrete owners (#37915) 2026-11-03 14:45:49 -08:00
Kelvin Amoaba d2f874dbb5 fix(terminal): rename link popover's "Open in this group" to "Open in new tab" (#37920) 2026-11-03 21:42:47 +00:00
Kelvin Amoaba 7f885c19a6 fix(skills): rename computer use to orca-computer-use (#37659)
* fix(skills): rename computer use to orca-computer-use

* test(skills): resolve rename fixture after main cleanup
2026-11-03 21:05:37 +00:00
0a1e420212 feat(editor): add Insert Final Newline on save (#37786)
* feat(editor): insert final newline on save

Based on the community contribution in #33567 by @johnie.

Co-authored-by: Johnie Hjelm <864992+johnie@users.noreply.github.com>

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: credit community final-newline contribution

Co-authored-by: Johnie Hjelm <864992+johnie@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(editor): exclude CSV previews from clean saves

Restore the final-newline setting imports after the upstream settings refactor.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(editor): skip unnecessary clean saves

Only run the final-newline clean-save fallback when text needs a newline, while preserving empty and terminated drafts.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(editor): preserve pending edits on keyboard save

Use current tab state and retain loaded fallback text when pending edits have not produced a draft yet.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Johnie Hjelm <864992+johnie@users.noreply.github.com>
2026-11-03 12:53:26 -08:00
Neil 3a714906a0 plugins: keep status bar plugins' workers running while enabled (#37630)
A plugin with a worker and status bar items is now started when enabled and kept warm,
like a plugin with hooks, through one shared resident-worker predicate and warmer.
2026-11-02 19:03:21 -08:00
Neil 36f17a04a7 plugins: Box layout container for components panels (catalog 4) (#37420)
- Box (ui.catalog 4, since 1.4.248): direction, gap/padding/paddingX/
  paddingY/margin on the gap scale, align, justify, wrap, grow, shrink,
  width/height/min/max (px up to 4096 or whole percents), columns,
  scroll, border (true or a tone), radius, background (theme surfaces or
  tone tints). Strict props: hex colours, CSS lengths and unknown props
  are dropped with a warning like other components.
- Renderer maps props to theme tokens through data attributes; sizes
  reach inline style only as validated px or percents.
- Typings, docs, skill references, gallery Layout tab; orca/testing
  finds Boxes and summarizes danger or warning Boxes.
2026-11-02 01:05:57 -08:00
Neil cb031c4517 fix(plugins): merge disk writes into a custom editor's unsaved edits (#37343)
* fix(plugins): merge disk writes into a custom editor's unsaved edits

A write to the file on disk while a custom editor tab had unsaved edits
never reached the editor: the buffer kept the edits, Orca showed its
Reload/Overwrite banner, and the session's merge never ran. Now a frame
whose document session has merge announces it (editor.merges), and Orca
sends it the new disk text with the text the edits started from
(editor.document with ancestor). The session merges, posts the result,
and the tab stays unsaved with it; the banner is skipped. Without merge
the banner decides as before.

* test(editor): an explicit save under the changed-on-disk banner keeps the edits

* fix(plugins): make the outside merge exact, and narrow it to where it is

- The host settles a merge only on the session's edit made against the
  merge's own document version, and always commits that result to the
  buffer (even when it equals the disk text); silence past the 1 s flush
  deadline, a remount or a throwing merge leave the banner and the
  suspended autosave in place.
- Orca stamps exactly the disk snapshot the acknowledged merge used and
  records it as the next ancestor; a write that landed meanwhile puts the
  conflict back for its own merge.
- The ancestor is the tab's disk text recorded while clean and advanced on
  acknowledged merges, and must match the tab's disk baseline.
- Only a local file in one merging editor view, with no other tab on its
  buffer, takes this path; anything else gets the banner.
- Frame events keep reaching their handlers after the merge capability.

* fix(plugins): move nothing on a failed disk merge; advance the saved text on success

A merge that throws now leaves the session's document, baseline and
version untouched, so the user's next edit goes out against the old
version: Orca refuses it and keeps the banner instead of taking it as the
merge's acknowledgement. After an acknowledged merge the tab's saved text
moves to the disk text it used, so typing back to the old ancestor no
longer looks clean.

* chore(plugins): regenerate the testing bundle on main
2026-11-02 06:35:01 +00:00
NeilandClaude 5846caf2ba plugins: editor.save.before hook (format on save, or refuse the save) (#37347)
* plugins: editor.save.before hook (format on save, or refuse the save)

Explicit saves of text files from an editor tab run plugins' editor.save.before
in main before the write (local, SSH, WSL and folder workspaces all save through
that one renderer path). A plugin can return changed text or deny with a reason,
shown in the save error. The buffer takes the hook's text only if it is unchanged
since the hook started; edits made meanwhile stay dirty. Autosave, restart saves
and bytes documents are not hooked, and with no live plugin listing the hook the
save path adds no await.

Co-Authored-By: Claude <noreply@anthropic.com>

* test(plugins): editor.save.before text reaches the visible Monaco model

The hook writes its text into the tab's draft, which reaches the model through
the same content-sync bridge as Reload from disk. A model-level test covers the
formatted text showing, the next keystroke editing it, and edits typed during the
hook staying in the model.

Co-Authored-By: Claude <noreply@anthropic.com>

* plugins: editor.save.before review fixes: flush held typing, update the surviving view, keep the denial

- After the hook answers, pending view text (rich Markdown's serialize timer) is
  flushed before deciding whether the buffer still holds the hook's input.
- The hook's text goes to whichever view of the buffer record is still open, so
  closing the saving view keeps a sibling view in step with disk.
- The save-request bridge passes Errors through whole, so the toast shows the
  plugin and its reason instead of a generic failure.

Co-Authored-By: Claude <noreply@anthropic.com>

* chore(plugins): regenerate after rebasing the save hook on main

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-11-02 06:19:50 +00:00
Neil c8112e643f docs(plugins): regenerate the manifest reference and schema for the priority wording (#37351) 2026-11-01 22:03:34 -08:00
Neil 999160b3db feat(plugins): commands[].replaces lets a mod take over a built-in action (#37316)
* plugins: commands[].replaces lets a worker command take over a built-in action

A worker command with replaces: <action> (the declarative action list,
engines >=1.4.248, needs main) runs in place of that built-in from its
shortcut, plugin aliases and the sidebar menus; a failed or silent (2 s)
worker falls back to Orca's own. The install screen names each replacement,
the plugin page has a per-action switch (settings.pluginReplacementsOff), and
when two plugins replace one action the one installed first wins and the
other's log says so. Update review counts replaces as new access.

* fix(plugins): narrow replaced action ids before looking up their titles

* plugins: harden command replaces after review

- Consent includes the replaced actions, so adding replaces to a dev folder
  needs a new approval; CLI approval and update review name each takeover.
- Shortcuts main intercepts (Tasks, Workspace Board, history), search with a
  selection and floating maximize now go through the replacement too.
- A worker that only starts after the 2 s fallback no longer runs the
  command as well: main refuses to start it past the deadline.
- A workspace-only command does not replace an action without a workspace.
- An update keeps the plugin's first install time, so it keeps its takeover.
- Drop the commands.run promise until Orca offers it.

* plugins: commands.run always runs the built-in, never a replacement

A5b's commands.run dispatches with the 'plugin-host' source; the app
dispatcher now builds handlers that run Orca's own action for it, so a
plugin that replaces an action and calls commands.run cannot loop. Docs and
schema describe wrapping the built-in with commands.run; regenerated.
2026-11-01 21:59:27 -08:00
NeilandClaude 2fb3a0975e feat(plugins): workers can open files and run built-in actions (#37323)
* feat(plugins): add editor.open and commands.run for full-access workers

Both ride the existing plugin view request channel (the one plugin open and
plugin shot use) to the one main window's renderer; headless serve and a
missing window answer unavailable. editor.open takes a path that stays
inside the workspace (Git worktrees and folders) and opens it as a tab
without switching what the person sees. commands.run takes only the built-in
contributes.commands action list and dispatches Orca's own handler, never a
plugin replacement. Both need workspace:write.

workspace.list now also lists project-group folder workspaces.

* fix(plugins): open folder workspaces and host-naming ids through Orca's window

workspace.open of a project-group folder asks the window to show it
(setActiveFolderWorkspace), since the runtime activates Git worktrees only.
editor.open resolves the identity: ids workspace.list now returns. commands.run
documents that it must call the raw built-in, never a replacement wrapper.

* plugins: regenerate the testing bundle and skill guides on the rebased host actions

Generated files only (lint-staged's oxlint refuses a commit with no lintable files).

Co-Authored-By: Claude <noreply@anthropic.com>

* chore(plugins): regenerate the testing bundle on main

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-11-02 05:47:35 +00:00
NeilandClaude 544577fe02 feat(plugins): workers can list and open workspaces, create and read terminals, launch agents (#37312)
* feat(plugins): let full-access workers act on workspaces, terminals and agents

Host API minor 3 adds worker-only rows: workspace.list (workspace:read),
workspace.open (workspace:write), terminal.create (terminal:send),
terminal.read (terminal:read, at most 2,000 lines) and agent.launch
(agent:launch). Each dispatches the runtime RPC method the orca CLI calls,
in process with the CLI's identity, so it runs on the host that owns the
workspace (local, WSL or SSH) and covers folder workspaces. The new
capabilities arrive in 1.4.248; typings, docs and skill references are
generated from the table.

* fix(plugins): make workspace host actions safe to revoke, cancel and target

- Each runtime effect re-checks the plugin's approval right before the
  runtime acts (a dispatcher beforeInvoke hook, after its own waits), and a
  worker's cancel or exit now aborts the call's signal through to the runtime.
- agent.launch starts the agent's CLI in a background terminal and returns
  its handle at once; a prompt is delivered afterwards through the runtime's
  own reuse-terminal path, so the host call never waits for the composer.
- workspace.list adds project-group folder workspaces and returns ids that
  name their host (identity: keys), so twins on two hosts are never ambiguous.
- terminal.create passes presentation background; terminal.read counts a
  runtime-limited read as truncated. workspace.open refuses folders as
  unavailable until a window-side open lands.

* plugins: keep both API extensions and host-call tracking under the line limits after rebasing on the hooks

Worker-only status bar and workspace methods spread into PLUGIN_HOST_API through
one module, the in-flight host call map moves into PluginWorkerHostCalls, and
bindPluginRuntime builds the runtime RPC so main-process-plugins.ts keeps its
size; regenerated docs and typings.

Co-Authored-By: Claude <noreply@anthropic.com>

* chore(plugins): regenerate after rebasing on the editor recipe and shots

* chore(plugins): regenerate after rebasing on the plugin shot recipe

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-11-02 05:44:29 +00:00
Brennan BensonandClaude Opus 5.5 06b8f7dc37 Keep Windows and Linux chat switching separate from worktree shortcuts (#37199)
* fix(shortcuts): give Windows and Linux chat switching a working default

* test(shortcuts): verify the worktree and chat chords independently

* fix(shortcuts): preserve agent newline keys while switching chat views

* fix(shortcuts): use Ctrl+Shift+F11 for Windows and Linux chat switching

The Ctrl+Alt+Shift+J default could not match non-Latin layouts or IME
input, because Ctrl+Alt chords skip the physical-key fallback that
protects AltGr characters. A function-key primary matches on every
layout; Ctrl+Shift+J stays as the alias where Switch worktree doesn't
claim it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(shortcuts): keep the chat toggle's own default alias when its shortcuts are edited

Saving any change to Switch between Chat UI and terminal on Windows and
Linux kept its untouched Ctrl+Shift+J alias, which the conflict check
then refused against the worktree palette's default, so replacing or
removing the primary failed. The check now exempts a chord per binding
when it is the action's own default and every other owner also holds it
as a default, so non-default explicit claims are still refused.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* test(shortcuts): expect the new Windows/Linux chat switch label

The context menus show the toggle's primary binding, which is now
Ctrl+Shift+F11 on Windows and Linux.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-11-01 21:41:39 -08:00
Neil f908d2f40a feat(plugins): plugin shot --editor to screenshot and drive custom editors (#37302)
* plugins: orca plugin shot --editor, and plugin open --editor opens the file's folder

plugin open --editor now opens the file in the workspace that holds it,
matching its real path too (/tmp vs /private/tmp), and otherwise adds the
file's folder as a folder workspace instead of failing with no_workspace.

plugin shot <dir|key> --editor <file> [--theme] [--width --height]
[--act] loads the editor afresh in a hidden tab, waits for it to get its
document and ask for it (a new editor.subscribed frame event), captures
the editor's guest with the panel shot's capture path, and sends --act
clicks, drags and keys as real input via sendInputEvent. It exits 1 with
the frame's errors when the editor throws, is blocked by its content
policy, or never asks for its document within 5s; theme and size are put
back. New RPC method plugins.shotEditor so older hosts fail cleanly.

* plugins: shot --editor never changes the author's file

With --act the shot copies the file into a temporary folder, shoots the
copy in a temporary folder workspace, then closes its tab and removes the
workspace and folder; --in-place acts on the file itself. Without --act
it opens the real file, leaves its buffer alone and closes the tab if the
shot opened it. Afterwards Orca shows the workspace it showed before.
Regenerates the skill's CLI reference.

* plugins: fix editor shot typecheck (refusal next step, RPC test mock)

* plugins: editor shot targets its own guest, flushes before reload, scopes cleanup per capture

- Main captures the exact guest WebContents the shot's tab mounted, not the first with its URL.
- An open editor's pending edits are flushed before the shot reloads it; the shot fails if not.
- --act --in-place keeps the acted tab and its unsaved edits.
- end-capture undoes only its own capture (per-capture id), so overlapping shots don't clash.
- The file's workspace lookup includes local folder workspaces.

* plugins: shot --status-bar/--window, key acts focus the editor guest, panel shot selects a workspace

- Editor --act keys first give the guest's <webview> DOM focus and refocus its target, so a canvas
  that keeps focus no longer drops later keys; never webContents.focus(), which focuses the window.
- orca plugin shot <dir|key> --status-bar | --window [--theme]: captures Orca's status bar or the
  whole hidden window (plugins.shotChrome) with the existing capture helpers.
- With no workspace showing, a panel shot selects this machine's most recent workspace in the
  hidden window instead of failing.

* plugins: a shot's end restores only its own capture overrides

* plugins: regenerate the testing runtime with the editor subscribed signal

* plugins: editor capture refusals go through one CLI-message helper

* fix(plugins): give the chrome shot refusal a next step
2026-11-01 21:38:04 -08:00
Neil fa7c3f95fe feat(plugins): check custom editors and test them with mockEditorHost (#37293)
* feat(plugins): check custom editors statically and add mockEditorHost

orca plugin check now follows each custom editor entry (local script src,
inline module scripts, relative imports), transpiled and resolved as the
frame loader does, and fails at file:line:column on syntax errors, missing
files, refused imports, string eval/Function and files over their size
cap, with no running Orca. The check ships in the test hooks bundle.

orca/testing gains mockEditorHost: it runs the frame shell's real
window.orca (document API and session) over an in-memory text or bytes
file, with edits, replace, conflictNext, flush, setTheme and dispose.
Typings, docs and the skill guide cover both.

* feat(plugins): warn on editor eval and check editors in dev watch

editor_eval comes from a lexical scan that can misread rare code, so it is
now a warning that does not fail orca plugin check. dev watch --check runs
the editor check beside the panel check and reports it under editors.

* fix(plugins): check editors by the frame protocol's rules; mock blocks lost saves

The static editor check now reads files through the loader's contained
reader (symlinks out of the plugin are refused), runs only JavaScript
script types, honors the page's import map for native modules, classifies
files by the protocol's content types (JSON imports, extensionless HTML
entries), decodes escaped script URLs, and uses the protocol's caps: 10 MiB
entry, 1 MiB transpiled module, 16 MiB checked dependency.

mockEditorHost refuses an oversized edit before reading it and then rejects
flush() until a later edit lands, as Orca blocks the save.

* test(plugins): type mockEditorHost's frame API instead of reflecting on it

* fix(plugins): keep main's case-spelling check and hook typings through the rebase

The rebase onto main resolved plugin-artifact-validation.ts and
orca-testing.d.ts to B4's side; this puts back main's exact-path-spelling
check and hooks typings beside B4's contained reader and mockEditorHost
types, and regenerates the testing bundle.
2026-11-01 21:28:13 -08:00
NeilandClaude 7989c05ad3 feat(plugins): agent tool-call hooks (Claude Code can block or rewrite) (#37303)
* plugins: agent.tool.before and agent.tool.after hooks; Claude Code PreToolUse blocks

The hook server runs plugins' tool hooks after recording status. Claude Code's
POSIX hook script now asks for an answer on PreToolUse (X-Orca-Hook-Decision),
waits at most 8 s (Orca answers within 6 s) and prints the decision: a deny
with the plugin's reason, or updatedInput without approving it. Timeouts,
errors and an unreachable Orca print {}, so the call proceeds. Other events
still print {} before posting. Every other agent, Windows scripts and SSH/WSL
relays only observe or are not hooked yet (per-agent table in the
agent-hooks README).

Co-Authored-By: Claude <noreply@anthropic.com>

* plugins: verify PreToolUse answers against the real Claude CLI; 204 at once when no tool hook listens

A real-CLI test (ORCA_REAL_CLAUDE_CLI_TEST=1) shows Claude Code 2.1.316 runs
updatedInput sent without permissionDecision, still asks for approval of the
rewritten call, and refuses a deny. With no plugin listing a tool hook, the
server answers 204 before any await and runs no chain.

Co-Authored-By: Claude <noreply@anthropic.com>

* test(claude): type the real-CLI PreToolUse answer

* plugins: harden agent tool hooks (partial replies, fenced posts, OpenClaude, Amp/Copilot/Cursor)

- Drop a failed curl's partial body and print only a complete PreToolUse decision.
- Run tool plugins only for posts status ingestion accepted; name the owner's terminal.
- Enable PreToolUse decision mode only for Claude Code, not CLIs reusing its script.
- Decode Amp tool.call/tool.result, Copilot envelope/implied events and Cursor shell/MCP calls.

Co-Authored-By: Claude <noreply@anthropic.com>

* plugins: regenerate the testing bundle on the rebased tool hooks

Generated file only (lint-staged's oxlint refuses a commit with no lintable files).

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-11-01 21:24:49 -08:00