* feat(plugins): add editor.readTypography and editor.typography.changed Plugins that render code next to Orca's editor had to open the private profile database and reimplement Orca's font rules to match it. Host API 1.6 adds editor.readTypography (no capability; worker, panel and editor frames): the font family and CSS stack, size with editor zoom, weight, line height in px, ligatures, word wrap and minimap that the file editor resolves now. editor.typography.changed carries the same values after a settings write or an editor zoom changes them; it is the first event that needs no capability. The pure resolution (editor typography, editor zoom, monospace font stack) moves to src/shared so main and the renderer use one implementation. * fix(plugins): keep editor typography resolution from throwing in main A hand-edited profile with a non-string font family made the shared resolver throw, which the main-process watcher would surface at startup and on every settings write. Font values are now type-checked, the watcher catches and logs, and it reads live settings so a write without listeners cannot leave the event payload stale. Over-long font families are cut to the schema bound instead of failing every read. Regenerates the testing runtime.
21 KiB
Host API by surface
Host API 1.6 ("pluginApi": 1). Call host.call(method, params, { signal }); a surface not listed for a method gets not_permitted. Params are strict: a field this Orca does not know is refused with invalid_params, so check host.has(method) or api.minor before using a method newer than your engines.orca floor. Each call also needs the manifest capability and the user's approval.
Methods per surface
- Worker (
trusted-worker,main):orcapassed toactivate(orca):workspace.readContext,terminal.sendText,notifications.show,storage.get,storage.set,storage.delete,storage.keys,secrets.get,secrets.set,secrets.delete,secrets.protection,settings.get,settings.set,agents.list,statusBar.update,appearance.read,appearance.apply,workspace.list,workspace.open,workspace.rename,terminal.create,terminal.read,agent.launch,editor.open,commands.run,editor.readTypography - Panel: global
orcain an HTML panel;hostimported fromorcain a components panel:workspace.readContext,notifications.show,storage.get,storage.set,storage.delete,storage.keys,secrets.get,secrets.set,secrets.delete,secrets.protection,settings.get,settings.set,shell.openExternal,files.saveAs,plugin.request,agents.list,events.subscribe,editor.readTypography - Custom editor: global
orcain the editor frame:workspace.readContext,notifications.show,storage.get,storage.set,storage.delete,storage.keys,secrets.get,secrets.set,secrets.delete,secrets.protection,settings.get,settings.set,shell.openExternal,files.saveAs,plugin.request,agents.list,events.subscribe,editor.readTypography
Worker API (import type { OrcaWorkerApi } from 'orca/worker')
api: HostApiVersioncommands.register(commandId: string, handler: (args: unknown, context: CommandContext) => unknown): A string result is shown as-is in Orca; anything else as JSON.events.on(event: E, handler: (payload: PluginEvents[E]) => void | Promise<void>): Only events listed in the manifest'scontributes.eventsarrive.hooks.on(hook: H, handler: HookHandler<H>): Only hooks listed incontributes.hooksrun. Returnnext(event)to go on (with the fields the hook allows changed), a result such as{ deny: reason }to stop, or nothing to pass. Plugins run in turn, then Orca acts; past 2 s, or on a throw, Orca carries on as if this plugin passed and logs it.host: HostApi<'worker'>panels: PanelsApi: This plugin's panels and custom editors.storage: StorageApi: Same store as panels'storage(storage:own);onDidChangehears their writes too.settings: SettingsApi:settings:own;onDidChangealso hears the person changing them in Settings.secrets: SecretsApi:secrets:own.grantedCapabilities: readonly string[]: Consented capability kinds; informational, the host re-checks every call.log(message: string): Lands inorca plugin logs.
Frame API (global orca, also window.orca, in HTML panels and custom editors)
api: HostApiVersionhost: HostApi<'panel' | 'editor'>on(event: E, handler: (payload: FrameEvents[E]) => unknown)locale: string: Orca's UI language as a BCP-47 tag.plugin: PluginBridgeApi: Your plugin's own worker:requestits handlers, hear what itposts.storage: StorageApi: The plugin's own storage, the same its worker uses (storage:own).settings: SettingsApi: The plugin's settings, declared ones with their defaults (settings:own).secrets: SecretsApi: The plugin's encrypted secrets (secrets:own).shell.openExternal(url: string):reason: the person cancelled, or Orca's window was hidden so nobody could confirm.files.saveAs(options: SaveAsOptions): Desktop isolated frames only;cancelledwhen the person closes the dialog.document?: EditorDocumentApi: Custom editors only. Orca keeps no undo history for them: Cmd/Ctrl+Z reaches the frame.document.get(): The latest document, with your accepted edits' content at the same version.document.onDidChange(handler: (document: EditorDocument) => void): Replays the current document, then each one Orca sends when the file changes outside.document.applyEdit(edit: EditorEdit): Rejects withconflict,too_largeornot_permitted(read-only).document.session(options?: EditorSessionOptions<T>): Document sync in one call; resolves once theme, locale and the first document are in.
Theme variables in frames
HTML panels and custom editors get these CSS variables from Orca's active theme, plus a light or dark class on <html>. A theme change updates them in place and emits theme (orca.on('theme', ...)); the frame is not reloaded. Style from them, not fixed colours.
--orca-background: App canvas--orca-foreground: Body text--orca-card: Cards and panels--orca-card-foreground: Text on--orca-card--orca-popover: Menus, popovers and dialogs--orca-popover-foreground: Text on--orca-popover--orca-primary: Primary buttons--orca-primary-foreground: Text on--orca-primary--orca-secondary: Secondary buttons--orca-secondary-foreground: Text on--orca-secondary--orca-muted: Quiet fills--orca-muted-foreground: Secondary text--orca-accent: Hover and selected fills--orca-accent-foreground: Text on--orca-accent--orca-destructive--orca-destructive-foreground--orca-border: Borders and separators--orca-input: Input borders--orca-ring: Focus rings--orca-radius: Corner radius of cards and controls
Methods
workspace.readContext(capabilityworkspace:read; worker, panel, editor; since 1.0): 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 }[] } | nullterminal.sendText(capabilityterminal:send; worker; since 1.0): Types text into one terminal named by id, optionally pressing Enter.(params: { terminalId: string; text: string; enter?: boolean }) => { accepted: boolean }notifications.show(capabilitynotifications:show; worker, panel, editor; since 1.0): Shows a desktop notification.(params: { title: string; body?: string }) => { delivered: boolean }storage.get(capabilitystorage:own; worker, panel, editor; since 1.0): Reads a JSON value from the plugin's private store; null when absent.(params: { key: string }) => { value: JSON }storage.set(capabilitystorage:own; worker, panel, editor; since 1.0): Writes a JSON value to the plugin's private store.(params: { key: string; value: JSON }) => { ok: true }storage.delete(capabilitystorage:own; worker, panel, editor; since 1.0): Removes a key from the plugin's private store.(params: { key: string }) => { ok: true }storage.keys(capabilitystorage:own; worker, panel, editor; since 1.0): Lists the keys in the plugin's private store.(params?: {}) => { keys: string[] }secrets.get(capabilitysecrets:own; worker, panel, editor; since 1.0): Reads a secret from the plugin's encrypted secrets file; null when absent.(params: { key: string }) => { value: string | null }secrets.set(capabilitysecrets:own; worker, panel, editor; since 1.0): Stores a secret in the plugin's encrypted secrets file.(params: { key: string; value: string }) => { ok: true }secrets.delete(capabilitysecrets:own; worker, panel, editor; since 1.0): Removes a secret from the plugin's encrypted secrets file.(params: { key: string }) => { ok: true }secrets.protection(capabilitysecrets:own; worker, panel, editor; since 1.1): How secrets are protected at rest:os-keyring, orunavailable(no OS key store, or Linuxbasic_text), wheresecrets.setfails.(params?: {}) => { status: "os-keyring" | "unavailable" }settings.get(capabilitysettings:own; worker, panel, editor; since 1.0): Reads every setting the plugin owns.(params?: {}) => { settings: Record<string, JSON> }settings.set(capabilitysettings:own; worker, panel, editor; since 1.0): Writes one setting the plugin owns.(params: { key: string; value: JSON }) => { ok: true }shell.openExternal(no capability; panel, editor; since 1.1; needs a user gesture): 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;openedis false if they cancel.(params: { url: string }) => { opened: boolean; reason?: "cancelled" | "consent-unavailable" }files.saveAs(no capability; panel, editor; since 1.1; needs a user gesture): 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(no capability; panel, editor; since 1.2): Asks the plugin's own worker to run the handler it registered withorca.panels.handle(name), starting the worker if needed; resolves with what the handler returned.unknown_methodwhen no handler has that name.(params: { name: string; input?: JSON; timeoutMs?: number }) => { output: JSON }agents.list(capabilityagent:read; worker, panel, editor; since 1.2): The agents in the focused workspace: their terminal, title, agent kind and status.agent.status.changedreports each change after.(params?: {}) => { agents: { terminalId: string | null; title: string | null; agent: string | null; state: "working" | "blocked" | "waiting" | "done"; stateStartedAt: number }[] }events.subscribe(no capability; panel, editor; since 1.2): Delivers a workspace, agent or editor event to this panel asorca.on(event); needs the event's read capability, when it has one. The frame APIs call it for you.(params: { event: "workspace.created" | "workspace.removed" | "agent.status.changed" | "workspace.changed" | "editor.typography.changed" }) => { ok: true }statusBar.update(no capability; worker; since 1.2): Changes one of the plugin'scontributes.statusBarItems(named byid): fields given replace their current value,visible: falsehides it. Desktop app only; lasts until Orca restarts, and is hidden while the plugin is disabled. A plugin withmainand status bar items has its worker started when it is enabled and kept running while it stays enabled, so a worker can poll and update its items without an event or a hook.(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 }appearance.read(capabilityappearance:write; worker; since 1.4): Reads the current desktop appearance revision for a conditional update.(params?: {}) => { revision: string }appearance.apply(capabilityappearance:write; worker; since 1.4): Publishes owned app/editor and terminal colors and selects them in the desktop. Returns publication, not renderer acknowledgement.(params: { expectedRevision: string; app: string; terminal: string }) => { revision: string; themeId: string; mode: "dark" | "light"; delivery: "published" }workspace.list(capabilityworkspace:read; worker; since 1.3): Lists every workspace, Git worktrees and folders, on every host Orca reaches.(params?: {}) => { workspaces: { id: string; kind: "worktree" | "folder"; name: string; project?: string | null; path: string; branch: string | null; hostId: string | null }[]; truncated: boolean }workspace.open(capabilityworkspace:write; worker; since 1.3): Shows a workspace in Orca, as clicking it in the sidebar does; a folder workspace needs the desktop app.(params: { workspaceId: string }) => { opened: true }workspace.rename(capabilityworkspace:write; worker; since 1.5): Renames a workspace in Orca's sidebar, as renaming it by hand does; the name stays until it is renamed again.(params: { workspaceId: string; name: string }) => { name: string }terminal.create(capabilityterminal:send; worker; since 1.3): Opens a terminal in a workspace, optionally running a command, in the background.(params: { workspaceId: string; title?: string; command?: string }) => { terminalId: string }terminal.read(capabilityterminal:read; worker; since 1.3): Reads a terminal's newest output as plain text, at most 2000 lines.(params: { terminalId: string; maxLines?: number }) => { lines: string[]; truncated: boolean }agent.launch(capabilityagent:launch; worker; since 1.3): Starts an agent's CLI in a new terminal of a workspace, in the background, and returns as soon as it starts; apromptis sent once the agent is ready.(params: { workspaceId: string; agent: string; prompt?: string }) => { terminalId: string }editor.open(capabilityworkspace:write; worker; since 1.3): Opens a file of a workspace in an editor tab of Orca's window; desktop app only.workspace.openshows the workspace.(params: { workspaceId: string; path: string }) => { opened: true }commands.run(capabilityworkspace:write; worker; since 1.3): Runs one of Orca's built-in actions in its window, never a plugin's replacement of it; desktop app only.(params: { action: "worktree.history.back" | "worktree.history.forward" | "sidebar.left.toggle" | "sidebar.sleepingWorkspaces.toggle" | "floatingWorkspace.maximize" | "tab.rename" | "workspace.rename" | "workspace.openBoard" | "view.tasks" | "sidebar.right.toggle" | "sidebar.explorer.toggle" | "sidebar.search.toggle" | "sidebar.sourceControl.toggle" | "sidebar.checks.toggle" | "sidebar.ports.toggle" }) => { ran: true }editor.readTypography(no capability; worker, panel, editor; since 1.6): The font family, size, weight, line height, ligatures, word wrap and minimap Orca's file editor uses now, editor zoom included.editor.typography.changedreports each change after.(params?: {}) => { fontFamily: string; cssFontFamily: string; fontSize: number; fontWeight: string; lineHeight: number; fontLigatures: boolean; wordWrap: "on" | "off"; minimap: boolean }
Hooks
List each in contributes.hooks ({ "event", "onError"? }; main and trusted-worker) and handle it with orca.hooks.on(event, (event, next) => ...). Return next(event) (or nothing) to go on, or the stop result. Plugins run in turn, 2 s each; a throw, timeout or crash passes unless the entry says "onError": "deny". Decide first: an awaited call before you return (a notification, say) can fail or time out and turn your deny into a pass.
terminal.send(since 1.4.248): 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 takeagent.prompt.beforeinstead. Writes to one terminal stay in order while a hook decides. Rewritetextorenterwithnext, or return{ deny }to refuse the send. event{ terminalId: string; text: string; enter: boolean; inputKind: "driving" | "orchestration" | "launch" };nextmay changetext,enter; stop with{ deny: string }workspace.create.before(since 1.4.248): A worktree or folder workspace is about to be created. Changename,branchorbasewithnext, or return{ deny }to refuse it. event{ kind: "worktree" | "folder"; projectId: string; name: string; branch?: string; base?: string };nextmay changename,branch,base; stop with{ deny: string }workspace.create.after(since 1.4.248): A worktree or folder workspace was created. Observe only: Orca does not wait for it, and results are ignored. event{ workspaceId: string; kind: "worktree" | "folder"; path: string; branch: string };nextmay change nothing; stop with nothing (observe only)agent.launch.before(since 1.4.248): Orca is about to start an agent CLI in a terminal, from any launcher (new tab, workspace create, CLI, automation, orchestration). Changecommandor addenvwithnext, or return{ deny }to refuse the launch. Restores of saved sessions are not hooked. event{ agent: string; workspaceId: string | null; workspaceKind: "worktree" | "folder" | null; terminalId?: string; command: string; env: Record<string, string> };nextmay changecommand,env; stop with{ deny: string }agent.prompt.before(since 1.4.248): Orca is about to submit a prompt to an agent in a terminal: a launch prompt, an orchestration dispatch, an issue or task prompt, ororca terminal sendto an agent. A prompt a person submits is never hooked, and writes to one terminal stay in order while it decides. Rewritepromptwithnext, or return{ deny }to refuse it. event{ agent: string | null; terminalId: string; prompt: string; inputKind: "driving" | "orchestration" | "launch" };nextmay changeprompt; stop with{ deny: string }agent.tool.before(since 1.4.248): 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 changeinputwithnext; 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. event{ agent: string; workspaceId: string | null; terminalId?: string; tool: string; input: JSON; callId?: string };nextmay changeinput; stop with{ deny: string }agent.tool.after(since 1.4.248): An agent in an Orca terminal finished a tool call (successfully or not). Observe only: Orca does not wait for it. event{ agent: string; workspaceId: string | null; terminalId?: string; tool: string; input: JSON; callId?: string; output?: JSON };nextmay change nothing; stop with nothing (observe only)editor.save.before(since 1.4.248): The person is about to save a text file from an Orca editor tab (an explicit save, not autosave). Changetextwithnext(format on save) or return{ deny }to refuse the save, which shows your reason. Edits made while the hook ran stay unsaved. event{ workspaceId: string | null; path: string; languageId?: string; text: string };nextmay changetext; stop with{ deny: string }
Capabilities
workspace:read(since 1.4.160): Read the name, branch, and terminal list of your focused workspace, and get notified when workspaces are created or removedworkspace:write(since 1.4.248): Open and rename your workspaces, open their files in Orca, and run its built-in commandsagent:read(since 1.4.245): Get notified when agent status changesagent:launch(since 1.4.248): Start agents in your workspaces, local and SSHterminal:send(since 1.4.160): Type and run commands in your terminals, local and SSHterminal:read(since 1.4.248): Read the output of your terminals, local and SSHnotifications:show(since 1.4.160): Show desktop notifications labeled with the plugin namestorage:own(since 1.4.245): Store data in the plugin's own storage foldersecrets:own(since 1.4.245): Store and read secrets in the plugin's own encrypted filesettings:own(since 1.4.160): Read and change the plugin's own settingsappearance:write(since 1.4.249): Publish and select desktop application, editor and terminal colors
Errors
A failed call rejects with an Error carrying code, message, retryable and, for rate_limited, retryAfterMs. Treat an unknown code as action_failed.
invalid_request: the call itself is malformedinvalid_params: params do not match the method, including a field this Orca does not know; the message names itunknown_method: the method does not exist in this Orcacapability_denied: the manifest does not declare the capabilityconsent_required: the plugin is disabled or the user has not approved this versionnot_permitted: the method or document is not available here, e.g. a worker-only method from a panelrate_limited: calls arrive faster than the budget;retryAfterMssays when to try againunavailable: the service behind the method is not running yetaction_failed: the method ran and failedcancelled: the caller aborted the call through itssignaltimeout: Orca gave up waitingconflict: the call was made against a stale version, e.g. an editor edit after an outside changetoo_large: the message is larger than Orca accepts