# Plugin API reference Every namespace and method an extension can call — ctx.api in the main process and pluginAPI in the window — with the permission each needs and whether it is in the published types. Source: https://docs.vampikez.fun/reference/plugin-api/ Look up the exact signature of anything an extension can call, the permission it needs, and whether the shipped type declarations know about it. For what each namespace is *for*, with worked examples, read [The plugin API](/build/plugin-api/) first — this page is the lookup layer that guide defers to. ## How to read this Members are grouped by where the code runs: - **`ctx.api.*`** — the main-process half, reached through the `ctx` passed to `activate(ctx)`. Declared as `PluginAPI` in `@wamp/extension-sdk`. - **`pluginAPI.*`** — the window half, imported from `@wamp/plugin-api`. Every table carries an **In types** column. `No` means the member exists at runtime but is absent from the published declarations, so calling it is a compile error until you cast around it. Treat a `No` as unsupported: undeclared means unversioned, and it can change without notice. The **Permission** noted on a namespace is the manifest permission that must be declared for the namespace to exist. Without it the member is `undefined` — not a thrown error — so guard or declare. See [Permissions](/build/permissions/). Four permission strings are not runtime gates: `ai`, `filesystem`, `network`, and `terminal` are disclosure labels shown to the user, and the host does not withhold those capabilities today. Read this column as a statement about which namespaces are present, not as a security boundary. Permissions constrain host APIs only; installed Node and UI code remains trusted code. `ctx.api.db`, the old raw-SQLite handle, no longer exists. Raw SQL survives in exactly two places: the `migrations` array of a typed-data schema, and `data.raw(sql, params)`. ## The context object `ctx` carries three members besides `api`. | Member | In types | Description | | --- | --- | --- | | `ctx.pluginId: string` | Yes | This extension's id — its directory name | | `ctx.pluginPath: string` | Yes | Absolute path to the extension's own directory | | `ctx.log` | Yes | `info` / `warn` / `error` / `debug`, each `(message, ...args)` | ## ctx.api.ai Permission: none. | Member | In types | Description | | --- | --- | --- | | `complete(input, options?): Promise` | Yes | One-shot completion; resolves to the assistant text | | `stream(input, callbacks, options?): Promise` | Yes | The same completion, streamed through `callbacks.onChunk` | | `generateObject(input, schema, options?): Promise` | Yes | Completion constrained to a JSON schema | | `completeWithUsage(input, options?)` | Yes | `complete` plus `{ text, usage }` | | `abort(requestId): void` | Yes | Cancels an in-flight `stream` by the id `onRequestStart` gave | `input` is either a prompt string or a `{ role, content }[]` array. `options` is `ExtensionAIOptions`: `model?` (defaults to the user's selected model), `system?`, `maxTokens?` (defaults to 4096), `temperature?`. `callbacks` for `stream`: | Callback | In types | Description | | --- | --- | --- | | `onChunk(text)` | Yes | Fires per streamed text fragment | | `onEnd(usage, stopReason?)` | Yes | `stopReason: 'max_tokens'` means the answer was cut off, not finished | | `onError(error: Error)` | Yes | Terminal failure | | `onRequestStart(requestId)` | Yes | The id `abort` takes | Cancelling a main-process stream takes both members: keep the id `onRequestStart` hands over, then call `abort(id)`. In the window, `pluginAPI.ai.stream` returns a cancel function instead. There is no multi-turn session primitive. A chat-shaped UI keeps its own message history (component state, `pluginAPI.storage`, or typed data) and calls `complete`/`stream` once per turn with that history as `input`. ## ctx.api.agents Permission: none. | Member | In types | Description | | --- | --- | --- | | `delegate(agentType, task, options?)` | Yes | Runs an agent to completion and resolves with its result | | `getAvailable(): string[]` | Yes | Agent ids currently registered | `options`: `context?`, `todos?`, `skills?`, `modelOverride?` (a tier name — `fast`, `balanced`, `powerful` — or a full model id). The result is `{ text, toolsUsed, stopReason, usage, costUsd }`; `costUsd` is `0` for unpriced models. The window's `delegate` additionally accepts `outputSchema` and returns `object` / `schemaError`, which the main-process declaration does not carry. ## ctx.api.tools Permission: none. | Member | In types | Description | | --- | --- | --- | | `register(tool): Disposable` | Yes | Registers a tool the assistant can call | `tool` fields: `name`, `description`, `inputSchema` (JSON Schema object), `execute(input, ctx)`, plus optional `category`, `group`, `display`, and `policy`. `execute` returns a string or an array of content blocks (`{ type: 'text', text }` / `{ type: 'image', data, mediaType, dims? }`). Tools sharing a `group` appear to the model as one grouped operation. Policy supports `readOnly`, `mutates`, `parallelSafe`, `untrustedSource`, `egress`, `longRunning`, `compactable`, and `persistenceQuota`; read-only tools run concurrently unless `parallelSafe` is `false`. Declare `egress: 'url'` when the call sends a request to the host its `url` argument names, or `egress: 'any'` when it can reach hosts it picks while running. Outside Full access the user then allows each host (or, for `'any'`, the tool) once per chat, and an unattended run is refused. Tool names must match `^[a-zA-Z0-9_-]+$`. A name containing a dot throws inside `activate()`, which faults the whole extension — so write `notes_search`, never `notes.search`. Schemas, display metadata, and policy flags are covered in [Contributing tools](/build/tools/). ### Inside a tool's execute The second argument to `execute` is typed `unknown`. The SDK publishes one narrow shape for it, `ToolContext`, and the runtime object has more. | Member | In types | Description | | --- | --- | --- | | `ctx.services?.client?.request(method, params?, opts?)` | Yes | Reverse-RPC to the connected client; absent on a desktop host, which *is* the client | | `ctx.sendToolUse(name, input, status, result?, meta?)` | **No** | Pushes an interim tool-use card to the UI; `status` is `running`, `success`, or `error` | ## ctx.api.data Permission: none. Typed data is stored in the engine profile; remote renderer queries cross the engine transport. | Member | In types | Description | | --- | --- | --- | | `defineSchema(schema): Promise>` | Yes | Creates or migrates the tables and returns the typed query surface | The returned object has one property per table plus `raw`: | Member | In types | Description | | --- | --- | --- | | `findMany(args?): Promise` | Yes | `where`, `orderBy`, `limit`, `offset` | | `findUnique(args): Promise` | Yes | Single row by `where` | | `create(args): Promise` | Yes | Inserts `args.data` and returns the stored row | | `update(args): Promise<{ changes }>` | Yes | Applies `args.data` to rows matching `args.where` | | `delete(args): Promise<{ changes }>` | Yes | Deletes rows matching `args.where` | | `count(args?): Promise` | Yes | Row count | | `raw(sql, params?): Promise` | Yes | Raw SQL escape hatch; rows come back untyped | The predicate builder `q` (`eq`, `ne`, `in`, `notIn`, `like`, `isNull`, `isNotNull`, `now`, and the comparison operators) is imported from `@wamp/extension-sdk`. Schema shape, column types, and migrations are in [Typed data](/build/typed-data/). ## ctx.api.storage, secrets, settings Permission: none for all three. | Member | In types | Description | | --- | --- | --- | | `storage.get(key, defaultValue?): Promise` | Yes | Per-extension key/value read | | `storage.set(key, value): Promise` | Yes | Write | | `storage.delete(key): Promise` | Yes | Remove one key | | `storage.keys(): Promise` | Yes | Every key this extension has stored | | `secrets.set(key, value): Promise` | Yes | Encrypted write | | `secrets.get(key): Promise` | Yes | Encrypted read | | `secrets.delete(key): Promise` | Yes | Remove | | `secrets.has(key): Promise` | Yes | Existence check without decrypting | | `settings.get(key): Promise` | Yes | The user's override, falling back to the `contributes.settings` default | | `settings.onDidChange(key, callback): Disposable` | Yes | Observe writes to one setting in the current host process; dispose the returned subscription during deactivation | | `settings.set(key, value): Promise` | Yes | Persists under `settings:` in this extension's storage | ## ctx.api.commands, services Permission: none for both. | Member | In types | Description | | --- | --- | --- | | `commands.register(cmd): Disposable` | Yes | `cmd` carries the handler inside the descriptor | | `commands.execute(id, args?): Promise` | Yes | Invokes any registered command | | `commands.getAll(): Command[]` | Yes | Every registered command | | `services.register(id, impl): Disposable` | Yes | Publishes a service under `id` | | `services.get(id): T \| undefined` | Yes | Lookup that tolerates absence | | `services.require(id): T` | Yes | Lookup that throws when absent | | `services.onRegister(id, cb): Disposable` | Yes | Fires when a service appears | | `services.onUnregister(id, cb): Disposable` | Yes | Fires when it goes away | A `Command` is `{ id, title, keybinding?, category?, scope? }`, where `scope` is `focused-view`, `focused-slot`, `global`, or an app-specific string. Registering with no handler in either position throws. ## ctx.api.ipc, events Permission: none for both. | Member | In types | Description | | --- | --- | --- | | `ipc.handle(channel, handler): () => void` | Yes | Answers `pluginAPI.ipc.invoke` from your UI half | | `ipc.broadcast(channel, payload): void` | Yes | Pushes to every window; throws synchronously if the channel's prefix is not a declared `ipcNamespaces` entry | | `events.emit(event, ...args): void` | Yes | Deprecated; use `ipc.broadcast` for UI notifications | `ipc.handle` is generic in its argument tuple, so a typed handler such as `(_e, req: SpawnRequest) => …` infers without a cast. ## ctx.api.fs Permission: none. These call Node directly with the path you pass, so they are not restricted to the workspace. | Member | In types | Description | | --- | --- | --- | | `readFile(path): Promise` | Yes | UTF-8 read | | `readFileBuffer(path): Promise` | Yes | Binary read | | `writeFile(path, content): Promise` | Yes | UTF-8 write | | `exists(path): Promise` | Yes | Access check | | `readDir(path): Promise` | Yes | Entry names | | `mkdir(path): Promise` | Yes | Recursive create | | `copyFile(src, dest): Promise` | Yes | Copy | | `remove(path): Promise` | Yes | Delete | | `stat(path)` | Yes | `{ size, isDirectory, isFile, mtime }` | | `getDataPath(subdir?): string` | Yes | This extension's own data directory, created if missing | | `join`, `dirname`, `basename`, `extname` | Yes | Path helpers, synchronous | ## ctx.api.dialog, notifications, shell | Member | Permission | In types | Description | | --- | --- | --- | --- | | `dialog.openFile(options?): Promise` | none | Yes | Native picker; `options.directory` picks folders, `options.multiSelections` allows several | | `dialog.saveFile(options?): Promise` | none | Yes | Save picker with `title`, `defaultPath`, `filters` | | `notifications.show({ title, body, silent? }): void` | `notifications` | Yes | System notification; works with no window open | | `shell.onWillOpenExternal(handler): Disposable` | none | Yes | Claims a URL open before the OS browser sees it | An interceptor returns `true` to claim the URL. Handlers run in registration order across extensions, the first `true` wins, and a throw is treated as fall-through. Only user-initiated opens travel through this surface. ## ctx.api.webviews Permission: none, but navigation control, script injection, and request interception each need a matching entry in the manifest's `capabilities`. | Member | In types | Description | | --- | --- | --- | | `create(options): WebviewHandle` | Yes | `options`: `id`, `html?`, `url?`, `enableScripts?`, `partition?`, `surface?`, `webRequestRewrite?` | `enableScripts` defaults to `true`; pass `false` to disable page JavaScript. Native views always retain their page while hidden. `WebviewHandle`: | Member | In types | Description | | --- | --- | --- | | `id` | Yes | Readonly string | | `webview.asWebviewUri(localPath): string` | Yes | Owner-bound URL for a file inside this extension | | `webview.cspSource` | Yes | CSP source authorizing URLs returned by `asWebviewUri` | | `loadURL(url): Promise` | Yes | Navigate; resolves on load | | `goBack()`, `goForward()`, `reload(opts?)` | Yes | History and refresh | | `executeScript(source, options?): Promise` | Yes | `options.world` is `isolated` or `main` | | `onWillNavigate(handler): Disposable` | Yes | Return `allow`, `deny`, or `{ redirect }` | | `onResourceRequest(handler): Disposable` | Yes | Return `{ cancel?, redirect?, modifyHeaders? }` | | `getState()`, `setState(state)` | Yes | Per-webview state slot | | `setSurface(surface): void` | Yes | Moves between `visible` and `headless` without reloading | | `reveal(): void` | Yes | `setSurface('visible')` plus raise-to-front | | `setBounds(rect): void` | Yes | Window pixels, not CSS pixels — multiply by `devicePixelRatio` | | `openDevTools(opts?): void` | Yes | `mode`: `bottom`, `right`, `undocked`, `detach` | | `dispose(): void` | Yes | Destroys the webview | ## ctx.api.http | Member | Permission | In types | Description | | --- | --- | --- | --- | | `http.route(method, path, handler): registration` | `http-routes` | Yes | Callable disposer with `ready: Promise`; `method` is `GET`, `POST`, `PUT`, `DELETE`, or `PATCH` | | `http.baseUrl(): string` | `http-routes` | Yes | Authoritative after the first registration's `ready` resolves | A route handler receives `{ method, path, params, query, body, headers }` and returns `{ status?, body?, headers? }`. Await `registration.ready` before publishing the URL or completing activation. Scheduled agent work is covered in [Scheduled work](/build/scheduled-work/). ## ctx.api.process, pty, git All three are gated on the single `process` permission — every `git` operation is a `git` process spawn, so it grants nothing `process` does not already imply. | Member | In types | Description | | --- | --- | --- | | `process.spawn(command, args, options?): PluginChildProcess` | Yes | `options`: `cwd`, `env`, `stdio` | | `process.killAll(): void` | Yes | Kills every child this extension started | | `pty.spawn(options): PtyHandle` | Yes | `options`: `command`, `args?`, `cwd`, `env?`, `cols?`, `rows?` (defaults 80×24) | | `pty.get(id): PtyHandle \| undefined` | Yes | `undefined` once the session has exited | | `pty.list(): PtyHandle[]` | Yes | Live sessions this extension owns | | `git.isRepo(cwd): Promise` | Yes | Whether `cwd` is inside a repository | | `git.getRepoRoot(cwd): Promise` | Yes | Repository root for `cwd` | | `git.worktree.list(cwd): Promise` | Yes | Every worktree of the repository | | `git.worktree.diff(ref, mainRepoCwd?): Promise` | Yes | Per-file status and line counts | `PluginChildProcess` exposes `pid`, `stdin`, `stdout`, `stderr`, `kill(signal?)`, and `on('exit' | 'error', handler)`. `PtyHandle` exposes `id`, `pid`, `write(data)`, `resize(cols, rows)`, `kill(signal?)`, and the disposable-returning `onData(cb)` and `onExit(cb)`. Reach for `pty` whenever a human or a terminal UI is on the other end; `process` gives line-buffered pipes with no resize. `GitWorktree` is `{ path, branch, head, isMain, lockReason? }`. `GitFileDiff` is `{ path, status, oldPath?, additions, deletions, patch?, binary? }`, where `patch` is populated on request rather than by list operations. ## ctx.api.auth Permission: `auth.identity`. Outbound origins are declared separately as `{ "auth.outbound": [origin, …] }`. | Member | In types | Description | | --- | --- | --- | | `getSession(): Promise` | Yes | Cached identity; `null` when signed out. Never round-trips | | `fetch(url, init?): Promise` | Yes | Signed call to your own backend, forwarded through the main process | | `onChange(cb): () => void` | Yes | `cb(session, reason)`, where `reason` is `logged-in`, `logged-out`, `token-refreshed`, or `session-expired` | `AuthSession` is `{ user: { id, email, name? }, extensionId, audience, childTokenExpiresAt }`. Your extension never sees WAMP's own token; `fetch` attaches a child token whose audience is `ext:` and returns `{ status, data, headers }`. It throws when the URL's origin is not in the `auth.outbound` allowlist (loopback origins are auto-allowed in a dev build) or when nobody is signed in. See [Sign in with WAMP](/identity/sign-in-with-wamp/). ## ctx.api.tokens and disposables | Member | Permission | In types | Description | | --- | --- | --- | --- | | `tokens.onUsage(handler): () => void` | none | Yes | `handler({ input_tokens, output_tokens, model?, conversationId? })` | | `disposables.add(d): void` | none | Yes | Accepts a `Disposable` or a plain function | | `disposables.disposed: boolean` | none | Yes | Readonly; `true` after the host tears the extension down | Use `ToolContext.workspacePath` for tools and `ExtensionIpcCallEvent.workspacePath` for UI requests. Each identifies the project that invoked the operation. ## ctx.api.sessions, clientAffordances These depend on services supplied by the host rather than permissions. Desktop supplies both; a headless core without those host services supplies neither. Guard their presence instead of inferring it from local versus remote mode. | Member | In types | Description | | --- | --- | --- | | `sessions.getActive(): ActiveSession \| null` | Yes | The session on screen; `null` on a fresh launch | | `sessions.getKnownIds(): readonly string[] \| null` | Yes | Every session that exists. `null` means the catalog has not loaded — which is not "no sessions" | | `sessions.onDidChange(cb): Disposable` | Yes | Fires when the active session or the known-id set changes | | `clientAffordances.register(config): Disposable` | Yes | Claims a `wamp.client..*` namespace | `ActiveSession` is `{ id, name, context }`, where `context` is absent until the user picks a project. There is deliberately no session-deleted event: deletes have an undo window and can happen while the app is closed, so reconcile against `getKnownIds()` instead of listening. `ClientAffordanceConfig` is `{ namespace, methods, rateLimit: { burst, perMinute }, singleFlight?, handle(submethod, params) }`. Registering a namespace another extension already claimed throws. ## The window surface `pluginAPI` is a closed interface: every member is required, so a typo is a compile error rather than a runtime `undefined`. Sensitive calls with declared permission boundaries are re-checked outside the window. `pluginAPI` has no MCP namespace; MCP server configuration lives in Settings. Skill package mutations share the host's trusted renderer realm; they are not a per-extension authorization boundary. The engine still requires a validated preview plus its exact digest and applies the same trust, containment, and atomic-install policy to every caller. ### pluginAPI.notify, ai, agents, skills, tasks | Member | In types | Description | | --- | --- | --- | | `notify(message, type?): void` | Yes | In-app toast; `type` is `info`, `success`, or `error` | | `notify.success(message)`, `.error(message)`, `.info(message)` | Yes | The same, spelled per level | | `ai.complete(input, options?): Promise` | Yes | One-shot completion | | `ai.stream(input, callbacks, options?): () => void` | Yes | Streams, and returns a cancel function | | `ai.generateObject(input, schema, options?): Promise` | Yes | Schema-constrained completion | | `ai.listModels(): Promise` | Yes | `{ models, aliases }` — the surface a model picker needs | | `agents.delegate(agentType, task, options?): Promise` | Yes | `options` adds `outputSchema`; the result adds `object` and `schemaError` | | `agents.list(): Promise` | Yes | Agent ids available for delegation | | `agents.listEntries(): Promise` | Yes | Full catalog with scope, triggers, tools, and skills | | `agents.save(payload): Promise<{ success, error? }>` | Yes | Creates or overwrites a user- or workspace-scope agent | | `agents.delete(agentId): Promise<{ success, error? }>` | Yes | Deletes a user- or workspace-scope agent | | `agents.listLoadErrors(): Promise` | Yes | Agent files that currently fail to parse | | `agents.listScheduled(): Promise` | Yes | Cron-scheduled agents with next fire time | | `agents.onRegistryChanged(cb): () => void` | Yes | Fires on any registry change | | `skills.list(): Promise` | Yes | Every installed skill | | `skills.getDetails(qualifiedId): Promise` | Yes | Exact manifest, body, and resource metadata, loaded on demand | | `skills.listLoadErrors(): Promise` | Yes | Invalid packages excluded from the usable catalog | | `skills.prepareInstall(registryId, skillId): Promise` | Yes | Read-only validation and full package preview with exact digest | | `skills.install(registryId, skillId, expectedDigest): Promise` | Yes | Installs only the exact reviewed digest | | `skills.uninstall(registryId, skillId): Promise` | Yes | Removes a registry-installed skill | | `skills.checkUpdates(skills): Promise<…>` | Yes | Compares installed hashes against the registries | | `skills.listRegistries(): Promise` | Yes | Configured registries | | `skills.setRegistryEnabled(id, enabled): Promise` | Yes | Persists the effective registry layer | | `skills.searchRegistries(query, maxResults?): Promise` | Yes | `{ hits, errors }` | | `skills.onChanged(callback): () => void` | Yes | Install, uninstall, extension, and watched-file changes | | `tasks.enqueue(input): Promise<{ runId }>` | Yes | `runId` is `null` when the requested `agentId` is not registered | | `tasks.list(runId): Promise` | Yes | Tasks for a run, in creation order | | `tasks.get(runId, id): Promise` | Yes | One task | | `tasks.subscribe(runId, callbacks): () => void` | Yes | `onCreated`, `onUpdated`, `onDeleted`. Call the returned function or listeners leak | Neither `listModels`, `skills`, nor `agents.listEntries` has a main-process equivalent, so build pickers in the window. [Agents and skills](/build/agents-and-skills/) covers delegation and the registries. ### pluginAPI.data, storage | Member | In types | Description | | --- | --- | --- | | `data.defineSchema(schema): DataAPI` | Yes | Not awaited here; `await`ed in main | | `storage.get(key): Promise` | Yes | Per-extension read. Returns `null`, where main returns `undefined` | | `storage.set(key, value): Promise` | Yes | Write | The window's `storage` has no `delete` or `keys` — do those from the main half. ### pluginAPI.fs, dialog, http | Member | In types | Description | | --- | --- | --- | | `fs.read(path): Promise` | Yes | Named `readFile` in main | | `fs.write(path, content): Promise` | Yes | Named `writeFile` in main | | `fs.listDir(path): Promise` | Yes | `{ name, path, isDirectory }` per entry | | `fs.exists(path): Promise` | Yes | Existence check | | `fs.stat(path): Promise` | Yes | `{ type, size, mtimeMs, ctimeMs }`, or `null` when absent or outside the workspace | | `fs.mkdir(path): Promise` | Yes | Create | | `fs.delete(path): Promise` | Yes | Named `remove` in main | | `fs.move(sourcePath, destPath): Promise` | Yes | No main-process equivalent | | `dialog.openFile(options?): Promise` | Yes | `options`: `filters`, `multiple` | | `dialog.openFolder(): Promise` | Yes | No main-process equivalent | | `dialog.saveFile(options?): Promise` | Yes | `options`: `title`, `defaultPath`, `filters` | | `http.fetch(url, options?): Promise` | Yes | Routed through the main process, so CORS does not apply. Returns `{ status, data, headers }` | The window's `fs` goes through the host's file service and its path rules; the main-process `fs` does not. It also has no path helpers — `join`, `dirname`, `basename`, `extname`, and `getDataPath` exist only on `ctx.api.fs`. ### Host availability Every `pluginAPI` namespace is present. An operation the host cannot serve rejects with an error whose `code` is `host_unsupported`; `navigation.go` and `navigation.back` throw it synchronously. Unavailable subscriptions return a no-op unsubscribe. A failed `fs.exists` or `storage.get` call rejects instead of reporting `false` or `null`. | Capability | Desktop workspace | Desktop Cloud-session realm | Cloud web | | --- | --- | --- | --- | | `notify`, `workspace.path`, navigation params, `ui.exitAppMode` | Available | Available; `workspace.path` is `null` | Available | | `navigation.go`, `navigation.back` | Available | `host_unsupported` | Available | | `fs.read`, `tools.call`, `ipc.invoke` / `ipc.subscribe` | Available | Available | Available | | Other `fs` operations, `workspace.pluginsPath`, `ui.showExtension` | Available | `host_unsupported` | `host_unsupported` | | `shell.openExternal` | Available | Available | `host_unsupported` | | `ai`, `agents`, `skills`, `auth.getSession` / `auth.fetch`, `http`, `storage`, `dialog` | Available | `host_unsupported` | `host_unsupported` | | `auth.resourceFetch` | Available when the viewer has a resource binding | `host_unsupported` | `host_unsupported` | ### pluginAPI.ui, navigation, shell, workspace, tools, events, auth | Member | In types | Description | | --- | --- | --- | | `ui.showExtension(pluginId): Promise` | Yes | Surfaces another extension's primary view | | `ui.exitAppMode(): void` | Yes | Host-specific: closes a Cloud panel; does not close or navigate a Desktop app window | | `navigation.go(pageId, params?): void` | Yes | Unknown ids no-op with a console warning | | `navigation.back(): void` | Yes | Desktop restores the previous page and its params; Cloud closes the panel | | `navigation.params(): Record` | Yes | Params of the current page or panel | | `shell.openExternal(url): Promise` | Yes | Opens in the OS browser; a rejected scheme throws | | `workspace.path(): string \| null` | Yes | Workspace root, or `null` | | `workspace.pluginsPath(): Promise` | Yes | Where extensions are installed | | `tools.call(toolName, input?): Promise` | Yes | Invokes one of this extension's own registered tools | | `events.on(event, callback): () => void` | Yes | `event` is `file.changed` or `ai.toolUse` | | `auth.getSession(): Promise` | Yes | Same shape as the main-process member | | `auth.fetch(url, init?): Promise` | Yes, Desktop only | For portable extensions, call the server half over `pluginAPI.ipc` and use `ctx.api.auth.fetch` there | | `auth.resourceFetch({ audience, path }, { signal }?): Promise` | Yes | `GET` of a connected resource as the viewer, sent by WAMP Desktop; `{ status, headers, body }`, never the token. Requires the audience in `auth.resource`; other hosts reject with `ResourceFetchError` `host_unsupported`. See [Read your service from extension UI](/identity/connected-resources/#read-your-service-from-extension-ui) | | `auth.onChange(cb): () => void` | Yes | Session lifecycle events | Never call `window.alert`, `prompt`, or `confirm` — they are blocked. Use `` from `@wamp/ui`; see [Interface kit](/build/ui/). ### pluginAPI.ipc | Member | In types | Description | |---|---|---| | `ipc.invoke(channel, ...args): Promise` | Yes | Request to your Node half; works over Desktop IPC or the Cloud engine connection | | `ipc.subscribe(channel, handler): () => void` | Yes | Listen to a broadcast; call the returned function on cleanup | Declare the channel prefix in `contributes.ipcNamespaces`. A host without an IPC binding rejects invocation explicitly. Subscribe before requesting initial state when events can arrive during that request. ## The window's activate host A UI bundle may export `activate(host: ExtensionUIHost)` to register renderer-side contributions. Import `ExtensionUIHost` and its dock, chat and tool types from `@wamp/extension-sdk`. This is how a bundle reaches tool-card rendering, chat blocks, new-chat destinations and dock tabs. | Member | In types | Description | | --- | --- | --- | | `host.ownerId: string` | Yes | The extension's own id | | `host.surface: 'workspace' \| 'cloud-session'` | Yes | Which shell mounted the host | | `host.tools.registerCardRenderer(toolName, component)` | Yes | Draws the chat card for one tool's results with your own React component. Keyed by the tool's name | | `host.chat.registerBlockKind(kind)` | Yes | Registers a chat-input block kind with a chip and a serializer | | `host.chat.insertBlock(block)` | Yes | Inserts a block into the active chat draft | | `host.chat.setSessionDestinations(viewId, provider)` | Yes | Publishes a creator for the active project's new-chat destination picker | | `host.chat.clearSessionDestinations(viewId)` | Yes | Removes that view's destination creator | | `host.dock.setTabs(viewId, provider)` | Yes | Publishes a `session.dock` view's tabs into the dock's tab row | | `host.dock.clearTabs(viewId): void` | Yes | Removes the provider's published tabs | | `host.dock.present(request)` | Yes | Presents a resource through a view that advertises and validates it | | `host.dock.listViews()` | Yes | Lists registered views and the resource kinds they accept | | `host.dock.canPresent(sessionId)` | Yes | Whether the host can present a resource for that session | `registerCardRenderer` and `registerBlockKind` return disposables. The tab and session-destination setters and clearers return void. The host drops all of these registrations on unload, including after an activation throw. Return `{ dispose() }` from `activate` for any other extension-owned resources; the same cleanup runs on a Cloud session switch. Every host passes `ownerId`, `surface` and the whole of `dock`. Desktop's workspace adds `chat` and `tools`; the Cloud web host and the desktop's own Cloud session realm pass neither, and activate lazily when a panel or its workbench resources are requested. Guard host-specific namespaces before use — a host publishes a capability group whole or not at all, so `host.chat && …` is the check, and `activate` runs bare, where a missing member is a `TypeError` that costs the extension its panel. `surface` is how a view avoids drawing a second copy of something the shell already draws: a `cloud-session` shell reviews that session's changes and pull requests itself, so Git Workbench publishes only its history graph there. The Cloud host reports an activation throw but still attempts to mount the view; do not mistake a visible panel for successful provider registration. ## Next - [The plugin API](/build/plugin-api/) — the same surfaces, organized by task. - [Permissions](/build/permissions/) — declaring them, and writing for absence. - [Manifest reference](/reference/manifest/) — every field, including `contributes` and `capabilities`. - [Troubleshooting](/reference/troubleshooting/) — what a specific error means.