Manifest reference
This page is the exhaustive field list for extension.json, derived from the
schema the host validates against. Use it to look up a type, a default, or the
full value set of an enum. For the reasoning behind the choices — which fields
you actually need and which ones fail quietly — read
The manifest first.
Where it lives, and where the id comes from
Section titled “Where it lives, and where the id comes from”extension.json sits in the extension’s root directory, beside main/, ui/,
skills/, and dist/. It is the only required file: a directory with UI or
main code but no manifest is reported as a failed scan.
The extension’s id is the directory name, not a manifest field. There is no
id key. The directory name must be lowercase kebab-case (^[a-z][a-z0-9-]*$)
because every id-shaped field elsewhere — dependencies, catalog slugs — is
validated against that pattern.
Top-level fields
Section titled “Top-level fields”The root object is permissive: an unknown top-level key is accepted and ignored rather than failing validation.
| Field | Type | Required | Default | Constraint |
|---|---|---|---|---|
name |
string | yes | — | non-empty |
version |
string | yes | — | ^\d+\.\d+\.\d+$ — no pre-release tags |
description |
string | no | — | — |
origin |
object | no | — | Generated, read-only provenance for an imported Agent Plugins, Claude, or Codex plugin. See Portable plugins. |
longDescription |
string | no | — | Markdown |
icon |
string | no | — | a lucide-react export name |
author |
string | no | — | — |
permissions |
array | no | [] |
see Permissions |
capabilities |
string[] | no | [] |
unknown strings accepted |
products |
string[] | no | — | each ^[a-z0-9][a-z0-9-]*$ |
webRequestRewrite |
object | no | — | see webRequestRewrite |
contributes |
object | no | — | see contributes |
build |
object | no | — | strict; see build |
activationEvents |
string[] | no | ["onStartupFinished"] |
see Activation events |
dependencies |
string[] | no | — | each ^[a-z][a-z0-9-]*$ |
main |
string | no | — | path relative to the extension root |
server |
string | no | — | path relative to the extension root |
requiresElectron |
boolean | no | false |
— |
compat |
{ pluginApi: string } |
no | — | pluginApi non-empty; a semver range |
What each one controls:
| Field | Controls |
|---|---|
name |
The human label in the sidebar, the catalog card, and search |
version |
Update comparison at install; the version reported to the registry |
description |
One-line summary on catalog cards, in search results, and in the detail hero |
longDescription |
The Markdown body of the catalog detail page |
icon |
The sidebar and catalog icon |
author |
Attribution shown in the catalog |
permissions |
Which host APIs are exposed or allowed, and what the catalog discloses |
capabilities |
Call-time gates on ctx.api.webviews, and which webview backing is used |
products |
Which branded catalogs list the extension. Absent or empty means all |
webRequestRewrite |
Default header rewriting for webviews this extension creates |
contributes |
Everything declarative: pages, views, commands, skills, settings, servers |
build |
Per-path loader overrides for the public SDK builder |
activationEvents |
When the main module is loaded and activate() runs |
dependencies |
Other extensions installed recursively alongside this one |
main |
Entry point of the host/Electron-side module |
server |
Entry point of the headless (electron-free) module |
requiresElectron |
A headless core skips this extension at scan unless server is also declared |
compat.pluginApi |
Plugin-API range checked against the host at registration; a mismatch refuses activation |
Plugin API compatibility
Section titled “Plugin API compatibility”The host checks compat.pluginApi when registering an extension. A missing
range is permissive; an incompatible range refuses activation.
The host’s current plugin-API version is 3.0.0. A range with syntax the host
cannot parse falls back to exact string comparison against that version, so a
typo blocks activation rather than matching everything.
Permissions
Section titled “Permissions”permissions is an array whose entries are either a string from the closed set
below, or one of the two structured forms.
Permissions gate named WAMP host APIs only. They do not sandbox the package: Node entry points retain ambient Node access, and UI code runs in the host renderer’s page realm. Installing an extension means trusting its code.
| Permission | What declaring it does |
|---|---|
http-routes |
Adds ctx.api.http |
notifications |
Adds ctx.api.notifications |
process |
Adds ctx.api.process, ctx.api.pty, and ctx.api.git |
auth.identity |
Adds ctx.api.auth; fetch additionally requires auth.outbound |
ai |
Model-call disclosure; also required for an ACP runtime declaring modelAccess: "host" |
network |
Disclosure only |
terminal |
Disclosure only |
filesystem |
Disclosure only — ctx.api.fs is present either way |
The structured forms, which may share one object:
{ "permissions": [ { "auth.outbound": ["https://api.example.com"], "auth.resource": [{ "audience": "internal-docs", "baseUrl": "https://docs.example.com/api/v1" }] } ]}auth.outbound takes an array of absolute URLs — each entry must parse as a
URL or the manifest fails validation. It declares the origins
ctx.api.auth.fetch may target; other origins are refused at the host proxy.
auth.resource takes one to eight { audience, baseUrl } entries, each
audience at most once. audience matches ^[a-z][a-z0-9-]{1,127}$; baseUrl
is a canonical HTTPS URL with a path and no credentials, query, fragment or
trailing slash. It declares the connected resources
pluginAPI.auth.resourceFetch may read as the viewer; see
Read your service from extension UI.
The object and each auth.resource entry are strict: any other key fails
validation.
The local typed store (ctx.api.data) is present with no permission at all. See
Permissions for the enforcement model and
Plugin API reference for the per-namespace table.
Capabilities
Section titled “Capabilities”capabilities is a separate list, unrelated to permissions. Four strings are
recognized:
| Capability | Gates |
|---|---|
webviews.navigate |
Navigation control on a webview handle |
webviews.executeScript |
Script injection into a webview |
webviews.interceptRequests |
Per-request inspection and rewriting |
webRequestRewrite |
Header rewriting declared via the webRequestRewrite field |
Declaring any of the three webviews.* values also selects the
WebContentsView backing for ctx.api.webviews; without one of them the
webview uses the sandboxed-iframe backing.
The allowlist is informational. An unrecognized string validates and then matches nothing the host gates on, so a misspelling produces a capability that is silently never granted.
Activation events
Section titled “Activation events”Every entry must match one of six forms. The literal * is rejected.
| Form | Fires when |
|---|---|
onStartupFinished |
The initial extension scan completes |
onCommand:<id> |
A contributed command with that id is invoked |
onAgent:<id> |
An agent with that id is about to run |
onTool:<name> |
A tool with that name is about to execute |
onMcpServer:<id> |
An MCP server with that id is starting |
onChatCommand:<command> |
That chat command is typed |
The id segment accepts [\w.:-]+. An absent or empty array is replaced with
["onStartupFinished"] for an extension with a selected Node entry (main, or
server on a headless host). Omitting it does not make that entry lazy.
webRequestRewrite
Section titled “webRequestRewrite”Default header rewriting applied to webviews this extension creates. All three fields are optional.
| Field | Type | Effect |
|---|---|---|
stripCSP |
boolean | Removes Content-Security-Policy response headers |
stripFrameOptions |
boolean | Removes X-Frame-Options response headers |
allowlist |
string[] | Restricts the rewrite to these hosts |
Using it requires the webRequestRewrite capability.
Per-path loader overrides for wamp ext dev and wamp ext pack, so a modest
asset need stays on the standard public builder. The build object and each
rule are strict.
| Field | Type | Required | Constraint |
|---|---|---|---|
loaders |
array | no | — |
loaders[].match |
string | yes | non-empty glob, relative to the extension root |
loaders[].loader |
enum | yes | text | json | base64 | dataurl | binary |
{ "build": { "loaders": [{ "match": "presets/**.svg", "loader": "text" }] } }contributes
Section titled “contributes”Every contribution key is optional. The contributes object itself is
permissive; strictness varies per contribution type and is tabulated in
Strict and permissive objects.
| Key | Type |
|---|---|
pages |
array |
views |
array |
commands |
array |
skills |
boolean, string, or string[] |
services |
array |
settings |
array |
toolMetadata |
array |
mcpServers |
array |
agentRuntimes |
array |
ipcNamespaces |
string[] |
contributes.pages
Section titled “contributes.pages”A page is a top-level workspace surface plus a sidebar entry. Strict — an unknown key fails the manifest.
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
id |
string | yes | — | non-empty; the bundle’s views[<id>] export is auto-registered into workspace.main with type: <id> |
title |
string | yes | — | non-empty; the sidebar label |
icon |
string | no | — | lucide-react export name |
context |
enum | no | both |
project (needs an open workspace) | both |
presentation |
enum | no | docked |
docked | app |
placement |
enum | no | top |
docked pages only: top | footer |
context is normalized to both at parse time, so every consumer reads a
concrete value. presentation: 'app' hides the WAMP chrome and lets the page
render its own via AppShell; placement: 'footer' puts the sidebar row in the
fixed bottom group instead of the reorderable list. See
Pages and windows.
Do not also declare a views entry for the page’s own surface — the page id
already registers one.
contributes.views
Section titled “contributes.views”A component mounted into a named slot. Use this only for slots other than the
page’s own workspace.main surface.
| Field | Type | Required | Notes |
|---|---|---|---|
id |
string | yes | non-empty; must match a key of the bundle’s views export |
slot |
enum | yes | one of the five slots below |
type |
string | no | discriminator within workspace.main |
title |
string | no | label where the slot shows one |
icon |
string | no | lucide-react export name |
Every ViewSlot value, the props its component receives, and whether a host
renders it today:
| Slot | Props | Renders today |
|---|---|---|
workspace.main |
{ paneId: string; type: string } |
yes — the main workspace pane |
session.dock |
SessionDockContext (below) |
yes — the session dock strip |
chat.newSessionControls |
{ workspace; activeWorkspacePath; draftGeneration; setSubmissionPending(pending) } |
yes — one compact control beside the local new-chat run-destination pill. To add a place a chat can run, publish destinations with host.chat.setSessionDestinations instead of drawing a control |
chat.composerStatus |
{ chatId; visible; workspace; activeWorkspacePath; isStreaming; onInsert(text) } |
yes — status and next action above the active chat composer |
chatInput.attachments |
{ draftText: string; onInsert(text): void; onAttachmentRemoved(id): void } |
yes — below the chat composer |
Publishing validates the slot strictly, so a typo is refused at upload. An INSTALLED manifest is treated differently: a view whose slot this build no longer knows is dropped at load and named in the log, and everything else the extension contributes still loads. An extension published against a slot that is later retired therefore keeps working without that one view.
SessionDockContext, the props a session.dock tool receives:
| Field | Type | Notes |
|---|---|---|
instanceId |
string | undefined |
Present for a rendered instance; absent for a hidden controller that publishes tabs |
restoredTabIds |
readonly string[] | undefined |
Saved provider-local tab references; restore lazily, without starting resources |
visible |
boolean | undefined |
Hidden instances retain state and should suspend unnecessary work |
workspace |
SessionWorkspaceSource |
Discriminated engine or Desktop Cloud authority; described below |
sessionId |
string | null |
null on the new-session surface, before a record exists |
scopeId |
string | Opaque shell-owned dock-layout identity for transient UI state; never workspace authority |
context |
SessionContext | undefined |
undefined until a project is chosen |
hydrated |
boolean | false while the workspace is still loading |
activeTabId |
string | null |
This instance’s provider-local resource tab; null means render a default surface. Controllers also receive null |
requestFocus |
(tabId?: string) => void |
brings your tool to the front of the dock strip |
SessionContext is { kind: 'dir'; path: string; coreUrl?: string; worktree?: string }.
SessionWorkspaceSource has two authority branches:
engine:{ authority: 'engine', context, hydrated }. The host can addresscontext.paththrough its engine. The Cloud browser also uses this branch because its engine is the sandbox.cloud:{ authority: 'cloud', organizationId, sessionId, state }. Desktop brokers the Cloud session’s files; the private sandbox root is not a local filesystem path.stateisconnecting,ready,reconnecting,paused,read-only,unavailable, oraccess-lost.pausedis the normal resting state of a finished session whose sandbox was released and can resume, so present it neutrally;unavailablemeans the workspace cannot come back. Before a session exists,sessionIdis null,stateisunavailable, andorganizationIdmay also be null.
Use workspace.authority to select the supported file path. The older
context and hydrated props are only the engine projection. A dock view that
supports engine files alone should render an explicit unavailable state for
Desktop Cloud authority. Lifecycle example.
contributes.commands
Section titled “contributes.commands”Palette entries. Strict.
| Field | Type | Required | Notes |
|---|---|---|---|
id |
string | yes | non-empty |
title |
string | yes | non-empty; the palette label |
keybinding |
string | no | e.g. "mod+shift+k" |
category |
string | no | palette grouping |
scope |
string | no | non-empty. focused-view | focused-slot | global, or a custom string |
Declaring a command does not give it behavior — ctx.api.commands.register
supplies the handler, and nothing registers on your behalf. The declaration is
what the marketplace listing shows and what the contract check holds your
activation to; a declared command your activation never registers never reaches
the palette, and the contract check fails the build naming it.
contributes.skills
Section titled “contributes.skills”| Value | Meaning |
|---|---|
true |
Scan <extension root>/skills |
false |
Register nothing |
"some/dir" |
Load a relative skill root; absolute paths and escapes are rejected |
["a", "b"] |
Load each relative skill root; results accumulate under one extension id |
The string forms name directories, not individual files. A root containing
SKILL.md is one skill; "." names the extension root. Otherwise, its immediate
child directories containing SKILL.md form a collection. A loose <name>.md
file is not a skill package. A skills/ directory without this declaration
loads nothing. Skill directories and manifests must stay inside the extension;
symlinks escaping it are rejected.
wamp ext pack currently copies skill packages from skills/ only. Use
"skills" or true for a packed WAMP extension; custom roots work in a
directly loaded extension directory.
contributes.services
Section titled “contributes.services”Declares service ids this extension intends to register. Permissive.
| Field | Type | Required | Notes |
|---|---|---|---|
id |
string | yes | non-empty; the id other extensions pass to services.require |
interface |
string | no | documentation only |
The declaration is bookkeeping; ctx.api.services.register does the actual
registering. Consumers read a registered service with ctx.api.services.require
(throws if the id isn’t registered yet) or subscribe with
ctx.api.services.onRegister to wait for it.
contributes.settings
Section titled “contributes.settings”User-editable settings surfaced in the host’s settings UI. Strict.
| Field | Type | Required | Notes |
|---|---|---|---|
key |
string | yes | ^[a-zA-Z][a-zA-Z0-9._-]*$ |
title |
string | yes | non-empty |
description |
string | no | helper text |
kind |
enum | yes | boolean | string | number | enum |
default |
any | no | returned by ctx.api.settings.get until the user stores an override |
enum |
string[] | no | the choice list for kind: 'enum' |
Reads and writes go through ctx.api.settings, persisted in the extension’s
storage under the key prefix settings:.
contributes.toolMetadata
Section titled “contributes.toolMetadata”Display and behavior hints for tools, keyed by tool name. Strict.
| Field | Type | Required | Notes |
|---|---|---|---|
id |
string | yes | non-empty; the tool name |
icon |
string | no | lucide-react export name |
label |
string | no | display label on the tool card |
category |
string | no | grouping |
persistenceQuota |
number | no | >= 0 |
compactable |
boolean | no | the result may be dropped during compaction |
longRunning |
boolean | no | relaxes execution timeouts |
readOnly |
boolean | no | the tool makes no changes |
contributes.mcpServers
Section titled “contributes.mcpServers”Each entry registers a runnable MCP server under <extensionId>_<serverId>.
Permissive, but with one cross-field rule: transport: 'http' or 'sse'
requires url; anything else requires command. Violating it fails the
manifest with the message “stdio MCP servers require command; http/sse MCP
servers require url.”
Declare an npm-packaged server as "command": "npx", "args": ["-y", "<name>@<exact version>", …]. WAMP installs that exact pin once into its own
package store and then runs the package’s bin directly, so later starts need
neither npm nor the network. A range, a tag or any other launcher is run
exactly as declared.
A server the extension ships itself names its files with a value that starts
with ${extensionDir}/, in command, args or an envVars default — the rule
contributes.agentRuntimes uses. WAMP replaces it with the installed path when
the extension loads:
{ "id": "words", "command": "node", "args": ["${extensionDir}/runtime/server.mjs"] }Keep the server under runtime/, which wamp ext pack ships as-is; every
build replaces dist/. The token must start the value and name a path inside
the extension. wamp ext pack refuses any other use of it, and a path that is
not in the archive. The server’s working directory is the open project, not the
extension, so it finds its other files from its own path (import.meta.url).
Other ${NAME} and ${NAME:-default} references in command and args
expand when the server starts, from its envVars values and then the engine’s
environment.
| Field | Type | Required | Notes |
|---|---|---|---|
id |
string | yes | non-empty |
transport |
enum | no | stdio | http | sse. Absent means stdio |
command |
string | no | required for stdio; non-empty |
args |
string[] | no | stdio only |
envVars |
array | no | surfaced in Settings so users can edit credentials |
envVars[].key |
string | yes | non-empty |
envVars[].description |
string | no | — |
envVars[].required |
boolean | no | — |
envVars[].default |
string | no | — |
url |
string | no | required for http/sse; must parse as a URL |
headers |
Record<string,string> |
no | http/sse only |
oauth.clientId |
string | no | non-empty; skips dynamic registration |
oauth.flow |
"device_code" |
no | requires oauth.clientId; absent keeps authorization code + PKCE |
wampAuth.audience |
string | no | WAMP-managed identity for a registered resource slug; http/sse over HTTPS only |
Prefer http (Streamable HTTP) over sse; sse exists because several hosted
servers still ship SSE-only endpoints. OAuth flows are run by the host — the
extension never sees tokens. A pre-registered public client may select device
authorization with { "clientId": "…", "flow": "device_code" }; the bundled
GitHub MCP connector uses this path. wampAuth is mutually exclusive with
oauth and a static Authorization header. The host requires the URL origin to
match the origin registered for that audience; see
Connected resources.
contributes.agentRuntimes
Section titled “contributes.agentRuntimes”An external process that can answer a chat turn — the agent analogue of an MCP
server. Strict at every level, including the nested home, usage, limits,
and identity objects. command, args, env and a terminal login’s
command and args name a file the extension ships the way an MCP server does:
a value starting ${extensionDir}/.
| Field | Type | Required | Notes |
|---|---|---|---|
id |
string | yes | ^[a-z][a-z0-9-]*$; stored on the chat |
label |
string | yes | non-empty |
transport |
literal | yes | "acp" — the only accepted value |
command |
string | yes | non-empty; executable speaking the protocol on stdio |
args |
string[] | no | — |
env |
Record<string,string> |
no | layered over the inherited environment |
icon |
string | no | brand-icon registry key |
requires |
string | no | the underlying CLI, probed to report “not installed” |
modelAccess |
literal | no | "host" requests the host’s metered model plane; requires the extension’s ai permission |
steering.method |
string | yes within steering |
^_[a-zA-Z0-9][a-zA-Z0-9._/-]*$; private ACP request for mid-turn delivery |
steering.idleBehavior |
literal | yes within steering |
"promptRequired"; an idle request must reject without consuming its content |
sessionFork.point |
literal | yes within sessionFork |
"jetbrains.air.v1"; qualified exact-message native forks, requiring live fork and resume/load capabilities |
tools |
string[] | no | WAMP tools lent over MCP. Omitted lends only present_files; [] lends nothing |
authMethods |
array | no | min 1 entries; ACP method ids or declarative headless login commands, in order. Omitted means every ACP method |
home.env |
string | yes within home |
non-empty; env var that repoints the agent at a WAMP-owned directory |
home.credentials |
string[] | yes within home |
min 1; files inside that directory carrying the login |
home.defaultDir |
string | no | inherited machine login location when home.env is unset; read-only |
home.continuationRoots |
string[] | no | non-empty safe relative POSIX directories eligible for exact native-session resume |
skills.handover |
string | string[] | yes within skills |
"directory", "plugin-root", "agents-root", distinct, tried in order |
skills.directory |
string | yes when handover includes "directory" |
safe relative POSIX directory inside the managed home that receives the skill-set link |
mcpServers |
string[] | no | non-empty namespaced server ids; WAMP starts a private copy for each ACP session and offers the ones that start within 30 s |
usage.kind |
"http" | "service" |
yes | selects the usage provider |
usage.url |
string | yes for HTTP usage | must parse as a URL |
usage.credential |
string | yes for HTTP usage | non-empty; file in the home holding the token |
usage.tokenPath |
string | yes for HTTP usage | non-empty; dotted path to the token in that file |
usage.headers |
Record<string,string> |
no | HTTP form only |
usage.windows |
array | yes for HTTP usage | min 1 of { label, path }, both non-empty; dotted paths to { utilization, resets_at } |
usage.service |
string | yes for service usage | extension-owned service id implementing AgentRuntimeUsageProvider |
limits.patterns |
string[] | yes within limits |
non-empty quota-exhaustion patterns; case-insensitive |
limits.transient |
string[] | no | non-empty throttle patterns; report without benching or rotating the account |
limits.terminalAuth |
array | no | exact structured authentication failures: { code, data?: { path, equals } }; code is integer and equals is string, finite number, boolean, or null |
identity.kind |
"declarative" | "service" |
no for declarative; yes for service | omitted remains the backward-compatible declarative form |
identity.credential |
string | yes for declarative identity | non-empty |
identity.path |
string | no | dotted path to the identity inside credential |
identity.claim |
string | no | JWT claim name, when the value at path is a JWT |
identity.url |
string | no | must parse as a URL — the endpoint mode |
identity.tokenPath |
string | no | dotted path to the bearer, for endpoint mode |
identity.headers |
Record<string,string> |
no | — |
identity.paths |
string[] | no | min 1; response paths tried in order |
identity.service |
string | yes for service identity | extension-owned service id implementing AgentRuntimeIdentityProvider |
steering requires both this declaration and the agent’s initialize response
_meta.steering.supported; a declaration alone does not enable delivery.
sessionFork is optional. Declare it only after verifying that native forks
preserve the exact selected prefix, including compacted context. WAMP retains
private message boundaries and loads the fork before its first prompt; it does
not clone vendor transcript files. Unsupported adapters and older conversations
without those boundaries continue through retained portable history.
modelAccess: "host" lets the host choose the model endpoint and credential;
you cannot override those through env.
args[], env values, and object authMethods[].args[] may contain a whole
${npmPackage:<name>@<x.y.z>} value, for example
${npmPackage:@agentclientprotocol/claude-agent-acp@0.84.0}. The version must be
exact, without build metadata. The engine replaces the value with an absolute,
read-only package directory before preparing credentials or starting the
process. It installs runtime packages without lifecycle scripts, so an adapter
must use shipped files as-is and write derived data under its own runtime cache.
Commands and substrings cannot contain this placeholder. A pack using it must
declare compat.pluginApi: ">=4.0.0 <5.0.0"; older hosts refuse the pack.
skills hands the user’s WAMP skill set to the agent. WAMP stages one
content-addressed copy of the set in its own store and hands it over through
the first handover kind whose precondition holds. "directory" links the
staged set at skills.directory inside a WAMP-managed home and therefore
requires home; it never writes into the machine’s own vendor login.
"plugin-root" passes the set as a local plugin directory for one session, in
both adapter fields that accept one: Claude Code’s
_meta.claudeCode.options.plugins and Grok Build’s _meta.pluginDirs. "agents-root" passes it as an extra
.agents/skills root through _meta["dev.wamp/skillRoots"], which the Codex
adapter accepts without widening its sandbox. When no kind applies, the runtime
reports the omission instead of copying skills anywhere. Declare handover
explicitly; a directory value also requires a managed home.
mcpServers selects
configured MCP servers independently of tools, which lends the WAMP tool
registry. A selected server is offered when it starts. One that does not start
within 30 s is left out of that session, the transcript says why, and the turn
runs without it.
Each authMethods entry is either an ACP method id string or a strict object
{ id, name, description?, command, args?, input? }. Use the object only when
the ACP agent exposes a local-browser login but the same vendor CLI provides an
official remote-safe command. WAMP runs it in the managed home; set
input: false for device-code commands that poll after printing their URL/code.
usage and identity are only meaningful alongside home, which supplies the
managed credential layout and optional inherited defaultDir. The HTTP usage
form covers one bearer GET; declarative identity covers JSON, JWT, and one
bearer GET. For dynamic headers, RPCs, fallback endpoints, vendor payloads, or
dynamic auth-file layouts, register an extension service and declare
{ "kind": "service", "service": "…" }. The service must belong to the same
extension and implement AgentRuntimeUsageProvider or
AgentRuntimeIdentityProvider. It receives the credential home currently used
by the runtime plus an abort signal and must remain read-only. WAMP owns service
ownership checks, timeout, normalization, caching, and persistence of the
credential-free result. An inherited machine login is read in place; it is
never copied into WAMP or added to account rotation by a metadata probe.
contributes.ipcNamespaces
Section titled “contributes.ipcNamespaces”Namespace prefixes this extension claims for its own IPC channels.
| Constraint | Value |
|---|---|
| Type | string[] |
| Each entry | ^[a-z][a-z0-9._-]*$ |
Claims happen at registration, before first use. A channel whose prefix is not
declared is refused by ctx.api.ipc.broadcast, synchronously. Two extensions
claiming the same prefix is a collision: the second faults and does not load.
Prefix with your extension id.
Strict and permissive objects
Section titled “Strict and permissive objects”Two failure modes, and which one you get depends on where the typo is. A strict object rejects unknown keys and names the offending field; a permissive one accepts and silently ignores them.
| Object | Unknown keys |
|---|---|
| Manifest root | ignored |
contributes |
ignored |
contributes.pages[] |
rejected |
contributes.commands[] |
rejected |
contributes.settings[] |
rejected |
contributes.toolMetadata[] |
rejected |
contributes.agentRuntimes[] and its nested objects |
rejected |
build and build.loaders[] |
rejected |
{ "auth.outbound": [...], "auth.resource": [...] } and each auth.resource entry |
rejected |
contributes.services[] |
ignored |
contributes.views[] |
ignored |
contributes.mcpServers[] |
ignored |
A rejected manifest is reported as a failed scan with the field path, and the extension does not appear. An ignored key costs you a debugging session, so when something you declared has no effect, check the spelling of the key one level up first.
A complete manifest
Section titled “A complete manifest”Every optional field is left out except the ones this extension needs. It has a
page, a palette command, and a tool contributed at runtime from main.
{ "name": "Release Watch", "description": "Lists release feeds and files a task on refresh.", "version": "1.4.0", "compat": { "pluginApi": "^4.0.0" }, "icon": "Radar", "author": "Example Corp", "main": "dist/main.js", "permissions": ["notifications"], "activationEvents": ["onStartupFinished"], "contributes": { "pages": [ { "id": "release-watch", "title": "Releases", "icon": "Radar", "context": "both" } ], "commands": [ { "id": "release-watch.refresh", "title": "Releases: Refresh now", "keybinding": "mod+shift+r" } ], "toolMetadata": [ { "id": "release_watch_check", "label": "Check releases", "readOnly": true } ], "ipcNamespaces": ["release-watch"] }}Related
Section titled “Related”- The manifest — which fields to set and why.
- Permissions — what each declaration actually unlocks.
- Plugin API reference — the namespaces each permission adds.
- Troubleshooting — manifest mistakes that fail quietly.