Files
orca/docs/reference/plugin-catalog.md
T
Neil ec0d3cedd1 Plugin panels talk to their worker; panel storage, settings and agent events (#37013)
* 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.
2026-11-01 04:40:17 -08:00

15 KiB
Raw Blame History

Orca plugin catalog reference

The orca-catalog.json file that lists installable plugins and their releases.

Add "$schema": "https://raw.githubusercontent.com/stablyai/orca/main/docs/schemas/plugins/orca-catalog.v2.schema.json" to orca-catalog.json for completion and inline errors in your editor. Since is the first Orca release that reads the field. Older releases ignore fields they do not know.

Fields

Field Type Required Since Description
$schema string no 1.4.245 JSON Schema URL for editor completion. Ignored by Orca.
catalogVersion 2 yes 1.4.245 Catalog format. Raised only for a change older Orca cannot ignore; a newer Orca reads this one.
name string yes 1.4.245 Catalog display name. Example: "Orca Plugins"
owner string yes 1.4.245 Account that publishes the catalog. Example: "stablyai"
plugins CatalogEntry[] yes 1.4.245 Listed plugins, at most 2048. Each id should appear once; Orca keeps the first. An entry this Orca cannot read is skipped, never fatal.

CatalogEntry

One installable plugin.

Field Type Required Since Description
id string yes 1.4.245 <publisher>.<id>: the publisher is the repository owner, lowercased (Orca installs under this key, whatever the manifest's deprecated publisher says), and the id is the manifest's id. Each half is at most 64 characters, and the reserved names constructor and prototype are refused. Example: "acme.gleam-syntax"
source CatalogGitSource yes Where the plugin source lives.
ownerId integer no 1.4.246 GitHub's numeric id of the repository owner, recorded the first time the plugin was listed. Names can be renamed or re-registered; this id cannot. Informational: Orca does not read it yet, and older Orca ignores it. Example: 1234567
description string no 1.4.245 Listing text shown in the catalog.
categories string[] no 1.4.245 Lowercase slugs used to filter the catalog, each listed once. At most 16; extras are ignored.
releases CatalogRelease[] yes 1.4.245 Installable releases, newest first. Orca installs the first one whose minimumOrca it meets, so keep older releases listed when you raise engines.orca. At most 32.

CatalogGitSource

Where the plugin source lives.

Field Type Required Since Description
kind "git" yes 1.4.245 Source kind. Only git is supported.
url string yes 1.4.245 HTTPS or SSH URL of the plugin repository. Example: "https://github.com/acme/orca-gleam-syntax.git"
ref string yes 1.4.245 Branch or tag the author publishes from. Each release pins an exact commit. Example: "main"
path string no 1.4.245 Directory inside the repository that holds orca-plugin.json, so one repository can publish many plugins. Use / separators, with no leading /, . or ... Omit when the plugin is the repository root. Example: "plugins/gleam-syntax"

CatalogRelease

The exact release the catalog vouches for. Orca refuses bytes that differ.

Field Type Required Since Description
version string yes 1.4.245 The manifest's version for this release. Example: "1.2.0"
commit string yes 1.4.245 Full SHA-1 commit Orca checks out for this release.
contentHash string yes 1.4.245 SHA-256 of the plugin's installed files, as Orca computes it at install.
execution "data" | "declarative" | "sandboxed-ui" | "trusted-worker" yes 1.4.245 The manifest's execution profile. Example: "data"
minimumOrca string yes 1.4.245 The manifest's engines.orca without >=. Older Orca lists the plugin but will not install it. Example: "1.4.245"
types ("interface-language" | "app-theme" | "terminal-theme" | "icon-theme" | "syntax" | "instructions" | "panel" | "editor" | "worker")[] no 1.4.245 Optional; Orca derives it from contributes and execution. When present, every kind of contribution the release carries, matching its manifest exactly. instructions needs the declarative profile or higher, panel and editor need sandboxed-ui, and worker needs trusted-worker. Example: ["syntax"]
name string no 1.4.245 The manifest's name, so Orca can suggest the plugin before downloading it. Example: "Gleam"
license string no 1.4.245 SPDX license expression. Example: "MIT"
repository string no 1.4.245 Source repository page, https only. Orca links it when it is a github.com or gitlab.com repository of the plugin's publisher. Example: "https://github.com/acme/orca-gleam-syntax"
sourceUnavailable true no 1.4.246 Set when the release's repository is gone and source serves an archived copy, so Orca links source instead. Example: true
homepage string no 1.4.245 Project homepage, https only. Example: "https://example.com/gleam"
updatedAt string no 1.4.245 When this release was published, as an ISO 8601 date or date-time. Example: "2026-10-20"
changelog string no 1.4.245 What changed in this release, as plain text. At most 2000 characters. Example: "Adds highlighting for use expressions."
images CatalogImages no 1.4.245 Images extracted from the release by catalog CI, which re-encodes them and strips metadata. Same rules as the manifest: icon PNG, WEBP, static, at least 128×128, square, at most 256 KB. Cover PNG, JPEG, WEBP, static, at least 800×500, at most 1024 KB. Screenshots PNG, JPEG, WEBP, static, at least 1280×800, at most 1536 KB. Images must not imitate Orca's trust badges.
themePreview CatalogThemePreview[] no 1.4.245 Up to 4 theme palettes for listing art. Display only; the installed theme files decide the real colors.
contributes CatalogContribution[] no 1.4.245 What the release adds, as the listing shows it. Generate it with the catalog tooling; Orca refuses an install whose manifest says otherwise.
capabilities ("workspace:read" | "agent:read" | "terminal:send" | "notifications:show" | "storage:own" | "secrets:own" | "settings:own")[] no 1.4.245 Every capability the release asks for. Orca refuses an install whose manifest asks for different ones. Example: ["notifications:show"]
fileTypes string[] no 1.4.245 Extensions, without the dot, that this release opens. Orca suggests the plugin when such a file has no viewer.
editorFileTypes string[] no 1.4.245 The fileTypes this release opens in a custom editor. Orca suggests the plugin for such a file even when it already opens it as text.

CatalogImages

Images extracted from the release by catalog CI, which re-encodes them and strips metadata. Same rules as the manifest: icon PNG, WEBP, static, at least 128×128, square, at most 256 KB. Cover PNG, JPEG, WEBP, static, at least 800×500, at most 1024 KB. Screenshots PNG, JPEG, WEBP, static, at least 1280×800, at most 1536 KB. Images must not imitate Orca's trust badges.

Field Type Required Since Description
icon CatalogImage no An image stored in the catalog repository.
cover CatalogImage no An image stored in the catalog repository.
screenshots CatalogImage[] no 1.4.245 Screenshots, at most 6.

CatalogImage

An image stored in the catalog repository.

Field Type Required Since Description
path string yes 1.4.245 File in the catalog repository, relative to its root. Relative to the plugin root; .. segments and Windows device names such as CON are refused. Example: "assets/acme.gleam/cover.png"
alt string yes 1.4.245 What the image shows, for screen readers. Required. Example: "The Dusk theme on a terminal"
sha256 string yes 1.4.245 SHA-256 of the file's bytes; Orca refuses bytes that differ.
width integer yes 1.4.245 Width in pixels. Example: 1600
height integer yes 1.4.245 Height in pixels. Example: 1000

CatalogThemePreview

Colors to draw a theme card before install.

Field Type Required Since Description
label string yes 1.4.245 Theme name. Example: "Dusk"
base "light" | "dark" yes 1.4.245 Light or dark theme.
background string yes 1.4.245 Window background. Example: "#1b1430"
foreground string yes 1.4.245 Main text. Example: "#1b1430"
accent string yes 1.4.245 Accent, such as buttons. Example: "#1b1430"
surface string yes 1.4.245 Cards and sidebars. Example: "#1b1430"

CatalogContribution

One thing the release adds to Orca.

Field Type Required Since Description
kind "panels" | "commands" | "events" | "languagePacks" | "keybindings" | "vmRecipes" | "themes" | "terminalThemes" | "iconThemes" | "syntaxes" | "customEditors" | "newFiles" | "settings" yes 1.4.245 Contribution point. Example: "themes"
name string yes 1.4.245 What the item is called in Orca. Example: "Dusk"
detail string no 1.4.245 Extra context, such as file extensions.

Versions and compatibility

  • What only lists is tolerant. A catalog never decides what runs (installing re-checks the exact bytes), so Orca keeps whatever it can read: unknown keys are ignored at every level, an entry it cannot read is skipped, an invalid display-only field is dropped, a list over its limit is trimmed, and a repeated id keeps the first entry. Validation in CI is where these become errors.
  • The file name never changes. catalogVersion (now 2) rises only for a change older clients cannot ignore; an Orca that sees a higher number refuses the catalog with "update Orca to browse this catalog" rather than misreading it.
  • Releases are a list, newest first. Orca offers the first release whose minimumOrca it meets, so keep the previous release listed when a new one raises engines.orca; Orca shows "Needs Orca X" only when none fits.

Publisher names and trust

  • Publisher is the repository owner. Like a Go module path, a plugin's publisher is where it lives, never what its manifest says: the official and community catalogs set each entry's id to <owner>.<manifest id> from the GitHub user or organization that owns the repository (acme for github.com/acme/gleam-syntax), and Orca installs under that id. A Git URL install uses the URL's owner and a folder uses local. The manifest's publisher is deprecated and ignored. Orca does not check the owner for catalogs you add yourself; their id is what they say.
  • Listed names are pinned to GitHub ids. The community catalog records the numeric owner and repository ids when a plugin is first listed, pauses new versions after a rename or transfer until Stably confirms it, and refuses a repository that was deleted and recreated under the same name. Each listed version is permanent: a moved tag never replaces it.
  • Reserved names install only from Orca's official catalog. The publishers stablyai, official, anthropic, openai, github and any starting orca (any case), and ids starting orca-, are refused from any other catalog, a Git URL or a local folder. A catalog that lists one is refused whole. pnpm validate:plugin rejects these publishers except stablyai.
  • Trust comes from the catalog's address. Orca labels a plugin with where it came from: the Official mark for Orca's catalog, otherwise the catalog's name and the owner in its Git URL (github.com/acme, gitlab.com/acme/tools), never the manifest's text. Development folders show Dev · unverified, Git URL installs Unverified.
  • An install stays with its catalog. Orca records the catalog each plugin was installed from and updates it only from there. When another catalog lists the same id, the plugin page says so, and switching means removing the plugin and installing it again.

One repository can hold many plugins

Give each plugin its own directory and set source.path to it. Orca downloads the pinned commit but installs only that directory, and contentHash covers only its files, so a commit that changes a sibling plugin leaves this plugin's hash, and its users, untouched. Moving a plugin to another directory counts as moving repositories: users review the update before it installs.

{ "id": "acme.gleam-syntax", "source": { "kind": "git", "url": "https://github.com/acme/orca-plugins.git", "ref": "main", "path": "plugins/gleam-syntax" } }

Run pnpm validate:plugin plugins/gleam-syntax in that repository to print the directory's path and its release entry. Listing images stay relative to the catalog repository.

An install link opens Orca at a plugin's page in Settings → Plugins. It never installs: the user reads the page and presses Install, and the usual review applies. If plugins are off, the page waits behind Turn on plugins.

orca://plugins/<publisher>.<id>?catalog=<official | community | catalog Git URL>
  • catalog defaults to official. community is Orca's community catalog. Any other value must be an HTTPS or SSH Git URL of at most 512 characters, read at its main branch, without a password or token. Orca asks Add catalog …? before fetching a catalog the user hasn't added, and that includes community.
  • Reserved publishers (above) are accepted only with the official catalog. A link longer than 2048 characters, with another query parameter, or with an id that isn't <publisher>.<id> shows a short error and does nothing else. Repeats of the same link within a few seconds are ignored.
  • Browsers and README viewers often block custom schemes, so link to the directory page instead. It opens the orca:// link and shows install steps when Orca is missing: https://onorca.dev/plugins/<publisher>.<id>?catalog=<same value>.

For a README:

[Open in Orca](https://onorca.dev/plugins/acme.gleam-syntax?catalog=community)

Clients before format 2

Orca reads orca-catalog.json first and falls back to orca-marketplace.json. That older file is frozen: shipped clients reject any key they do not know, so it carries only name, owner and each plugin's id, source, description and categories, and leaves out plugins with a source.path, which those clients would install from the repository root. Generate it from orca-catalog.json rather than editing two files:

node config/scripts/export-legacy-plugin-catalog.mjs orca-catalog.json orca-marketplace.json