Navigace
ELOWEN · Dokumentace

Poslední aktualizace: 20. října 2026

Procházet dokumentaci · Manifest identity and contributions

Tento článek zatím není přeložen. Je dostupný v angličtině.

Developer reference

Manifest identity and contributions

Manifest

The manifest file is named elowen-plugin.json. The required fields are name, version, apiVersion, description, and entry.

This example shows the common manifest vocabulary. Fields it leaves out are described in the sections below or on the page for their feature, for example service in Independent services, liveConfig and operationDock in the seam catalog, and alerts in Plugin settings and internationalization.

{
  "name": "my-plugin",
  "version": "1.0.0",
  "apiVersion": "2",
  "requiresCore": "0.30.4",
  "requiresSharedApi": 14,
  "label": "My plugin",
  "description": "Adds a small example tool.",
  "entry": "index.mjs",
  "provides": {
    "tools": ["MyTool"],
    "readOnlyTools": ["MyTool"],
    "skills": ["my-skill"],
    "platforms": ["my-platform"],
    "destinations": ["my-platform"],
    "httpRoutes": ["callback"],
    "apiRoutes": ["status"],
    "wsRoutes": ["stream"],
    "controls": ["my-domain"]
  },
  "consumesControls": ["shared-domain"],
  "icons": {
    "MyTool": "🔧"
  },
  "showOutput": ["MyTool"],
  "deferLoading": ["LargeTool", "Mcp*"],
  "icon": "icon.svg",
  "configSchema": [],
  "retiredConfigKeys": ["obsoleteSetting"],
  "userConfigSchema": [],
  "userConfigLabel": "My account",
  "userConfigPlacement": "account",
  "userGrantable": false,
  "capabilities": {
    "reads": ["db"],
    "mutates": ["events"],
    "network": true
  },
  "requiresControls": ["shared-domain"],
  "web": {
    "entry": "web/index.js",
    "css": "web/index.css",
    "requiresApiVersion": 17,
    "adminOnly": false,
    "label": "My plugin",
    "layout": "document",
    "nav": [
      { "label": "My plugin", "icon": "Puzzle", "route": "" }
    ],
    "account": [
      { "id": "connection", "label": "Connection", "icon": "Link" }
    ],
    "user": [
      { "id": "details", "label": "Details", "icon": "User" }
    ],
    "project": [
      { "id": "status", "label": "Status", "icon": "Folder" }
    ],
    "projectRows": true,
    "dashboardMetrics": true,
    "chatPickers": true,
    "chatCards": true,
    "chatRailSections": true,
    "chatDock": true,
    "historyBranches": true,
    "settings": [
      {
        "id": "settings",
        "label": "Settings",
        "icon": "Settings",
        "placement": "page"
      }
    ],
    "strings": {
      "title": "My plugin"
    }
  }
}

Identity and compatibility fields

  • name is the technical id. It must equal the directory name and keys configuration, controls, API mounts, data directories, and stored grants. Renaming it is a migration.
  • version is the plugin release version. It is also part of loader cache busting and marketplace update detection.
  • apiVersion is the breaking plugin API contract. The current value is "2" and must match exactly.
  • requiresCore is an optional minimum Elowen version. The curated registry installer rejects a plugin that needs a newer core before copying it into the instance plugin directory. It is a minimum version, not an exact match. The check judges the core the plugin will run on: the daemon's own version for a manual install, the transaction's target (the release being installed, else the running version) for an automatic update. A planned automatic update whose requirement exceeds that target is deferred with a needs-newer-core skip report and advances on a later run once the core is new enough.
  • requiresSharedApi is an optional positive integer for the elowen-plugin-shared contract. It must match the host exactly. The host checks it before importing the entry, because an incompatible shared package can fail during module linking. Omit it when the plugin does not import that package. The current contract is API 14; the seam catalog names the subpaths and helpers it carries. Platform adapters use ctx.host.voice() with an explicit incoming-message SessionSource; core owns transcription, speech preparation, credentials and billing. The host checks the linked account, voice grant, enabled Voice mode and spend limit. Declare capabilities.reads: ['voice']; see the host voice row in the seam catalog for its refusal contract. Answer text is preserved and reasoning uses its separate API channel. applyVisionModel(access, vision) accepts only those two arguments. Consumers declare API 14 and require the core artifact shipping it. The exact-match manifest gate refuses older consumers before entry import; update the core and registry consumers together.
  • label is the human-facing plugin name. If it is absent, surfaces use name.
  • description is the plugin description shown in management surfaces.
  • entry is the relative path to the ESM module exporting register(ctx). It must stay inside the plugin directory.

Declared contributions

