Skip to content

Latest commit

 

History

History
240 lines (140 loc) · 20.6 KB

File metadata and controls

240 lines (140 loc) · 20.6 KB

Plugin manifest

Every field of protoagent.plugin.yaml — the file that makes a directory a plugin. Generated from the PluginManifest dataclass in graph/plugins/manifest.py, which is what the loader actually reads, so this list is complete by construction.

The manifest is declarative on purpose: it is parsed before the plugin's Python is ever imported, so the host can gate, configure, and display a plugin it has never run. That is what makes an install reviewable and a disabled plugin inert.

See Plugins for how to use these fields, and Install & publish plugins for the distribution ones.

At a glance

Field Type Default Meaning
id str required the plugin's slug — required, unique, namespaces everything it owns
name str required the human label the console shows — required
version str '0.0.0' semantic version string; drives update + pin resolution
description str '' one-line summary shown in the console's plugin list
enabled bool False enabled: true in the manifest is an author opt-in (for plugins you wrote/dropped in yourself)
builtin bool False builtin: true marks a plugin as core runtime infrastructure (e.g. the delegate registry): it ALWAYS loads — ignoring both the enable gate and the plugins.disabled list — and is hidden from the…
requires_env list[str] [] Env vars that must be set for the plugin to load — a HARD gate: a missing one skips the plugin with a logged reason rather than half-loading it
capabilities dict {} Declarative, for transparency in the console — not yet enforced
entrypoint str '' The module filename the loader imports to find register(registry)
config_section str '' the top-level YAML section the plugin claims (default: id)
config dict {} defaults for that section (key → default value)
secrets list[str] [] keys in the section routed to the secrets.yaml overlay
settings list[dict] [] Settings-schema field specs ({key, label, type, ...})
settings_tabs list[dict] [] Ordered Configure-dialog tabs (#3179/#3180)
test bool False Test action (ADR 0029) — when true, the plugin serves a credential check at POST /api/config/test-<config_section> (e.g. the chat_surface wirer mounts…
guide_url str '' Optional setup-guide URL (ADR 0059) — the console renders a generic "Setup guide" link next to the plugin's settings, so no per-plugin frontend is needed
views list[dict] [] Console surfaces (ADR 0026) — each entry adds a left-rail icon opening a full view (an iframe of a page the plugin serves at path)
commands list[dict] [] Palette commands (ADR 0057) — declarative command-palette entries, parsed and never imported, exactly like views
public_paths list[str] [] Auth-exempt paths — prefixes under THIS plugin's own /plugins/<id>/ (or /api/plugins/<id>/) namespace that the default-deny auth middleware lets through WITHOUT a bearer
federation_paths list[str] [] Federation-tier paths (#2747) — prefixes under THIS plugin's own namespace that accept the federation credential ([ADR…
emits list[str] [] topics this plugin broadcasts — its public event API
subscribes list[str] [] topics it listens for, declared so the wiring is visible to operators
emits_schemas dict[str, dict] {} Typed event contracts (#1636) — topic → {"summary": str, "schema": dict} for emits: entries that declared more than a bare name
requires_pip list[str] [] declared pip deps
optional_pip list[str] [] the optional tier (#1953) — specs from optional: true entries
pip_scopes dict[str, str] {} pkg name -> "host" | "runtime" (#2246)
repository str '' provenance, shown in the install review
homepage str '' provenance, shown in the install review
min_protoagent_version str '' compat guard — the loader refuses to load the plugin when the host is older than declared (malformed strings only warn and load)
supersedes list[str] [] The standalone repo(s) this BUNDLED plugin replaces — how an external plugin moves into core under the SAME id (so plugins.enabled, its config section and every archetype's enable list keep…
enables list[str] [] Other plugins this BUNDLED plugin turns on (#3450)

Fields

id {#field-id}

  • Type str · Default required

the plugin's slug — required, unique, namespaces everything it owns

name {#field-name}

  • Type str · Default required

the human label the console shows — required

version {#field-version}

  • Type str · Default '0.0.0'

semantic version string; drives update + pin resolution

description {#field-description}

  • Type str · Default ''

one-line summary shown in the console's plugin list

enabled {#field-enabled}

  • Type bool · Default False

enabled: true in the manifest is an author opt-in (for plugins you wrote/dropped in yourself). An operator can also enable by id via plugins.enabled in config. Either path counts as consent.

builtin {#field-builtin}

  • Type bool · Default False

builtin: true marks a plugin as core runtime infrastructure (e.g. the delegate registry): it ALWAYS loads — ignoring both the enable gate and the plugins.disabled list — and is hidden from the Plugins management list, since it isn't an optional add-on the operator toggles. Its config lives in the core Workspace settings, not the Plugins panel.

requires_env {#field-requires-env}

  • Type list[str] · Default []

Env vars that must be set for the plugin to load — a HARD gate: a missing one skips the plugin with a logged reason rather than half-loading it. Use it for what the plugin cannot function without; use settings[].required (ADR 0019) instead when the operator should be prompted in the console rather than blocked at boot.

capabilities {#field-capabilities}

  • Type dict · Default {}

Declarative, for transparency in the console — not yet enforced.

entrypoint {#field-entrypoint}

  • Type str · Default ''

The module filename the loader imports to find register(registry). Empty means the default search: __init__.py, then plugin.py.

config_section {#field-config-section}

  • Type str · Default ''

the top-level YAML section the plugin claims (default: id)

config {#field-config}

  • Type dict · Default {}

defaults for that section (key → default value)

secrets {#field-secrets}

  • Type list[str] · Default []

keys in the section routed to the secrets.yaml overlay

settings {#field-settings}

  • Type list[dict] · Default []

Settings-schema field specs ({key, label, type, ...})

settings_tabs {#field-settings-tabs}

  • Type list[dict] · Default []

Ordered Configure-dialog tabs (#3179/#3180). A schema-backed {id, label} descriptor is targeted by settings[].tab; a path-backed {id, label, path} descriptor embeds plugin-owned UI from /plugins/<id>/... through the sandboxed view bridge. One descriptor has one kind — settings cannot target a path-backed tab. Plugins that omit this keep the flat Configuration form.

test {#field-test}

  • Type bool · Default False

Test action (ADR 0029) — when true, the plugin serves a credential check at POST /api/config/test-<config_section> (e.g. the chat_surface wirer mounts one), and the console renders a generic "Test connection" button for the group. No console edit needed per plugin.

guide_url {#field-guide-url}

  • Type str · Default ''

Optional setup-guide URL (ADR 0059) — the console renders a generic "Setup guide" link next to the plugin's settings, so no per-plugin frontend is needed.

views {#field-views}

  • Type list[dict] · Default []

Console surfaces (ADR 0026) — each entry adds a left-rail icon opening a full view (an iframe of a page the plugin serves at path). Declared as data so it's known without importing the plugin, and surfaced to the frontend via /api/runtime/status. Each: {id, label, icon, path, tabs?, slot?, palette?}. path must (1) be a path a registered router actually serves — the console iframes it verbatim, so a path no router answers is a blank surface — and (2) be a same-origin RELATIVE path (no scheme/host/port): an absolute URL escapes the ADR 0042 fleet proxy origin and breaks the same-origin postMessage token handshake. See _parse_views (warns on non-same-origin) and docs/guides/building-react-plugin-views.md. palette (ADR 0057) opts the view into the command palette's INLINE morph, where picking its entry expands the view's iframe inside the palette body instead of navigating to its rail panel. Exactly two spellings are honored: the literal string inline (morph the same page path already renders), or a mapping {path: ...} naming a DIFFERENT page to morph — a tighter palette-sized editor beside the full rail panel. That mapping's page is auto-exempted from the auth gate like any other view page. Every view is already a "Go to …" palette entry without opting in, so palette is only ever about the inline morph. Any other value — true being the tempting wrong guess — is dropped with a warning and the view itself still loads, because the console ignores an unrecognized shape silently.

commands {#field-commands}

  • Type list[dict] · Default []

Palette commands (ADR 0057) — declarative command-palette entries, parsed and never imported, exactly like views. Each: {id, title, hint?, keywords?, icon?, group?, action?, provider?}, where action is what the entry DOES and provider makes it a live-search row that queries the plugin for results as the operator types. The console compiles both into behavior inside its own trusted adapter — plugin code never enters the bundle — so the vocabulary is closed and every field is validated at parse time: navigate and open_view take a view naming a view THIS manifest declares — navigate opens it on its rail/dock, open_view morphs it into the palette body and always ships inline: true (there is no non-inline open_view; navigate is that); tool takes a route under /api/plugins/<id>/ plus an optional method (absent ⇒ POST — a verb is never guessed); emit takes a topic published on the ADR 0039 bus and an optional data mapping (the payload POST /api/events/publish carries); command takes a command naming another entry in this same list. A provider makes the entry a live search: it takes the route the console queries as the operator types plus the result_action that running one of its result rows performs — required, because the rows are data and that declared action is the only thing the palette can run. Unlike a bad view path (which only blanks an iframe) a bad route becomes an AUTHENTICATED call carrying the operator bearer, so anything reaching outside the plugin's own namespace — an absolute or cross-origin route, a ../ escape (percent-encoded spellings unwrapped first), a route carrying a tab, newline or other control character (the URL parser DELETES those before it resolves the dot segments, so .<TAB>./.<TAB>./config validates clean and requests /api/config), a topic in another plugin's namespace, a view or command this manifest never declared — is DROPPED with a warning rather than kept. See _parse_commands. The console's trusted adapter (apps/web/src/app/pluginPaletteCommands.ts) is what turns a surviving entry into a palette row — grouped with the plugin's view rows and chipped with its name, in the console palette and the desktop launcher alike. It MIRRORS the route and topic namespace checks above rather than trusting the status payload, so an entry that fails the mirror is simply absent. Three console-side limits worth knowing while writing a manifest: navigate and open_view may only name a view the console mounts as a SURFACE — a rail, right-panel or bottom-dock view — because a slot: "chat" claimant renders under the core chat id and a utility widget is a bottom-left pill opening a dialog, so neither is reconciled onto a dock and neither has anywhere for a row to go (a command naming one is dropped with a warning, and the console drops it again on its own side); open_view needs its target view to have opted into the inline morph via views[].palette — something this parser cannot see, so a command naming a view that did not opt in opens it on its rail instead of morphing; and a provider is parsed and shipped but not compiled yet (ADR 0057 §8 leaves its per-query timeout/cancel + result-cap budget open), so an entry declaring only a provider contributes no row.

public_paths {#field-public-paths}

  • Type list[str] · Default []

Auth-exempt paths — prefixes under THIS plugin's own /plugins/<id>/ (or /api/plugins/<id>/) namespace that the default-deny auth middleware lets through WITHOUT a bearer. The escape hatch for an inbound webhook (no bearer — the plugin verifies its own signature) or a public view page that must load in a browser iframe under a token-gated deployment. Namespace-scoped by the parser so a plugin can never exempt a core route.

federation_paths {#field-federation-paths}

  • Type list[str] · Default []

Federation-tier paths (#2747) — prefixes under THIS plugin's own namespace that accept the federation credential (ADR 0066) where the /api operator ceiling would otherwise 403 it. NOT auth-exempt: a valid bearer is still required; only the tier ceiling is lowered. The seam for a deterministic plugin-owned RPC that a peer holding only the federation token must reach (a second device syncing a plugin-owned store) without being issued the operator bearer. Same namespace-scoping as public_paths — a plugin can never lower a core route.

emits {#field-emits}

  • Type list[str] · Default []

topics this plugin broadcasts — its public event API

subscribes {#field-subscribes}

  • Type list[str] · Default []

topics it listens for, declared so the wiring is visible to operators

emits_schemas {#field-emits-schemas}

  • Type dict[str, dict] · Default {}

Typed event contracts (#1636) — topic → {"summary": str, "schema": dict} for emits: entries that declared more than a bare name. emits above stays the names-only topic list (every entry, bare or typed), so existing consumers are untouched; this map carries the optional payload contract a cross-plugin consumer can discover instead of reverse-engineering the emitter. Purely declarative (like capabilities) — payloads are NOT validated at publish time. See _parse_emits.

requires_pip {#field-requires-pip}

  • Type list[str] · Default []

declared pip deps. NOT auto-installed (install ≠ code exec); the operator installs them explicitly. Missing → clear error on enable. An entry is a bare PEP 508 spec string (a HARD dep) or a mapping {pkg: "pillow>=10", optional: true} — see _parse_requires_pip.

optional_pip {#field-optional-pip}

  • Type list[str] · Default []

the optional tier (#1953) — specs from optional: true entries. The plugin runs without them (lazy import, graceful degradation), so the frozen-app gate (ADR 0058 D2) warns instead of refusing, and install-deps installs them best-effort.

pip_scopes {#field-pip-scopes}

  • Type dict[str, str] · Default {}

pkg name -> "host" | "runtime" (#2246). Which INTERPRETER has to be able to import the dep. runtime (the default, matching the compute-plugin pattern) means the managed Python runtime that serves execute_code children; host means this process. They have separate site-packages, so a dep the managed runtime satisfies is NOT importable in a frozen host — and a plugin whose tools import it in-process would pass the install gate and then die at tool time. Only non-default (host) entries are recorded; absent ⇒ runtime.

repository {#field-repository}

  • Type str · Default ''

provenance, shown in the install review.

homepage {#field-homepage}

  • Type str · Default ''

provenance, shown in the install review.

min_protoagent_version {#field-min-protoagent-version}

  • Type str · Default ''

compat guard — the loader refuses to load the plugin when the host is older than declared (malformed strings only warn and load).

supersedes {#field-supersedes}

  • Type list[str] · Default []

The standalone repo(s) this BUNDLED plugin replaces — how an external plugin moves into core under the SAME id (so plugins.enabled, its config section and every archetype's enable list keep working). Honored only on the copy shipped in protoAgent's own plugins/ tree; inert anywhere else. Each entry is the git URL of a retired repo. When plugins.lock records the installed copy of this id as fetched from one of them, the bundled copy wins over it — at any version — and the operator gets a setup gap saying the installed copy can be removed. Installing or updating from a listed URL (directly, or as a bundle/archetype member) is skipped rather than refused, so archetype repos that still list the old URL keep working on old and new hosts alike. A copy installed from any OTHER URL (a fork) still wins as a deliberate override. Matching ignores the transport spelling (https://, ssh://, git@host:path), userinfo, port, letter case, and a trailing .git or slash. An entry that isn't a remote git URL naming a repo (a local path, file://, a glob) is dropped with a warning; a bare string is read as a one-entry list.

enables {#field-enables}

  • Type list[str] · Default []

Other plugins this BUNDLED plugin turns on (#3450). While this plugin is enabled, each listed plugin id is enabled too, unless the operator turned that plugin off explicitly (plugins.disabled always wins). Turning this plugin off returns each one to its own default (off, unless the operator enabled it themselves). Honored only on the copy shipped in protoAgent's own plugins/ tree, like supersedes: a plugin that can switch another on could switch on code execution, so it's inert anywhere else. A bare string is read as a one-entry list; a non-string, blank or self entry is dropped with a warning.