Skip to content

Manifest (plugin.yaml) ​

Planned. The package format and every rule on this page come from openspec/changes/keel-plugin-core/specs/plugin-system/spec.md ("插件包与清单" and "发现、安装与包存储"). Nothing here is implemented in the current checkout; the implemented in-process contract is described at the end of this page for contrast.

1. The package ​

A plugin is a directory:

<plugins-root>/<id>/
├─ plugin.yaml            the manifest (required)
├─ ui/                    webview resources (required for kind: webview)
│   ├─ index.html
│   └─ assets/…
└─ bin/                   sidecar entry points (required for kind: sidecar)

The package contents are stored in a managed directory; a manifest may not point at arbitrary external paths, and installation records a content hash (spec: 发现、安装与包存储).

2. The manifest — full field list ​

yaml
id: vendor.name                # ^[a-z][a-z0-9._-]{1,63}$; globally unique; one enabled version per id
version: 1.2.3                 # semver
kind: in-process | sidecar | webview
description: one line
engines: { keel: ">=0.2.0 <0.3.0" }        # host version gate
provides:
  tools: [name, ...]           # model-facing tool names; globally unique (conflict = fail-closed)
  hooks: [event, ...]          # must belong to the closed hook set
  triggers: [id, ...]
  ui: { entry: ui/index.html, title: "..." }   # required for webview
requires: [plugin-or-service-id, ...]          # missing dependency ⇒ not loaded (no half-load)
permissions:
  fs: [workspace-write]        # file effect classes
  net: [host, ...]             # allowed hosts
  secrets: [NAME, ...]         # allowed secret references
  tools: [name, ...]           # allowed tool calls
entry: { command: [...], args: [...], env: [...] }   # required for sidecar; may be empty for in-process
source: { type: builtin | local | git | npm, ... }

Field-by-field semantics and enforcement points:

FieldMeaningEnforced where
idglobal identity; the directory name; the audit subjectinstall validation
versionsemver; a second enabled version of the same id is refusedinstall/upgrade
kindone of three; drives which entry/ui shape is validinstall validation, Kinds
engines.keelsemver range the plugin supports; mismatch refuses install and enable with the mismatched range namedversion gate
provides.toolstool names the plugin contributes; a duplicate across enabled plugins is a fail-closed conflicttool dispatch, assembly
provides.hookssubscriptions to the closed event set (today: subagent.*, trigger.*, turn.*, decision.*; internal/agentext/hooks.go:18)subscription registration
provides.triggerstrigger ids the plugin owns (interval/cron/webhook)trigger engine
provides.uiwebview entry document + title; required for kind: webviewUI hosting
requiresplugin or service ids that must be present; missing ⇒ disabled with a reason, never a half-loadlifecycle assembly
permissionsthe capability budget; see Permissionsdispatch, env construction, audit
entrythe sidecar process command/args/env (env only from allowlisted names)process supervision
sourceprovenance of the package (builtin, local, git, npm, …)install + audit

Rules that are not field-level but are part of the same contract:

  • invalid manifest → refusal with a field-level reason; the plugin directory is not adopted (spec: 清单非法即拒绝);
  • version gate — engines.keel mismatch refuses install and enable, naming the supported range (spec: 版本门);
  • content hash + audit — installation validates manifest, version gate and package content hash, and writes an audit record (spec: 发现、安装与包存储);
  • install is immediately visible — after install, GET /v1/plugins lists the plugin (state installed/enabled) and the UI inventory refreshes (spec: 安装即出现).

3. Discovery, install and storage (planned API surface) ​

OperationSurfaceSemantics
discoverscan plugins-root at bootplugins found on disk appear in the inventory
installinstall API (local dir / git / package)validate manifest + gate + hash; write audit
listGET /v1/plugins (extended by the change)states installed/enabled/disabled, diagnostics
uninstalluninstall APImanaged-directory removal + audit; cannot remove while providing to live runs
upgradesame id, new versionversion replacement; enable path re-runs dependency-ordered assembly

Today's GET /v1/plugins already exists and answers the assembled surface — generation, plugin states, hooks summary, tool names (internal/agentapi/ext.go:18, internal/agentext/plugin.go:505). The change extends that response with installed/disabled packages rather than inventing a second endpoint.

4. What exists today instead of this manifest ​

The implemented in-process contract carries the same concepts without a file: ID, Version, Provides, Requires, Tools, Hooks, Triggers, Effects, Start, Stop (internal/agentext/plugin.go:67). The mapping is one-way — the planned manifest is a serialization of the same declaration plus everything the Go type deliberately left out (permissions, source, UI).

plugin.yaml conceptToday's Go equivalentNote
id / versionPlugin.ID / Plugin.Versionreported by the snapshot
provides / requiresPlugin.Provides / Plugin.Requiressame dependency-topological assembly
provides.toolsPlugin.Toolsduplicate names fail-closed
provides.hooksPlugin.Hooksclosed set, enforced by Subscribe
provides.triggersPlugin.Triggersowner is filled from the plugin id
effects (reversal contract)Plugin.Effectsreverse-order cleanup with a partial-unload guard
kind / permissions / source—no equivalent today; in-process code is trusted by construction

Apache-2.0 (MIT notices kept for files moved from pigo)