The optional provides object contains these arrays:

  • tools: tool names the plugin registers.
  • skills: skill names the plugin contributes.
  • platforms: platform adapter names.
  • destinations: proactive notification destination providers.
  • httpRoutes: public webhook paths.
  • apiRoutes: complete authenticated API patterns, or full absolute patterns for a rootMount. Both allow lowercase literal segments and :name parameters. Undeclared suffixes never reach a handler.
  • wsRoutes: WebSocket paths.
  • controls: control keys published for other plugins.
  • readOnlyTools: exact plugin tool names that only read. This is the one read-only declaration: plan mode admits these tools, read-only delegated sub-agents (explore, plan, review, read_only: true) receive them, and a sub-agent continued from a plan-mode turn may run them. Declare a tool only when it changes nothing outside the agent's own working state; a conversation's own task list counts as working state. Each name must also appear in provides.tools; an undeclared name is ignored with a plugin.tool_metadata_invalid warning (setReadOnlyTools in src/plugins/registry.ts). A declared tool the plugin leaves unregistered, for example while it is not configured yet, is skipped silently. mcp__* is the only pattern accepted. It must also appear in provides.tools and selects this plugin's tools that carry external registration metadata; read-only sub-agents receive those, but plan mode never admits an external tool, because such a declaration vouches for a remote server's whole tool list, write tools included (planModeToolNames in src/brain/toolMetadata.ts).
  • desktop: true when the plugin registers a desktop provider through registerDesktopProvider (src/plugins/manifest.ts, src/plugins/api.ts). Registration also requires the desktop API paths in provides.apiRoutes and the desktop/vnc WebSocket declaration.

consumesControls is not a provides key. It is a top-level manifest field listing the exact control keys this plugin is authorized to resolve through ctx.control (src/plugins/manifest.ts, src/plugins/loader.ts); it also needs capabilities.reads: ['controls']. This is distinct from requiresControls, which is an enablement dependency.

The runtime registration methods for platforms, destinations, public routes, authenticated routes, WebSockets, and MCP tools are deny-by-default. Their names or paths must be declared in the matching provides list. Controls must be declared as well. There is no provides.hooks field. Hooks are registered at runtime and their mutations are controlled by capabilities.mutates.

Tool display metadata

  • icons maps tool names or patterns to display icons.
  • showOutput lists exact names or prefix* patterns whose successful output is shown in the transcript. Failures remain visible even when a tool is omitted.
  • deferLoading lists exact names or prefix* patterns deferred by default, even when the session holds too few tools for the automatic threshold. A pattern expands only to tools registered by the same plugin. A pattern that matches no name in provides.tools is ignored with a plugin.tool_metadata_invalid warning; a declared tool the plugin leaves unregistered while it is not configured is skipped silently. See "Tool deferral and the core set" below.
  • There is no registration option that keeps a tool out of deferral: only the locked core set does.
  • external registration metadata identifies externally authored tool schemas, independently of tool names. It controls schema bounds, status-note schema augmentation and redacted cache diagnostic labels, not execution grants.
  • icon is an optional SVG path relative to the plugin directory. When omitted, the host looks for icon.svg.

Tool names become durable permission and event data. Choose stable names before publishing. Renaming a tool requires a coordinated migration of stored rules and references.

Grants and capabilities

Set userGrantable: true only when the plugin must be granted separately to accounts. It is deny-by-default for every account, including administrators, until an administrator grants it in Users. Grants filter the plugin's tools, skills, routes, and browser UI. An administrator can edit their own grant; with no grant the plugin remains unavailable, even to an administrator.

Grants do not filter prompt fragments, platform prompts, prompt commands, or hooks. Those reach every session, and the loader warns when a grantable plugin registers any of them (src/plugins/loader.ts:458-468). A grantable plugin must not put account-sensitive behavior in those surfaces. Plugin slash commands are different: the command listing and the picker route check the grant (src/api/routes/brainChat.ts:366, src/api/routes/brainChat.ts:392).

tests/plugins/grantableContributions.test.ts pins the bundled grantable set to Terminal and loads it through the authoritative registry with an in-memory plugin database. Successful registration is proven by Terminal's exact four tools and control, together with the empty global prompt, slash-command and hook assertions; a skipped plugin or unknown grantable plugin cannot satisfy the registration proof. Services stop before the fixture database closes.

capabilities is deny-by-default:

{
  "reads": ["db", "controls", "providers", "alerts"],
  "mutates": ["prompt", "turnContext", "tools", "events", "workflow-dag", "users", "conversations"],
  "network": true
}

src/plugins/capabilities.ts owns PLUGIN_MUTATIONS and the TypeBox PluginCapabilitiesSchema; PluginCapabilities is inferred from that schema. The manifest validator uses it directly. The public plugin API imports the same type and retains its separately ordered CONSENT_REQUIRED_MUTATES subset: a vocabulary declaration does not itself grant operator consent. Absent capabilities still permit no mutation; reads remain extensible strings. Validation scans only the declared arrays and adds no policy lookup or I/O.

mutates accepts prompt, turnContext, tools, events, workflow-dag, users, and conversations. The retired memory value is no longer accepted: it never granted anything, no bundled, registry, installed or archived manifest declares it, and a manifest that still names it fails validation like any other unknown value.

