diff --git a/docs/plugins/architecture.md b/docs/plugins/architecture.md
index 9d886a97390..4de8c088eef 100644
--- a/docs/plugins/architecture.md
+++ b/docs/plugins/architecture.md
@@ -119,31 +119,22 @@ signals also appear in `openclaw status --all` and `openclaw plugins doctor`.
## Architecture overview
-OpenClaw's plugin system is split into four planes:
+OpenClaw's plugin system has four layers:
-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.
+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.
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 fecdeaa36da..7788b0d2b82 100644
--- a/docs/plugins/manifest.md
+++ b/docs/plugins/manifest.md
@@ -239,15 +239,11 @@ 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 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.
+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.
```json
{
@@ -261,24 +257,23 @@ fields as fallback hints for load planning, not as the primary plugin contract.
}
```
-| 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. |
+| 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. |
Current live consumers:
-- 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`
+- 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
## setup reference
diff --git a/docs/plugins/sdk-migration.md b/docs/plugins/sdk-migration.md
index fd726846b22..f6d18fc4475 100644
--- a/docs/plugins/sdk-migration.md
+++ b/docs/plugins/sdk-migration.md
@@ -25,14 +25,12 @@ 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 an
-approved breaking release removes them.
+plugins must not use them, and existing plugins should migrate before the next
+major release removes them.
- 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.
+ The backwards-compatibility layer will be removed in a future major release.
+ Plugins that still import from these surfaces will break when that happens.
## Why this changed
@@ -376,15 +374,13 @@ check the source at `src/plugin-sdk/` or ask in Discord.
## Removal timeline
-| 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. |
+| When | What happens |
+| ---------------------- | ----------------------------------------------------------------------- |
+| **Now** | Deprecated surfaces emit runtime warnings |
+| **Next major release** | Deprecated surfaces will be removed; plugins still using them will fail |
-All core plugins have already been migrated. External plugins should migrate,
-but documented external plugins should not break without the compatibility path
-above.
+All core plugins have already been migrated. External plugins should migrate
+before the next major release.
## Suppressing the warnings temporarily
diff --git a/src/plugin-sdk/compat.ts b/src/plugin-sdk/compat.ts
index 5a13f5089ff..5ad6e71f26a 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 during the documented migration window. Migration guide: https://docs.openclaw.ai/plugins/sdk-migration",
+ "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",
},
);
}
diff --git a/src/plugins/activation-planner.test.ts b/src/plugins/activation-planner.test.ts
index 92e33182f9f..083a0bbdd5f 100644
--- a/src/plugins/activation-planner.test.ts
+++ b/src/plugins/activation-planner.test.ts
@@ -9,14 +9,10 @@ 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, resolveManifestActivationPlan } =
- await import("./activation-planner.js"));
- ({ PLUGIN_COMPAT_REASON } = await import("./compat-reasons.js"));
+ ({ resolveManifestActivationPluginIds } = await import("./activation-planner.js"));
});
beforeEach(() => {
@@ -74,20 +70,6 @@ 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: [],
});
@@ -186,7 +168,7 @@ describe("resolveManifestActivationPluginIds", () => {
capability: "tool",
},
}),
- ).toEqual(["demo-channel", "legacy-activation-only"]);
+ ).toEqual(["demo-channel"]);
expect(
resolveManifestActivationPluginIds({
@@ -209,96 +191,4 @@ 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 0c27be58c9a..2206c689a7e 100644
--- a/src/plugins/activation-planner.ts
+++ b/src/plugins/activation-planner.ts
@@ -1,7 +1,6 @@
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";
@@ -15,18 +14,6 @@ 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;
@@ -36,218 +23,101 @@ export function resolveManifestActivationPluginIds(params: {
origin?: PluginOrigin;
onlyPluginIds?: readonly string[];
}): string[] {
- return resolveManifestActivationPlan(params).pluginIds;
-}
-
-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]] : [],
- ),
+ 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));
}
-type ManifestActivationMatch = {
- reason: string;
- compatReason?: PluginCompatReason;
-};
-
-function matchManifestActivationTrigger(
+function matchesManifestActivationTrigger(
plugin: PluginManifestRecord,
trigger: PluginActivationPlannerTrigger,
-): ManifestActivationMatch | null {
+): boolean {
switch (trigger.kind) {
case "command":
- return matchActivationCommand(plugin, trigger.command);
+ return listActivationCommandIds(plugin).includes(normalizeCommandId(trigger.command));
case "provider":
- return matchActivationProvider(plugin, trigger.provider);
+ return listActivationProviderIds(plugin).includes(normalizeProviderId(trigger.provider));
case "agentHarness":
- return matchLegacyActivationList(
- listActivationAgentHarnessIds(plugin),
- normalizeCommandId(trigger.runtime),
- "agent-harness",
- );
+ return listActivationAgentHarnessIds(plugin).includes(normalizeCommandId(trigger.runtime));
case "channel":
- return matchActivationChannel(plugin, trigger.channel);
+ return listActivationChannelIds(plugin).includes(normalizeCommandId(trigger.channel));
case "route":
- return matchLegacyActivationList(
- listActivationRouteIds(plugin),
- normalizeCommandId(trigger.route),
- "route",
- );
+ return listActivationRouteIds(plugin).includes(normalizeCommandId(trigger.route));
case "capability":
- return matchActivationCapability(plugin, trigger.capability);
+ return hasActivationCapability(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 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)
+function listActivationCommandIds(plugin: PluginManifestRecord): string[] {
+ return [
+ ...(plugin.activation?.onCommands ?? []),
+ ...(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 matchActivationProvider(
- plugin: PluginManifestRecord,
- provider: string,
-): ManifestActivationMatch | null {
- const normalizedProvider = normalizeProviderId(provider);
- if (!normalizedProvider) {
- return null;
- }
- const stableProviderIds = [
+function listActivationProviderIds(plugin: PluginManifestRecord): string[] {
+ return [
+ ...(plugin.activation?.onProviders ?? []),
...plugin.providers,
- ...(plugin.setup?.providers?.map((entry) => entry.id) ?? []),
+ ...(plugin.setup?.providers?.map((provider) => provider.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 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 listActivationChannelIds(plugin: PluginManifestRecord): string[] {
+ return [...(plugin.activation?.onChannels ?? []), ...plugin.channels]
+ .map(normalizeCommandId)
+ .filter(Boolean);
}
function listActivationRouteIds(plugin: PluginManifestRecord): string[] {
return (plugin.activation?.onRoutes ?? []).map(normalizeCommandId).filter(Boolean);
}
-function matchActivationCapability(
+function hasActivationCapability(
plugin: PluginManifestRecord,
capability: PluginManifestActivationCapability,
-): ManifestActivationMatch | null {
- switch (capability) {
- case "provider": {
- const hasProviderOwnership =
- plugin.providers.length > 0 || (plugin.setup?.providers?.length ?? 0) > 0;
- if (hasProviderOwnership) {
- return { reason: "capability:provider" };
- }
- break;
- }
- case "channel":
- if (plugin.channels.length > 0) {
- return { reason: "capability:channel" };
- }
- break;
- case "tool":
- if ((plugin.contracts?.tools?.length ?? 0) > 0) {
- return { reason: "capability:tool" };
- }
- break;
- case "hook":
- if (plugin.hooks.length > 0) {
- return { reason: "capability:hook" };
- }
- break;
- }
+): boolean {
if (plugin.activation?.onCapabilities?.includes(capability)) {
- return {
- reason: `capability:${capability}`,
- compatReason: PLUGIN_COMPAT_REASON.legacyActivationField,
- };
+ return true;
}
- return null;
+ switch (capability) {
+ case "provider":
+ return listActivationProviderIds(plugin).length > 0;
+ case "channel":
+ return listActivationChannelIds(plugin).length > 0;
+ case "tool":
+ return (plugin.contracts?.tools?.length ?? 0) > 0;
+ case "hook":
+ return plugin.hooks.length > 0;
+ }
+ const unreachableCapability: never = capability;
+ return unreachableCapability;
}
function normalizeCommandId(value: string | undefined): string {
diff --git a/src/plugins/compat-reasons.ts b/src/plugins/compat-reasons.ts
deleted file mode 100644
index 03d4c56b533..00000000000
--- a/src/plugins/compat-reasons.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-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 c391b8ef9ec..eaaa01eed5c 100644
--- a/src/plugins/manifest.ts
+++ b/src/plugins/manifest.ts
@@ -57,19 +57,19 @@ export type PluginManifestActivationCapability = "provider" | "channel" | "tool"
export type PluginManifestActivation = {
/**
- * Legacy provider activation hints. Prefer top-level `providers` and setup
- * provider ownership for new manifests.
+ * Provider ids that should activate this plugin when explicitly requested.
+ * This is metadata only; runtime loading still happens through the loader.
*/
onProviders?: string[];
- /** Legacy agent harness runtime activation hints. */
+ /** Agent harness runtime ids that should activate this plugin. */
onAgentHarnesses?: string[];
- /** Legacy command activation hints. Prefer command aliases or CLI descriptors. */
+ /** Command ids that should activate this plugin. */
onCommands?: string[];
- /** Legacy channel activation hints. Prefer top-level `channels`. */
+ /** Channel ids that should activate this plugin. */
onChannels?: string[];
- /** Legacy route activation hints. */
+ /** Route kinds that should activate this plugin. */
onRoutes?: string[];
- /** Legacy broad capability hints. Do not add new uses. */
+ /** Cheap capability hints used by future activation planning. */
onCapabilities?: PluginManifestActivationCapability[];
};