mirror of
https://github.com/stablyai/orca.git
synced 2026-11-13 09:27:29 +00:00
* plugins: agent.launch.before and agent.prompt.before hooks; orca plugin dev hook agent.launch.before runs at the PTY spawn every agent terminal goes through (window tabs, runtime/CLI agent launch, automations, orchestration), before dispatch to its host: change the command line, add env (Orca's own ORCA_* variables and the agent's environment stay hidden), or deny. Restores and resumes are not hooked. agent.prompt.before runs in sendTerminalAgentPrompt (launch prompts, orchestration dispatch, terminal send to an agent) with the agent named: rewrite or deny. The terminal hooks move into one runtime layer. orca plugin dev hook runs a hook through one dev plugin and prints the decision. Co-Authored-By: Claude <noreply@anthropic.com> * plugins: recheck rewritten launch commands, forward hook env to WSL, keep prompt delivery outside the hook order - A command agent.launch.before rewrote passes the AI Vault resume check again on both spawn paths. - Variables a hook adds are named in WSLENV on Windows so WSL agents get them. - agent.prompt.before holds the terminal's order only while it decides; a delivery waiting on a permission answer no longer blocks later input. Co-Authored-By: Claude <noreply@anthropic.com> * plugins: plugin dev hook honours onError: deny Co-Authored-By: Claude <noreply@anthropic.com> * plugins: regenerate the testing bundle and skill guides on the rebased hooks Generated files 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>
238 lines
17 KiB
Markdown
238 lines
17 KiB
Markdown
# Orca plugin host API reference
|
|
|
|
<!-- Generated by `pnpm generate:plugin-schemas` from src/shared/plugins. Do not edit. -->
|
|
|
|
Host API 1.2 (`"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](./plugin-authoring.md) 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`](#workspacereadcontext) | `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`](#terminalsendtext) | `terminal:send` | worker | yes | 1.0 | Types text into one terminal named by id, optionally pressing Enter. |
|
|
| [`notifications.show`](#notificationsshow) | `notifications:show` | worker, panel, editor | yes | 1.0 | Shows a desktop notification. |
|
|
| [`storage.get`](#storageget) | `storage:own` | worker, panel, editor | no | 1.0 | Reads a JSON value from the plugin's private store; null when absent. |
|
|
| [`storage.set`](#storageset) | `storage:own` | worker, panel, editor | yes | 1.0 | Writes a JSON value to the plugin's private store. |
|
|
| [`storage.delete`](#storagedelete) | `storage:own` | worker, panel, editor | yes | 1.0 | Removes a key from the plugin's private store. |
|
|
| [`storage.keys`](#storagekeys) | `storage:own` | worker, panel, editor | no | 1.0 | Lists the keys in the plugin's private store. |
|
|
| [`secrets.get`](#secretsget) | `secrets:own` | worker, panel, editor | no | 1.0 | Reads a secret from the plugin's encrypted secrets file; null when absent. |
|
|
| [`secrets.set`](#secretsset) | `secrets:own` | worker, panel, editor | yes | 1.0 | Stores a secret in the plugin's encrypted secrets file. |
|
|
| [`secrets.delete`](#secretsdelete) | `secrets:own` | worker, panel, editor | yes | 1.0 | Removes a secret from the plugin's encrypted secrets file. |
|
|
| [`secrets.protection`](#secretsprotection) | `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`](#settingsget) | `settings:own` | worker, panel, editor | no | 1.0 | Reads every setting the plugin owns. |
|
|
| [`settings.set`](#settingsset) | `settings:own` | worker, panel, editor | yes | 1.0 | Writes one setting the plugin owns. |
|
|
| [`shell.openExternal`](#shellopenexternal) | 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`](#filessaveas) | 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`](#pluginrequest) | 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`](#agentslist) | `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`](#eventssubscribe) | 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`](#statusbarupdate) | 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.readContext`
|
|
|
|
Branch, name and terminal ids of the focused workspace, or null when none is focused.
|
|
|
|
```ts
|
|
(params?: {}) => { branch: string; displayName: string; terminals: { id: string; title?: string }[] } | null
|
|
```
|
|
|
|
### `terminal.sendText`
|
|
|
|
Types text into one terminal named by id, optionally pressing Enter.
|
|
|
|
```ts
|
|
(params: { terminalId: string; text: string; enter?: boolean }) => { accepted: boolean }
|
|
```
|
|
|
|
### `notifications.show`
|
|
|
|
Shows a desktop notification.
|
|
|
|
```ts
|
|
(params: { title: string; body?: string }) => { delivered: boolean }
|
|
```
|
|
|
|
### `storage.get`
|
|
|
|
Reads a JSON value from the plugin's private store; null when absent.
|
|
|
|
```ts
|
|
(params: { key: string }) => { value: JSON }
|
|
```
|
|
|
|
### `storage.set`
|
|
|
|
Writes a JSON value to the plugin's private store.
|
|
|
|
```ts
|
|
(params: { key: string; value: JSON }) => { ok: true }
|
|
```
|
|
|
|
### `storage.delete`
|
|
|
|
Removes a key from the plugin's private store.
|
|
|
|
```ts
|
|
(params: { key: string }) => { ok: true }
|
|
```
|
|
|
|
### `storage.keys`
|
|
|
|
Lists the keys in the plugin's private store.
|
|
|
|
```ts
|
|
(params?: {}) => { keys: string[] }
|
|
```
|
|
|
|
### `secrets.get`
|
|
|
|
Reads a secret from the plugin's encrypted secrets file; null when absent.
|
|
|
|
```ts
|
|
(params: { key: string }) => { value: string | null }
|
|
```
|
|
|
|
### `secrets.set`
|
|
|
|
Stores a secret in the plugin's encrypted secrets file.
|
|
|
|
```ts
|
|
(params: { key: string; value: string }) => { ok: true }
|
|
```
|
|
|
|
### `secrets.delete`
|
|
|
|
Removes a secret from the plugin's encrypted secrets file.
|
|
|
|
```ts
|
|
(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.
|
|
|
|
```ts
|
|
(params?: {}) => { status: "os-keyring" | "unavailable" }
|
|
```
|
|
|
|
### `settings.get`
|
|
|
|
Reads every setting the plugin owns.
|
|
|
|
```ts
|
|
(params?: {}) => { settings: Record<string, JSON> }
|
|
```
|
|
|
|
### `settings.set`
|
|
|
|
Writes one setting the plugin owns.
|
|
|
|
```ts
|
|
(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.
|
|
|
|
```ts
|
|
(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.
|
|
|
|
```ts
|
|
(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.
|
|
|
|
```ts
|
|
(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.
|
|
|
|
```ts
|
|
(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.
|
|
|
|
```ts
|
|
(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.
|
|
|
|
```ts
|
|
(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 }
|
|
```
|
|
|
|
## 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. |
|
|
|
|
## 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; `retryAfterMs` says 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 its `signal`.
|
|
- `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](./plugin-authoring.md#stability) may change.
|
|
|
|
- **Host API 1 is frozen.** Within `pluginApi` 1, methods, params and results only grow: a new method or a new optional param arrives with a higher `Since` minor, 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 `pluginApi` major beside the old one. Orca refuses a manifest whose `pluginApi` names 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)` or `orca.api.minor` before calling a method newer than the oldest Orca you support; a call that slips through fails with `unknown_method`.
|