A plugin declaring prompt, tool, event, workflow-DAG, user, or conversation mutation requires explicit acknowledgement from the operator. That acknowledgement is STORED per plugin rather than attached to the request that gave it, so a version whose claims are all inside the stored set is never asked about again. The doors that judge a candidate before it takes a place on disk are the install, enable and marketplace-update routes, which answer 409 grants require consent with the whole consent-required set the plugin now declares — including grants the operator approved for an earlier version. Install and update pin that gated artifact in the request queue. The coordinator rechecks stored consent when staging, including boot-requested repair of an enabled receipted plugin that is missing or unloaded. That request has no caller to ask, so a refusal is reported as needs-consent, not installed. A plugin that grows one of these capabilities in a later version therefore needs the operator to agree again before the new version can land, and no refusal clears the enabled flag. A manifest already on disk, loaded as usual at startup, is not re-judged. turnContext is the one mutation exempt from this acknowledgement because it reaches only one turn.

reads gates host reads such as db, controls, providers, alerts, prompts, stores, git, project-files, conversation-files, inference, push, usage, voice, elowen-cli, and agent-files (Subagent only). Declare only what the implementation uses. network: true declares network intent and enables the validated public HTTP transport. It is not permission to use an arbitrary client or to trust remote data.

Browser metadata

At plugin load, src/plugins/pluginWebUi.ts prepares bundle and stylesheet paths, content hashes and browser metadata without mutating the live registry. The loader publishes staged tools, hooks, alert strings and browser metadata only after registration and asset preparation succeed. An escaping path or a read failure on an existing asset skips the plugin and discards its staged contributions; siblings still load. This boundary covers registry publication, not side effects performed by the plugin's registration code.

For example, MCP declares web.entry: "web/index.js" and web.css: "web/index.css". Preparation reads each present asset to initialize its independent content hash and byte snapshot. PluginWebUi.readBundle() and optional readCss() are the single source for both the URLs advertised by GET /plugins/ui and the bytes served by GET /plugins/:name/web/:file. Each call stats the file; only a changed mtime or size causes a new read and SHA-256 hash. The cache retains one byte snapshot per asset for the lifetime of its registry entry. A stale hash returns 404, and current URLs cache immutably for a year. Rebuild plugin JS or CSS and reload the page to fetch the new listing and content-hash URLs without restarting the daemon. No web declaration means no asset reads or browser metadata. A missing bundle logs a warning and omits the UI while retaining server contributions and the declared admin-only policy. A missing stylesheet logs a warning and retains the UI without plugin CSS. Neither absence rejects the plugin.

The optional web object is declarative metadata plus the bundle entry:

  • entry is the built browser ESM path.
  • css is an optional plugin-owned stylesheet.
  • requiresApiVersion is the browser runtime version required by the bundle.
  • adminOnly hides navigation and browser assets from non-admins.
  • label names the plugin's navigation world.
  • layout is document or workbench.
  • wordmark set to false closes the plugin's /p/<plugin> pages without the instance wordmark the host mounts under every other page (except /chat). Use it for a full-height application surface that owns its bottom edge, such as the editor with its panes and status bar. Absent, and on a host too old to know the key, the word closes the page as usual. It rides on the plugin UI listing, so the word never appears first and then vanishes.
  • presentation is page or overlay. overlay keeps the plugin's normal primary-navigation entries and /p/<plugin> address, but presents the page in the host's shared reading modal, with the same fullscreen phone mode and history-aware close behavior as Settings and Account. The plugin receives surface="deck" because the host owns the outer title and frame. A plugin that depends on this presentation must set requiresCore to the first core release that provides it.
  • nav lists main-navigation entries with label, optional icon, and optional route.
  • account, user, and project list panel metadata with id, label, and optional icon. Account panels also accept placement: "section" | "linkedAccount" and an optional integer order, the panel's place in the Account menu on the scale the core sections use (Account 100, Computers 260, Models 400, then 500, 600 and so on up to Terminal 1100; Sandbox's Desktops declares 250). Without order a panel takes 200, right after Profile, where plugin panels always sat. A non-integer order fails manifest validation. Equal orders draw core sections first, then plugin panels by plugin name and then panel id, so the menu is the same on every load. Cores before 0.29.118 ignore the field. Project panels accept placement: "tab" | "overview" | "overviewTop" (default tab): a tab is a text tab in the Project drawer's header, overview renders inside its Overview tab after the host's facts, and overviewTop at the top of Overview. The host draws each user panel as its own header tab of the User detail drawer, labelled with the panel's label (tabs carry no icons). The panel component is not mounted until its tab is chosen. A panel that wants a status mark registers a userStatus component under the same id (UI API 46, see the catalog row for web.user); the host draws it as the first line of the tab, right-aligned beside the panel's label. It is mounted with the tab, so it must fetch only what it shows. Without it the tab starts directly with the panel.
  • projectRows declares a project-register indicator contribution.
  • settings lists sections with id, label, optional icon, optional placement: "page" | "pluginDetail".
  • strings is a flat map of English view strings.
  • projectEditIcon, dashboardMetrics, chatPickers, chatCards, chatRailSections, chatDock and historyBranches are boolean flags. Each one tells the host to load the browser registration for that surface, and the seam catalog describes what each surface does.
  • operationDock takes { "desktopOnly": true } and declares that the plugin contributes items to the shared operation dock; desktopOnly: true keeps them off a phone. A plugin without the declaration gets no dock request.