diff --git a/docs/plugins/architecture.md b/docs/plugins/architecture.md
index 4de8c088eef..9d886a97390 100644
--- a/docs/plugins/architecture.md
+++ b/docs/plugins/architecture.md
@@ -119,22 +119,31 @@ signals also appear in `openclaw status --all` and `openclaw plugins doctor`.
## Architecture overview
-OpenClaw's plugin system has four layers:
+OpenClaw's plugin system is split into four planes:
-1. **Manifest + discovery**
- OpenClaw finds candidate plugins from configured paths, workspace roots,
- global plugin roots, and bundled plugins. Discovery reads native
- `openclaw.plugin.json` manifests plus supported bundle manifests first.
-2. **Enablement + validation**
- Core decides whether a discovered plugin is enabled, disabled, blocked, or
- selected for an exclusive slot such as memory.
-3. **Runtime loading**
- Native OpenClaw plugins are loaded in-process via jiti and register
- capabilities into a central registry. Compatible bundles are normalized into
- registry records without importing runtime code.
-4. **Surface consumption**
- The rest of OpenClaw reads the registry to expose tools, channels, provider
- setup, hooks, HTTP routes, CLI commands, and services.
+1. **Source plane**
+ OpenClaw decides where a plugin comes from and how it can be installed. This
+ includes bundled catalogs, official external catalogs, ClawHub/npm specs,
+ local source paths, minimum host version, expected npm integrity, and install
+ policy checks.
+2. **Control plane**
+ OpenClaw reads package and manifest metadata before runtime code executes.
+ This includes discovery, config schemas, provider/channel ownership,
+ setup/onboarding hints, contracts, auth choices, and enablement policy.
+3. **Load plane**
+ OpenClaw builds deterministic plans for concrete needs such as a provider,
+ channel, command, hook stage, or contract. Legacy `activation.*` fields are
+ compatibility hints in this plane, not the preferred public contract.
+4. **Runtime plane**
+ OpenClaw imports plugin code only for actual execution. Native plugins
+ register capabilities into scoped or compatibility registries; compatible
+ bundles can still normalize into registry records without importing runtime
+ code.
+
+The important compatibility rule: documented external plugins and existing
+bundled plugins must keep working while contracts migrate. Breaking changes need
+a replacement contract, compatibility adapter, diagnostics, tests, docs, and an
+approved deprecation window before removal.
For plugin CLI specifically, root command discovery is split in two phases:
diff --git a/docs/plugins/manifest.md b/docs/plugins/manifest.md
index 7788b0d2b82..fecdeaa36da 100644
--- a/docs/plugins/manifest.md
+++ b/docs/plugins/manifest.md
@@ -239,11 +239,15 @@ runtime still owns actual CLI registration through a lightweight
| `commandName` | Yes | `string` | Subcommand mounted beneath `openclaw qa`, for example `matrix`. |
| `description` | No | `string` | Fallback help text used when the shared host needs a stub command. |
-This block is metadata only. It does not register runtime behavior, and it does
-not replace `register(...)`, `setupEntry`, or other runtime/plugin entrypoints.
-Current consumers use it as a narrowing hint before broader plugin loading, so
-missing activation metadata usually only costs performance; it should not
-change correctness while legacy manifest ownership fallbacks still exist.
+This block is legacy hint metadata. It does not register runtime behavior, and
+it does not replace `register(...)`, `setupEntry`, or other runtime/plugin
+entrypoints. Existing plugins may keep these fields, but new manifests should
+prefer explicit ownership fields such as `providers`, `channels`, `contracts`,
+`commandAliases`, and `setup`.
+
+Current consumers still parse `activation` through the compatibility layer so
+existing bundled and external plugins keep working. New code should treat these
+fields as fallback hints for load planning, not as the primary plugin contract.
```json
{
@@ -257,23 +261,24 @@ change correctness while legacy manifest ownership fallbacks still exist.
}
```
-| Field | Required | Type | What it means |
-| ---------------- | -------- | ---------------------------------------------------- | ----------------------------------------------------------------- |
-| `onProviders` | No | `string[]` | Provider ids that should activate this plugin when requested. |
-| `onCommands` | No | `string[]` | Command ids that should activate this plugin. |
-| `onChannels` | No | `string[]` | Channel ids that should activate this plugin. |
-| `onRoutes` | No | `string[]` | Route kinds that should activate this plugin. |
-| `onCapabilities` | No | `Array<"provider" \| "channel" \| "tool" \| "hook">` | Broad capability hints used by control-plane activation planning. |
+| Field | Required | Type | What it means |
+| ---------------- | -------- | ---------------------------------------------------- | ----------------------------------------------------------------------------- |
+| `onProviders` | No | `string[]` | Legacy provider load hint. Prefer top-level `providers`. |
+| `onCommands` | No | `string[]` | Legacy command load hint. Prefer command aliases or CLI descriptors. |
+| `onChannels` | No | `string[]` | Legacy channel load hint. Prefer top-level `channels`. |
+| `onRoutes` | No | `string[]` | Legacy route load hint. Keep only when no narrower route metadata exists yet. |
+| `onCapabilities` | No | `Array<"provider" \| "channel" \| "tool" \| "hook">` | Legacy broad capability hint. Do not add new uses. |
Current live consumers:
-- command-triggered CLI planning falls back to legacy
- `commandAliases[].cliCommand` or `commandAliases[].name`
-- channel-triggered setup/channel planning falls back to legacy `channels[]`
- ownership when explicit channel activation metadata is missing
-- provider-triggered setup/runtime planning falls back to legacy
- `providers[]` and top-level `cliBackends[]` ownership when explicit provider
- activation metadata is missing
+- command-triggered CLI planning prefers `commandAliases[].cliCommand` or
+ `commandAliases[].name` before legacy `activation.onCommands`
+- channel-triggered setup/channel planning prefers `channels[]` before legacy
+ `activation.onChannels`
+- provider-triggered setup/runtime planning prefers `providers[]` and
+ `setup.providers[]` before legacy `activation.onProviders`
+- broad capability planning prefers explicit ownership metadata before legacy
+ `activation.onCapabilities`
## setup reference
diff --git a/docs/plugins/sdk-migration.md b/docs/plugins/sdk-migration.md
index f6d18fc4475..fd726846b22 100644
--- a/docs/plugins/sdk-migration.md
+++ b/docs/plugins/sdk-migration.md
@@ -25,12 +25,14 @@ anything they needed from a single entry point:
host-side helpers like the embedded agent runner.
Both surfaces are now **deprecated**. They still work at runtime, but new
-plugins must not use them, and existing plugins should migrate before the next
-major release removes them.
+plugins must not use them, and existing plugins should migrate before an
+approved breaking release removes them.
- The backwards-compatibility layer will be removed in a future major release.
- Plugins that still import from these surfaces will break when that happens.
+ The backwards-compatibility layer remains supported during the migration
+ window. Any removal must go through a documented deprecation path first:
+ replacement contract, compatibility adapter, diagnostics, tests, docs, and an
+ explicitly approved breaking release.
## Why this changed
@@ -374,13 +376,15 @@ check the source at `src/plugin-sdk/` or ask in Discord.
## Removal timeline
-| When | What happens |
-| ---------------------- | ----------------------------------------------------------------------- |
-| **Now** | Deprecated surfaces emit runtime warnings |
-| **Next major release** | Deprecated surfaces will be removed; plugins still using them will fail |
+| When | What happens |
+| ---------------------------------- | ---------------------------------------------------------------------------- |
+| **Now** | Deprecated surfaces emit runtime warnings and keep working through adapters. |
+| **Migration window** | Replacement contracts, diagnostics, tests, and docs stay available together. |
+| **Approved breaking release only** | Deprecated surfaces may be removed after the migration window. |
-All core plugins have already been migrated. External plugins should migrate
-before the next major release.
+All core plugins have already been migrated. External plugins should migrate,
+but documented external plugins should not break without the compatibility path
+above.
## Suppressing the warnings temporarily
diff --git a/src/plugin-sdk/compat.ts b/src/plugin-sdk/compat.ts
index 5ad6e71f26a..5a13f5089ff 100644
--- a/src/plugin-sdk/compat.ts
+++ b/src/plugin-sdk/compat.ts
@@ -12,7 +12,7 @@ if (shouldWarnCompatImport) {
{
code: "OPENCLAW_PLUGIN_SDK_COMPAT_DEPRECATED",
detail:
- "Bundled plugins must use scoped plugin-sdk subpaths. External plugins may keep compat temporarily while migrating. Migration guide: https://docs.openclaw.ai/plugins/sdk-migration",
+ "Bundled plugins must use scoped plugin-sdk subpaths. External plugins may keep compat during the documented migration window. Migration guide: https://docs.openclaw.ai/plugins/sdk-migration",
},
);
}
diff --git a/src/plugins/activation-planner.test.ts b/src/plugins/activation-planner.test.ts
index 083a0bbdd5f..92e33182f9f 100644
--- a/src/plugins/activation-planner.test.ts
+++ b/src/plugins/activation-planner.test.ts
@@ -9,10 +9,14 @@ vi.mock("./manifest-registry.js", () => ({
}));
let resolveManifestActivationPluginIds: typeof import("./activation-planner.js").resolveManifestActivationPluginIds;
+let resolveManifestActivationPlan: typeof import("./activation-planner.js").resolveManifestActivationPlan;
+let PLUGIN_COMPAT_REASON: typeof import("./compat-reasons.js").PLUGIN_COMPAT_REASON;
describe("resolveManifestActivationPluginIds", () => {
beforeAll(async () => {
- ({ resolveManifestActivationPluginIds } = await import("./activation-planner.js"));
+ ({ resolveManifestActivationPluginIds, resolveManifestActivationPlan } =
+ await import("./activation-planner.js"));
+ ({ PLUGIN_COMPAT_REASON } = await import("./compat-reasons.js"));
});
beforeEach(() => {
@@ -70,6 +74,20 @@ describe("resolveManifestActivationPluginIds", () => {
},
origin: "workspace",
},
+ {
+ id: "legacy-activation-only",
+ providers: [],
+ activation: {
+ onProviders: ["legacy-provider"],
+ onChannels: ["legacy-channel"],
+ onCapabilities: ["tool"],
+ },
+ channels: [],
+ cliBackends: [],
+ skills: [],
+ hooks: [],
+ origin: "workspace",
+ },
],
diagnostics: [],
});
@@ -168,7 +186,7 @@ describe("resolveManifestActivationPluginIds", () => {
capability: "tool",
},
}),
- ).toEqual(["demo-channel"]);
+ ).toEqual(["demo-channel", "legacy-activation-only"]);
expect(
resolveManifestActivationPluginIds({
@@ -191,4 +209,96 @@ describe("resolveManifestActivationPluginIds", () => {
}),
).toEqual([]);
});
+
+ it("reports legacy activation field compat reasons without changing plugin-id resolution", () => {
+ expect(
+ resolveManifestActivationPlan({
+ trigger: {
+ kind: "command",
+ command: "demo-tools",
+ },
+ }),
+ ).toEqual({
+ pluginIds: ["demo-channel"],
+ entries: [
+ {
+ pluginId: "demo-channel",
+ reasons: ["command:demo-tools"],
+ compatReasons: [PLUGIN_COMPAT_REASON.legacyActivationField],
+ },
+ ],
+ compatReasons: {
+ "demo-channel": [PLUGIN_COMPAT_REASON.legacyActivationField],
+ },
+ });
+
+ expect(
+ resolveManifestActivationPlan({
+ trigger: {
+ kind: "provider",
+ provider: "legacy-provider",
+ },
+ }).compatReasons,
+ ).toEqual({
+ "legacy-activation-only": [PLUGIN_COMPAT_REASON.legacyActivationField],
+ });
+
+ expect(
+ resolveManifestActivationPluginIds({
+ trigger: {
+ kind: "provider",
+ provider: "legacy-provider",
+ },
+ }),
+ ).toEqual(["legacy-activation-only"]);
+ });
+
+ it("does not report compat reasons for stable ownership metadata", () => {
+ expect(
+ resolveManifestActivationPlan({
+ trigger: {
+ kind: "provider",
+ provider: "openai",
+ },
+ }),
+ ).toEqual({
+ pluginIds: ["openai"],
+ entries: [
+ {
+ pluginId: "openai",
+ reasons: ["provider:openai"],
+ compatReasons: [],
+ },
+ ],
+ compatReasons: {},
+ });
+ });
+
+ it("reports legacy activation capability hints separately from stable capabilities", () => {
+ expect(
+ resolveManifestActivationPlan({
+ trigger: {
+ kind: "capability",
+ capability: "tool",
+ },
+ }),
+ ).toEqual({
+ pluginIds: ["demo-channel", "legacy-activation-only"],
+ entries: [
+ {
+ pluginId: "demo-channel",
+ reasons: ["capability:tool"],
+ compatReasons: [],
+ },
+ {
+ pluginId: "legacy-activation-only",
+ reasons: ["capability:tool"],
+ compatReasons: [PLUGIN_COMPAT_REASON.legacyActivationField],
+ },
+ ],
+ compatReasons: {
+ "legacy-activation-only": [PLUGIN_COMPAT_REASON.legacyActivationField],
+ },
+ });
+ });
});
diff --git a/src/plugins/activation-planner.ts b/src/plugins/activation-planner.ts
index 2206c689a7e..0c27be58c9a 100644
--- a/src/plugins/activation-planner.ts
+++ b/src/plugins/activation-planner.ts
@@ -1,6 +1,7 @@
import { normalizeProviderId } from "../agents/provider-id.js";
import type { OpenClawConfig } from "../config/types.js";
import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js";
+import { PLUGIN_COMPAT_REASON, type PluginCompatReason } from "./compat-reasons.js";
import { loadPluginManifestRegistry, type PluginManifestRecord } from "./manifest-registry.js";
import type { PluginManifestActivationCapability } from "./manifest.js";
import type { PluginOrigin } from "./plugin-origin.types.js";
@@ -14,6 +15,18 @@ export type PluginActivationPlannerTrigger =
| { kind: "route"; route: string }
| { kind: "capability"; capability: PluginManifestActivationCapability };
+export type PluginActivationPlanEntry = {
+ pluginId: string;
+ reasons: string[];
+ compatReasons: PluginCompatReason[];
+};
+
+export type PluginActivationPlan = {
+ pluginIds: string[];
+ entries: PluginActivationPlanEntry[];
+ compatReasons: Record;
+};
+
export function resolveManifestActivationPluginIds(params: {
trigger: PluginActivationPlannerTrigger;
config?: OpenClawConfig;
@@ -23,101 +36,218 @@ export function resolveManifestActivationPluginIds(params: {
origin?: PluginOrigin;
onlyPluginIds?: readonly string[];
}): string[] {
- const onlyPluginIdSet = createPluginIdScopeSet(normalizePluginIdScope(params.onlyPluginIds));
-
- return [
- ...new Set(
- loadPluginManifestRegistry({
- config: params.config,
- workspaceDir: params.workspaceDir,
- env: params.env,
- cache: params.cache,
- })
- .plugins.filter(
- (plugin) =>
- (!params.origin || plugin.origin === params.origin) &&
- (!onlyPluginIdSet || onlyPluginIdSet.has(plugin.id)) &&
- matchesManifestActivationTrigger(plugin, params.trigger),
- )
- .map((plugin) => plugin.id),
- ),
- ].toSorted((left, right) => left.localeCompare(right));
+ return resolveManifestActivationPlan(params).pluginIds;
}
-function matchesManifestActivationTrigger(
+export function resolveManifestActivationPlan(params: {
+ trigger: PluginActivationPlannerTrigger;
+ config?: OpenClawConfig;
+ workspaceDir?: string;
+ env?: NodeJS.ProcessEnv;
+ cache?: boolean;
+ origin?: PluginOrigin;
+ onlyPluginIds?: readonly string[];
+}): PluginActivationPlan {
+ const onlyPluginIdSet = createPluginIdScopeSet(normalizePluginIdScope(params.onlyPluginIds));
+
+ const entries = loadPluginManifestRegistry({
+ config: params.config,
+ workspaceDir: params.workspaceDir,
+ env: params.env,
+ cache: params.cache,
+ })
+ .plugins.flatMap((plugin): PluginActivationPlanEntry[] => {
+ if (
+ (params.origin && plugin.origin !== params.origin) ||
+ (onlyPluginIdSet && !onlyPluginIdSet.has(plugin.id))
+ ) {
+ return [];
+ }
+ const match = matchManifestActivationTrigger(plugin, params.trigger);
+ if (!match) {
+ return [];
+ }
+ return [
+ {
+ pluginId: plugin.id,
+ reasons: [match.reason],
+ compatReasons: match.compatReason ? [match.compatReason] : [],
+ },
+ ];
+ })
+ .toSorted((left, right) => left.pluginId.localeCompare(right.pluginId));
+
+ return {
+ pluginIds: entries.map((entry) => entry.pluginId),
+ entries,
+ compatReasons: Object.fromEntries(
+ entries.flatMap((entry) =>
+ entry.compatReasons.length > 0 ? [[entry.pluginId, entry.compatReasons]] : [],
+ ),
+ ),
+ };
+}
+
+type ManifestActivationMatch = {
+ reason: string;
+ compatReason?: PluginCompatReason;
+};
+
+function matchManifestActivationTrigger(
plugin: PluginManifestRecord,
trigger: PluginActivationPlannerTrigger,
-): boolean {
+): ManifestActivationMatch | null {
switch (trigger.kind) {
case "command":
- return listActivationCommandIds(plugin).includes(normalizeCommandId(trigger.command));
+ return matchActivationCommand(plugin, trigger.command);
case "provider":
- return listActivationProviderIds(plugin).includes(normalizeProviderId(trigger.provider));
+ return matchActivationProvider(plugin, trigger.provider);
case "agentHarness":
- return listActivationAgentHarnessIds(plugin).includes(normalizeCommandId(trigger.runtime));
+ return matchLegacyActivationList(
+ listActivationAgentHarnessIds(plugin),
+ normalizeCommandId(trigger.runtime),
+ "agent-harness",
+ );
case "channel":
- return listActivationChannelIds(plugin).includes(normalizeCommandId(trigger.channel));
+ return matchActivationChannel(plugin, trigger.channel);
case "route":
- return listActivationRouteIds(plugin).includes(normalizeCommandId(trigger.route));
+ return matchLegacyActivationList(
+ listActivationRouteIds(plugin),
+ normalizeCommandId(trigger.route),
+ "route",
+ );
case "capability":
- return hasActivationCapability(plugin, trigger.capability);
+ return matchActivationCapability(plugin, trigger.capability);
}
const unreachableTrigger: never = trigger;
return unreachableTrigger;
}
+function matchLegacyActivationList(
+ ids: readonly string[],
+ normalizedId: string,
+ reasonPrefix: string,
+): ManifestActivationMatch | null {
+ if (!normalizedId || !ids.includes(normalizedId)) {
+ return null;
+ }
+ return {
+ reason: `${reasonPrefix}:${normalizedId}`,
+ compatReason: PLUGIN_COMPAT_REASON.legacyActivationField,
+ };
+}
+
function listActivationAgentHarnessIds(plugin: PluginManifestRecord): string[] {
return [...(plugin.activation?.onAgentHarnesses ?? [])].map(normalizeCommandId).filter(Boolean);
}
-function listActivationCommandIds(plugin: PluginManifestRecord): string[] {
- return [
- ...(plugin.activation?.onCommands ?? []),
- ...(plugin.commandAliases ?? []).flatMap((alias) => alias.cliCommand ?? alias.name),
- ]
+function matchActivationCommand(
+ plugin: PluginManifestRecord,
+ command: string,
+): ManifestActivationMatch | null {
+ const normalizedCommand = normalizeCommandId(command);
+ if (!normalizedCommand) {
+ return null;
+ }
+ const commandAliases = (plugin.commandAliases ?? [])
+ .flatMap((alias) => alias.cliCommand ?? alias.name)
.map(normalizeCommandId)
.filter(Boolean);
+ if (commandAliases.includes(normalizedCommand)) {
+ return { reason: `command:${normalizedCommand}` };
+ }
+ return matchLegacyActivationList(
+ (plugin.activation?.onCommands ?? []).map(normalizeCommandId).filter(Boolean),
+ normalizedCommand,
+ "command",
+ );
}
-function listActivationProviderIds(plugin: PluginManifestRecord): string[] {
- return [
- ...(plugin.activation?.onProviders ?? []),
+function matchActivationProvider(
+ plugin: PluginManifestRecord,
+ provider: string,
+): ManifestActivationMatch | null {
+ const normalizedProvider = normalizeProviderId(provider);
+ if (!normalizedProvider) {
+ return null;
+ }
+ const stableProviderIds = [
...plugin.providers,
- ...(plugin.setup?.providers?.map((provider) => provider.id) ?? []),
+ ...(plugin.setup?.providers?.map((entry) => entry.id) ?? []),
]
.map((value) => normalizeProviderId(value))
.filter(Boolean);
+ if (stableProviderIds.includes(normalizedProvider)) {
+ return { reason: `provider:${normalizedProvider}` };
+ }
+ return matchLegacyActivationList(
+ (plugin.activation?.onProviders ?? [])
+ .map((value) => normalizeProviderId(value))
+ .filter(Boolean),
+ normalizedProvider,
+ "provider",
+ );
}
-function listActivationChannelIds(plugin: PluginManifestRecord): string[] {
- return [...(plugin.activation?.onChannels ?? []), ...plugin.channels]
- .map(normalizeCommandId)
- .filter(Boolean);
+function matchActivationChannel(
+ plugin: PluginManifestRecord,
+ channel: string,
+): ManifestActivationMatch | null {
+ const normalizedChannel = normalizeCommandId(channel);
+ if (!normalizedChannel) {
+ return null;
+ }
+ const stableChannelIds = plugin.channels.map(normalizeCommandId).filter(Boolean);
+ if (stableChannelIds.includes(normalizedChannel)) {
+ return { reason: `channel:${normalizedChannel}` };
+ }
+ return matchLegacyActivationList(
+ (plugin.activation?.onChannels ?? []).map(normalizeCommandId).filter(Boolean),
+ normalizedChannel,
+ "channel",
+ );
}
function listActivationRouteIds(plugin: PluginManifestRecord): string[] {
return (plugin.activation?.onRoutes ?? []).map(normalizeCommandId).filter(Boolean);
}
-function hasActivationCapability(
+function matchActivationCapability(
plugin: PluginManifestRecord,
capability: PluginManifestActivationCapability,
-): boolean {
- if (plugin.activation?.onCapabilities?.includes(capability)) {
- return true;
- }
+): ManifestActivationMatch | null {
switch (capability) {
- case "provider":
- return listActivationProviderIds(plugin).length > 0;
+ case "provider": {
+ const hasProviderOwnership =
+ plugin.providers.length > 0 || (plugin.setup?.providers?.length ?? 0) > 0;
+ if (hasProviderOwnership) {
+ return { reason: "capability:provider" };
+ }
+ break;
+ }
case "channel":
- return listActivationChannelIds(plugin).length > 0;
+ if (plugin.channels.length > 0) {
+ return { reason: "capability:channel" };
+ }
+ break;
case "tool":
- return (plugin.contracts?.tools?.length ?? 0) > 0;
+ if ((plugin.contracts?.tools?.length ?? 0) > 0) {
+ return { reason: "capability:tool" };
+ }
+ break;
case "hook":
- return plugin.hooks.length > 0;
+ if (plugin.hooks.length > 0) {
+ return { reason: "capability:hook" };
+ }
+ break;
}
- const unreachableCapability: never = capability;
- return unreachableCapability;
+ if (plugin.activation?.onCapabilities?.includes(capability)) {
+ return {
+ reason: `capability:${capability}`,
+ compatReason: PLUGIN_COMPAT_REASON.legacyActivationField,
+ };
+ }
+ return null;
}
function normalizeCommandId(value: string | undefined): string {
diff --git a/src/plugins/compat-reasons.ts b/src/plugins/compat-reasons.ts
new file mode 100644
index 00000000000..03d4c56b533
--- /dev/null
+++ b/src/plugins/compat-reasons.ts
@@ -0,0 +1,10 @@
+export const PLUGIN_COMPAT_REASON = {
+ legacyActivationField: "legacy-activation-field",
+ legacySetupApi: "legacy-setup-api",
+ legacyRootSdkImport: "legacy-root-sdk-import",
+ legacyGlobalRegistry: "legacy-global-registry",
+ legacyManifestOwnerFallback: "legacy-manifest-owner-fallback",
+ legacyHookStage: "legacy-hook-stage",
+} as const;
+
+export type PluginCompatReason = (typeof PLUGIN_COMPAT_REASON)[keyof typeof PLUGIN_COMPAT_REASON];
diff --git a/src/plugins/manifest.ts b/src/plugins/manifest.ts
index eaaa01eed5c..c391b8ef9ec 100644
--- a/src/plugins/manifest.ts
+++ b/src/plugins/manifest.ts
@@ -57,19 +57,19 @@ export type PluginManifestActivationCapability = "provider" | "channel" | "tool"
export type PluginManifestActivation = {
/**
- * Provider ids that should activate this plugin when explicitly requested.
- * This is metadata only; runtime loading still happens through the loader.
+ * Legacy provider activation hints. Prefer top-level `providers` and setup
+ * provider ownership for new manifests.
*/
onProviders?: string[];
- /** Agent harness runtime ids that should activate this plugin. */
+ /** Legacy agent harness runtime activation hints. */
onAgentHarnesses?: string[];
- /** Command ids that should activate this plugin. */
+ /** Legacy command activation hints. Prefer command aliases or CLI descriptors. */
onCommands?: string[];
- /** Channel ids that should activate this plugin. */
+ /** Legacy channel activation hints. Prefer top-level `channels`. */
onChannels?: string[];
- /** Route kinds that should activate this plugin. */
+ /** Legacy route activation hints. */
onRoutes?: string[];
- /** Cheap capability hints used by future activation planning. */
+ /** Legacy broad capability hints. Do not add new uses. */
onCapabilities?: PluginManifestActivationCapability[];
};