* 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>
20 KiB
Orca plugin host API reference
Host API 1.3 ("pluginApi": 1). Workers call a method with await orca.host.call(name, params); panels and custom editors call the same methods through window.orca.host.call(name, params). Each method lists the surfaces allowed to call it; any other surface gets not_permitted. orca.host.has(name) answers synchronously whether this Orca offers a method on the current surface, and orca.api is { major, minor }. See Plugin authoring for the frame API.
Every call is checked against the capabilities the user approved, and params are validated before Orca runs anything: params are strict, so a field this Orca does not know is refused with invalid_params instead of being dropped. Mutations are recorded in the audit log as plugin:<publisher>.<id>.
Methods whose capability is none need no capability but still need the plugin enabled and approved. shell.openExternal and files.saveAs also need transient user activation (a click or key press in the calling frame, within a few seconds), are rate-limited, and run only in the desktop app; frames also reach them as orca.shell.openExternal(url) and orca.files.saveAs({ suggestedName, mimeType, data }), which resolves with { status: "saved" }, or { status: "cancelled" } when the person cancels the dialog. Saved files carry the OS mark for downloaded files.
Methods
| Method | Capability | Surfaces | Mutation | Since | Summary |
|---|---|---|---|---|---|
workspace.readContext |
workspace:read |
worker, panel, editor | no | 1.0 | Branch, name and terminal ids of the focused workspace, or null when none is focused. |
terminal.sendText |
terminal:send |
worker | yes | 1.0 | Types text into one terminal named by id, optionally pressing Enter. |
notifications.show |
notifications:show |
worker, panel, editor | yes | 1.0 | Shows a desktop notification. |
storage.get |
storage:own |
worker, panel, editor | no | 1.0 | Reads a JSON value from the plugin's private store; null when absent. |
storage.set |
storage:own |
worker, panel, editor | yes | 1.0 | Writes a JSON value to the plugin's private store. |
storage.delete |
storage:own |
worker, panel, editor | yes | 1.0 | Removes a key from the plugin's private store. |
storage.keys |
storage:own |
worker, panel, editor | no | 1.0 | Lists the keys in the plugin's private store. |
secrets.get |
secrets:own |
worker, panel, editor | no | 1.0 | Reads a secret from the plugin's encrypted secrets file; null when absent. |
secrets.set |
secrets:own |
worker, panel, editor | yes | 1.0 | Stores a secret in the plugin's encrypted secrets file. |
secrets.delete |
secrets:own |
worker, panel, editor | yes | 1.0 | Removes a secret from the plugin's encrypted secrets file. |
secrets.protection |
secrets:own |
worker, panel, editor | no | 1.1 | How secrets are protected at rest: os-keyring, or unavailable (no OS key store, or Linux basic_text), where secrets.set fails. |
settings.get |
settings:own |
worker, panel, editor | no | 1.0 | Reads every setting the plugin owns. |
settings.set |
settings:own |
worker, panel, editor | yes | 1.0 | Writes one setting the plugin owns. |
shell.openExternal |
none | panel, editor | no | 1.1 | Opens an http:, https: or mailto: URL in the default browser. Needs a click or key press in the frame; desktop app only. Orca asks the person before an http(s) host the plugin was not allowed; opened is false if they cancel. |
files.saveAs |
none | panel, editor | yes | 1.1 | Offers data (a string or ArrayBuffer, up to 64 MiB) in Orca's save dialog; the plugin never learns the path. Needs a click or key press in the frame; desktop app only. |
plugin.request |
none | panel, editor | no | 1.2 | Asks the plugin's own worker to run the handler it registered with orca.panels.handle(name), starting the worker if needed; resolves with what the handler returned. unknown_method when no handler has that name. |
agents.list |
agent:read |
worker, panel, editor | no | 1.2 | The agents in the focused workspace: their terminal, title, agent kind and status. agent.status.changed reports each change after. |
events.subscribe |
none | panel, editor | no | 1.2 | Delivers a workspace or agent event to this panel as orca.on(event); needs the event's read capability. The frame APIs call it for you. |
statusBar.update |
none | worker | no | 1.2 | Changes one of the plugin's contributes.statusBarItems (named by id): fields given replace their current value, visible: false hides it. Desktop app only; lasts until Orca restarts, and is hidden while the plugin is disabled. |
workspace.list |
workspace:read |
worker | no | 1.3 | Lists every workspace, Git worktrees and folders, on every host Orca reaches. |
workspace.open |
workspace:write |
worker | yes | 1.3 | Shows a workspace in Orca, as clicking it in the sidebar does. |
terminal.create |
terminal:send |
worker | yes | 1.3 | Opens a terminal in a workspace, optionally running a command, in the background. |
terminal.read |
terminal:read |
worker | no | 1.3 | Reads a terminal's newest output as plain text, at most 2000 lines. |
agent.launch |
agent:launch |
worker | yes | 1.3 | Starts an agent's CLI in a new terminal of a workspace, in the background, and returns as soon as it starts; a prompt is sent once the agent is ready. |
workspace.readContext
Branch, name and terminal ids of the focused workspace, or null when none is focused.
(params?: {}) => { branch: string; displayName: string; terminals: { id: string; title?: string }[] } | null
terminal.sendText
Types text into one terminal named by id, optionally pressing Enter.
(params: { terminalId: string; text: string; enter?: boolean }) => { accepted: boolean }
notifications.show
Shows a desktop notification.
(params: { title: string; body?: string }) => { delivered: boolean }
storage.get
Reads a JSON value from the plugin's private store; null when absent.
(params: { key: string }) => { value: JSON }
storage.set
Writes a JSON value to the plugin's private store.
(params: { key: string; value: JSON }) => { ok: true }
storage.delete
Removes a key from the plugin's private store.
(params: { key: string }) => { ok: true }
storage.keys
Lists the keys in the plugin's private store.
(params?: {}) => { keys: string[] }
secrets.get
Reads a secret from the plugin's encrypted secrets file; null when absent.
(params: { key: string }) => { value: string | null }
secrets.set
Stores a secret in the plugin's encrypted secrets file.
(params: { key: string; value: string }) => { ok: true }
secrets.delete
Removes a secret from the plugin's encrypted secrets file.
(params: { key: string }) => { ok: true }
secrets.protection
How secrets are protected at rest: os-keyring, or unavailable (no OS key store, or Linux basic_text), where secrets.set fails.
(params?: {}) => { status: "os-keyring" | "unavailable" }
settings.get
Reads every setting the plugin owns.
(params?: {}) => { settings: Record<string, JSON> }
settings.set
Writes one setting the plugin owns.
(params: { key: string; value: JSON }) => { ok: true }
shell.openExternal
Opens an http:, https: or mailto: URL in the default browser. Needs a click or key press in the frame; desktop app only. Orca asks the person before an http(s) host the plugin was not allowed; opened is false if they cancel.
(params: { url: string }) => { opened: boolean; reason?: "cancelled" | "consent-unavailable" }
files.saveAs
Offers data (a string or ArrayBuffer, up to 64 MiB) in Orca's save dialog; the plugin never learns the path. Needs a click or key press in the frame; desktop app only.
(params: { suggestedName?: string; mimeType?: string; data: string | ArrayBuffer }) => { status: "saved" | "cancelled" }
plugin.request
Asks the plugin's own worker to run the handler it registered with orca.panels.handle(name), starting the worker if needed; resolves with what the handler returned. unknown_method when no handler has that name.
(params: { name: string; input?: JSON; timeoutMs?: number }) => { output: JSON }
agents.list
The agents in the focused workspace: their terminal, title, agent kind and status. agent.status.changed reports each change after.
(params?: {}) => { agents: { terminalId: string | null; title: string | null; agent: string | null; state: "working" | "blocked" | "waiting" | "done"; stateStartedAt: number }[] }
events.subscribe
Delivers a workspace or agent event to this panel as orca.on(event); needs the event's read capability. The frame APIs call it for you.
(params: { event: "workspace.created" | "workspace.removed" | "agent.status.changed" | "workspace.changed" }) => { ok: true }
statusBar.update
Changes one of the plugin's contributes.statusBarItems (named by id): fields given replace their current value, visible: false hides it. Desktop app only; lasts until Orca restarts, and is hidden while the plugin is disabled.
(params: { id: string; text?: string; icon?: "activity" | "bar-chart-3" | "bell" | "blocks" | "book" | "bot" | "bug" | "calendar" | "cloud" | "code" | "database" | "file-text" | "flag" | "folder" | "gauge" | "globe" | "hammer" | "layers" | "lightbulb" | "package" | "plug" | "puzzle" | "rocket" | "star" | "terminal" | "wrench" | "zap" | "add" | "remove" | "delete" | "edit" | "refresh" | "search" | "settings" | "check" | "close" | "info" | "warning" | "error" | "open-external" | "copy" | "download" | "upload" | "play" | "pause" | "stop" | "more" | "expand" | "collapse" | "next" | "filter" | "branch" | "file" | "agent" | "link"; tone?: "neutral" | "accent" | "success" | "warning" | "danger"; tooltip?: string; visible?: boolean }) => { ok: true }
workspace.list
Lists every workspace, Git worktrees and folders, on every host Orca reaches.
(params?: {}) => { workspaces: { id: string; kind: "worktree" | "folder"; name: string; path: string; branch: string | null; hostId: string | null }[]; truncated: boolean }
workspace.open
Shows a workspace in Orca, as clicking it in the sidebar does.
(params: { workspaceId: string }) => { opened: true }
terminal.create
Opens a terminal in a workspace, optionally running a command, in the background.
(params: { workspaceId: string; title?: string; command?: string }) => { terminalId: string }
terminal.read
Reads a terminal's newest output as plain text, at most 2000 lines.
(params: { terminalId: string; maxLines?: number }) => { lines: string[]; truncated: boolean }
agent.launch
Starts an agent's CLI in a new terminal of a workspace, in the background, and returns as soon as it starts; a prompt is sent once the agent is ready.
(params: { workspaceId: string; agent: string; prompt?: string }) => { terminalId: string }
Events
A worker receives the events its manifest lists in contributes.events through orca.events.on(event, handler), which returns { dispose() }. Each needs main, the trusted-worker profile and its domain's capability (workspace:read or agent:read). orca plugin types writes the payloads as PluginEvents in orca/host.
| Event | Payload | Summary |
|---|---|---|
workspace.created |
{ workspaceId: string; kind: "worktree" | "folder"; path: string; branch: string } |
A worktree workspace was created. |
workspace.removed |
{ workspaceId: string; kind: "worktree" | "folder"; path: string } |
A worktree workspace was removed. |
agent.status.changed |
{ workspaceId: string | null; paneKey: string; terminalId?: string; title?: string; state: "working" | "blocked" | "waiting" | "done"; receivedAt: number; mainAgent?: { state: "working" | "blocked" | "waiting" | "done"; outcome?: "success" | "failure" | "cancellation" | "superseded" | "interruption" | "unconfirmed"; stateStartedAt: number } } |
An agent in a terminal pane changed status; workspaceId is null when Orca cannot tie the pane to a workspace, and terminalId names its terminal when Orca lists it. |
workspace.changed |
{ context: { branch: string; displayName: string; terminals: { id: string; title?: string }[] } | null } |
The focused workspace changed, or its terminals were opened, closed or renamed; context is what workspace.readContext now answers. Each new subscriber first gets the current context. |
Hooks
A worker changes or stops what Orca is about to do with orca.hooks.on(hook, (event, next) => ...), which returns { dispose() }. Each hook must be listed in contributes.hooks (main and the trusted-worker profile; no capability). Return next(event) to go on, with the fields the hook allows changed; return a result such as { deny: reason } to stop; return nothing to pass. Plugins run one after another (installed plugins by key, then development folders), then Orca acts on what is left. Orca starts the workers of plugins that list hooks as soon as they are enabled and keeps them running. A handler that takes longer than 2 s, a worker that does not start within the worker ready timeout, a throw, a crash or a disabled plugin counts as having passed, and orca plugin logs shows the hook, the plugin and how long it took. A guard that must fail closed sets "onError": "deny" on its contributes.hooks entry; then those failures refuse the action, naming the plugin. Decide before any other awaited call: an awaited host call ahead of your { deny } can fail or time out and let the action through.
Hooks run in the workers of the Orca that handles the request, before it acts: for an SSH or WSL terminal or workspace, that is the desktop, before the work is sent to its host. A paired client reaching a remote Orca server is decided by that server's plugins. orca plugin types writes the events as PluginHooks in orca/host; orca/testing mountWorker(...).hooks.fire(hook, event) returns what the chain decided.
| Hook | Event | Stop result | next may change |
Summary |
|---|---|---|---|---|
terminal.send |
{ terminalId: string; text: string; enter: boolean; inputKind: "driving" | "orchestration" | "launch" } |
{ deny: string } |
text, enter |
Text Orca, the CLI or a plugin is about to send to a terminal. A person typing (on the desktop, a phone or a paired client) is never hooked, and prompts Orca submits to an agent take agent.prompt.before instead. Writes to one terminal stay in order while a hook decides. Rewrite text or enter with next, or return { deny } to refuse the send. |
workspace.create.before |
{ kind: "worktree" | "folder"; projectId: string; name: string; branch?: string; base?: string } |
{ deny: string } |
name, branch, base |
A worktree or folder workspace is about to be created. Change name, branch or base with next, or return { deny } to refuse it. |
workspace.create.after |
{ workspaceId: string; kind: "worktree" | "folder"; path: string; branch: string } |
none | nothing | A worktree or folder workspace was created. Observe only: Orca does not wait for it, and results are ignored. |
agent.launch.before |
{ agent: string; workspaceId: string | null; workspaceKind: "worktree" | "folder" | null; terminalId?: string; command: string; env: Record<string, string> } |
{ deny: string } |
command, env |
Orca is about to start an agent CLI in a terminal, from any launcher (new tab, workspace create, CLI, automation, orchestration). Change command or add env with next, or return { deny } to refuse the launch. Restores of saved sessions are not hooked. |
agent.prompt.before |
{ agent: string | null; terminalId: string; prompt: string; inputKind: "driving" | "orchestration" | "launch" } |
{ deny: string } |
prompt |
Orca is about to submit a prompt to an agent in a terminal: a launch prompt, an orchestration dispatch, an issue or task prompt, or orca terminal send to an agent. A prompt a person submits is never hooked, and writes to one terminal stay in order while it decides. Rewrite prompt with next, or return { deny } to refuse it. |
agent.tool.before |
{ agent: string; workspaceId: string | null; terminalId?: string; tool: string; input: JSON; callId?: string } |
{ deny: string } |
input |
An agent in an Orca terminal is about to run a tool. Claude Code waits for the answer: return { deny } to refuse the call (the agent sees the reason) or change input with next; the person is still asked as usual. Other agents only report the call, so their results are ignored. Agents on SSH or WSL hosts are not hooked yet. |
agent.tool.after |
{ agent: string; workspaceId: string | null; terminalId?: string; tool: string; input: JSON; callId?: string; output?: JSON } |
none | nothing | An agent in an Orca terminal finished a tool call (successfully or not). Observe only: Orca does not wait for it. |
Errors
A failed call rejects with an Error carrying code, message, retryable and, for rate_limited, retryAfterMs. Treat a code not listed here as action_failed:
invalid_request: the call itself is malformed.invalid_params: params do not match the method, including a field this Orca does not know; the message names it.unknown_method: the method does not exist in this Orca.capability_denied: the manifest does not declare the capability.consent_required: the plugin is disabled or the user has not approved this version.not_permitted: the method or document is not available here, e.g. a worker-only method from a panel.rate_limited: calls arrive faster than the budget;retryAfterMssays when to try again.unavailable: the service behind the method is not running yet.action_failed: the method ran and failed.cancelled: the caller aborted the call through itssignal.timeout: Orca gave up waiting.conflict: the call was made against a stale version, e.g. an editor edit after an outside change.too_large: the message is larger than Orca accepts.
Stability and deprecation
Plugins are in Alpha: the manifest v2 format and catalog format are stable contracts; items listed as experimental in Plugin authoring may change.
- Host API 1 is frozen. Within
pluginApi1, methods, params and results only grow: a new method or a new optional param arrives with a higherSinceminor, and nothing listed here is removed, renamed or narrowed. - Deprecation. A method to be retired is marked deprecated in this table and keeps working for the rest of the major.
- New majors. A breaking change ships as a new
pluginApimajor beside the old one. Orca refuses a manifest whosepluginApinames a major it does not implement, with "update Orca", instead of loading it against the wrong API. - Detect, do not assume. Check
orca.host.has(name)ororca.api.minorbefore calling a method newer than the oldest Orca you support; a call that slips through fails withunknown_method.